[ Index ] |
|
Code source de Kupu-1.3.5 |
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
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
Généré le : Sun Feb 25 15:30:41 2007 | par Balluche grâce à PHPXref 0.7 |