[ Index ]
 

Code source de Kupu-1.3.5

Accédez au Source d'autres logiciels libresSoutenez Angelica Josefina !

title

Body

[fermer]

/common/ -> kupubasetools.js (source)

   1  /*****************************************************************************
   2   *
   3   * Copyright (c) 2003-2005 Kupu Contributors. All rights reserved.
   4   *
   5   * This software is distributed under the terms of the Kupu
   6   * License. See LICENSE.txt for license text. For a list of Kupu
   7   * Contributors see CREDITS.txt.
   8   *
   9   *****************************************************************************/
  10  
  11  // $Id: kupubasetools.js 21219 2005-12-16 15:45:56Z duncan $
  12  
  13  
  14  //----------------------------------------------------------------------------
  15  //
  16  // Toolboxes
  17  //
  18  //  These are addons for Kupu, simple plugins that implement a certain 
  19  //  interface to provide functionality and control view aspects.
  20  //
  21  //----------------------------------------------------------------------------
  22  
  23  //----------------------------------------------------------------------------
  24  // Superclasses
  25  //----------------------------------------------------------------------------
  26  
  27  function KupuTool() {
  28      /* Superclass (or actually more of an interface) for tools 
  29      
  30          Tools must implement at least an initialize method and an 
  31          updateState method, and can implement other methods to add 
  32          certain extra functionality (e.g. createContextMenuElements).
  33      */
  34  
  35      this.toolboxes = {};
  36  
  37      // methods
  38      this.initialize = function(editor) {
  39          /* Initialize the tool.
  40  
  41              Obviously this can be overriden but it will do
  42              for the most simple cases
  43          */
  44          this.editor = editor;
  45      };
  46  
  47      this.registerToolBox = function(id, toolbox) {
  48          /* register a ui box 
  49          
  50              Note that this needs to be called *after* the tool has been 
  51              registered to the KupuEditor
  52          */
  53          this.toolboxes[id] = toolbox;
  54          toolbox.initialize(this, this.editor);
  55      };
  56      
  57      this.updateState = function(selNode, event) {
  58          /* Is called when user moves cursor to other element 
  59  
  60              Calls the updateState for all toolboxes and may want perform
  61              some actions itself
  62          */
  63          for (id in this.toolboxes) {
  64              this.toolboxes[id].updateState(selNode, event);
  65          };
  66      };
  67  
  68      this.enable = function() {
  69          // Called when the tool is enabled after a form is dismissed.
  70      }
  71  
  72      this.disable = function() {
  73          // Called when the tool is disabled (e.g. for a modal form)
  74      }
  75      // private methods
  76      addEventHandler = addEventHandler;
  77      
  78      this._selectSelectItem = function(select, item) {
  79          this.editor.logMessage(_('Deprecation warning: KupuTool._selectSelectItem'));
  80      };
  81      this._fixTabIndex = function(element) {
  82          var tabIndex = this.editor.getDocument().getEditable().tabIndex-1;
  83          if (tabIndex && !element.tabIndex) {
  84              element.tabIndex = tabIndex;
  85          }
  86      }
  87  }
  88  
  89  function KupuToolBox() {
  90      /* Superclass for a user-interface object that controls a tool */
  91  
  92      this.initialize = function(tool, editor) {
  93          /* store a reference to the tool and the editor */
  94          this.tool = tool;
  95          this.editor = editor;
  96      };
  97  
  98      this.updateState = function(selNode, event) {
  99          /* update the toolbox according to the current iframe's situation */
 100      };
 101      
 102      this._selectSelectItem = function(select, item) {
 103          this.editor.logMessage(_('Deprecation warning: KupuToolBox._selectSelectItem'));
 104      };
 105  };
 106  
 107  function NoContextMenu(object) {
 108      /* Decorator for a tool to suppress the context menu */
 109      object.createContextMenuElements = function(selNode, event) {
 110          return [];
 111      }
 112      return object;
 113  }
 114  
 115  // Helper function for enabling/disabling tools
 116  function KupuButtonDisable(button) {
 117      button = button || this.button;
 118      button.disabled = "disabled";
 119      button.className += ' disabled';
 120  }
 121  function KupuButtonEnable(button) {
 122      button = button || this.button;
 123      button.disabled = "";
 124      button.className = button.className.replace(/ *\bdisabled\b/g, '');
 125  }
 126  
 127  
 128  //----------------------------------------------------------------------------
 129  // Implementations
 130  //----------------------------------------------------------------------------
 131  
 132  function KupuButton(buttonid, commandfunc, tool) {
 133      /* Base prototype for kupu button tools */
 134      this.buttonid = buttonid;
 135      this.button = getFromSelector(buttonid);
 136      this.commandfunc = commandfunc;
 137      this.tool = tool;
 138  
 139      this.initialize = function(editor) {
 140          this.editor = editor;
 141          this._fixTabIndex(this.button);
 142          addEventHandler(this.button, 'click', this.execCommand, this);
 143      };
 144  
 145      this.execCommand = function() {
 146          /* exec this button's command */
 147          this.commandfunc(this, this.editor, this.tool);
 148      };
 149  
 150      this.updateState = function(selNode, event) {
 151          /* override this in subclasses to determine whether a button should
 152              look 'pressed in' or not
 153          */
 154      };
 155      this.disable = KupuButtonDisable;
 156      this.enable = KupuButtonEnable;
 157  };
 158  
 159  KupuButton.prototype = new KupuTool;
 160  function KupuStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
 161      /* A button that can have two states (e.g. pressed and
 162         not-pressed) based on CSS classes */
 163      this.buttonid = buttonid;
 164      this.button = getFromSelector(buttonid);
 165      this.commandfunc = commandfunc;
 166      this.checkfunc = checkfunc;
 167      this.offclass = offclass;
 168      this.onclass = onclass;
 169      this.pressed = false;
 170  
 171      this.execCommand = function() {
 172          /* exec this button's command */
 173          this.button.className = (this.pressed ? this.offclass : this.onclass);
 174          this.pressed = !this.pressed;
 175          this.editor.focusDocument();
 176          this.commandfunc(this, this.editor);
 177      };
 178  
 179      this.updateState = function(selNode, event) {
 180          /* check if we need to be clicked or unclicked, and update accordingly 
 181          
 182              if the state of the button should be changed, we set the class
 183          */
 184          var currclass = this.button.className;
 185          var newclass = null;
 186          if (this.checkfunc(selNode, this, this.editor, event)) {
 187              newclass = this.onclass;
 188              this.pressed = true;
 189          } else {
 190              newclass = this.offclass;
 191              this.pressed = false;
 192          };
 193          if (currclass != newclass) {
 194              this.button.className = newclass;
 195          };
 196      };
 197  };
 198  
 199  KupuStateButton.prototype = new KupuButton;
 200  
 201  /* Same as the state button, but the focusDocument call is delayed.
 202   * Mozilla&Firefox have a bug on windows which can cause a crash if you
 203   * change CSS positioning styles on an element which has focus.
 204   */
 205  function KupuLateFocusStateButton(buttonid, commandfunc, checkfunc, offclass, onclass) {
 206      KupuStateButton.apply(this, [buttonid, commandfunc, checkfunc, offclass, onclass]);
 207      this.execCommand = function() {
 208          /* exec this button's command */
 209          this.button.className = (this.pressed ? this.offclass : this.onclass);
 210          this.pressed = !this.pressed;
 211          this.commandfunc(this, this.editor);
 212          this.editor.focusDocument();
 213      };
 214  }
 215  KupuLateFocusStateButton.prototype = new KupuStateButton;
 216  
 217  function KupuRemoveElementButton(buttonid, element_name, cssclass) {
 218      /* A button specialized in removing elements in the current node
 219         context. Typical usages include removing links, images, etc. */
 220      this.button = getFromSelector(buttonid);
 221      this.onclass = 'invisible';
 222      this.offclass = cssclass;
 223      this.pressed = false;
 224  
 225      this.commandfunc = function(button, editor) {
 226          editor.removeNearestParentOfType(editor.getSelectedNode(), element_name);
 227      };
 228  
 229      this.checkfunc = function(currnode, button, editor, event) {
 230          var element = editor.getNearestParentOfType(currnode, element_name);
 231          return (element ? false : true);
 232      };
 233  };
 234  
 235  KupuRemoveElementButton.prototype = new KupuStateButton;
 236  
 237  function KupuUI(textstyleselectid) {
 238      /* View 
 239      
 240          This is the main view, which controls most of the toolbar buttons.
 241          Even though this is probably never going to be removed from the view,
 242          it was easier to implement this as a plain tool (plugin) as well.
 243      */
 244      
 245      // attributes
 246      this.tsselect = getFromSelector(textstyleselectid);
 247      var paraoptions = [];
 248      var tableoptions = [];
 249      this.optionstate = -1;
 250      this.otherstyle = null;
 251      this.tablestyles = {};
 252      this.styles = {}; // use an object here so we can use the 'in' operator later on
 253  
 254      this.initialize = function(editor) {
 255          /* initialize the ui like tools */
 256          this.editor = editor;
 257          this.cleanStyles();
 258          this.enableOptions(false);
 259          this._fixTabIndex(this.tsselect);
 260          this._selectevent = addEventHandler(this.tsselect, 'change', this.setTextStyleHandler, this);
 261      };
 262  
 263      this.getStyles = function() {
 264          if (!paraoptions) {
 265              this.cleanStyles();
 266          }
 267          return [ paraoptions, tableoptions ];
 268      }
 269  
 270      this.setTextStyleHandler = function(event) {
 271          this.setTextStyle(this.tsselect.options[this.tsselect.selectedIndex].value);
 272      };
 273      
 274      // event handlers
 275      this.basicButtonHandler = function(action) {
 276          /* event handler for basic actions (toolbar buttons) */
 277          this.editor.execCommand(action);
 278          this.editor.updateState();
 279      };
 280  
 281      this.saveButtonHandler = function() {
 282          /* handler for the save button */
 283          this.editor.saveDocument();
 284      };
 285  
 286      this.saveAndExitButtonHandler = function(redirect_url) {
 287          /* save the document and, if successful, redirect */
 288          this.editor.saveDocument(redirect_url);
 289      };
 290  
 291      this.cutButtonHandler = function() {
 292          try {
 293              this.editor.execCommand('Cut');
 294          } catch (e) {
 295              if (this.editor.getBrowserName() == 'Mozilla') {
 296                  alert(_('Cutting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
 297              } else {
 298                  throw e;
 299              };
 300          };
 301          this.editor.updateState();
 302      };
 303  
 304      this.copyButtonHandler = function() {
 305          try {
 306              this.editor.execCommand('Copy');
 307          } catch (e) {
 308              if (this.editor.getBrowserName() == 'Mozilla') {
 309                  alert(_('Copying from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
 310              } else {
 311                  throw e;
 312              };
 313          };
 314          this.editor.updateState();
 315      };
 316  
 317      this.pasteButtonHandler = function() {
 318          try {
 319              this.editor.execCommand('Paste');
 320          } catch (e) {
 321              if (this.editor.getBrowserName() == 'Mozilla') {
 322                  alert(_('Pasting from JavaScript is disabled on your Mozilla due to security settings. For more information, read http://www.mozilla.org/editor/midasdemo/securityprefs.html'));
 323              } else {
 324                  throw e;
 325              };
 326          };
 327          this.editor.updateState();
 328      };
 329  
 330      this.cleanStyles = function() {
 331          var options = this.tsselect.options;
 332          var parastyles = this.styles;
 333          var tablestyles = this.tablestyles;
 334  
 335          tableoptions.push([options[0].text, 'td|']);
 336          tablestyles['td'] = 0;
 337          paraoptions.push([options[0].text, 'p|']);
 338          parastyles['p'] = 0;
 339          while (options.length > 1) {
 340              opt = options[1];
 341              var v = opt.value;
 342              if (/^thead|tbody|table|t[rdh]\b/i.test(v)) {
 343                  var otable = tableoptions;
 344                  var styles = tablestyles;
 345              } else {
 346                  var otable = paraoptions;
 347                  var styles = parastyles;
 348              }
 349              if (v.indexOf('|') > -1) {
 350                  var split = v.split('|');
 351                  v = split[0].toLowerCase() + "|" + split[1];
 352              } else {
 353                  v = v.toLowerCase()+"|";
 354              };
 355              otable.push([opt.text, v]);
 356              styles[v] = otable.length - 1;
 357              options[1] = null;
 358          }
 359          options[0] = null;
 360      }
 361  
 362      // Remove otherstyle and switch to appropriate style set.
 363      this.enableOptions = function(inTable) {
 364          var select = this.tsselect;
 365          var options = select.options;
 366          if (this.otherstyle) {
 367              options[options.length-1] = null;
 368              this.otherstyle = null;
 369          }
 370          if (this.optionstate == inTable) return; /* No change */
 371  
 372          var valid = inTable ? tableoptions : paraoptions;
 373  
 374          while (options.length) options[0] = null;
 375          this.otherstyle = null;
 376  
 377          for (var i = 0; i < valid.length; i++) {
 378              var opt = document.createElement('option');
 379              opt.text = valid[i][0];
 380              opt.value = valid[i][1];
 381              options.add(opt);
 382          }
 383          select.selectedIndex = 0;
 384          this.optionstate = inTable;
 385      }
 386      
 387      this.setIndex = function(currnode, tag, index, styles) {
 388          var className = currnode.className;
 389          this.styletag = tag;
 390          this.classname = className;
 391          var style = tag+'|'+className;
 392  
 393          if (style in styles) {
 394              return styles[style];
 395          } else if (!className && tag in styles) {
 396              return styles[tag];
 397          }
 398          return index;
 399      }
 400  
 401      this.nodeStyle = function(node) {
 402          var currnode = node;
 403          var index = -1;
 404          var options = this.tsselect.options;
 405          this.styletag = undefined;
 406          this.classname = '';
 407          this.intable = false;
 408  
 409          while (currnode) {
 410              var tag = currnode.nodeName.toLowerCase();
 411  
 412              if (/^body$/.test(tag)) {
 413                  if (!this.styletag) {
 414                      // Force style setting
 415                      //this.setTextStyle(options[0].value, true);
 416                      // Forced style messes up in Firefox: return -1 to
 417                      // indicate no style 
 418                      return -1;
 419                  }
 420                  break;
 421              }
 422              if (/^(p|div|h.|ul|ol|dl|menu|dir|pre|blockquote|address|center)$/.test(tag)) {
 423                  index = this.setIndex(currnode, tag, index, this.styles);
 424              }
 425              if (/^thead|tbody|table|t[rdh]$/.test(tag)) {
 426                  this.intable = true;
 427                  index = this.setIndex(currnode, tag, index, this.tablestyles);
 428  
 429                  if (index > 0 || tag=='table') {
 430                      return index; // Stop processing if in a table
 431                  }
 432              }
 433              currnode = currnode.parentNode;
 434          }
 435          return index;
 436      }
 437  
 438      this.updateState = function(selNode) {
 439          /* set the text-style pulldown */
 440  
 441          // first get the nearest style
 442          // search the list of nodes like in the original one, break if we encounter a match,
 443          // this method does some more than the original one since it can handle commands in
 444          // the form of '<style>|<classname>' next to the plain
 445          // '<style>' commands
 446          var index = undefined;
 447          var mixed = false;
 448          var styletag, classname;
 449  
 450          var selection = this.editor.getSelection();
 451  
 452          for (var el=selNode.firstChild; el; el=el.nextSibling) {
 453              if (el.nodeType==1 && selection.containsNode(el)) {
 454                  var i = this.nodeStyle(el);
 455                  if (index===undefined) {
 456                      index = i;
 457                      styletag = this.styletag;
 458                      classname = this.classname;
 459                  }
 460                  if (index != i || styletag!=this.styletag || classname != this.classname) {
 461                      mixed = true;
 462                      break;
 463                  }
 464              }
 465          };
 466  
 467          if (index===undefined) {
 468              index = this.nodeStyle(selNode);
 469          }
 470  
 471          this.enableOptions(this.intable);
 472  
 473          if (index < 0 || mixed) {
 474              if (mixed) {
 475                  var caption = 'Mixed styles';
 476              } else if (this.styletag) {
 477                  var caption = 'Other: ' + this.styletag + ' '+ this.classname;
 478              } else {
 479                  var caption = '<no style>';
 480              }
 481  
 482              var opt = document.createElement('option');
 483              opt.text = caption;
 484              this.otherstyle = opt;
 485              this.tsselect.options.add(opt);
 486  
 487              index = this.tsselect.length-1;
 488          }
 489          this.tsselect.selectedIndex = Math.max(index,0);
 490      };
 491  
 492      this._cleanNode = function(node) {
 493                  /* Clean up a block style node (e.g. P, DIV, Hn)
 494                   * Remove trailing whitespace, then also remove up to one
 495                   * trailing <br>
 496                   * If the node is now empty, remove the node itself.
 497                   */
 498          var len = node.childNodes.length;
 499          function stripspace() {
 500              var c;
 501              while ((c = node.lastChild) && c.nodeType==3 && /^\s*$/.test(c.data)) {
 502                  node.removeChild(c);
 503              }
 504          }
 505          stripspace();
 506          var c = node.lastChild;
 507          if (c && c.nodeType==1 && c.tagName=='BR') {
 508              node.removeChild(c);
 509          }
 510          stripspace();
 511          if (node.childNodes.length==0) {
 512              node.parentNode.removeChild(node);
 513          };
 514      }
 515  
 516      this._cleanCell = function(eltype, classname) {
 517          var selNode = this.editor.getSelectedNode();
 518          var el = this.editor.getNearestParentOfType(selNode, eltype);
 519          if (!el) {
 520                  // Maybe changing type
 521              el = this.editor.getNearestParentOfType(selNode, eltype=='TD'?'TH':'TD');
 522          }
 523          if (!el) return;
 524  
 525              // Remove formatted div or p from a cell
 526          var node, nxt, n;
 527          for (node = el.firstChild; node;) {
 528              if (/DIV|P/.test(node.nodeName)) {
 529                  for (var n = node.firstChild; n;) {
 530                      var nxt = n.nextSibling;
 531                      el.insertBefore(n, node); // Move nodes out of div
 532                      n = nxt;
 533                  }
 534                  nxt = node.nextSibling;
 535                  el.removeChild(node);
 536                  node = nxt;
 537              } else {
 538                  node = node.nextSibling;
 539              }
 540          }
 541          if (eltype != el.tagName) {
 542                  // Change node type.
 543              var node = el.ownerDocument.createElement(eltype);
 544              var parent = el.parentNode;
 545              parent.insertBefore(node, el);
 546              while (el.firstChild) {
 547                  node.appendChild(el.firstChild);
 548              }
 549              parent.removeChild(el);
 550              el = node;
 551          }
 552              // now set the classname
 553          if (classname) {
 554              el.className = classname;
 555          } else {
 556              el.removeAttribute("class");
 557              el.removeAttribute("className");
 558          }
 559  
 560      }
 561  
 562      this._setClass = function(el, classname) {
 563          var parent = el.parentNode;
 564          if (parent.tagName=='DIV') {
 565              // fixup buggy formatting
 566              var gp = parent.parentNode;
 567              if (el != parent.firstChild) {
 568                  var previous = parent.cloneNode(false);
 569                  while (el != parent.firstChild) {
 570                      previous.appendChild(parent.firstChild);
 571                  }
 572                  gp.insertBefore(previous, parent);
 573                  this._cleanNode(previous);
 574              }
 575              gp.insertBefore(el, parent);
 576              this._cleanNode(el);
 577              this._cleanNode(parent);
 578          } else {
 579              this._cleanNode(el);
 580          }
 581          // now set the classname
 582          if (classname) {
 583              el.className = classname;
 584          } else {
 585              el.removeAttribute("class");
 586              el.removeAttribute("className");
 587          }
 588      }
 589      this.setTextStyle = function(style, noupdate) {
 590              /* parse the argument into a type and classname part
 591                 generate a block element accordingly 
 592              */
 593          var classname = '';
 594          var eltype = style.toUpperCase();
 595          if (style.indexOf('|') > -1) {
 596              style = style.split('|');
 597              eltype = style[0].toUpperCase();
 598              classname = style[1];
 599          };
 600  
 601          var command = eltype;
 602              // first create the element, then find it and set the classname
 603          if (this.editor.getBrowserName() == 'IE') {
 604              command = '<' + eltype + '>';
 605          };
 606          if (/T[RDH]/.test(eltype)) {
 607              this._cleanCell(eltype, classname);
 608          }
 609          else {
 610              this.editor.getDocument().execCommand('formatblock', command);
 611  
 612                  // now get a reference to the element just added
 613              var selNode = this.editor.getSelectedNode();
 614              var el = this.editor.getNearestParentOfType(selNode, eltype);
 615              if (el) {
 616                  this._setClass(el, classname);
 617              } else {
 618                  var selection = this.editor.getSelection();
 619                  var elements = selNode.getElementsByTagName(eltype);
 620                  for (var i = 0; i < elements.length; i++) {
 621                      el = elements[i];
 622                      if (selection.containsNode(el)) {
 623                          this._setClass(el, classname);
 624                      }
 625                  }
 626              }
 627          }
 628          if (el) {
 629              this.editor.getSelection().selectNodeContents(el);
 630          }
 631          if (!noupdate) {
 632              this.editor.updateState();
 633          }
 634      };
 635    
 636      this.createContextMenuElements = function(selNode, event) {
 637          var ret = new Array();
 638          ret.push(new ContextMenuElement(_('Cut'), 
 639                      this.cutButtonHandler, this));
 640          ret.push(new ContextMenuElement(_('Copy'), 
 641                      this.copyButtonHandler, this));
 642          ret.push(new ContextMenuElement(_('Paste'), 
 643                      this.pasteButtonHandler, this));
 644          return ret;
 645      };
 646      this.disable = function() {
 647          this.tsselect.disabled = "disabled";
 648      }
 649      this.enable = function() {
 650          this.tsselect.disabled = "";
 651      }
 652  }
 653  
 654  KupuUI.prototype = new KupuTool;
 655  
 656  function ColorchooserTool(fgcolorbuttonid, hlcolorbuttonid, colorchooserid) {
 657      /* the colorchooser */
 658      
 659      this.fgcolorbutton = getFromSelector(fgcolorbuttonid);
 660      this.hlcolorbutton = getFromSelector(hlcolorbuttonid);
 661      this.ccwindow = getFromSelector(colorchooserid);
 662      this.command = null;
 663  
 664      this.initialize = function(editor) {
 665          /* attach the event handlers */
 666          this.editor = editor;
 667          
 668          this.createColorchooser(this.ccwindow);
 669  
 670          addEventHandler(this.fgcolorbutton, "click", this.openFgColorChooser, this);
 671          addEventHandler(this.hlcolorbutton, "click", this.openHlColorChooser, this);
 672          addEventHandler(this.ccwindow, "click", this.chooseColor, this);
 673  
 674          this.hide();
 675  
 676          this.editor.logMessage(_('Colorchooser tool initialized'));
 677      };
 678  
 679      this.updateState = function(selNode) {
 680          /* update state of the colorchooser */
 681          this.hide();
 682      };
 683  
 684      this.openFgColorChooser = function() {
 685          /* event handler for opening the colorchooser */
 686          this.command = "forecolor";
 687          this.show();
 688      };
 689  
 690      this.openHlColorChooser = function() {
 691          /* event handler for closing the colorchooser */
 692          if (this.editor.getBrowserName() == "IE") {
 693              this.command = "backcolor";
 694          } else {
 695              this.command = "hilitecolor";
 696          }
 697          this.show();
 698      };
 699  
 700      this.chooseColor = function(event) {
 701          /* event handler for choosing the color */
 702          var target = _SARISSA_IS_MOZ ? event.target : event.srcElement;
 703          var cell = this.editor.getNearestParentOfType(target, 'td');
 704          this.editor.execCommand(this.command, cell.getAttribute('bgColor'));
 705          this.hide();
 706      
 707          this.editor.logMessage(_('Color chosen'));
 708      };
 709  
 710      this.show = function(command) {
 711          /* show the colorchooser */
 712          this.ccwindow.style.display = "block";
 713      };
 714  
 715      this.hide = function() {
 716          /* hide the colorchooser */
 717          this.command = null;
 718          this.ccwindow.style.display = "none";
 719      };
 720  
 721      this.createColorchooser = function(table) {
 722          /* create the colorchooser table */
 723          
 724          var chunks = new Array('00', '33', '66', '99', 'CC', 'FF');
 725          table.setAttribute('id', 'kupu-colorchooser-table');
 726          table.style.borderWidth = '2px';
 727          table.style.borderStyle = 'solid';
 728          table.style.position = 'absolute';
 729          table.style.cursor = 'default';
 730          table.style.display = 'none';
 731  
 732          var tbody = document.createElement('tbody');
 733  
 734          for (var i=0; i < 6; i++) {
 735              var tr = document.createElement('tr');
 736              var r = chunks[i];
 737              for (var j=0; j < 6; j++) {
 738                  var g = chunks[j];
 739                  for (var k=0; k < 6; k++) {
 740                      var b = chunks[k];
 741                      var color = '#' + r + g + b;
 742                      var td = document.createElement('td');
 743                      td.setAttribute('bgColor', color);
 744                      td.style.backgroundColor = color;
 745                      td.style.borderWidth = '1px';
 746                      td.style.borderStyle = 'solid';
 747                      td.style.fontSize = '1px';
 748                      td.style.width = '10px';
 749                      td.style.height = '10px';
 750                      var text = document.createTextNode('\u00a0');
 751                      td.appendChild(text);
 752                      tr.appendChild(td);
 753                  }
 754              }
 755              tbody.appendChild(tr);
 756          }
 757          table.appendChild(tbody);
 758  
 759          return table;
 760      };
 761      this.enable = function() {
 762          KupuButtonEnable(this.fgcolorbutton);
 763          KupuButtonEnable(this.hlcolorbutton);
 764      }
 765      this.disable = function() {
 766          KupuButtonDisable(this.fgcolorbutton);
 767          KupuButtonDisable(this.hlcolorbutton);
 768      }
 769  }
 770  
 771  ColorchooserTool.prototype = new KupuTool;
 772  
 773  function PropertyTool(titlefieldid, descfieldid) {
 774      /* The property tool */
 775  
 776      this.titlefield = getFromSelector(titlefieldid);
 777      this.descfield = getFromSelector(descfieldid);
 778  
 779      this.initialize = function(editor) {
 780          /* attach the event handlers and set the initial values */
 781          this.editor = editor;
 782          addEventHandler(this.titlefield, "change", this.updateProperties, this);
 783          addEventHandler(this.descfield, "change", this.updateProperties, this);
 784          
 785          // set the fields
 786          var heads = this.editor.getInnerDocument().getElementsByTagName('head');
 787          if (!heads[0]) {
 788              this.editor.logMessage(_('No head in document!'), 1);
 789          } else {
 790              var head = heads[0];
 791              var titles = head.getElementsByTagName('title');
 792              if (titles.length) {
 793                  this.titlefield.value = titles[0].text;
 794              }
 795              var metas = head.getElementsByTagName('meta');
 796              if (metas.length) {
 797                  for (var i=0; i < metas.length; i++) {
 798                      var meta = metas[i];
 799                      if (meta.getAttribute('name') && 
 800                              meta.getAttribute('name').toLowerCase() == 
 801                              'description') {
 802                          this.descfield.value = meta.getAttribute('content');
 803                          break;
 804                      }
 805                  }
 806              }
 807          }
 808  
 809          this.editor.logMessage(_('Property tool initialized'));
 810      };
 811  
 812      this.updateProperties = function() {
 813          /* event handler for updating the properties form */
 814          var doc = this.editor.getInnerDocument();
 815          var heads = doc.getElementsByTagName('HEAD');
 816          if (!heads) {
 817              this.editor.logMessage(_('No head in document!'), 1);
 818              return;
 819          }
 820  
 821          var head = heads[0];
 822  
 823          // set the title
 824          var titles = head.getElementsByTagName('title');
 825          if (!titles) {
 826              var title = doc.createElement('title');
 827              var text = doc.createTextNode(this.titlefield.value);
 828              title.appendChild(text);
 829              head.appendChild(title);
 830          } else {
 831              var title = titles[0];
 832              // IE6 title has no children, and refuses appendChild.
 833              // Delete and recreate the title.
 834              if (title.childNodes.length == 0) {
 835                  title.removeNode(true);
 836                  title = doc.createElement('title');
 837                  title.innerText = this.titlefield.value;
 838                  head.appendChild(title);
 839              } else {
 840                  title.childNodes[0].nodeValue = this.titlefield.value;
 841              }
 842          }
 843          document.title = this.titlefield.value;
 844  
 845          // let's just fulfill the usecase, not think about more properties
 846          // set the description
 847          var metas = doc.getElementsByTagName('meta');
 848          var descset = 0;
 849          for (var i=0; i < metas.length; i++) {
 850              var meta = metas[i];
 851              if (meta.getAttribute('name') && 
 852                      meta.getAttribute('name').toLowerCase() == 'description') {
 853                  meta.setAttribute('content', this.descfield.value);
 854                  descset = 1;
 855              }
 856          }
 857  
 858          if (!descset) {
 859              var meta = doc.createElement('meta');
 860              meta.setAttribute('name', 'description');
 861              meta.setAttribute('content', this.descfield.value);
 862              head.appendChild(meta);
 863          }
 864  
 865          this.editor.logMessage(_('Properties modified'));
 866      };
 867  }
 868  
 869  PropertyTool.prototype = new KupuTool;
 870  
 871  function LinkTool() {
 872      /* Add and update hyperlinks */
 873      
 874      this.initialize = function(editor) {
 875          this.editor = editor;
 876          this.editor.logMessage(_('Link tool initialized'));
 877      };
 878      
 879      this.createLinkHandler = function(event) {
 880          /* create a link according to a url entered in a popup */
 881          var linkWindow = openPopup('kupupopups/link.html', 300, 200);
 882          linkWindow.linktool = this;
 883          linkWindow.focus();
 884      };
 885  
 886      this.updateLink = function (linkel, url, type, name, target, title) {
 887          if (type && type == 'anchor') {
 888              linkel.removeAttribute('href');
 889              linkel.setAttribute('name', name);
 890          } else {
 891              linkel.href = url;
 892              if (linkel.innerHTML == "") {
 893                  var doc = this.editor.getInnerDocument();
 894                  linkel.appendChild(doc.createTextNode(title || url));
 895              }
 896              if (title) {
 897                  linkel.title = title;
 898              } else {
 899                  linkel.removeAttribute('title');
 900              }
 901              if (target && target != '') {
 902                  linkel.setAttribute('target', target);
 903              }
 904              else {
 905                  linkel.removeAttribute('target');
 906              };
 907              linkel.style.color = this.linkcolor;
 908          };
 909      };
 910  
 911      this.formatSelectedLink = function(url, type, name, target, title) {
 912          var currnode = this.editor.getSelectedNode();
 913  
 914          // selection inside link
 915          var linkel = this.editor.getNearestParentOfType(currnode, 'A');
 916          if (linkel) {
 917              this.updateLink(linkel, url, type, name, target, title);
 918              return true;
 919          }
 920  
 921          if (currnode.nodeType!=1) return false;
 922  
 923          // selection contains links
 924          var linkelements = currnode.getElementsByTagName('A');
 925          var selection = this.editor.getSelection();
 926          var containsLink = false;
 927          for (var i = 0; i < linkelements.length; i++) {
 928              linkel = linkelements[i];
 929              if (selection.containsNode(linkel)) {
 930                  this.updateLink(linkel, url, type, name, target, title);
 931                  containsLink = true;
 932              }
 933          };
 934          return containsLink;
 935      }
 936  
 937      // Can create a link in the following circumstances:
 938      //   The selection is inside a link:
 939      //      just update the link attributes.
 940      //   The selection contains links:
 941      //      update the attributes of the contained links
 942      //   No links inside or outside the selection:
 943      //      create a link around the selection
 944      //   No selection:
 945      //      insert a link containing the title
 946      //
 947      // the order of the arguments is a bit odd here because of backward
 948      // compatibility
 949      this.createLink = function(url, type, name, target, title) {
 950          if (!this.formatSelectedLink(url, type, name, target, title)) {
 951              // No links inside or outside.
 952              this.editor.execCommand("CreateLink", url);
 953              if (!this.formatSelectedLink(url, type, name, target, title)) {
 954                  // Insert link with no text selected, insert the title
 955                  // or URI instead.
 956                  var doc = this.editor.getInnerDocument();
 957                  linkel = doc.createElement("a");
 958                  linkel.setAttribute('href', url);
 959                  linkel.setAttribute('class', 'generated');
 960                  this.editor.getSelection().replaceWithNode(linkel, true);
 961                  this.updateLink(linkel, url, type, name, target, title);
 962              };
 963          }
 964          this.editor.logMessage(_('Link added'));
 965          this.editor.updateState();
 966      };
 967      
 968      this.deleteLink = function() {
 969          /* delete the current link */
 970          var currnode = this.editor.getSelectedNode();
 971          var linkel = this.editor.getNearestParentOfType(currnode, 'a');
 972          if (!linkel) {
 973              this.editor.logMessage(_('Not inside link'));
 974              return;
 975          };
 976          while (linkel.childNodes.length) {
 977              linkel.parentNode.insertBefore(linkel.childNodes[0], linkel);
 978          };
 979          linkel.parentNode.removeChild(linkel);
 980          
 981          this.editor.logMessage(_('Link removed'));
 982          this.editor.updateState();
 983      };
 984      
 985      this.createContextMenuElements = function(selNode, event) {
 986          /* create the 'Create link' or 'Remove link' menu elements */
 987          var ret = new Array();
 988          var link = this.editor.getNearestParentOfType(selNode, 'a');
 989          if (link) {
 990              ret.push(new ContextMenuElement(_('Delete link'), this.deleteLink, this));
 991          } else {
 992              ret.push(new ContextMenuElement(_('Create link'), this.createLinkHandler, this));
 993          };
 994          return ret;
 995      };
 996  }
 997  
 998  LinkTool.prototype = new KupuTool;
 999  
1000  function LinkToolBox(inputid, buttonid, toolboxid, plainclass, activeclass) {
1001      /* create and edit links */
1002      
1003      this.input = getFromSelector(inputid);
1004      this.button = getFromSelector(buttonid);
1005      this.toolboxel = getFromSelector(toolboxid);
1006      this.plainclass = plainclass;
1007      this.activeclass = activeclass;
1008      
1009      this.initialize = function(tool, editor) {
1010          /* attach the event handlers */
1011          this.tool = tool;
1012          this.editor = editor;
1013          addEventHandler(this.input, "blur", this.updateLink, this);
1014          addEventHandler(this.button, "click", this.addLink, this);
1015      };
1016  
1017      this.updateState = function(selNode) {
1018          /* if we're inside a link, update the input, else empty it */
1019          var linkel = this.editor.getNearestParentOfType(selNode, 'a');
1020          if (linkel) {
1021              // check first before setting a class for backward compatibility
1022              if (this.toolboxel) {
1023                  this.toolboxel.className = this.activeclass;
1024              };
1025              this.input.value = linkel.getAttribute('href');
1026          } else {
1027              // check first before setting a class for backward compatibility
1028              if (this.toolboxel) {
1029                  this.toolboxel.className = this.plainclass;
1030              };
1031              this.input.value = '';
1032          }
1033      };
1034      
1035      this.addLink = function(event) {
1036          /* add a link */
1037          var url = this.input.value;
1038          this.tool.createLink(url);
1039      };
1040      
1041      this.updateLink = function() {
1042          /* update the current link */
1043          var currnode = this.editor.getSelectedNode();
1044          var linkel = this.editor.getNearestParentOfType(currnode, 'A');
1045          if (!linkel) {
1046              return;
1047          }
1048  
1049          var url = this.input.value;
1050          linkel.setAttribute('href', url);
1051  
1052          this.editor.logMessage(_('Link modified'));
1053      };
1054  };
1055  
1056  LinkToolBox.prototype = new LinkToolBox;
1057  
1058  function ImageTool() {
1059      /* Image tool to add images */
1060      
1061      this.initialize = function(editor) {
1062          /* attach the event handlers */
1063          this.editor = editor;
1064          this.editor.logMessage(_('Image tool initialized'));
1065      };
1066  
1067      this.createImageHandler = function(event) {
1068          /* create an image according to a url entered in a popup */
1069          var imageWindow = openPopup('kupupopups/image.html', 300, 200);
1070          imageWindow.imagetool = this;
1071          imageWindow.focus();
1072      };
1073  
1074      this.createImage = function(url, alttext, imgclass) {
1075          /* create an image */
1076          var img = this.editor.getInnerDocument().createElement('img');
1077          img.src = url;
1078          img.removeAttribute('height');
1079          img.removeAttribute('width');
1080          if (alttext) {
1081              img.alt = alttext;
1082          };
1083          if (imgclass) {
1084              img.className = imgclass;
1085          };
1086          img = this.editor.insertNodeAtSelection(img, 1);
1087          this.editor.logMessage(_('Image inserted'));
1088          this.editor.updateState();
1089          return img;
1090      };
1091  
1092      this.setImageClass = function(imgclass) {
1093          /* set the class of the selected image */
1094          var currnode = this.editor.getSelectedNode();
1095          var currimg = this.editor.getNearestParentOfType(currnode, 'IMG');
1096          if (currimg) {
1097              currimg.className = imgclass;
1098          };
1099      };
1100  
1101      this.createContextMenuElements = function(selNode, event) {
1102          return new Array(new ContextMenuElement(_('Create image'), this.createImageHandler, this));
1103      };
1104  }
1105  
1106  ImageTool.prototype = new KupuTool;
1107  
1108  function ImageToolBox(inputfieldid, insertbuttonid, classselectid, toolboxid, plainclass, activeclass) {
1109      /* toolbox for adding images */
1110  
1111      this.inputfield = getFromSelector(inputfieldid);
1112      this.insertbutton = getFromSelector(insertbuttonid);
1113      this.classselect = getFromSelector(classselectid);
1114      this.toolboxel = getFromSelector(toolboxid);
1115      this.plainclass = plainclass;
1116      this.activeclass = activeclass;
1117  
1118      this.initialize = function(tool, editor) {
1119          this.tool = tool;
1120          this.editor = editor;
1121          addEventHandler(this.classselect, "change", this.setImageClass, this);
1122          addEventHandler(this.insertbutton, "click", this.addImage, this);
1123      };
1124  
1125      this.updateState = function(selNode, event) {
1126          /* update the state of the toolbox element */
1127          var imageel = this.editor.getNearestParentOfType(selNode, 'img');
1128          if (imageel) {
1129              // check first before setting a class for backward compatibility
1130              if (this.toolboxel) {
1131                  this.toolboxel.className = this.activeclass;
1132                  this.inputfield.value = imageel.getAttribute('src');
1133                  var imgclass = imageel.className ? imageel.className : 'image-inline';
1134                  selectSelectItem(this.classselect, imgclass);
1135              };
1136          } else {
1137              if (this.toolboxel) {
1138                  this.toolboxel.className = this.plainclass;
1139              };
1140          };
1141      };
1142  
1143      this.addImage = function() {
1144          /* add an image */
1145          var url = this.inputfield.value;
1146          var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
1147          this.tool.createImage(url, null, sel_class);
1148          this.editor.focusDocument();
1149      };
1150  
1151      this.setImageClass = function() {
1152          /* set the class for the current image */
1153          var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
1154          this.tool.setImageClass(sel_class);
1155          this.editor.focusDocument();
1156      };
1157  };
1158  
1159  ImageToolBox.prototype = new KupuToolBox;
1160  
1161  function TableTool() {
1162      /* The table tool */
1163  
1164      // XXX There are some awfully long methods in here!!
1165      this.createContextMenuElements = function(selNode, event) {
1166          var table =  this.editor.getNearestParentOfType(selNode, 'table');
1167          if (!table) {
1168              ret = new Array();
1169              var el = new ContextMenuElement(_('Add table'), this.addPlainTable, this);
1170              ret.push(el);
1171              return ret;
1172          } else {
1173              var ret = new Array();
1174              ret.push(new ContextMenuElement(_('Add row'), this.addTableRow, this));
1175              ret.push(new ContextMenuElement(_('Delete row'), this.delTableRow, this));
1176              ret.push(new ContextMenuElement(_('Add column'), this.addTableColumn, this));
1177              ret.push(new ContextMenuElement(_('Delete column'), this.delTableColumn, this));
1178              ret.push(new ContextMenuElement(_('Delete Table'), this.delTable, this));
1179              return ret;
1180          };
1181      };
1182  
1183      this.addPlainTable = function() {
1184          /* event handler for the context menu */
1185          this.createTable(2, 3, 1, 'plain');
1186      };
1187  
1188      this.createTable = function(rows, cols, makeHeader, tableclass) {
1189          /* add a table */
1190          if (rows < 1 || rows > 99 || cols < 1 || cols > 99) {
1191              this.editor.logMessage(_('Invalid table size'), 1);
1192              return;
1193          };
1194  
1195          var doc = this.editor.getInnerDocument();
1196  
1197          table = doc.createElement("table");
1198          table.className = tableclass;
1199  
1200          // If the user wants a row of headings, make them
1201          if (makeHeader) {
1202              var tr = doc.createElement("tr");
1203              var thead = doc.createElement("thead");
1204              for (i=0; i < cols; i++) {
1205                  var th = doc.createElement("th");
1206                  th.appendChild(doc.createTextNode("Col " + i+1));
1207                  tr.appendChild(th);
1208              }
1209              thead.appendChild(tr);
1210              table.appendChild(thead);
1211          }
1212  
1213          tbody = doc.createElement("tbody");
1214          for (var i=0; i < rows; i++) {
1215              var tr = doc.createElement("tr");
1216              for (var j=0; j < cols; j++) {
1217                  var td = doc.createElement("td");
1218                  var content = doc.createTextNode('\u00a0');
1219                  td.appendChild(content);
1220                  tr.appendChild(td);
1221              }
1222              tbody.appendChild(tr);
1223          }
1224          table.appendChild(tbody);
1225          this.editor.insertNodeAtSelection(table);
1226  
1227          this._setTableCellHandlers(table);
1228  
1229          this.editor.logMessage(_('Table added'));
1230          this.editor.updateState();
1231          return table;
1232      };
1233  
1234      this._setTableCellHandlers = function(table) {
1235          // make each cell select its full contents if it's clicked
1236          addEventHandler(table, 'click', this._selectContentIfEmpty, this);
1237  
1238          var cells = table.getElementsByTagName('td');
1239          for (var i=0; i < cells.length; i++) {
1240              addEventHandler(cells[i], 'click', this._selectContentIfEmpty, this);
1241          };
1242          
1243          // select the nbsp in the first cell
1244          var firstcell = cells[0];
1245          if (firstcell) {
1246              var children = firstcell.childNodes;
1247              if (children.length == 1 && children[0].nodeType == 3 && 
1248                      children[0].nodeValue == '\xa0') {
1249                  var selection = this.editor.getSelection();
1250                  selection.selectNodeContents(firstcell);
1251              };
1252          };
1253      };
1254      
1255      this._selectContentIfEmpty = function() {
1256          var selNode = this.editor.getSelectedNode();
1257          var cell = this.editor.getNearestParentOfType(selNode, 'td');
1258          if (!cell) {
1259              return;
1260          };
1261          var children = cell.childNodes;
1262          if (children.length == 1 && children[0].nodeType == 3 && 
1263                  children[0].nodeValue == '\xa0') {
1264              var selection = this.editor.getSelection();
1265              selection.selectNodeContents(cell);
1266          };
1267      };
1268  
1269      this.addTableRow = function() {
1270          /* Find the current row and add a row after it */
1271          var currnode = this.editor.getSelectedNode();
1272          var currtbody = this.editor.getNearestParentOfType(currnode, "TBODY");
1273          var bodytype = "tbody";
1274          if (!currtbody) {
1275              currtbody = this.editor.getNearestParentOfType(currnode, "THEAD");
1276              bodytype = "thead";
1277          }
1278          var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
1279          var nextrow = parentrow.nextSibling;
1280  
1281          // get the number of cells we should place
1282          var colcount = 0;
1283          for (var i=0; i < currtbody.childNodes.length; i++) {
1284              var el = currtbody.childNodes[i];
1285              if (el.nodeType != 1) {
1286                  continue;
1287              }
1288              if (el.nodeName.toLowerCase() == 'tr') {
1289                  var cols = 0;
1290                  for (var j=0; j < el.childNodes.length; j++) {
1291                      if (el.childNodes[j].nodeType == 1) {
1292                          cols++;
1293                      }
1294                  }
1295                  if (cols > colcount) {
1296                      colcount = cols;
1297                  }
1298              }
1299          }
1300  
1301          var newrow = this.editor.getInnerDocument().createElement("TR");
1302  
1303          for (var i = 0; i < colcount; i++) {
1304              var newcell;
1305              if (bodytype == 'tbody') {
1306                  newcell = this.editor.getInnerDocument().createElement("TD");
1307              } else {
1308                  newcell = this.editor.getInnerDocument().createElement("TH");
1309              }
1310              var newcellvalue = this.editor.getInnerDocument().createTextNode("\u00a0");
1311              newcell.appendChild(newcellvalue);
1312              newrow.appendChild(newcell);
1313          }
1314  
1315          if (!nextrow) {
1316              currtbody.appendChild(newrow);
1317          } else {
1318              currtbody.insertBefore(newrow, nextrow);
1319          }
1320          
1321          this.editor.focusDocument();
1322          this.editor.logMessage(_('Table row added'));
1323      };
1324  
1325      this.delTableRow = function() {
1326          /* Find the current row and delete it */
1327          var currnode = this.editor.getSelectedNode();
1328          var parentrow = this.editor.getNearestParentOfType(currnode, "TR");
1329          if (!parentrow) {
1330              this.editor.logMessage(_('No row to delete'), 1);
1331              return;
1332          }
1333  
1334          // move selection aside
1335          // XXX: doesn't work if parentrow is the only row of thead/tbody/tfoot
1336          // XXX: doesn't preserve the colindex
1337          var selection = this.editor.getSelection();
1338          if (parentrow.nextSibling) {
1339              selection.selectNodeContents(parentrow.nextSibling.firstChild);
1340          } else if (parentrow.previousSibling) {
1341              selection.selectNodeContents(parentrow.previousSibling.firstChild);
1342          };
1343  
1344          // remove the row
1345          parentrow.parentNode.removeChild(parentrow);
1346  
1347          this.editor.focusDocument();
1348          this.editor.logMessage(_('Table row removed'));
1349      };
1350  
1351      this.addTableColumn = function() {
1352          /* Add a new column after the current column */
1353          var currnode = this.editor.getSelectedNode();
1354          var currtd = this.editor.getNearestParentOfType(currnode, 'TD');
1355          if (!currtd) {
1356              currtd = this.editor.getNearestParentOfType(currnode, 'TH');
1357          }
1358          if (!currtd) {
1359              this.editor.logMessage(_('No parentcolumn found!'), 1);
1360              return;
1361          }
1362          var currtr = this.editor.getNearestParentOfType(currnode, 'TR');
1363          var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
1364          
1365          // get the current index
1366          var tdindex = this._getColIndex(currtd);
1367          // XXX this looks like a debug message, remove
1368          this.editor.logMessage(_('tdindex: $tdindex}'));
1369  
1370          // now add a column to all rows
1371          // first the thead
1372          var theads = currtable.getElementsByTagName('THEAD');
1373          if (theads) {
1374              for (var i=0; i < theads.length; i++) {
1375                  // let's assume table heads only have ths
1376                  var currthead = theads[i];
1377                  for (var j=0; j < currthead.childNodes.length; j++) {
1378                      var tr = currthead.childNodes[j];
1379                      if (tr.nodeType != 1) {
1380                          continue;
1381                      }
1382                      var currindex = 0;
1383                      for (var k=0; k < tr.childNodes.length; k++) {
1384                          var th = tr.childNodes[k];
1385                          if (th.nodeType != 1) {
1386                              continue;
1387                          }
1388                          if (currindex == tdindex) {
1389                              var doc = this.editor.getInnerDocument();
1390                              var newth = doc.createElement('th');
1391                              var text = doc.createTextNode('\u00a0');
1392                              newth.appendChild(text);
1393                              if (tr.childNodes.length == k+1) {
1394                                  // the column will be on the end of the row
1395                                  tr.appendChild(newth);
1396                              } else {
1397                                  tr.insertBefore(newth, tr.childNodes[k + 1]);
1398                              }
1399                              break;
1400                          }
1401                          currindex++;
1402                      }
1403                  }
1404              }
1405          }
1406  
1407          // then the tbody
1408          var tbodies = currtable.getElementsByTagName('TBODY');
1409          if (tbodies) {
1410              for (var i=0; i < tbodies.length; i++) {
1411                  // let's assume table heads only have ths
1412                  var currtbody = tbodies[i];
1413                  for (var j=0; j < currtbody.childNodes.length; j++) {
1414                      var tr = currtbody.childNodes[j];
1415                      if (tr.nodeType != 1) {
1416                          continue;
1417                      }
1418                      var currindex = 0;
1419                      for (var k=0; k < tr.childNodes.length; k++) {
1420                          var td = tr.childNodes[k];
1421                          if (td.nodeType != 1) {
1422                              continue;
1423                          }
1424                          if (currindex == tdindex) {
1425                              var doc = this.editor.getInnerDocument();
1426                              var newtd = doc.createElement('td');
1427                              var text = doc.createTextNode('\u00a0');
1428                              newtd.appendChild(text);
1429                              if (tr.childNodes.length == k+1) {
1430                                  // the column will be on the end of the row
1431                                  tr.appendChild(newtd);
1432                              } else {
1433                                  tr.insertBefore(newtd, tr.childNodes[k + 1]);
1434                              }
1435                              break;
1436                          }
1437                          currindex++;
1438                      }
1439                  }
1440              }
1441          }
1442          this.editor.focusDocument();
1443          this.editor.logMessage(_('Table column added'));
1444      };
1445  
1446      this.delTableColumn = function() {
1447          /* remove a column */
1448          var currnode = this.editor.getSelectedNode();
1449          var currtd = this.editor.getNearestParentOfType(currnode, 'TD');
1450          if (!currtd) {
1451              currtd = this.editor.getNearestParentOfType(currnode, 'TH');
1452          }
1453          var currcolindex = this._getColIndex(currtd);
1454          var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
1455  
1456          // move selection aside
1457          var selection = this.editor.getSelection();
1458          if (currtd.nextSibling) {
1459              selection.selectNodeContents(currtd.nextSibling);
1460          } else if (currtd.previousSibling) {
1461              selection.selectNodeContents(currtd.previousSibling);
1462          };
1463  
1464          // remove the theaders
1465          var heads = currtable.getElementsByTagName('THEAD');
1466          if (heads.length) {
1467              for (var i=0; i < heads.length; i++) {
1468                  var thead = heads[i];
1469                  for (var j=0; j < thead.childNodes.length; j++) {
1470                      var tr = thead.childNodes[j];
1471                      if (tr.nodeType != 1) {
1472                          continue;
1473                      }
1474                      var currindex = 0;
1475                      for (var k=0; k < tr.childNodes.length; k++) {
1476                          var th = tr.childNodes[k];
1477                          if (th.nodeType != 1) {
1478                              continue;
1479                          }
1480                          if (currindex == currcolindex) {
1481                              tr.removeChild(th);
1482                              break;
1483                          }
1484                          currindex++;
1485                      }
1486                  }
1487              }
1488          }
1489  
1490          // now we remove the column field, a bit harder since we need to take 
1491          // colspan and rowspan into account XXX Not right, fix theads as well
1492          var bodies = currtable.getElementsByTagName('TBODY');
1493          for (var i=0; i < bodies.length; i++) {
1494              var currtbody = bodies[i];
1495              var relevant_rowspan = 0;
1496              for (var j=0; j < currtbody.childNodes.length; j++) {
1497                  var tr = currtbody.childNodes[j];
1498                  if (tr.nodeType != 1) {
1499                      continue;
1500                  }
1501                  var currindex = 0
1502                  for (var k=0; k < tr.childNodes.length; k++) {
1503                      var cell = tr.childNodes[k];
1504                      if (cell.nodeType != 1) {
1505                          continue;
1506                      }
1507                      var colspan = cell.colSpan;
1508                      if (currindex == currcolindex) {
1509                          tr.removeChild(cell);
1510                          break;
1511                      }
1512                      currindex++;
1513                  }
1514              }
1515          }
1516          this.editor.focusDocument();
1517          this.editor.logMessage(_('Table column deleted'));
1518      };
1519  
1520      this.delTable = function() {
1521          /* delete the current table */
1522          var currnode = this.editor.getSelectedNode();
1523          var table = this.editor.getNearestParentOfType(currnode, 'table');
1524          if (!table) {
1525              this.editor.logMessage(_('Not inside a table!'));
1526              return;
1527          };
1528          table.parentNode.removeChild(table);
1529          this.editor.logMessage(_('Table removed'));
1530      };
1531  
1532      this.setColumnAlign = function(newalign) {
1533          /* change the alignment of a full column */
1534          var currnode = this.editor.getSelectedNode();
1535          var currtd = this.editor.getNearestParentOfType(currnode, "TD");
1536          var bodytype = 'tbody';
1537          if (!currtd) {
1538              currtd = this.editor.getNearestParentOfType(currnode, "TH");
1539              bodytype = 'thead';
1540          }
1541          var currcolindex = this._getColIndex(currtd);
1542          var currtable = this.editor.getNearestParentOfType(currnode, "TABLE");
1543  
1544          // unfortunately this is not enough to make the browsers display
1545          // the align, we need to set it on individual cells as well and
1546          // mind the rowspan...
1547          for (var i=0; i < currtable.childNodes.length; i++) {
1548              var currtbody = currtable.childNodes[i];
1549              if (currtbody.nodeType != 1 || 
1550                      (currtbody.nodeName.toUpperCase() != "THEAD" &&
1551                          currtbody.nodeName.toUpperCase() != "TBODY")) {
1552                  continue;
1553              }
1554              for (var j=0; j < currtbody.childNodes.length; j++) {
1555                  var row = currtbody.childNodes[j];
1556                  if (row.nodeType != 1) {
1557                      continue;
1558                  }
1559                  var index = 0;
1560                  for (var k=0; k < row.childNodes.length; k++) {
1561                      var cell = row.childNodes[k];
1562                      if (cell.nodeType != 1) {
1563                          continue;
1564                      }
1565                      if (index == currcolindex) {
1566                          if (this.editor.config.use_css) {
1567                              cell.style.textAlign = newalign;
1568                          } else {
1569                              cell.setAttribute('align', newalign);
1570                          }
1571                          cell.className = 'align-' + newalign;
1572                      }
1573                      index++;
1574                  }
1575              }
1576          }
1577      };
1578  
1579      this.setTableClass = function(sel_class) {
1580          /* set the class for the table */
1581          var currnode = this.editor.getSelectedNode();
1582          var currtable = this.editor.getNearestParentOfType(currnode, 'TABLE');
1583  
1584          if (currtable) {
1585              currtable.className = sel_class;
1586          }
1587      };
1588  
1589      this._getColIndex = function(currcell) {
1590          /* Given a node, return an integer for which column it is */
1591          var prevsib = currcell.previousSibling;
1592          var currcolindex = 0;
1593          while (prevsib) {
1594              if (prevsib.nodeType == 1 && 
1595                      (prevsib.tagName.toUpperCase() == "TD" || 
1596                          prevsib.tagName.toUpperCase() == "TH")) {
1597                  var colspan = prevsib.colSpan;
1598                  if (colspan) {
1599                      currcolindex += parseInt(colspan);
1600                  } else {
1601                      currcolindex++;
1602                  }
1603              }
1604              prevsib = prevsib.previousSibling;
1605              if (currcolindex > 30) {
1606                  alert(_("Recursion detected when counting column position"));
1607                  return;
1608              }
1609          }
1610  
1611          return currcolindex;
1612      };
1613  
1614      this._getColumnAlign = function(selNode) {
1615          /* return the alignment setting of the current column */
1616          var align;
1617          var td = this.editor.getNearestParentOfType(selNode, 'td');
1618          if (!td) {
1619              td = this.editor.getNearestParentOfType(selNode, 'th');
1620          };
1621          if (td) {
1622              align = td.getAttribute('align');
1623              if (this.editor.config.use_css) {
1624                  align = td.style.textAlign;
1625              };
1626          };
1627          return align;
1628      };
1629  
1630      this.fixTable = function(event) {
1631          /* fix the table so it can be processed by Kupu */
1632          // since this can be quite a nasty creature we can't just use the
1633          // helper methods
1634          
1635          // first we create a new tbody element
1636          var currnode = this.editor.getSelectedNode();
1637          var table = this.editor.getNearestParentOfType(currnode, 'TABLE');
1638          if (!table) {
1639              this.editor.logMessage(_('Not inside a table!'));
1640              return;
1641          };
1642          this._fixTableHelper(table);
1643      };
1644  
1645      this._isBodyRow = function(row) {
1646          for (var node = row.firstChild; node; node=node.nextSibling) {
1647              if (/TD/.test(node.nodeName)) {
1648                  return true;
1649              }
1650          }
1651          return false;
1652      }
1653  
1654      this._cleanCell = function(el) {
1655          dump('_cleanCell('+el.innerHTML+')\n');
1656          // Remove formatted div or p from a cell
1657          var node, nxt, n;
1658          for (node = el.firstChild; node;) {
1659              if (/DIV|P/.test(node.nodeName)) {
1660                  for (var n = node.firstChild; n;) {
1661                      var nxt = n.nextSibling;
1662                      el.insertBefore(n, node); // Move nodes out of div
1663                      n = nxt;
1664                  }
1665                  nxt = node.nextSibling;
1666                  el.removeChild(node);
1667                  node = nxt;
1668              } else {
1669                  node = node.nextSibling;
1670              }
1671          }
1672          var c;
1673          while (el.firstChild && (c = el.firstChild).nodeType==3 && /^\s+/.test(c.data)) {
1674              c.data = c.data.replace(/^\s+/, '');
1675              if (!c.data) {
1676                  el.removeChild(c);
1677              } else {
1678                  break;
1679              };
1680          };
1681          while (el.lastChild && (c = el.lastChild).nodeType==3 && /\s+$/.test(c.data)) {
1682              c.data = c.data.replace(/\s+$/, '');
1683              if (!c.data) {
1684                  el.removeChild(c);
1685              } else {
1686                  break;
1687              };
1688          };
1689          el.removeAttribute('colSpan');
1690          el.removeAttribute('rowSpan');
1691      }
1692      this._countCols = function(rows, numcols) {
1693          for (var i=0; i < rows.length; i++) {
1694              var row = rows[i];
1695              var currnumcols = 0;
1696              for (var node = row.firstChild; node; node=node.nextSibling) {
1697                  if (/td|th/i.test(node.nodeName)) {
1698                      currnumcols += parseInt(node.getAttribute('colSpan') || '1');
1699                  };
1700              };
1701              if (currnumcols > numcols) {
1702                  numcols = currnumcols;
1703              };
1704          };
1705          return numcols;
1706      }
1707  
1708      this._cleanRows = function(rows, container, numcols) {
1709          // now walk through all rows to clean them up
1710          for (var i=0; i < rows.length; i++) {
1711              dump("row "+i+'\n');
1712              var row = rows[i];
1713              var doc = this.editor.getInnerDocument();
1714              var newrow = doc.createElement('tr');
1715              if (row.className) {
1716                  newrow.className = row.className;
1717              }
1718              for (var node = row.firstChild; node;) {
1719                  dump("child\n");
1720                  var nxt = node.nextSibling;
1721                  if (/TD|TH/.test(node.nodeName)) {
1722                      this._cleanCell(node);
1723                      newrow.appendChild(node);
1724                  };
1725                  node = nxt;
1726              };
1727              if (newrow.childNodes.length) {
1728                  container.appendChild(newrow);
1729              };
1730          };
1731          // now make sure all rows have the correct length
1732          for (row = container.firstChild; row; row=row.nextSibling) {
1733              var cellname = row.lastChild.nodeName;
1734              while (row.childNodes.length < numcols) {
1735                  var cell = doc.createElement(cellname);
1736                  var nbsp = doc.createTextNode('\u00a0');
1737                  cell.appendChild(nbsp);
1738                  row.appendChild(cell);
1739              };
1740          };
1741      };
1742  
1743      this._fixTableHelper = function(table) {
1744          /* the code to actually fix tables */
1745          var doc = this.editor.getInnerDocument();
1746          var thead = doc.createElement('thead');
1747          var tbody = doc.createElement('tbody');
1748          var tfoot = doc.createElement('tfoot');
1749  
1750          var table_classes = this.editor.config.table_classes;
1751          function cleanClassName(name) {
1752              var allowed_classes = table_classes['class'];
1753              for (var i = 0; i < allowed_classes.length; i++) {
1754                  var classname = allowed_classes[i];
1755                  classname = classname.classname || classname;
1756                  if (classname==name) return name;
1757              };
1758              return allowed_classes[0];
1759          }
1760          if (table_classes) {
1761              table.className = cleanClassName(table.className);
1762          } else {
1763              table.removeAttribute('class');
1764              table.removeAttribute('className');
1765          };
1766          table.removeAttribute('border');
1767          table.removeAttribute('cellpadding');
1768          table.removeAttribute('cellPadding');
1769          table.removeAttribute('cellspacing');
1770          table.removeAttribute('cellSpacing');
1771  
1772          // now get all the rows of the table, the rows can either be
1773          // direct descendants of the table or inside a 'tbody', 'thead'
1774          // or 'tfoot' element
1775  
1776          var hrows = [], brows = [], frows = [];
1777          for (var node = table.firstChild; node; node = node.nextSibling) {
1778              var nodeName = node.nodeName;
1779              if (/TR/.test(node.nodeName)) {
1780                  brows.push(node);
1781              } else if (/THEAD|TBODY|TFOOT/.test(node.nodeName)) {
1782                  var rows = nodeName=='THEAD' ? hrows : nodeName=='TFOOT' ? frows : brows;
1783                  for (var inode = node.firstChild; inode; inode = inode.nextSibling) {
1784                      if (/TR/.test(inode.nodeName)) {
1785                          rows.push(inode);
1786                      };
1787                  };
1788              };
1789          };
1790          /* Extract thead and tfoot from tbody */
1791          dump('extract head and foot\n');
1792          while (brows.length && !this._isBodyRow(brows[0])) {
1793              hrows.push(brows[0]);
1794              brows.shift();
1795          }
1796          while (brows.length && !this._isBodyRow(brows[brows.length-1])) {
1797              var last = brows[brows.length-1];
1798              brows.length -= 1;
1799              frows.unshift(last);
1800          }
1801          dump('count cols\n');
1802          // now find out how many cells our rows should have
1803          var numcols = this._countCols(hrows, 0);
1804          numcols = this._countCols(brows, numcols);
1805          numcols = this._countCols(frows, numcols);
1806  
1807          dump('clean rows\n');
1808          // now walk through all rows to clean them up
1809          this._cleanRows(hrows, thead);
1810          this._cleanRows(brows, tbody);
1811          this._cleanRows(frows, tfoot);
1812  
1813          // now remove all the old stuff from the table and add the new
1814          // tbody
1815          dump('remove old\n');
1816          while (table.firstChild) {
1817              table.removeChild(table.firstChild);
1818          }
1819          if (hrows.length)
1820              table.appendChild(thead);
1821          if (brows.length)
1822              table.appendChild(tbody);
1823          if (frows.length)
1824              table.appendChild(tfoot);
1825          dump('finish up\n');
1826  
1827          this.editor.focusDocument();
1828          this.editor.logMessage(_('Table cleaned up'));
1829      };
1830  
1831      this.fixAllTables = function() {
1832          /* fix all the tables in the document at once */
1833          var tables = this.editor.getInnerDocument().getElementsByTagName('table');
1834          for (var i=0; i < tables.length; i++) {
1835              this._fixTableHelper(tables[i]);
1836          };
1837      };
1838  };
1839  
1840  TableTool.prototype = new KupuTool;
1841  
1842  function TableToolBox(addtabledivid, edittabledivid, newrowsinputid, 
1843                      newcolsinputid, makeheaderinputid, classselectid, alignselectid, addtablebuttonid,
1844                      addrowbuttonid, delrowbuttonid, addcolbuttonid, delcolbuttonid, fixbuttonid,
1845                      fixallbuttonid, toolboxid, plainclass, activeclass) {
1846      /* The table tool */
1847  
1848      // XXX There are some awfully long methods in here!!
1849      
1850  
1851      // a lot of dependencies on html elements here, but most implementations
1852      // will use them all I guess
1853      this.addtablediv = getFromSelector(addtabledivid);
1854      this.edittablediv = getFromSelector(edittabledivid);
1855      this.newrowsinput = getFromSelector(newrowsinputid);
1856      this.newcolsinput = getFromSelector(newcolsinputid);
1857      this.makeheaderinput = getFromSelector(makeheaderinputid);
1858      this.classselect = getFromSelector(classselectid);
1859      this.alignselect = getFromSelector(alignselectid);
1860      this.addtablebutton = getFromSelector(addtablebuttonid);
1861      this.addrowbutton = getFromSelector(addrowbuttonid);
1862      this.delrowbutton = getFromSelector(delrowbuttonid);
1863      this.addcolbutton = getFromSelector(addcolbuttonid);
1864      this.delcolbutton = getFromSelector(delcolbuttonid);
1865      this.fixbutton = getFromSelector(fixbuttonid);
1866      this.fixallbutton = getFromSelector(fixallbuttonid);
1867      this.toolboxel = getFromSelector(toolboxid);
1868      this.plainclass = plainclass;
1869      this.activeclass = activeclass;
1870  
1871      // register event handlers
1872      this.initialize = function(tool, editor) {
1873          /* attach the event handlers */
1874          this.tool = tool;
1875          this.editor = editor;
1876          // build the select list of table classes if configured
1877          if (this.editor.config.table_classes) {
1878              var classes = this.editor.config.table_classes['class'];
1879              while (this.classselect.hasChildNodes()) {
1880                  this.classselect.removeChild(this.classselect.firstChild);
1881              };
1882              for (var i=0; i < classes.length; i++) {
1883                  var classname = classes[i];
1884                  classname = classname.classname || classname;
1885                  var option = document.createElement('option');
1886                  var content = document.createTextNode(classname);
1887                  option.appendChild(content);
1888                  option.setAttribute('value', classname);
1889                  this.classselect.appendChild(option);
1890              };
1891          };
1892          addEventHandler(this.addtablebutton, "click", this.addTable, this);
1893          addEventHandler(this.addrowbutton, "click", this.tool.addTableRow, this.tool);
1894          addEventHandler(this.delrowbutton, "click", this.tool.delTableRow, this.tool);
1895          addEventHandler(this.addcolbutton, "click", this.tool.addTableColumn, this.tool);
1896          addEventHandler(this.delcolbutton, "click", this.tool.delTableColumn, this.tool);
1897          addEventHandler(this.alignselect, "change", this.setColumnAlign, this);
1898          addEventHandler(this.classselect, "change", this.setTableClass, this);
1899          addEventHandler(this.fixbutton, "click", this.tool.fixTable, this.tool);
1900          addEventHandler(this.fixallbutton, "click", this.tool.fixAllTables, this.tool);
1901          this.addtablediv.style.display = "block";
1902          this.edittablediv.style.display = "none";
1903          this.editor.logMessage(_('Table tool initialized'));
1904      };
1905  
1906      this.updateState = function(selNode) {
1907          /* update the state (add/edit) and update the pulldowns (if required) */
1908          var table = this.editor.getNearestParentOfType(selNode, 'table');
1909          if (table) {
1910              this.addtablediv.style.display = "none";
1911              this.edittablediv.style.display = "block";
1912  
1913              var align = this.tool._getColumnAlign(selNode);
1914              selectSelectItem(this.alignselect, align);
1915              selectSelectItem(this.classselect, table.className);
1916              if (this.toolboxel) {
1917                  this.toolboxel.className = this.activeclass;
1918              };
1919          } else {
1920              this.edittablediv.style.display = "none";
1921              this.addtablediv.style.display = "block";
1922              this.alignselect.selectedIndex = 0;
1923              this.classselect.selectedIndex = 0;
1924              if (this.toolboxel) {
1925                  this.toolboxel.className = this.plainclass;
1926              };
1927          };
1928      };
1929  
1930      this.addTable = function() {
1931          /* add a table */
1932          var rows = this.newrowsinput.value;
1933          var cols = this.newcolsinput.value;
1934          var makeHeader = this.makeheaderinput.checked;
1935          // XXX getFromSelector
1936          var classchooser = getFromSelector("kupu-table-classchooser-add");
1937          var tableclass = this.classselect.options[this.classselect.selectedIndex].value;
1938          
1939          this.tool.createTable(rows, cols, makeHeader, tableclass);
1940      };
1941  
1942      this.setColumnAlign = function() {
1943          /* set the alignment of the current column */
1944          var newalign = this.alignselect.options[this.alignselect.selectedIndex].value;
1945          this.tool.setColumnAlign(newalign);
1946      };
1947  
1948      this.setTableClass = function() {
1949          /* set the class for the current table */
1950          var sel_class = this.classselect.options[this.classselect.selectedIndex].value;
1951          if (sel_class) {
1952              this.tool.setTableClass(sel_class);
1953          };
1954      };
1955  };
1956  
1957  TableToolBox.prototype = new KupuToolBox;
1958  
1959  function ListTool(addulbuttonid, addolbuttonid, ulstyleselectid, olstyleselectid) {
1960      /* tool to set list styles */
1961  
1962      this.addulbutton = getFromSelector(addulbuttonid);
1963      this.addolbutton = getFromSelector(addolbuttonid);
1964      this.ulselect = getFromSelector(ulstyleselectid);
1965      this.olselect = getFromSelector(olstyleselectid);
1966  
1967      this.style_to_type = {'decimal': '1',
1968                              'lower-alpha': 'a',
1969                              'upper-alpha': 'A',
1970                              'lower-roman': 'i',
1971                              'upper-roman': 'I',
1972                              'disc': 'disc',
1973                              'square': 'square',
1974                              'circle': 'circle',
1975                              'none': 'none'
1976                              };
1977      this.type_to_style = {'1': 'decimal',
1978                              'a': 'lower-alpha',
1979                              'A': 'upper-alpha',
1980                              'i': 'lower-roman',
1981                              'I': 'upper-roman',
1982                              'disc': 'disc',
1983                              'square': 'square',
1984                              'circle': 'circle',
1985                              'none': 'none'
1986                              };
1987      
1988      this.initialize = function(editor) {
1989          /* attach event handlers */
1990          this.editor = editor;
1991          this._fixTabIndex(this.addulbutton);
1992          this._fixTabIndex(this.addolbutton);
1993          this._fixTabIndex(this.ulselect);
1994          this._fixTabIndex(this.olselect);
1995  
1996          addEventHandler(this.addulbutton, "click", this.addUnorderedList, this);
1997          addEventHandler(this.addolbutton, "click", this.addOrderedList, this);
1998          addEventHandler(this.ulselect, "change", this.setUnorderedListStyle, this);
1999          addEventHandler(this.olselect, "change", this.setOrderedListStyle, this);
2000          this.ulselect.style.display = "none";
2001          this.olselect.style.display = "none";
2002  
2003          this.editor.logMessage(_('List style tool initialized'));
2004      };
2005  
2006      this._handleStyles = function(currnode, onselect, offselect) {
2007          if (this.editor.config.use_css) {
2008              var currstyle = currnode.style.listStyleType;
2009          } else {
2010              var currstyle = this.type_to_style[currnode.getAttribute('type')];
2011          }
2012          selectSelectItem(onselect, currstyle);
2013          offselect.style.display = "none";
2014          onselect.style.display = "inline";
2015          offselect.selectedIndex = 0;
2016      };
2017  
2018      this.updateState = function(selNode) {
2019          /* update the visibility and selection of the list type pulldowns */
2020          // we're going to walk through the tree manually since we want to 
2021          // check on 2 items at the same time
2022          for (var currnode=selNode; currnode; currnode=currnode.parentNode) {
2023              var tag = currnode.nodeName.toLowerCase();
2024              if (tag == 'ul') {
2025                  this._handleStyles(currnode, this.ulselect, this.olselect);
2026                  return;
2027              } else if (tag == 'ol') {
2028                  this._handleStyles(currnode, this.olselect, this.ulselect);
2029                  return;
2030              }
2031          }
2032          with(this.ulselect) {
2033              selectedIndex = 0;
2034              style.display = "none";
2035          };
2036          with(this.olselect) {
2037              selectedIndex = 0;
2038              style.display = "none";
2039          };
2040      };
2041  
2042      this.addList = function(command) {
2043          this.ulselect.style.display = "inline";
2044          this.olselect.style.display = "none";
2045          this.editor.execCommand(command);
2046          this.editor.focusDocument();
2047      };
2048      this.addUnorderedList = function() {
2049          /* add an unordered list */
2050          this.addList("insertunorderedlist");
2051      };
2052  
2053      this.addOrderedList = function() {
2054          /* add an ordered list */
2055          this.addList("insertorderedlist");
2056      };
2057  
2058      this.setListStyle = function(tag, select) {
2059          /* set the type of an ul */
2060          var currnode = this.editor.getSelectedNode();
2061          var l = this.editor.getNearestParentOfType(currnode, tag);
2062          var style = select.options[select.selectedIndex].value;
2063          if (this.editor.config.use_css) {
2064              l.style.listStyleType = style;
2065          } else {
2066              l.setAttribute('type', this.style_to_type[style]);
2067          }
2068          this.editor.focusDocument();
2069          this.editor.logMessage(_('List style changed'));
2070      };
2071  
2072      this.setUnorderedListStyle = function() {
2073          /* set the type of an ul */
2074          this.setListStyle('ul', this.ulselect);
2075      };
2076  
2077      this.setOrderedListStyle = function() {
2078          /* set the type of an ol */
2079          this.setListStyle('ol', this.olselect);
2080      };
2081  
2082      this.enable = function() {
2083          KupuButtonEnable(this.addulbutton);
2084          KupuButtonEnable(this.addolbutton);
2085          this.ulselect.disabled = "";
2086          this.olselect.disabled = "";
2087      }
2088      this.disable = function() {
2089          KupuButtonDisable(this.addulbutton);
2090          KupuButtonDisable(this.addolbutton);
2091          this.ulselect.disabled = "disabled";
2092          this.olselect.disabled = "disabled";
2093      }
2094  };
2095  
2096  ListTool.prototype = new KupuTool;
2097  
2098  function ShowPathTool() {
2099      /* shows the path to the current element in the status bar */
2100  
2101      this.updateState = function(selNode) {
2102          /* calculate and display the path */
2103          var path = '';
2104          var url = null; // for links we want to display the url too
2105          var currnode = selNode;
2106          while (currnode != null && currnode.nodeName != '#document') {
2107              if (currnode.nodeName.toLowerCase() == 'a') {
2108                  url = currnode.getAttribute('href');
2109              };
2110              path = '/' + currnode.nodeName.toLowerCase() + path;
2111              currnode = currnode.parentNode;
2112          }
2113          
2114          try {
2115              window.status = url ? 
2116                      (path.toString() + ' - contains link to \'' + 
2117                          url.toString() + '\'') :
2118                      path;
2119          } catch (e) {
2120              this.editor.logMessage(_('Could not set status bar message, ' +
2121                                      'check your browser\'s security settings.'
2122                                      ), 1);
2123          };
2124      };
2125  };
2126  
2127  ShowPathTool.prototype = new KupuTool;
2128  
2129  function ViewSourceTool() {
2130      /* tool to provide a 'show source' context menu option */
2131      this.sourceWindow = null;
2132      
2133      this.viewSource = function() {
2134          /* open a window and write the current contents of the iframe to it */
2135          if (this.sourceWindow) {
2136              this.sourceWindow.close();
2137          };
2138          this.sourceWindow = window.open('#', 'sourceWindow');
2139          
2140          //var transform = this.editor._filterContent(this.editor.getInnerDocument().documentElement);
2141          //var contents = transform.xml; 
2142          var contents = '<html>\n' + this.editor.getInnerDocument().documentElement.innerHTML + '\n</html>';
2143          
2144          var doc = this.sourceWindow.document;
2145          doc.write('\xa0');
2146          doc.close();
2147          var body = doc.getElementsByTagName("body")[0];
2148          while (body.hasChildNodes()) {
2149              body.removeChild(body.firstChild);
2150          };
2151          var pre = doc.createElement('pre');
2152          var textNode = doc.createTextNode(contents);
2153          body.appendChild(pre);
2154          pre.appendChild(textNode);
2155      };
2156      
2157      this.createContextMenuElements = function(selNode, event) {
2158          /* create the context menu element */
2159          return new Array(new ContextMenuElement(_('View source'), this.viewSource, this));
2160      };
2161  };
2162  
2163  ViewSourceTool.prototype = new KupuTool;
2164  
2165  function DefinitionListTool(dlbuttonid) {
2166      /* a tool for managing definition lists
2167  
2168          the dl elements should behave much like plain lists, and the keypress
2169          behaviour should be similar
2170      */
2171  
2172      this.dlbutton = getFromSelector(dlbuttonid);
2173      
2174      this.initialize = function(editor) {
2175          /* initialize the tool */
2176          this.editor = editor;
2177          this._fixTabIndex(this.dlbutton);
2178          addEventHandler(this.dlbutton, 'click', this.createDefinitionList, this);
2179          addEventHandler(editor.getInnerDocument(), 'keyup', this._keyDownHandler, this);
2180          addEventHandler(editor.getInnerDocument(), 'keypress', this._keyPressHandler, this);
2181      };
2182  
2183      // even though the following methods may seem view related, they belong 
2184      // here, since they describe core functionality rather then view-specific
2185      // stuff
2186      this.handleEnterPress = function(selNode) {
2187          var dl = this.editor.getNearestParentOfType(selNode, 'dl');
2188          if (dl) {
2189              var dt = this.editor.getNearestParentOfType(selNode, 'dt');
2190              if (dt) {
2191                  if (dt.childNodes.length == 1 && dt.childNodes[0].nodeValue == '\xa0') {
2192                      this.escapeFromDefinitionList(dl, dt, selNode);
2193                      return;
2194                  };
2195  
2196                  var selection = this.editor.getSelection();
2197                  var startoffset = selection.startOffset();
2198                  var endoffset = selection.endOffset(); 
2199                  if (endoffset > startoffset) {
2200                      // throw away any selected stuff
2201                      selection.cutChunk(startoffset, endoffset);
2202                      selection = this.editor.getSelection();
2203                      startoffset = selection.startOffset();
2204                  };
2205                  
2206                  var ellength = selection.getElementLength(selection.parentElement());
2207                  if (startoffset >= ellength - 1) {
2208                      // create a new element
2209                      this.createDefinition(dl, dt);
2210                  } else {
2211                      var doc = this.editor.getInnerDocument();
2212                      var newdt = selection.splitNodeAtSelection(dt);
2213                      var newdd = doc.createElement('dd');
2214                      while (newdt.hasChildNodes()) {
2215                          if (newdt.firstChild != newdt.lastChild || newdt.firstChild.nodeName.toLowerCase() != 'br') {
2216                              newdd.appendChild(newdt.firstChild);
2217                          };
2218                      };
2219                      newdt.parentNode.replaceChild(newdd, newdt);
2220                      selection.selectNodeContents(newdd);
2221                      selection.collapse();
2222                  };
2223              } else {
2224                  var dd = this.editor.getNearestParentOfType(selNode, 'dd');
2225                  if (!dd) {
2226                      this.editor.logMessage(_('Not inside a definition list element!'));
2227                      return;
2228                  };
2229                  if (dd.childNodes.length == 1 && dd.childNodes[0].nodeValue == '\xa0') {
2230                      this.escapeFromDefinitionList(dl, dd, selNode);
2231                      return;
2232                  };
2233                  var selection = this.editor.getSelection();
2234                  var startoffset = selection.startOffset();
2235                  var endoffset = selection.endOffset();
2236                  if (endoffset > startoffset) {
2237                      // throw away any selected stuff
2238                      selection.cutChunk(startoffset, endoffset);
2239                      selection = this.editor.getSelection();
2240                      startoffset = selection.startOffset();
2241                  };
2242                  var ellength = selection.getElementLength(selection.parentElement());
2243                  if (startoffset >= ellength - 1) {
2244                      // create a new element
2245                      this.createDefinitionTerm(dl, dd);
2246                  } else {
2247                      // add a break and continue in this element
2248                      var br = this.editor.getInnerDocument().createElement('br');
2249                      this.editor.insertNodeAtSelection(br, 1);
2250                      //var selection = this.editor.getSelection();
2251                      //selection.moveStart(1);
2252                      selection.collapse(true);
2253                  };
2254              };
2255          };
2256      };
2257  
2258      this.handleTabPress = function(selNode) {
2259      };
2260  
2261      this._keyDownHandler = function(event) {
2262          var selNode = this.editor.getSelectedNode();
2263          var dl = this.editor.getNearestParentOfType(selNode, 'dl');
2264          if (!dl) {
2265              return;
2266          };
2267          switch (event.keyCode) {
2268              case 13:
2269                  if (event.preventDefault) {
2270                      event.preventDefault();
2271                  } else {
2272                      event.returnValue = false;
2273                  };
2274                  break;
2275          };
2276      };
2277  
2278      this._keyPressHandler = function(event) {
2279          var selNode = this.editor.getSelectedNode();
2280          var dl = this.editor.getNearestParentOfType(selNode, 'dl');
2281          if (!dl) {
2282              return;
2283          };
2284          switch (event.keyCode) {
2285              case 13:
2286                  this.handleEnterPress(selNode);
2287                  if (event.preventDefault) {
2288                      event.preventDefault();
2289                  } else {
2290                      event.returnValue = false;
2291                  };
2292                  break;
2293              case 9:
2294                  if (event.preventDefault) {
2295                      event.preventDefault();
2296                  } else {
2297                      event.returnValue = false;
2298                  };
2299                  this.handleTabPress(selNode);
2300          };
2301      };
2302  
2303      this.createDefinitionList = function() {
2304          /* create a new definition list (dl) */
2305          var selection = this.editor.getSelection();
2306          var doc = this.editor.getInnerDocument();
2307  
2308          var selection = this.editor.getSelection();
2309          var cloned = selection.cloneContents();
2310          // first get the 'first line' (until the first break) and use it
2311          // as the dt's content
2312          var iterator = new NodeIterator(cloned);
2313          var currnode = null;
2314          var remove = false;
2315          while (currnode = iterator.next()) {
2316              if (currnode.nodeName.toLowerCase() == 'br') {
2317                  remove = true;
2318              };
2319              if (remove) {
2320                  var next = currnode;
2321                  while (!next.nextSibling) {
2322                      next = next.parentNode;
2323                  };
2324                  next = next.nextSibling;
2325                  iterator.setCurrent(next);
2326                  currnode.parentNode.removeChild(currnode);
2327              };
2328          };
2329  
2330          var dtcontentcontainer = cloned;
2331          var collapsetoend = false;
2332          
2333          var dl = doc.createElement('dl');
2334          this.editor.insertNodeAtSelection(dl);
2335          var dt = this.createDefinitionTerm(dl);
2336          if (dtcontentcontainer.hasChildNodes()) {
2337              collapsetoend = true;
2338              while (dt.hasChildNodes()) {
2339                  dt.removeChild(dt.firstChild);
2340              };
2341              while (dtcontentcontainer.hasChildNodes()) {
2342                  dt.appendChild(dtcontentcontainer.firstChild);
2343              };
2344          };
2345  
2346          var selection = this.editor.getSelection();
2347          selection.selectNodeContents(dt);
2348          selection.collapse(collapsetoend);
2349      };
2350  
2351      this.createDefinitionTerm = function(dl, dd) {
2352          /* create a new definition term inside the current dl */
2353          var doc = this.editor.getInnerDocument();
2354          var dt = doc.createElement('dt');
2355          // somehow Mozilla seems to add breaks to all elements...
2356          if (dd) {
2357              if (dd.lastChild.nodeName.toLowerCase() == 'br') {
2358                  dd.removeChild(dd.lastChild);
2359              };
2360          };
2361          // dd may be null here, if so we assume this is the first element in 
2362          // the dl
2363          if (!dd || dl == dd.lastChild) {
2364              dl.appendChild(dt);
2365          } else {
2366              var nextsibling = dd.nextSibling;
2367              if (nextsibling) {
2368                  dl.insertBefore(dt, nextsibling);
2369              } else {
2370                  dl.appendChild(dt);
2371              };
2372          };
2373          var nbsp = doc.createTextNode('\xa0');
2374          dt.appendChild(nbsp);
2375          var selection = this.editor.getSelection();
2376          selection.selectNodeContents(dt);
2377          selection.collapse();
2378  
2379          this.editor.focusDocument();
2380          return dt;
2381      };
2382  
2383      this.createDefinition = function(dl, dt, initial_content) {
2384          var doc = this.editor.getInnerDocument();
2385          var dd = doc.createElement('dd');
2386          var nextsibling = dt.nextSibling;
2387          // somehow Mozilla seems to add breaks to all elements...
2388          if (dt) {
2389              if (dt.lastChild.nodeName.toLowerCase() == 'br') {
2390                  dt.removeChild(dt.lastChild);
2391              };
2392          };
2393          while (nextsibling) {
2394              var name = nextsibling.nodeName.toLowerCase();
2395              if (name == 'dd' || name == 'dt') {
2396                  break;
2397              } else {
2398                  nextsibling = nextsibling.nextSibling;
2399              };
2400          };
2401          if (nextsibling) {
2402              dl.insertBefore(dd, nextsibling);
2403              //this._fixStructure(doc, dl, nextsibling);
2404          } else {
2405              dl.appendChild(dd);
2406          };
2407          if (initial_content) {
2408              for (var i=0; i < initial_content.length; i++) {
2409                  dd.appendChild(initial_content[i]);
2410              };
2411          };
2412          var nbsp = doc.createTextNode('\xa0');
2413          dd.appendChild(nbsp);
2414          var selection = this.editor.getSelection();
2415          selection.selectNodeContents(dd);
2416          selection.collapse();
2417      };
2418  
2419      this.escapeFromDefinitionList = function(dl, currel, selNode) {
2420          var doc = this.editor.getInnerDocument();
2421          var p = doc.createElement('p');
2422          var nbsp = doc.createTextNode('\xa0');
2423          p.appendChild(nbsp);
2424  
2425          if (dl.lastChild == currel) {
2426              dl.parentNode.insertBefore(p, dl.nextSibling);
2427          } else {
2428              for (var i=0; i < dl.childNodes.length; i++) {
2429                  var child = dl.childNodes[i];
2430                  if (child == currel) {
2431                      var newdl = this.editor.getInnerDocument().createElement('dl');
2432                      while (currel.nextSibling) {
2433                          newdl.appendChild(currel.nextSibling);
2434                      };
2435                      dl.parentNode.insertBefore(newdl, dl.nextSibling);
2436                      dl.parentNode.insertBefore(p, dl.nextSibling);
2437                  };
2438              };
2439          };
2440          currel.parentNode.removeChild(currel);
2441          var selection = this.editor.getSelection();
2442          selection.selectNodeContents(p);
2443          selection.collapse();
2444          this.editor.focusDocument();
2445      };
2446  
2447      this._fixStructure = function(doc, dl, offsetnode) {
2448          /* makes sure the order of the elements is correct */
2449          var currname = offsetnode.nodeName.toLowerCase();
2450          var currnode = offsetnode.nextSibling;
2451          while (currnode) {
2452              if (currnode.nodeType == 1) {
2453                  var nodename = currnode.nodeName.toLowerCase();
2454                  if (currname == 'dt' && nodename == 'dt') {
2455                      var dd = doc.createElement('dd');
2456                      while (currnode.hasChildNodes()) {
2457                          dd.appendChild(currnode.childNodes[0]);
2458                      };
2459                      currnode.parentNode.replaceChild(dd, currnode);
2460                  } else if (currname == 'dd' && nodename == 'dd') {
2461                      var dt = doc.createElement('dt');
2462                      while (currnode.hasChildNodes()) {
2463                          dt.appendChild(currnode.childNodes[0]);
2464                      };
2465                      currnode.parentNode.replaceChild(dt, currnode);
2466                  };
2467              };
2468              currnode = currnode.nextSibling;
2469          };
2470      };
2471  };
2472  
2473  DefinitionListTool.prototype = new KupuTool;
2474  
2475  function KupuZoomTool(buttonid, firsttab, lasttab) {
2476      this.button = getFromSelector(buttonid);
2477      firsttab = firsttab || 'kupu-tb-styles';
2478      lasttab = lasttab || 'kupu-logo-button';
2479  
2480      this.initialize = function(editor) {
2481          this.offclass = 'kupu-zoom';
2482          this.onclass = 'kupu-zoom-pressed';
2483          this.pressed = false;
2484  
2485          this.baseinitialize(editor);
2486          this.button.tabIndex = this.editor.document.editable.tabIndex;
2487          addEventHandler(window, "resize", this.onresize, this);
2488          addEventHandler(window, "scroll", this.onscroll, this);
2489  
2490          /* Toolbar tabbing */
2491          var lastbutton = getFromSelector(lasttab);
2492          var firstbutton = getFromSelector(firsttab);
2493          var iframe = editor.getInnerDocument();
2494          this.setTabbing(iframe, firstbutton, lastbutton);
2495          this.setTabbing(firstbutton, null, editor.getDocument().getWindow());
2496  
2497          this.editor.logMessage(_('Zoom tool initialized'));
2498      };
2499  };
2500  
2501  KupuZoomTool.prototype = new KupuLateFocusStateButton;
2502  KupuZoomTool.prototype.baseinitialize = KupuZoomTool.prototype.initialize;
2503  
2504  KupuZoomTool.prototype.onscroll = function() {
2505      if (!this.zoomed) return;
2506      /* XXX Problem here: Mozilla doesn't generate onscroll when window is
2507       * scrolled by focus move or selection. */
2508      var top = window.pageYOffset!=undefined ? window.pageYOffset : document.documentElement.scrollTop;
2509      var left = window.pageXOffset!=undefined ? window.pageXOffset : document.documentElement.scrollLeft;
2510      if (top || left) window.scrollTo(0, 0);
2511  }
2512  
2513  // Handle tab pressed from a control.
2514  KupuZoomTool.prototype.setTabbing = function(control, forward, backward) {
2515      function TabDown(event) {
2516          if (event.keyCode != 9 || !this.zoomed) return;
2517  
2518          var target = event.shiftKey ? backward : forward;
2519          if (!target) return;
2520  
2521          if (event.stopPropogation) event.stopPropogation();
2522          event.cancelBubble = true;
2523          event.returnValue = false;
2524  
2525          target.focus();
2526          return false;
2527      }
2528      addEventHandler(control, "keydown", TabDown, this);
2529  }
2530  
2531  KupuZoomTool.prototype.onresize = function() {
2532      if (!this.zoomed) return;
2533  
2534      var editor = this.editor;
2535      var iframe = editor.getDocument().editable;
2536      var sourcetool = editor.getTool('sourceedittool');
2537      var sourceArea = sourcetool?sourcetool.getSourceArea():null;
2538  
2539      var fulleditor = iframe.parentNode;
2540      var body = document.body;
2541  
2542      if (window.innerWidth) {
2543          var width = window.innerWidth;
2544          var height = window.innerHeight;
2545      } else if (document.documentElement) {
2546          var width = document.documentElement.offsetWidth-5;
2547          var height = document.documentElement.offsetHeight-5;
2548      } else {
2549          var width = document.body.offsetWidth-5;
2550          var height = document.body.offsetHeight-5;
2551      }
2552      width = width + 'px';
2553      var offset = iframe.offsetTop;
2554      if (sourceArea) offset = sourceArea.offsetTop-1;
2555      // XXX: TODO: Using wrong values here, figure out why.
2556      var nheight = Math.max(height - offset -1/*top border*/, 10);
2557      nheight = nheight + 'px';
2558      fulleditor.style.width = width; /*IE needs this*/
2559      iframe.style.width = width;
2560      iframe.style.height = nheight;
2561      if (sourceArea) {
2562          sourceArea.style.width = width;
2563          sourceArea.style.height = height;
2564      }
2565  }
2566  
2567  KupuZoomTool.prototype.checkfunc = function(selNode, button, editor, event) {
2568      return this.zoomed;
2569  }
2570  
2571  KupuZoomTool.prototype.commandfunc = function(button, editor) {
2572      /* Toggle zoom state */
2573      var zoom = button.pressed;
2574      this.zoomed = zoom;
2575  
2576      var zoomClass = 'kupu-fulleditor-zoomed';
2577      var iframe = editor.getDocument().getEditable();
2578  
2579      var body = document.body;
2580      var html = document.getElementsByTagName('html')[0];
2581      if (zoom) {
2582          html.style.overflow = 'hidden';
2583          window.scrollTo(0, 0);
2584          editor.setClass(zoomClass);
2585          body.className += ' '+zoomClass;
2586          this.onresize();
2587      } else {
2588          html.style.overflow = '';
2589          var fulleditor = iframe.parentNode;
2590          fulleditor.style.width = '';
2591          body.className = body.className.replace(/ *kupu-fulleditor-zoomed/, '');
2592          editor.clearClass(zoomClass);
2593  
2594          iframe.style.width = '';
2595          iframe.style.height = '';
2596  
2597          var sourcetool = editor.getTool('sourceedittool');
2598          var sourceArea = sourcetool?sourcetool.getSourceArea():null;
2599          if (sourceArea) {
2600              sourceArea.style.width = '';
2601              sourceArea.style.height = '';
2602          };
2603      }
2604      var doc = editor.getInnerDocument();
2605      // Mozilla needs this. Yes, really!
2606      doc.designMode=doc.designMode;
2607  
2608      window.scrollTo(0, iframe.offsetTop);
2609      editor.focusDocument();
2610  }


Généré le : Sun Feb 25 15:30:41 2007 par Balluche grâce à PHPXref 0.7