[ 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: 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 }
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 |