[ Index ]
 

Code source de Kupu-1.3.5

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

title

Body

[fermer]

/common/ -> kupueditor.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: kupueditor.js 18104 2005-10-03 14:10:11Z duncan $
  12  
  13  //----------------------------------------------------------------------------
  14  // Main classes
  15  //----------------------------------------------------------------------------
  16  
  17  /* KupuDocument
  18      
  19      This essentially wraps the iframe.
  20      XXX Is this overkill?
  21      
  22  */
  23  
  24  function KupuDocument(iframe) {
  25      /* Model */
  26      
  27      // attrs
  28      this.editable = iframe; // the iframe
  29      this.window = this.editable.contentWindow;
  30      this.document = this.window.document;
  31  
  32      this._browser = _SARISSA_IS_IE ? 'IE' : 'Mozilla';
  33      
  34      // methods
  35      this.execCommand = function(command, arg) {
  36          /* delegate execCommand */
  37          if (arg === undefined) arg = null;
  38          this.document.execCommand(command, false, arg);
  39      };
  40      
  41      this.reloadSource = function() {
  42          /* reload the source */
  43          
  44          // XXX To temporarily work around problems with resetting the
  45          // state after a reload, currently the whole page is reloaded.
  46          // XXX Nasty workaround!! to solve refresh problems...
  47          document.location = document.location;
  48      };
  49  
  50      this.getDocument = function() {
  51          /* returns a reference to the window.document object of the iframe */
  52          return this.document;
  53      };
  54  
  55      this.getWindow = function() {
  56          /* returns a reference to the window object of the iframe */
  57          return this.window;
  58      };
  59  
  60      this.getSelection = function() {
  61          if (this._browser == 'Mozilla') {
  62              return new MozillaSelection(this);
  63          } else {
  64              return new IESelection(this);
  65          };
  66      };
  67  
  68      this.getEditable = function() {
  69          return this.editable;
  70      };
  71  };
  72  
  73  /* KupuEditor
  74  
  75      This controls the document, should be used from the UI.
  76      
  77  */
  78  
  79  function KupuEditor(document, config, logger) {
  80      /* Controller */
  81      
  82      // attrs
  83      this.document = document; // the model
  84      this.config = config; // an object that holds the config values
  85      this.log = logger; // simple logger object
  86      this.tools = {}; // mapping id->tool
  87      this.filters = new Array(); // contentfilters
  88      
  89      this._designModeSetAttempts = 0;
  90      this._initialized = false;
  91  
  92      // some properties to save the selection, required for IE to remember 
  93      // where in the iframe the selection was
  94      this._previous_range = null;
  95  
  96      // this property is true if the content is changed, false if no changes 
  97      // are made yet
  98      this.content_changed = false;
  99  
 100      // methods
 101      this.initialize = function() {
 102          /* Should be called on iframe.onload, will initialize the editor */
 103          //DOM2Event.initRegistration();
 104          this._initializeEventHandlers();
 105          if (this.getBrowserName() == "IE") {
 106              var body = this.getInnerDocument().getElementsByTagName('body')[0];
 107              body.setAttribute('contentEditable', 'true');
 108              // provide an 'afterInit' method on KupuEditor.prototype
 109              // for additional bootstrapping (after editor init)
 110              this._initialized = true;
 111              if (this.afterInit) {
 112                  this.afterInit();
 113              };
 114              this._saveSelection();
 115          } else {
 116              this._setDesignModeWhenReady();
 117          };
 118          this.logMessage(_('Editor initialized'));
 119      };
 120  
 121      this.setContextMenu = function(menu) {
 122          /* initialize the contextmenu */
 123          menu.initialize(this);
 124      };
 125  
 126      this.registerTool = function(id, tool) {
 127          /* register a tool */
 128          this.tools[id] = tool;
 129          tool.initialize(this);
 130      };
 131  
 132      this.getTool = function(id) {
 133          /* get a tool by id */
 134          return this.tools[id];
 135      };
 136  
 137      this.registerFilter = function(filter) {
 138          /* register a content filter method
 139  
 140              the method will be called together with any other registered
 141              filters before the content is saved to the server, the methods
 142              can be used to filter any trash out of the content. they are
 143              called with 1 argument, which is a reference to the rootnode
 144              of the content tree (the html node)
 145          */
 146          this.filters.push(filter);
 147          filter.initialize(this);
 148      };
 149  
 150      this.updateStateHandler = function(event) {
 151          /* check whether the event is interesting enough to trigger the 
 152          updateState machinery and act accordingly */
 153          var interesting_codes = new Array(8, 13, 37, 38, 39, 40, 46);
 154          // unfortunately it's not possible to do this on blur, since that's
 155          // too late. also (some versions of?) IE 5.5 doesn't support the
 156          // onbeforedeactivate event, which would be ideal here...
 157          this._saveSelection();
 158  
 159          if (event.type == 'click' || event.type=='mouseup' ||
 160                  (event.type == 'keyup' && 
 161                      interesting_codes.contains(event.keyCode))) {
 162              // Filthy trick to make the updateState method get called *after*
 163              // the event has been resolved. This way the updateState methods can
 164              // react to the situation *after* any actions have been performed (so
 165              // can actually stay up to date).
 166              this.updateState(event);
 167          }
 168      };
 169      
 170      this.updateState = function(event) {
 171          /* let each tool change state if required */
 172          // first see if the event is interesting enough to trigger
 173          // the whole updateState machinery
 174          var selNode = this.getSelectedNode();
 175          for (var id in this.tools) {
 176              try {
 177                  this.tools[id].updateState(selNode, event);
 178              } catch (e) {
 179                  if (e == UpdateStateCancelBubble) {
 180                      this.updateState(event);
 181                      break;
 182                  } else {
 183                      this.logMessage(
 184                          _('Exception while processing updateState on ' +
 185                              '$id}: $msg}', {'id': id, 'msg': e}), 2);
 186                  };
 187              };
 188          };
 189      };
 190      
 191      this.saveDocument = function(redirect, synchronous) {
 192          /* save the document
 193  
 194              the (optional) redirect argument can be used to make the client 
 195              jump to another URL when the save action was successful.
 196  
 197              synchronous is a boolean to allow sync saving (usually better to
 198              not save synchronous, since it may make browsers freeze on errors,
 199              this is used for saveOnPart, though)
 200          */
 201          
 202          // if no dst is available, bail out
 203          if (!this.config.dst) {
 204              this.logMessage(_('No destination URL available!'), 2);
 205              return;
 206          }
 207          var sourcetool = this.getTool('sourceedittool');
 208          if (sourcetool) {sourcetool.cancelSourceMode();};
 209  
 210          // make sure people can't edit or save during saving
 211          if (!this._initialized) {
 212              return;
 213          }
 214          this._initialized = false;
 215          
 216          // set the window status so people can see we're actually saving
 217          window.status= _("Please wait while saving document...");
 218  
 219          // call (optional) beforeSave() method on all tools
 220          for (var id in this.tools) {
 221              var tool = this.tools[id];
 222              if (tool.beforeSave) {
 223                  try {
 224                      tool.beforeSave();
 225                  } catch(e) {
 226                      alert(e);
 227                      this._initialized = true;
 228                      return;
 229                  };
 230              };
 231          };
 232          
 233          // pass the content through the filters
 234          this.logMessage(_("Starting HTML cleanup"));
 235          var transform = this._filterContent(this.getInnerDocument().documentElement);
 236  
 237          // serialize to a string
 238          var contents = this._serializeOutputToString(transform);
 239          
 240          this.logMessage(_("Cleanup done, sending document to server"));
 241          var request = new XMLHttpRequest();
 242      
 243          if (!synchronous) {
 244              request.onreadystatechange = (new ContextFixer(this._saveCallback, 
 245                                                 this, request, redirect)).execute;
 246              request.open("PUT", this.config.dst, true);
 247              request.setRequestHeader("Content-type", this.config.content_type);
 248              request.send(contents);
 249              this.logMessage(_("Request sent to server"));
 250          } else {
 251              this.logMessage(_('Sending request to server'));
 252              request.open("PUT", this.config.dst, false);
 253              request.setRequestHeader("Content-type", this.config.content_type);
 254              request.send(contents);
 255              this.handleSaveResponse(request,redirect)
 256          };
 257      };
 258      
 259      this.prepareForm = function(form, id) {
 260          /* add a field to the form and place the contents in it
 261  
 262              can be used for simple POST support where Kupu is part of a
 263              form
 264          */
 265          var sourcetool = this.getTool('sourceedittool');
 266          if (sourcetool) {sourcetool.cancelSourceMode();};
 267  
 268          // make sure people can't edit or save during saving
 269          if (!this._initialized) {
 270              return;
 271          }
 272          this._initialized = false;
 273          
 274          // set the window status so people can see we're actually saving
 275          window.status= _("Please wait while saving document...");
 276  
 277          // call (optional) beforeSave() method on all tools
 278          for (var tid in this.tools) {
 279              var tool = this.tools[tid];
 280              if (tool.beforeSave) {
 281                  try {
 282                      tool.beforeSave();
 283                  } catch(e) {
 284                      alert(e);
 285                      this._initialized = true;
 286                      return;
 287                  };
 288              };
 289          };
 290          
 291          // set a default id
 292          if (!id) {
 293              id = 'kupu';
 294          };
 295          
 296          // pass the content through the filters
 297          this.logMessage(_("Starting HTML cleanup"));
 298          var transform = this._filterContent(this.getInnerDocument().documentElement);
 299          
 300          // XXX need to fix this.  Sometimes a spurious "\n\n" text 
 301          // node appears in the transform, which breaks the Moz 
 302          // serializer on .xml
 303          var contents =  this._serializeOutputToString(transform);
 304          
 305          this.logMessage(_("Cleanup done, sending document to server"));
 306          
 307          // now create the form input, since IE 5.5 doesn't support the 
 308          // ownerDocument property we use window.document as a fallback (which
 309          // will almost by definition be correct).
 310          var document = form.ownerDocument ? form.ownerDocument : window.document;
 311          var ta = document.createElement('textarea');
 312          ta.style.visibility = 'hidden';
 313          var text = document.createTextNode(contents);
 314          ta.appendChild(text);
 315          ta.setAttribute('name', id);
 316          
 317          // and add it to the form
 318          form.appendChild(ta);
 319  
 320          // let the calling code know we have added the textarea
 321          return true;
 322      };
 323  
 324      this.execCommand = function(command, param) {
 325          /* general stuff like making current selection bold, italics etc. 
 326              and adding basic elements such as lists
 327              */
 328          if (!this._initialized) {
 329              this.logMessage(_('Editor not initialized yet!'));
 330              return;
 331          };
 332          if (this.getBrowserName() == "IE") {
 333              this._restoreSelection();
 334          } else {
 335              this.focusDocument();
 336              if (command != 'useCSS') {
 337                  this.content_changed = true;
 338                  // note the negation: the argument doesn't work as
 339                  // expected...
 340                  // Done here otherwise it doesn't always work or gets lost
 341                  // after some commands
 342                  this.getDocument().execCommand('useCSS', !this.config.use_css);
 343              };
 344          };
 345          this.getDocument().execCommand(command, param);
 346          var message = _('Command $command} executed', {'command': command});
 347          if (param) {
 348              message = _('Command $command} executed with parameter $param}',
 349                              {'command': command, 'param': param});
 350          }
 351          this.updateState();
 352          this.logMessage(message);
 353      };
 354  
 355      this.getSelection = function() {
 356          /* returns a Selection object wrapping the current selection */
 357          this._restoreSelection();
 358          return this.getDocument().getSelection();
 359      };
 360  
 361      this.getSelectedNode = function() {
 362          /* returns the selected node (read: parent) or none */
 363          return this.getSelection().parentElement();
 364      };
 365  
 366      this.getNearestParentOfType = function(node, type) {
 367          /* well the title says it all ;) */
 368          var type = type.toLowerCase();
 369          while (node) {
 370              if (node.nodeName.toLowerCase() == type) {
 371                  return node
 372              }   
 373              var node = node.parentNode;
 374          }
 375          return false;
 376      };
 377  
 378      this.removeNearestParentOfType = function(node, type) {
 379          var nearest = this.getNearestParentOfType(node, type);
 380          if (!nearest) {
 381              return false;
 382          };
 383          var parent = nearest.parentNode;
 384          while (nearest.childNodes.length) {
 385              var child = nearest.firstChild;
 386              child = nearest.removeChild(child);
 387              parent.insertBefore(child, nearest);
 388          };
 389          parent.removeChild(nearest);
 390      };
 391  
 392      this.getDocument = function() {
 393          /* returns a reference to the document object that wraps the iframe */
 394          return this.document;
 395      };
 396  
 397      this.getInnerDocument = function() {
 398          /* returns a reference to the window.document object of the iframe */
 399          return this.getDocument().getDocument();
 400      };
 401  
 402      this.insertNodeAtSelection = function(insertNode, selectNode) {
 403          /* insert a newly created node into the document */
 404          if (!this._initialized) {
 405              this.logMessage(_('Editor not initialized yet!'));
 406              return;
 407          };
 408  
 409          this.content_changed = true;
 410  
 411          var browser = this.getBrowserName();
 412          if (browser != "IE") {
 413              this.focusDocument();
 414          };
 415          
 416          var ret = this.getSelection().replaceWithNode(insertNode, selectNode);
 417          this._saveSelection();
 418  
 419          return ret;
 420      };
 421  
 422      this.focusDocument = function() {
 423          this.getDocument().getWindow().focus();
 424      }
 425  
 426      this.logMessage = function(message, severity) {
 427          /* log a message using the logger, severity can be 0 (message, default), 1 (warning) or 2 (error) */
 428          this.log.log(message, severity);
 429      };
 430  
 431      this.registerContentChanger = function(element) {
 432          /* set this.content_changed to true (marking the content changed) when the 
 433              element's onchange is called
 434          */
 435          addEventHandler(element, 'change', function() {this.content_changed = true;}, this);
 436      };
 437      
 438      // helper methods
 439      this.getBrowserName = function() {
 440          /* returns either 'Mozilla' (for Mozilla, Firebird, Netscape etc.) or 'IE' */
 441          if (_SARISSA_IS_MOZ) {
 442              return "Mozilla";
 443          } else if (_SARISSA_IS_IE) {
 444              return "IE";
 445          } else {
 446              throw _("Browser not supported!");
 447          }
 448      };
 449      
 450      this.handleSaveResponse = function(request, redirect) {
 451          // mind the 1223 status, somehow IE gives that sometimes (on 204?)
 452          // at first we didn't want to add it here, since it's a specific IE
 453          // bug, but too many users had trouble with it...
 454          if (request.status != '200' && request.status != '204' &&
 455                  request.status != '1223') {
 456              var msg = _('Error saving your data.\nResponse status: ' + 
 457                              '$status}.\nCheck your server log for more ' +
 458                              'information.', {'status': request.status});
 459              alert(msg);
 460              window.status = _("Error saving document");
 461          } else if (redirect) { // && (!request.status || request.status == '200' || request.status == '204'))
 462              window.document.location = redirect;
 463              this.content_changed = false;
 464          } else {
 465              // clear content_changed before reloadSrc so saveOnPart is not triggered
 466              this.content_changed = false;
 467              if (this.config.reload_after_save) {
 468                  this.reloadSrc();
 469              };
 470              // we're done so we can start editing again
 471              window.status= _("Document saved");
 472          };
 473          this._initialized = true;
 474      };
 475  
 476      // private methods
 477      this._addEventHandler = addEventHandler;
 478  
 479      this._saveCallback = function(request, redirect) {
 480          /* callback for Sarissa */
 481          if (request.readyState == 4) {
 482              this.handleSaveResponse(request, redirect)
 483          };
 484      };
 485      
 486      this.reloadSrc = function() {
 487          /* reload the src, called after a save when reload_src is set to true */
 488          // XXX Broken!!!
 489          /*
 490          if (this.getBrowserName() == "Mozilla") {
 491              this.getInnerDocument().designMode = "Off";
 492          }
 493          */
 494          // XXX call reloadSrc() which has a workaround, reloads the full page
 495          // instead of just the iframe...
 496          this.getDocument().reloadSource();
 497          if (this.getBrowserName() == "Mozilla") {
 498              this.getInnerDocument().designMode = "On";
 499          };
 500          /*
 501          var selNode = this.getSelectedNode();
 502          this.updateState(selNode);
 503          */
 504      };
 505  
 506      this._initializeEventHandlers = function() {
 507          /* attache the event handlers to the iframe */
 508          // Initialize DOM2Event compatibility
 509          // XXX should come back and change to passing in an element
 510          this._addEventHandler(this.getInnerDocument(), "click", this.updateStateHandler, this);
 511          this._addEventHandler(this.getInnerDocument(), "dblclick", this.updateStateHandler, this);
 512          this._addEventHandler(this.getInnerDocument(), "keyup", this.updateStateHandler, this);
 513          this._addEventHandler(this.getInnerDocument(), "keyup", function() {this.content_changed = true}, this);
 514          this._addEventHandler(this.getInnerDocument(), "mouseup", this.updateStateHandler, this);
 515      };
 516  
 517      this._setDesignModeWhenReady = function() {
 518          /* Rather dirty polling loop to see if Mozilla is done doing it's
 519              initialization thing so design mode can be set.
 520          */
 521          this._designModeSetAttempts++;
 522          if (this._designModeSetAttempts > 25) {
 523              alert(_('Couldn\'t set design mode. Kupu will not work on this browser.'));
 524              return;
 525          };
 526          var success = false;
 527          try {
 528              this._setDesignMode();
 529              success = true;
 530          } catch (e) {
 531              // register a function to the timer_instance because 
 532              // window.setTimeout can't refer to 'this'...
 533              timer_instance.registerFunction(this, this._setDesignModeWhenReady, 100);
 534          };
 535          if (success) {
 536              // provide an 'afterInit' method on KupuEditor.prototype
 537              // for additional bootstrapping (after editor init)
 538              if (this.afterInit) {
 539                  this.afterInit();
 540              };
 541          };
 542      };
 543  
 544      this._setDesignMode = function() {
 545          this.getInnerDocument().designMode = "On";
 546          this.execCommand("undo");
 547          // note the negation: the argument doesn't work as expected...
 548          this._initialized = true;
 549      };
 550  
 551      this._saveSelection = function() {
 552          /* Save the selection, works around a problem with IE where the 
 553           selection in the iframe gets lost. We only save if the current 
 554           selection in the document */
 555          if (this._isDocumentSelected()) {
 556              var currange = this.getInnerDocument().selection.createRange();
 557              this._previous_range = currange;
 558          };
 559      };
 560  
 561      this._restoreSelection = function() {
 562          /* re-selects the previous selection in IE. We only restore if the
 563          current selection is not in the document.*/
 564          if (this._previous_range && !this._isDocumentSelected()) {
 565              try {
 566                  this._previous_range.select();
 567              } catch (e) {
 568                  alert("Error placing back selection");
 569                  this.logMessage(_('Error placing back selection'));
 570              };
 571          };
 572      };
 573      
 574      if (this.getBrowserName() != "IE") {
 575          this._saveSelection = function() {};
 576          this._restoreSelection = function() {};
 577      }
 578  
 579      this._isDocumentSelected = function() {
 580          var editable_body = this.getInnerDocument().getElementsByTagName('body')[0];
 581          try {
 582              var selrange = this.getInnerDocument().selection.createRange();
 583          } catch(e) {
 584              return false;
 585          }
 586          var someelement = selrange.parentElement ? selrange.parentElement() : selrange.item(0);
 587  
 588          while (someelement.nodeName.toLowerCase() != 'body') {
 589              someelement = someelement.parentNode;
 590          };
 591          
 592          return someelement == editable_body;
 593      };
 594  
 595      this._clearSelection = function() {
 596          /* clear the last stored selection */
 597          this._previous_range = null;
 598      };
 599  
 600      this._filterContent = function(documentElement) {            
 601          /* pass the content through all the filters */
 602          // first copy all nodes to a Sarissa document so it's usable
 603          var xhtmldoc = Sarissa.getDomDocument();
 604          var doc = this._convertToSarissaNode(xhtmldoc, documentElement);
 605          // now pass it through all filters
 606          for (var i=0; i < this.filters.length; i++) {
 607              var doc = this.filters[i].filter(xhtmldoc, doc);
 608          };
 609          // fix some possible structural problems, such as an empty or missing head, title
 610          // or script or textarea tags without closing tag...
 611          this._fixXML(doc, xhtmldoc);
 612          return doc;
 613      };
 614  
 615      this.getXMLBody = function(transform) {
 616          var bodies = transform.getElementsByTagName('body');
 617          var data = '';
 618          for (var i = 0; i < bodies.length; i++) {
 619              data += Sarissa.serialize(bodies[i]);
 620          }
 621          return this.escapeEntities(data);
 622      };
 623  
 624      this.getHTMLBody = function() {
 625          var doc = this.getInnerDocument();
 626          var docel = doc.documentElement;
 627          var bodies = docel.getElementsByTagName('body');
 628          var data = '';
 629          for (var i = 0; i < bodies.length; i++) {
 630              data += bodies[i].innerHTML;
 631          }
 632          return this.escapeEntities(data);
 633      };
 634  
 635      // If we have multiple bodies this needs to remove the extras.
 636      this.setHTMLBody = function(text) {
 637          var bodies = this.getInnerDocument().documentElement.getElementsByTagName('body');
 638          for (var i = 0; i < bodies.length-1; i++) {
 639              bodies[i].parentNode.removeChild(bodies[i]);
 640          }
 641          bodies[bodies.length-1].innerHTML = text;
 642      };
 643  
 644      this._fixXML = function(doc, document) {
 645          /* fix some structural problems in the XML that make it invalid XTHML */
 646          // find if we have a head and title, and if not add them
 647          var heads = doc.getElementsByTagName('head');
 648          var titles = doc.getElementsByTagName('title');
 649          if (!heads.length) {
 650              // assume we have a body, guess Kupu won't work without one anyway ;)
 651              var body = doc.getElementsByTagName('body')[0];
 652              var head = document.createElement('head');
 653              body.parentNode.insertBefore(head, body);
 654              var title = document.createElement('title');
 655              var titletext = document.createTextNode('');
 656              head.appendChild(title);
 657              title.appendChild(titletext);
 658          } else if (!titles.length) {
 659              var head = heads[0];
 660              var title = document.createElement('title');
 661              var titletext = document.createTextNode('');
 662              head.appendChild(title);
 663              title.appendChild(titletext);
 664          };
 665          // create a closing element for all elements that require one in XHTML
 666          var dualtons = new Array('a', 'abbr', 'acronym', 'address', 'applet', 
 667                                      'b', 'bdo', 'big', 'blink', 'blockquote', 
 668                                      'button', 'caption', 'center', 'cite', 
 669                                      'comment', 'del', 'dfn', 'dir', 'div',
 670                                      'dl', 'dt', 'em', 'embed', 'fieldset',
 671                                      'font', 'form', 'frameset', 'h1', 'h2',
 672                                      'h3', 'h4', 'h5', 'h6', 'i', 'iframe',
 673                                      'ins', 'kbd', 'label', 'legend', 'li',
 674                                      'listing', 'map', 'marquee', 'menu',
 675                                      'multicol', 'nobr', 'noembed', 'noframes',
 676                                      'noscript', 'object', 'ol', 'optgroup',
 677                                      'option', 'p', 'pre', 'q', 's', 'script',
 678                                      'select', 'small', 'span', 'strike', 
 679                                      'strong', 'style', 'sub', 'sup', 'table',
 680                                      'tbody', 'td', 'textarea', 'tfoot',
 681                                      'th', 'thead', 'title', 'tr', 'tt', 'u',
 682                                      'ul', 'xmp');
 683          // XXX I reckon this is *way* slow, can we use XPath instead or
 684          // something to speed this up?
 685          for (var i=0; i < dualtons.length; i++) {
 686              var elname = dualtons[i];
 687              var els = doc.getElementsByTagName(elname);
 688              for (var j=0; j < els.length; j++) {
 689                  var el = els[j];
 690                  if (!el.hasChildNodes()) {
 691                      var child = document.createTextNode('');
 692                      el.appendChild(child);
 693                  };
 694              };
 695          };
 696      };
 697  
 698      this.xhtmlvalid = new XhtmlValidation(this);
 699  
 700      this._convertToSarissaNode = function(ownerdoc, htmlnode) {
 701          /* Given a string of non-well-formed HTML, return a string of 
 702             well-formed XHTML.
 703  
 704             This function works by leveraging the already-excellent HTML 
 705             parser inside the browser, which generally can turn a pile 
 706             of crap into a DOM.  We iterate over the HTML DOM, appending 
 707             new nodes (elements and attributes) into a node.
 708  
 709             The primary problems this tries to solve for crappy HTML: mixed 
 710             element names, elements that open but don't close, 
 711             and attributes that aren't in quotes.  This can also be adapted 
 712             to filter out tags that you don't want and clean up inline styles.
 713  
 714             Inspired by Guido, adapted by Paul from something in usenet.
 715             Tag and attribute tables added by Duncan
 716          */
 717          return this.xhtmlvalid._convertToSarissaNode(ownerdoc, htmlnode);
 718      };
 719  
 720      this._fixupSingletons = function(xml) {
 721          return xml.replace(/<([^>]+)\/>/g, "<$1 />");
 722      }
 723      this._serializeOutputToString = function(transform) {
 724          // XXX need to fix this.  Sometimes a spurious "\n\n" text 
 725          // node appears in the transform, which breaks the Moz 
 726          // serializer on .xml
 727              
 728          if (this.config.strict_output) {
 729              var contents =  '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' + 
 730                              '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n' + 
 731                              '<html xmlns="http://www.w3.org/1999/xhtml">' +
 732                              Sarissa.serialize(transform.getElementsByTagName("head")[0]) +
 733                              Sarissa.serialize(transform.getElementsByTagName("body")[0]) +
 734                              '</html>';
 735          } else {
 736              var contents = '<html>' + 
 737                              Sarissa.serialize(transform.getElementsByTagName("head")[0]) +
 738                              Sarissa.serialize(transform.getElementsByTagName("body")[0]) +
 739                              '</html>';
 740          };
 741  
 742          contents = this.escapeEntities(contents);
 743  
 744          if (this.config.compatible_singletons) {
 745              contents = this._fixupSingletons(contents);
 746          };
 747          
 748          return contents;
 749      };
 750      this.escapeEntities = function(xml) {
 751          // XXX: temporarily disabled
 752          return xml;
 753          // Escape non-ascii characters as entities.
 754          return xml.replace(/[^\r\n -\177]/g,
 755              function(c) {
 756              return '&#'+c.charCodeAt(0)+';';
 757          });
 758      }
 759  
 760      this.getFullEditor = function() {
 761          var fulleditor = this.getDocument().getEditable();
 762          while (!/kupu-fulleditor/.test(fulleditor.className)) {
 763              fulleditor = fulleditor.parentNode;
 764          }
 765          return fulleditor;
 766      }
 767      // Control the className and hence the style for the whole editor.
 768      this.setClass = function(name) {
 769          this.getFullEditor().className += ' '+name;
 770      }
 771      
 772      this.clearClass = function(name) {
 773          var fulleditor = this.getFullEditor();
 774          fulleditor.className = fulleditor.className.replace(' '+name, '');
 775      }
 776  
 777      this.suspendEditing = function() {
 778          this._previous_range = this.getSelection().getRange();
 779          this.setClass('kupu-modal');
 780          for (var id in this.tools) {
 781              this.tools[id].disable();
 782          }
 783          if (this.getBrowserName() == "IE") {
 784              var body = this.getInnerDocument().getElementsByTagName('body')[0];
 785              body.setAttribute('contentEditable', 'false');
 786          } else {
 787  
 788              this.getInnerDocument().designMode = "Off";
 789              var iframe = this.getDocument().getEditable();
 790              iframe.style.position = iframe.style.position?"":"relative"; // Changing this disables designMode!
 791          }
 792          this.suspended = true;
 793      }
 794      
 795      this.resumeEditing = function() {
 796          if (!this.suspended) {
 797              return;
 798          }
 799          this.suspended = false;
 800          this.clearClass('kupu-modal');
 801          for (var id in this.tools) {
 802              this.tools[id].enable();
 803          }
 804          if (this.getBrowserName() == "IE") {
 805              this._restoreSelection();
 806              var body = this.getInnerDocument().getElementsByTagName('body')[0];
 807              body.setAttribute('contentEditable', 'true');
 808          } else {
 809              var doc = this.getInnerDocument();
 810              doc.designMode = "On";
 811              this.getSelection().restoreRange(this._previous_range);
 812          }
 813      }
 814  }
 815  


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