Annotation of kupuMPIWG/silva/kupusilvatools.js, revision 1.1

1.1     ! dwinter     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: kupusilvatools.js 14851 2005-07-21 11:39:15Z duncan $
        !            12: 
        !            13: // a mapping from namespace to field names, here you can configure which 
        !            14: // metadata fields should be editable with the property editor (needs to
        !            15: // be moved to somewhere in Silva or something?)
        !            16: EDITABLE_METADATA = {
        !            17:     'http://infrae.com/namespaces/metadata/silva-extra': 
        !            18:             [['contactname', 'text', 0, 'Contact name'],
        !            19:                 ['contactemail', 'text', 0, 'Contact email']
        !            20:             ],
        !            21:     'http://infrae.com/namespaces/metadata/abstract':
        !            22:             [['author', 'text', 1, 'Presenting author'],
        !            23:                 ['co_authors', 'textarea', 0, 'Co-author(s)']]
        !            24: }
        !            25:  
        !            26: function SilvaLinkTool() {
        !            27:     /* redefine the contextmenu elements */
        !            28: };
        !            29: 
        !            30: SilvaLinkTool.prototype = new LinkTool;
        !            31: 
        !            32: SilvaLinkTool.prototype.createContextMenuElements = function(selNode, event) {
        !            33:     /* create the 'Create link' or 'Remove link' menu elements */
        !            34:     var ret = new Array();
        !            35:     var link = this.editor.getNearestParentOfType(selNode, 'a');
        !            36:     if (link) {
        !            37:         ret.push(new ContextMenuElement('Delete link', this.deleteLink, this));
        !            38:     } else {
        !            39:         ret.push(new ContextMenuElement('Create link', getLink, this));
        !            40:     };
        !            41:     return ret;
        !            42: };
        !            43: 
        !            44: function SilvaLinkToolBox(inputid, targetselectid, targetinputid, addbuttonid, updatebuttonid, delbuttonid, toolboxid, plainclass, activeclass) {
        !            45:     /* create and edit links */
        !            46:     
        !            47:     this.input = getFromSelector(inputid);
        !            48:     this.targetselect = getFromSelector(targetselectid);
        !            49:     this.targetinput = getFromSelector(targetinputid);
        !            50:     this.addbutton = getFromSelector(addbuttonid);
        !            51:     this.updatebutton = getFromSelector(updatebuttonid);
        !            52:     this.delbutton = getFromSelector(delbuttonid);
        !            53:     this.toolboxel = getFromSelector(toolboxid);
        !            54:     this.plainclass = plainclass;
        !            55:     this.activeclass = activeclass;
        !            56: };
        !            57: 
        !            58: SilvaLinkToolBox.prototype = new LinkToolBox;
        !            59: 
        !            60: SilvaLinkToolBox.prototype.initialize = function(tool, editor) {
        !            61:     this.tool = tool;
        !            62:     this.editor = editor;
        !            63:     addEventHandler(this.targetselect, 'change', this.selectTargetHandler, this);
        !            64:     addEventHandler(this.targetinput, 'change', this.selectTargetHandler, this);
        !            65:     addEventHandler(this.addbutton, 'click', this.createLinkHandler, this);
        !            66:     addEventHandler(this.updatebutton, 'click', this.createLinkHandler, this);
        !            67:     addEventHandler(this.delbutton, 'click', this.tool.deleteLink, this);
        !            68:     this.targetinput.style.display = 'none';
        !            69:     this.editor.logMessage('Link tool initialized');
        !            70: };
        !            71: 
        !            72: SilvaLinkToolBox.prototype.selectTargetHandler = function(event) {
        !            73:     var select = this.targetselect;
        !            74:     var input = this.targetinput;
        !            75: 
        !            76:     var selvalue = select.options[select.selectedIndex].value;
        !            77:     if (selvalue != 'input') {
        !            78:         input.style.display = 'none';
        !            79:     } else {
        !            80:         input.style.display = 'inline';
        !            81:     };
        !            82: };
        !            83:  
        !            84: SilvaLinkToolBox.prototype.createLinkHandler = function(event) {
        !            85:     var url = this.input.value;
        !            86:     var target = this.targetselect.options[this.targetselect.selectedIndex].value;
        !            87:     if (target == 'input') {
        !            88:         target = this.targetinput.value;
        !            89:      };
        !            90:     this.tool.createLink(url, 'link', null, target);
        !            91: };
        !            92: 
        !            93: SilvaLinkToolBox.prototype.updateState = function(selNode, event) {
        !            94:     var currnode = selNode;
        !            95:     var link = false;
        !            96:     var href = '';
        !            97:     while (currnode) {
        !            98:         if (currnode.nodeName.toLowerCase() == 'a') {
        !            99:             href = currnode.getAttribute('href');
        !           100:             if (href) {
        !           101:                 if (this.toolboxel) {
        !           102:                     this.toolboxel.className = this.activeclass;
        !           103:                 };
        !           104:                 this.input.value = href;
        !           105:                 var target = currnode.getAttribute('target');
        !           106:                 if (!target) {
        !           107:                     this.targetselect.selectedIndex = 0;
        !           108:                     this.targetinput.style.display = 'none';
        !           109:                 } else {
        !           110:                     var target_found = false;
        !           111:                     for (var i=0; i < this.targetselect.options.length; i++) {
        !           112:                         var option = this.targetselect.options[i];
        !           113:                         if (option.value == target) {
        !           114:                             this.targetselect.selectedIndex = i;
        !           115:                             target_found = true;
        !           116:                             break;
        !           117:                         };
        !           118:                     };
        !           119:                     if (target_found) {
        !           120:                         this.targetinput.value = '';
        !           121:                         this.targetinput.style.display = 'none';
        !           122:                     } else {
        !           123:                         // XXX this is pretty hard-coded...
        !           124:                         this.targetselect.selectedIndex = this.targetselect.options.length - 1;
        !           125:                         this.targetinput.value = target;
        !           126:                         this.targetinput.style.display = 'inline';
        !           127:                     };
        !           128:                 };
        !           129:                 this.addbutton.style.display = 'none';
        !           130:                 this.updatebutton.style.display = 'inline';
        !           131:                 this.delbutton.style.display = 'inline';
        !           132:                 return;
        !           133:             };
        !           134:         };
        !           135:         currnode = currnode.parentNode;
        !           136:     };
        !           137:     this.targetselect.selectedIndex = 0;
        !           138:     this.targetinput.value = '';
        !           139:     this.targetinput.style.display = 'none';
        !           140:     this.updatebutton.style.display = 'none';
        !           141:     this.delbutton.style.display = 'none';
        !           142:     this.addbutton.style.display = 'inline';
        !           143:     if (this.toolboxel) {
        !           144:         this.toolboxel.className = this.plainclass;
        !           145:     };
        !           146:     this.input.value = '';
        !           147: };
        !           148:  
        !           149: function SilvaImageTool(editelid, urlinputid, targetselectid, targetinputid, 
        !           150:                         hireslinkradioid, linklinkradioid, linkinputid, 
        !           151:                         alignselectid, titleinputid, toolboxid, plainclass, 
        !           152:                         activeclass) {
        !           153:     /* Silva specific image tool */
        !           154:     this.editel = getFromSelector(editelid);
        !           155:     this.urlinput = getFromSelector(urlinputid);
        !           156:     this.targetselect = getFromSelector(targetselectid);
        !           157:     this.targetinput = getFromSelector(targetinputid);
        !           158:     this.hireslinkradio = getFromSelector(hireslinkradioid);
        !           159:     this.linklinkradio = getFromSelector(linklinkradioid);
        !           160:     this.linkinput = getFromSelector(linkinputid);
        !           161:     this.alignselect = getFromSelector(alignselectid);
        !           162:     this.titleinput = getFromSelector(titleinputid);
        !           163:     this.toolboxel = getFromSelector(toolboxid);
        !           164:     this.plainclass = plainclass;
        !           165:     this.activeclass = activeclass;
        !           166: }
        !           167: 
        !           168: SilvaImageTool.prototype = new ImageTool;
        !           169: 
        !           170: SilvaImageTool.prototype.initialize = function(editor) {
        !           171:    this.editor = editor;
        !           172:     addEventHandler(this.targetselect, 'change', this.setTarget, this);
        !           173:     addEventHandler(this.targetselect, 'change', this.selectTargetHandler, this);
        !           174:     addEventHandler(this.targetinput, 'change', this.setTarget, this);
        !           175:     addEventHandler(this.urlinput, 'change', this.setSrc, this);
        !           176:     addEventHandler(this.hireslinkradio, 'click', this.setHires, this);
        !           177:     addEventHandler(this.linklinkradio, 'click', this.setNoHires, this);
        !           178:     addEventHandler(this.linkinput, 'keypress', this.setLink, this);
        !           179:     addEventHandler(this.linkinput, 'change', this.setLink, this);
        !           180:     addEventHandler(this.alignselect, 'change', this.setAlign, this);
        !           181:     addEventHandler(this.titleinput, 'change', this.setTitle, this);
        !           182:     this.targetinput.style.display = 'none';
        !           183:     this.editor.logMessage('Image tool initialized');
        !           184: };
        !           185: 
        !           186: SilvaImageTool.prototype.createContextMenuElements = function(selNode, event) {
        !           187:     return new Array(new ContextMenuElement('Create image', getImage, this));
        !           188: };
        !           189: 
        !           190: SilvaImageTool.prototype.selectTargetHandler = function(event) {
        !           191:     var select = this.targetselect;
        !           192:     var input = this.targetinput;
        !           193: 
        !           194:     var selvalue = select.options[select.selectedIndex].value;
        !           195:     if (selvalue != 'input') {
        !           196:         input.style.display = 'none';
        !           197:     } else {
        !           198:         input.style.display = 'inline';
        !           199:     };
        !           200: };
        !           201: 
        !           202: SilvaImageTool.prototype.updateState = function(selNode, event) {
        !           203:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           204:     if (image) {
        !           205:         this.editel.style.display = 'block';
        !           206:         var src = image.getAttribute('src');
        !           207:         this.urlinput.value = src;
        !           208:         var target = image.getAttribute('target');
        !           209:         if (!target) {
        !           210:             this.targetselect.selectedIndex = 0;
        !           211:             this.targetinput.style.display = 'none';
        !           212:         } else {
        !           213:             var target_found = false;
        !           214:             for (var i=0; i < this.targetselect.options.length; i++) {
        !           215:                 var option = this.targetselect.options[i];
        !           216:                 if (option.value == target) {
        !           217:                     this.targetselect.selectedIndex = i;
        !           218:                     target_found = true;
        !           219:                     break;
        !           220:                 };
        !           221:             };
        !           222:             if (target_found) {
        !           223:                 this.targetinput.value = '';
        !           224:                 this.targetinput.style.display = 'none';
        !           225:             } else {
        !           226:                 this.targetselect.selectedIndex = this.targetselect.options.length - 1;
        !           227:                 this.targetinput.value = target;
        !           228:                 this.targetinput.style.display = 'inline';
        !           229:             };
        !           230:         };
        !           231:         var hires = image.getAttribute('link_to_hires') == '1';
        !           232:         if (!hires) {
        !           233:             var link = image.getAttribute('link');
        !           234:             this.linklinkradio.checked = 'selected';
        !           235:             this.linkinput.value = link == null ? '' : link;
        !           236:         } else {
        !           237:             this.hireslinkradio.checked = 'checked';
        !           238:             this.linkinput.value = '';
        !           239:         };
        !           240:         if (this.toolboxel) {
        !           241:             this.toolboxel.className = this.activeclass;
        !           242:         };
        !           243:         var align = image.getAttribute('alignment');
        !           244:         if (!align) {
        !           245:             align = 'left';
        !           246:         };
        !           247:         var title = image.getAttribute('title');
        !           248:         if (!title) {
        !           249:             title = '';
        !           250:         };
        !           251:         this.titleinput.value = title;
        !           252:         selectSelectItem(this.alignselect, align);
        !           253:     } else {
        !           254:         this.editel.style.display = 'none';
        !           255:         this.urlinput.value = '';
        !           256:         this.titleinput.value = '';
        !           257:         if (this.toolboxel) {
        !           258:             this.toolboxel.className = this.plainclass;
        !           259:         };
        !           260:         this.targetselect.selectedIndex = 0;
        !           261:         this.targetinput.value = '';
        !           262:         this.targetinput.style.display = 'none';
        !           263:     };
        !           264: };
        !           265: 
        !           266: SilvaImageTool.prototype.setTarget = function() {
        !           267:     var target = this.targetselect.options[this.targetselect.selectedIndex].value;
        !           268:     if (target == 'input') {
        !           269:         target = this.targetinput.value;
        !           270:     };
        !           271:     var selNode = this.editor.getSelectedNode();
        !           272:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           273:     if (!image) {
        !           274:         this.editor.logMessage('No image selected!', 1);
        !           275:     };
        !           276:     image.setAttribute('target', target);
        !           277: };
        !           278: 
        !           279: SilvaImageTool.prototype.setSrc = function() {
        !           280:     var selNode = this.editor.getSelectedNode();
        !           281:     var img = this.editor.getNearestParentOfType(selNode, 'img');
        !           282:     if (!img) {
        !           283:         this.editor.logMessage('Not inside an image!', 1);
        !           284:     };
        !           285:     
        !           286:     var src = this.urlinput.value;
        !           287:     img.setAttribute('src', src);
        !           288:     this.editor.logMessage('Image updated');
        !           289: };
        !           290: 
        !           291: SilvaImageTool.prototype.setHires = function() {
        !           292:     var selNode = this.editor.getSelectedNode();
        !           293:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           294:     if (!image) {
        !           295:         this.editor.logMessage('No image selected!', 1);
        !           296:         return;
        !           297:     };
        !           298:     image.setAttribute('link_to_hires', '1');
        !           299:     image.removeAttribute('link');
        !           300:     this.linkinput.value = '';
        !           301: };
        !           302: 
        !           303: SilvaImageTool.prototype.setNoHires = function() {
        !           304:     var selNode = this.editor.getSelectedNode();
        !           305:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           306:     if (!image) {
        !           307:         this.editor.logMessage('Not inside an image!', 1);
        !           308:         return;
        !           309:     };
        !           310:     var link = this.linkinput.value;
        !           311:     image.setAttribute('link_to_hires', '0');
        !           312:     image.setAttribute('link', link);
        !           313:     this.linklinkradio.setAttribute('selected', 'selected');
        !           314: };
        !           315: 
        !           316: SilvaImageTool.prototype.setLink = function() {
        !           317:     var link = this.linkinput.value;
        !           318:     var selNode = this.editor.getSelectedNode();
        !           319:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           320:     if (!image) {
        !           321:         this.editor.logMessage('No image selected!', 1);
        !           322:         return;
        !           323:     };
        !           324:     image.setAttribute('link', link);
        !           325:     image.setAttribute('link_to_hires', '0');
        !           326: };
        !           327: 
        !           328: SilvaImageTool.prototype.setTitle = function() {
        !           329:     var selNode = this.editor.getSelectedNode();
        !           330:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           331:     if (!image) {
        !           332:         this.editor.logMessage('No image selected!', 1);
        !           333:         return;
        !           334:     };
        !           335:     var title = this.titleinput.value;
        !           336:     image.setAttribute('title', title);
        !           337: };
        !           338: 
        !           339: SilvaImageTool.prototype.setAlign = function() {
        !           340:     var selNode = this.editor.getSelectedNode();
        !           341:     var image = this.editor.getNearestParentOfType(selNode, 'img');
        !           342:     if (!image) {
        !           343:         this.editor.logMessage('Not inside an image', 1);
        !           344:         return;
        !           345:     };
        !           346:     var align = this.alignselect.options[this.alignselect.selectedIndex].value;
        !           347:     image.setAttribute('alignment', align);
        !           348: };
        !           349:  
        !           350: function SilvaTableTool() {
        !           351:     /* Silva specific table functionality
        !           352:         overrides most of the table functionality, required because Silva requires
        !           353:         a completely different format for tables
        !           354:     */
        !           355: 
        !           356:     this.createTable = function(rows, cols, makeHeader, tableclass) {
        !           357:         /* add a Silvs specific table, with an (optional) header with colspan */
        !           358:         var doc = this.editor.getInnerDocument();
        !           359: 
        !           360:         var table = doc.createElement('table');
        !           361:         table.style.width = "100%";
        !           362:         table.className = tableclass;
        !           363: 
        !           364:         var tbody = doc.createElement('tbody');
        !           365:         
        !           366:         if (makeHeader) {
        !           367:             this._addRowHelper(doc, tbody, 'th', -1, cols);
        !           368:         }
        !           369: 
        !           370:         for (var i=0; i < rows; i++) {
        !           371:             this._addRowHelper(doc, tbody, 'td', -1, cols);
        !           372:         }
        !           373: 
        !           374:         table.appendChild(tbody);
        !           375: 
        !           376:         var iterator = new NodeIterator(table);
        !           377:         var currnode = null;
        !           378:         var contentcell = null;
        !           379:         while (currnode = iterator.next()) {
        !           380:             var nodename = currnode.nodeName.toLowerCase();
        !           381:             if (nodename == 'td' || nodename == 'th') {
        !           382:                 contentcell = currnode;
        !           383:                 break;
        !           384:             };
        !           385:         };
        !           386:         
        !           387:         var selection = this.editor.getSelection();
        !           388:         var docfrag = selection.cloneContents();
        !           389:         var setcursoratend = false;
        !           390:         if (contentcell && docfrag.hasChildNodes()) {
        !           391:             while (contentcell.hasChildNodes()) {
        !           392:                 contentcell.removeChild(contentcell.firstChild);
        !           393:             };
        !           394:             
        !           395:             while (docfrag.hasChildNodes()) {
        !           396:                 contentcell.appendChild(docfrag.firstChild);
        !           397:                 setcursoratend = true;
        !           398:             };
        !           399:         };
        !           400:         this.editor.insertNodeAtSelection(table);
        !           401: 
        !           402:         this._setTableCellHandlers(table);
        !           403: 
        !           404:         this.editor.logMessage('Table added');
        !           405:     };
        !           406: 
        !           407:     this.addTableRow = function() {
        !           408:         /* add a table row or header */
        !           409:         var currnode = this.editor.getSelectedNode();
        !           410:         var doc = this.editor.getInnerDocument();
        !           411:         var tbody = this.editor.getNearestParentOfType(currnode, 'tbody');
        !           412:         if (!tbody) {
        !           413:             this.editor.logMessage('No table found!', 1);
        !           414:             return;
        !           415:         }
        !           416:         var cols = this._countCells(tbody);
        !           417:         var currrow = this.editor.getNearestParentOfType(currnode, 'tr');
        !           418:         if (!currrow) {
        !           419:             this.editor.logMessage('Not inside a row!', 1);
        !           420:             return;
        !           421:         };
        !           422:         var index = this._getRowIndex(currrow) + 1;
        !           423:         // should check what to add as well
        !           424:         this._addRowHelper(doc, tbody, 'td', index, cols);
        !           425: 
        !           426:         this.editor.logMessage('Table row added');
        !           427:     };
        !           428: 
        !           429:     this.delTableRow = function() {
        !           430:         /* delete a table row or header */
        !           431:         var currnode = this.editor.getSelectedNode();
        !           432:         var currtr = this.editor.getNearestParentOfType(currnode, 'tr');
        !           433: 
        !           434:         if (!currtr) {
        !           435:             this.editor.logMessage('Not inside a row!', 1);
        !           436:             return;
        !           437:         };
        !           438: 
        !           439:         currtr.parentNode.removeChild(currtr);
        !           440: 
        !           441:         this.editor.logMessage('Table row removed');
        !           442:     };
        !           443: 
        !           444:     this.addTableColumn = function() {
        !           445:         /* add a table column */
        !           446:         var currnode = this.editor.getSelectedNode();
        !           447:         var doc = this.editor.getInnerDocument();
        !           448:         var body = this.editor.getNearestParentOfType(currnode, 'tbody');
        !           449:         if (!body) {
        !           450:             this.editor.logMessage('Not inside a table!');
        !           451:             return;
        !           452:         };
        !           453:         var currcell = this.editor.getNearestParentOfType(currnode, 'td');
        !           454:         if (!currcell) {
        !           455:             var currcell = this.editor.getNearestParentOfType(currnode, 'th');
        !           456:             if (!currcell) {
        !           457:                 this.editor.logMessage('Not inside a row!', 1);
        !           458:                 return;
        !           459:             } else {
        !           460:                 var index = -1;
        !           461:             };
        !           462:         } else {
        !           463:             var index = this._getColIndex(currcell) + 1;
        !           464:         };
        !           465:         var numcells = this._countCells(body);
        !           466:         this._addColHelper(doc, body, index, numcells);
        !           467: 
        !           468:         this.editor.logMessage('Column added');
        !           469:     };
        !           470: 
        !           471:     this.delTableColumn = function() {
        !           472:         /* delete a column */
        !           473:         var currnode = this.editor.getSelectedNode();
        !           474:         var body = this.editor.getNearestParentOfType(currnode, 'tbody');
        !           475:         if (!body) {
        !           476:             this.editor.logMessage('Not inside a table body!', 1);
        !           477:             return;
        !           478:         }
        !           479:         var currcell = this.editor.getNearestParentOfType(currnode, 'td');
        !           480:         if (!currcell) {
        !           481:             currcell = this.editor.getNearestParentOfType(currnode, 'th');
        !           482:             if (!currcell) {
        !           483:                 this.editor.logMessage('Not inside a cell!');
        !           484:                 return;
        !           485:             };
        !           486:             var index = -1;
        !           487:         } else {
        !           488:             var index = this._getColIndex(currcell);
        !           489:         };
        !           490: 
        !           491:         this._delColHelper(body, index);
        !           492: 
        !           493:         this.editor.logMessage('Column deleted');
        !           494:     };
        !           495: 
        !           496:     this.setColumnWidths = function(widths) {
        !           497:         /* sets relative column widths */
        !           498:         var selNode = this.editor.getSelectedNode();
        !           499:         var table = this.editor.getNearestParentOfType(selNode, 'table');
        !           500: 
        !           501:         // first remove all current width settings from the table
        !           502:         var iterator = new NodeIterator(table);
        !           503:         var currnode = null;
        !           504:         while (currnode = iterator.next()) {
        !           505:             if (currnode.nodeName.toLowerCase() == 'td') {
        !           506:                 if (currnode.getAttribute('width')) {
        !           507:                     currnode.removeAttribute('width');
        !           508:                 } else if (currnode.style.width) {
        !           509:                     delete currnode.style.width;
        !           510:                 };
        !           511:             };
        !           512:         };
        !           513:         
        !           514:         var silva_column_info = new Array();
        !           515:         widths = widths.split(',');
        !           516:         for (var i=0; i < widths.length; i++) {
        !           517:             widths[i] = widths[i].strip();
        !           518:             silva_column_info.push('C:' + widths[i]);
        !           519:             widths[i] = parseInt(widths[i]);
        !           520:         };
        !           521:         silva_column_info = silva_column_info.join(' ');
        !           522:         table.setAttribute('silva_column_info', silva_column_info);
        !           523:         
        !           524:         // now convert the relative widths to percentages
        !           525:         // first find the first row containing cells
        !           526:         var totalunits = 0;
        !           527:         for (var i=0; i < widths.length; i++) {
        !           528:             totalunits += widths[i];
        !           529:         };
        !           530:         var iterator = new NodeIterator(table);
        !           531:         var currnode = null;
        !           532:         var row = null;
        !           533:         while (currnode = iterator.next()) {
        !           534:             if (currnode.nodeName.toLowerCase() == 'td') {
        !           535:                 row = currnode.parentNode;
        !           536:                 break;
        !           537:             };
        !           538:         };
        !           539:         var iterator = new NodeIterator(row);
        !           540:         var percent_per_unit = 100.0 / totalunits;
        !           541:         var currcell = null;
        !           542:         for (var i=0; i < widths.length; i++) {
        !           543:             while (currcell = iterator.next()) {
        !           544:                 if (currcell.nodeName.toLowerCase() == 'td') {
        !           545:                     currcell.setAttribute('width', '' + (widths[i] * percent_per_unit) + '%');
        !           546:                     break;
        !           547:                 };
        !           548:             };
        !           549:         };
        !           550:     };
        !           551: 
        !           552:     this.getColumnWidths = function(table) {
        !           553:         var silvacolinfo = table.getAttribute('silva_column_info');
        !           554:         var widths = new Array();
        !           555:         if (!silvacolinfo) {
        !           556:             var body = null;
        !           557:             var iterator = new NodeIterator(table);
        !           558:             var body = iterator.next();
        !           559:             while (body.nodeName.toLowerCase() != 'tbody') {
        !           560:                 body = iterator.next();
        !           561:             };
        !           562:             var numcols = this._countCells(body);
        !           563:             for (var i=0; i < numcols; i++) {
        !           564:                 widths.push(1);
        !           565:             };
        !           566:         } else {
        !           567:             silvacolinfo = silvacolinfo.split(' ');
        !           568:             for (var i=0; i < silvacolinfo.length; i++) {
        !           569:                 var pair = silvacolinfo[i].split(':');
        !           570:                 widths.push(parseInt(pair[1]));
        !           571:             };
        !           572:             widths = this._factorWidths(widths);
        !           573:         };
        !           574:         return widths;
        !           575:     };
        !           576: 
        !           577:     this._factorWidths = function(widths) {
        !           578:         var highest = 0;
        !           579:         for (var i=0; i < widths.length; i++) {
        !           580:             if (widths[i] > highest) {
        !           581:                 highest = widths[i];
        !           582:             };
        !           583:         };
        !           584:         var factor = 1;
        !           585:         for (var i=0; i < highest; i++) {
        !           586:             var testnum = highest - i;
        !           587:             var isfactor = true;
        !           588:             for (var j=0; j < widths.length; j++) {
        !           589:                 if (widths[j] % testnum != 0) {
        !           590:                     isfactor = false;
        !           591:                     break;
        !           592:                 };
        !           593:             };
        !           594:             if (isfactor) {
        !           595:                 factor = testnum;
        !           596:                 break;
        !           597:             };
        !           598:         };
        !           599:         if (factor > 1) {
        !           600:             for (var i=0; i < widths.length; i++) {
        !           601:                 widths[i] = widths[i] / factor;
        !           602:             };
        !           603:         };
        !           604:         return widths;
        !           605:     };
        !           606: 
        !           607:     this._addRowHelper = function(doc, body, celltype, index, numcells) {
        !           608:         /* actually adds a row to the table */
        !           609:         var row = doc.createElement('tr');
        !           610: 
        !           611:         // fill the row with cells
        !           612:         if (celltype == 'td') {
        !           613:             for (var i=0; i < numcells; i++) {
        !           614:                 var cell = doc.createElement(celltype);
        !           615:                 var nbsp = doc.createTextNode("\u00a0");
        !           616:                 cell.appendChild(nbsp);
        !           617:                 row.appendChild(cell);
        !           618:             }
        !           619:         } else if (celltype == 'th') {
        !           620:             var cell = doc.createElement(celltype);
        !           621:             cell.setAttribute('colSpan', numcells);
        !           622:             var nbsp = doc.createTextNode("\u00a0");
        !           623:             cell.appendChild(nbsp);
        !           624:             row.appendChild(cell);
        !           625:         }
        !           626: 
        !           627:         // now append it to the tbody
        !           628:         var rows = this._getAllRows(body);
        !           629:         if (index == -1 || index >= rows.length) {
        !           630:             body.appendChild(row);
        !           631:         } else {
        !           632:             var nextrow = rows[index];
        !           633:             body.insertBefore(row, nextrow);
        !           634:         }
        !           635: 
        !           636:         return row;
        !           637:     };
        !           638: 
        !           639:     this._addColHelper = function(doc, body, index, numcells) {
        !           640:         /* actually adds a column to a table */
        !           641:         var rows = this._getAllRows(body);
        !           642:         for (var i=0; i < rows.length; i++) {
        !           643:             var row = rows[i];
        !           644:             var cols = this._getAllColumns(row);
        !           645:             var col = cols[0];
        !           646:             if (col.nodeName.toLowerCase() == 'th') {
        !           647:                 var colspan = col.getAttribute('colSpan');
        !           648:                 if (colspan) {
        !           649:                     colspan = parseInt(colspan);
        !           650:                 } else {
        !           651:                     colspan = 1;
        !           652:                 }
        !           653:                 col.setAttribute('colSpan', colspan + 1);
        !           654:             } else {
        !           655:                 var cell = doc.createElement('td');
        !           656:                 var nbsp = doc.createTextNode('\u00a0');
        !           657:                 cell.appendChild(nbsp);
        !           658:                 if (index == -1 || index >= rows.length) {
        !           659:                     row.appendChild(cell);
        !           660:                 } else {
        !           661:                     row.insertBefore(cell, cols[index]);
        !           662:                 };
        !           663:             };
        !           664:         };
        !           665:     };
        !           666: 
        !           667:     this._delColHelper = function(body, index) {
        !           668:         /* actually delete all cells in a column */
        !           669:         var rows = this._getAllRows(body);
        !           670:         for (var i=0; i < rows.length; i++) {
        !           671:             var row = rows[i];
        !           672:             var cols = this._getAllColumns(row);
        !           673:             if (cols[0].nodeName.toLowerCase() == 'th') {
        !           674:                 // is a table header, so reduce colspan
        !           675:                 var th = cols[0];
        !           676:                 var colspan = th.getAttribute('colSpan');
        !           677:                 if (!colspan || colspan == '1') {
        !           678:                     body.removeChild(row);
        !           679:                 } else {
        !           680:                     colspan = parseInt(colspan);
        !           681:                     th.setAttribute('colSpan', colspan - 1);
        !           682:                 };
        !           683:             } else {
        !           684:                 // is a table cell row, remove one
        !           685:                 if (index > -1) {
        !           686:                     row.removeChild(cols[index]);
        !           687:                 } else {
        !           688:                     row.removeChild(cols[cols.length - 1]);
        !           689:                 }
        !           690:             }
        !           691:         };
        !           692:     };
        !           693: 
        !           694:     this._getRowIndex = function(row) {
        !           695:         /* get the current rowindex */
        !           696:         var rowindex = 0;
        !           697:         var prevrow = row.previousSibling;
        !           698:         while (prevrow) {
        !           699:             if (prevrow.nodeName.toLowerCase() == 'tr') {
        !           700:                 rowindex++;
        !           701:             };
        !           702:             prevrow = prevrow.previousSibling;
        !           703:         };
        !           704:         return rowindex;
        !           705:     };
        !           706: 
        !           707:     this._countCells = function(body) {
        !           708:         /* get the current column index */
        !           709:         var numcols = 0;
        !           710:         var cols = this._getAllColumns(this._getAllRows(body)[0]);
        !           711:         for (var i=0; i < cols.length; i++) {
        !           712:             var node = cols[i];
        !           713:             if (node.nodeName.toLowerCase() == 'th') {
        !           714:                 var colspan = node.getAttribute('colSpan');
        !           715:                 if (colspan) {
        !           716:                     colspan = parseInt(colspan);
        !           717:                 } else {
        !           718:                     colspan = 1;
        !           719:                 };
        !           720:                 numcols += colspan;
        !           721:             } else {
        !           722:                 numcols++;
        !           723:             };
        !           724:         };
        !           725:         return numcols;
        !           726:     };
        !           727: 
        !           728:     this._getAllRows = function(body) {
        !           729:         /* returns an Array of all available rows */
        !           730:         var rows = new Array();
        !           731:         for (var i=0; i < body.childNodes.length; i++) {
        !           732:             var node = body.childNodes[i];
        !           733:             if (node.nodeName.toLowerCase() == 'tr') {
        !           734:                 rows.push(node);
        !           735:             };
        !           736:         };
        !           737:         return rows;
        !           738:     };
        !           739: 
        !           740:     this._getAllColumns = function(row) {
        !           741:         /* returns an Array of all columns in a row */
        !           742:         var cols = new Array();
        !           743:         for (var i=0; i < row.childNodes.length; i++) {
        !           744:             var node = row.childNodes[i];
        !           745:             if (node.nodeName.toLowerCase() == 'td' || 
        !           746:                     node.nodeName.toLowerCase() == 'th') {
        !           747:                 cols.push(node);
        !           748:             };
        !           749:         };
        !           750:         return cols;
        !           751:     };
        !           752: }
        !           753: 
        !           754: SilvaTableTool.prototype = new TableTool;
        !           755: 
        !           756: function SilvaTableToolBox(addtabledivid, edittabledivid, newrowsinputid, 
        !           757:                         newcolsinputid, makeheaderinputid, classselectid, alignselectid, widthinputid,
        !           758:                         addtablebuttonid, addrowbuttonid, delrowbuttonid, addcolbuttonid, delcolbuttonid, 
        !           759:                         fixbuttonid, delbuttonid, toolboxid, plainclass, activeclass) {
        !           760:     /* Silva specific table functionality
        !           761:         overrides most of the table functionality, required because Silva requires
        !           762:         a completely different format for tables
        !           763:     */
        !           764: 
        !           765:     this.addtablediv = getFromSelector(addtabledivid);
        !           766:     this.edittablediv = getFromSelector(edittabledivid);
        !           767:     this.newrowsinput = getFromSelector(newrowsinputid);
        !           768:     this.newcolsinput = getFromSelector(newcolsinputid);
        !           769:     this.makeheaderinput = getFromSelector(makeheaderinputid);
        !           770:     this.classselect = getFromSelector(classselectid);
        !           771:     this.alignselect = getFromSelector(alignselectid);
        !           772:     this.widthinput = getFromSelector(widthinputid);
        !           773:     this.addtablebutton = getFromSelector(addtablebuttonid);
        !           774:     this.addrowbutton = getFromSelector(addrowbuttonid);
        !           775:     this.delrowbutton = getFromSelector(delrowbuttonid);
        !           776:     this.addcolbutton = getFromSelector(addcolbuttonid);
        !           777:     this.delcolbutton = getFromSelector(delcolbuttonid);
        !           778:     this.fixbutton = getFromSelector(fixbuttonid);
        !           779:     this.delbutton = getFromSelector(delbuttonid);
        !           780:     this.toolboxel = getFromSelector(toolboxid);
        !           781:     this.plainclass = plainclass;
        !           782:     this.activeclass = activeclass;
        !           783: 
        !           784:     this.initialize = function(tool, editor) {
        !           785:         /* attach the event handlers */
        !           786:         this.tool = tool;
        !           787:         this.editor = editor;
        !           788:         addEventHandler(this.addtablebutton, "click", this.addTable, this);
        !           789:         addEventHandler(this.addrowbutton, "click", this.tool.addTableRow, this.tool);
        !           790:         addEventHandler(this.delrowbutton, "click", this.tool.delTableRow, this.tool);
        !           791:         addEventHandler(this.addcolbutton, "click", this.tool.addTableColumn, this.tool);
        !           792:         addEventHandler(this.delcolbutton, "click", this.tool.delTableColumn, this.tool);
        !           793:         addEventHandler(this.fixbutton, "click", this.fixTable, this);
        !           794:         addEventHandler(this.delbutton, "click", this.tool.delTable, this);
        !           795:         addEventHandler(this.alignselect, "change", this.setColumnAlign, this);
        !           796:         addEventHandler(this.classselect, "change", this.setTableClass, this);
        !           797:         addEventHandler(this.widthinput, "change", this.setColumnWidths, this);
        !           798:         this.addtablediv.style.display = "block";
        !           799:         this.edittablediv.style.display = "none";
        !           800:         this.editor.logMessage('Table tool initialized');
        !           801:     };
        !           802: 
        !           803:     this.updateState = function(selNode) {
        !           804:         /* update the state (add/edit) and update the pulldowns (if required) */
        !           805:         var table = this.editor.getNearestParentOfType(selNode, 'table');
        !           806:         if (table) {
        !           807:             this.addtablediv.style.display = "none";
        !           808:             this.edittablediv.style.display = "block";
        !           809:             var td = this.editor.getNearestParentOfType(selNode, 'td');
        !           810:             if (!td) {
        !           811:                 td = this.editor.getNearestParentOfType(selNode, 'th');
        !           812:                 this.widthinput.value = '';
        !           813:             } else {
        !           814:                 this.widthinput.value = this.tool.getColumnWidths(table);
        !           815:             };
        !           816:             if (td) {
        !           817:                 var align = td.getAttribute('align');
        !           818:                 if (this.editor.config.use_css) {
        !           819:                     align = td.style.textAlign;
        !           820:                 };
        !           821:                 selectSelectItem(this.alignselect, align);
        !           822:             };
        !           823:             selectSelectItem(this.classselect, table.className);
        !           824:             if (this.toolboxel) {
        !           825:                 this.toolboxel.className = this.activeclass;
        !           826:             };
        !           827:         } else {
        !           828:             this.edittablediv.style.display = "none";
        !           829:             this.addtablediv.style.display = "block";
        !           830:             this.alignselect.selectedIndex = 0;
        !           831:             this.classselect.selectedIndex = 0;
        !           832:             if (this.toolboxel) {
        !           833:                 this.toolboxel.className = this.plainclass;
        !           834:             };
        !           835:         };
        !           836:     };
        !           837: 
        !           838:     this.addTable = function() {
        !           839:         /* add a Silvs specific table, with an (optional) header with colspan */
        !           840:         var rows = parseInt(this.newrowsinput.value);
        !           841:         var cols = parseInt(this.newcolsinput.value);
        !           842:         var makeHeader = this.makeheaderinput.checked;
        !           843:         var classchooser = getFromSelector("kupu-table-classchooser-add");
        !           844:         var tableclass = this.classselect.options[this.classselect.selectedIndex].value;
        !           845:         this.tool.createTable(rows, cols, makeHeader, tableclass);
        !           846:     };
        !           847: 
        !           848:     this.setTableClass = function() {
        !           849:         var cls = this.classselect.options[this.classselect.selectedIndex].value;
        !           850:         this.tool.setTableClass(cls);
        !           851:     };
        !           852: 
        !           853:     this.setColumnWidths = function() {
        !           854:         var widths = this.widthinput.value;
        !           855:         this.tool.setColumnWidths(widths);
        !           856:     };
        !           857: 
        !           858:     this.fixTable = function(event) {
        !           859:         /* fix the table so it is Silva (and this tool) compliant */
        !           860:         // since this can be quite a nasty creature we can't just use the
        !           861:         // helper methods
        !           862:         
        !           863:         // first we create a new tbody element
        !           864:         var currnode = this.editor.getSelectedNode();
        !           865:         var table = this.editor.getNearestParentOfType(currnode, 'TABLE');
        !           866:         if (!table) {
        !           867:             this.editor.logMessage('Not inside a table!');
        !           868:             return;
        !           869:         };
        !           870:         var doc = this.editor.getInnerDocument();
        !           871:         var tbody = doc.createElement('tbody');
        !           872: 
        !           873:         var allowed_classes = new Array('plain', 'grid', 'list', 'listing', 'data');
        !           874:         if (!allowed_classes.contains(table.getAttribute('class'))) {
        !           875:             table.setAttribute('class', 'plain');
        !           876:         };
        !           877:         
        !           878:         table.setAttribute('cellpadding', '0');
        !           879:         table.setAttribute('cellspacing', '0');
        !           880: 
        !           881:         // now get all the rows of the table, the rows can either be
        !           882:         // direct descendants of the table or inside a 'tbody', 'thead'
        !           883:         // or 'tfoot' element
        !           884:         var rows = new Array();
        !           885:         var parents = new Array('thead', 'tbody', 'tfoot');
        !           886:         for (var i=0; i < table.childNodes.length; i++) {
        !           887:             var node = table.childNodes[i];
        !           888:             if (node.nodeName.toLowerCase() == 'tr') {
        !           889:                 rows.push(node);
        !           890:             } else if (parents.contains(node.nodeName.toLowerCase())) {
        !           891:                 for (var j=0; j < node.childNodes.length; j++) {
        !           892:                     var inode = node.childNodes[j];
        !           893:                     if (inode.nodeName.toLowerCase() == 'tr') {
        !           894:                         rows.push(inode);
        !           895:                     };
        !           896:                 };
        !           897:             };
        !           898:         };
        !           899:         
        !           900:         // now find out how many cells our rows should have
        !           901:         var numcols = 0;
        !           902:         for (var i=0; i < rows.length; i++) {
        !           903:             var row = rows[i];
        !           904:             var currnumcols = 0;
        !           905:             for (var j=0; j < row.childNodes.length; j++) {
        !           906:                 var node = row.childNodes[j];
        !           907:                 if (node.nodeName.toLowerCase() == 'td' ||
        !           908:                         node.nodeName.toLowerCase() == 'th') {
        !           909:                     var colspan = 1;
        !           910:                     if (node.getAttribute('colSpan')) {
        !           911:                         colspan = parseInt(node.getAttribute('colSpan'));
        !           912:                     };
        !           913:                     currnumcols += colspan;
        !           914:                 };
        !           915:             };
        !           916:             if (currnumcols > numcols) {
        !           917:                 numcols = currnumcols;
        !           918:             };
        !           919:         };
        !           920: 
        !           921:         // now walk through all rows to clean them up
        !           922:         for (var i=0; i < rows.length; i++) {
        !           923:             var row = rows[i];
        !           924:             var newrow = doc.createElement('tr');
        !           925:             var currcolnum = 0;
        !           926:             var inhead = -1;
        !           927:             while (row.childNodes.length > 0) {
        !           928:                 var node = row.childNodes[0];
        !           929:                 if (node.nodeName.toLowerCase() == 'td') {
        !           930:                     if (inhead == -1) {
        !           931:                         inhead = 0;
        !           932:                         node.setAttribute('colSpan', '1');
        !           933:                     };
        !           934:                 } else if (node.nodeName.toLowerCase() == 'th') {
        !           935:                     if (inhead == -1) {
        !           936:                         inhead = 1;
        !           937:                         newrow.appendChild(node);
        !           938:                         node.setAttribute('colSpan', '1');
        !           939:                         node.setAttribute('rowSpan', '1');
        !           940:                         continue;
        !           941:                     } else if (inhead == 0) {
        !           942:                         var td = doc.createElement('td');
        !           943:                         while (node.childNodes.length) {
        !           944:                             td.appendChild(node.childNodes[0]);
        !           945:                         };
        !           946:                         row.removeChild(node);
        !           947:                         node = td;
        !           948:                     };
        !           949:                 } else {
        !           950:                     row.removeChild(node);
        !           951:                     continue;
        !           952:                 };
        !           953:                 node.setAttribute('rowspan', '1');
        !           954:                 if (inhead) {
        !           955:                     while (node.childNodes.length) {
        !           956:                         newrow.childNodes[0].appendChild(node.childNodes[0]);
        !           957:                     };
        !           958:                     var colspan = node.getAttribute('colSpan');
        !           959:                     if (colspan) {
        !           960:                         colspan = parseInt(colspan);
        !           961:                     } else {
        !           962:                         colspan = 1;
        !           963:                     }
        !           964:                     var current_colspan = parseInt(newrow.childNodes[0].getAttribute('colSpan'));
        !           965:                     newrow.childNodes[0].setAttribute('colSpan', (current_colspan + colspan).toString());
        !           966:                     row.removeChild(node);
        !           967:                 } else {
        !           968:                     node.setAttribute('colSpan', 1);
        !           969:                     node.setAttribute('rowSpan', 1);
        !           970:                     newrow.appendChild(node);
        !           971:                 };
        !           972:             };
        !           973:             if (newrow.childNodes.length) {
        !           974:                 tbody.appendChild(newrow);
        !           975:             };
        !           976:         };
        !           977: 
        !           978:         // now make sure all rows have the correct length
        !           979:         for (var i=0; i < tbody.childNodes.length; i++) {
        !           980:             var row = tbody.childNodes[i];
        !           981:             if (row.childNodes.length && row.childNodes[0].nodeName.toLowerCase() == 'th') {
        !           982:                 row.childNodes[0].setAttribute('colSpan', numcols);
        !           983:             } else {
        !           984:                 while (row.childNodes.length < numcols) {
        !           985:                     var td = doc.createElement('td');
        !           986:                     var nbsp = doc.createTextNode('\u00a0');
        !           987:                     td.appendChild(nbsp);
        !           988:                     row.appendChild(td);
        !           989:                 };
        !           990:             };
        !           991:         };
        !           992:         
        !           993:         // now remove all the old stuff from the table and add the new tbody
        !           994:         var tlength = table.childNodes.length;
        !           995:         for (var i=0; i < tlength; i++) {
        !           996:             table.removeChild(table.childNodes[0]);
        !           997:         };
        !           998:         table.appendChild(tbody);
        !           999: 
        !          1000:         this.editor.getDocument().getWindow().focus();
        !          1001: 
        !          1002:         this.editor.logMessage('Table cleaned up');
        !          1003:     };
        !          1004: 
        !          1005:     this._fixAllTables = function() {
        !          1006:         /* fix all the tables in the document at once */
        !          1007:         return;
        !          1008:         var tables = this.editor.getInnerDocument().getElementsByTagName('table');
        !          1009:         for (var i=0; i < tables.length; i++) {
        !          1010:             this.fixTable(tables[i]);
        !          1011:         };
        !          1012:     };
        !          1013: }
        !          1014: 
        !          1015: SilvaTableToolBox.prototype = new TableToolBox;
        !          1016: 
        !          1017: function SilvaIndexTool(inputid, addbuttonid, updatebuttonid, deletebuttonid, toolboxid, plainclass, activeclass) {
        !          1018:     /* a tool to manage index items (named anchors) for Silva */
        !          1019:     this.input = getFromSelector(inputid);
        !          1020:     this.addbutton = getFromSelector(addbuttonid);
        !          1021:     this.updatebutton = getFromSelector(updatebuttonid);
        !          1022:     this.deletebutton = getFromSelector(deletebuttonid);
        !          1023:     this.toolboxel = getFromSelector(toolboxid);
        !          1024:     this.plainclass = plainclass;
        !          1025:     this.activeclass = activeclass;
        !          1026: 
        !          1027:     this.initialize = function(editor) {
        !          1028:         /* attach the event handlers */
        !          1029:         this.editor = editor;
        !          1030:         addEventHandler(this.input, 'blur', this.updateIndex, this);
        !          1031:         addEventHandler(this.addbutton, 'click', this.addIndex, this);
        !          1032:         addEventHandler(this.updatebutton, 'click', this.updateIndex, this);
        !          1033:         addEventHandler(this.deletebutton, 'click', this.deleteIndex, this);
        !          1034:         if (this.editor.getBrowserName() == 'IE') {
        !          1035:             // need to catch some additional events for IE
        !          1036:             addEventHandler(editor.getInnerDocument(), 'keyup', this.handleKeyPressOnIndex, this);
        !          1037:             addEventHandler(editor.getInnerDocument(), 'keydown', this.handleKeyPressOnIndex, this);
        !          1038:         };
        !          1039:         addEventHandler(editor.getInnerDocument(), 'keypress', this.handleKeyPressOnIndex, this);
        !          1040:         this.updatebutton.style.display = 'none';
        !          1041:         this.deletebutton.style.display = 'none';
        !          1042:     };
        !          1043: 
        !          1044:     this.addIndex = function(event) {
        !          1045:         /* create an index */
        !          1046:         var name = this.input.value;
        !          1047:         var currnode = this.editor.getSelectedNode();
        !          1048:         var indexel = this.editor.getNearestParentOfType(currnode, 'A');
        !          1049:         
        !          1050:         if (indexel && indexel.getAttribute('href')) {
        !          1051:             this.editor.logMessage('Can not add index items in anchors');
        !          1052:             return;
        !          1053:         };
        !          1054:         
        !          1055:         if (!indexel) {
        !          1056:             var doc = this.editor.getDocument();
        !          1057:             if (!name) {
        !          1058:                 var selection = this.editor.getSelection();
        !          1059:                 var cloned = selection.cloneContents();
        !          1060:                 var iterator = new NodeIterator(cloned);
        !          1061:                 var name = '';
        !          1062:                 var currnode = null;
        !          1063:                 while (currnode = iterator.next()) {
        !          1064:                     if (currnode.nodeValue) {
        !          1065:                         name += currnode.nodeValue;
        !          1066:                     };
        !          1067:                 };
        !          1068:                 if (name) {
        !          1069:                     this.input.value = name;
        !          1070:                 };
        !          1071:             };
        !          1072:             var docel = doc.getDocument();
        !          1073:             indexel = docel.createElement('a');
        !          1074:             var text = docel.createTextNode('[' + name + ']');
        !          1075:             indexel.appendChild(text);
        !          1076:             indexel = this.editor.insertNodeAtSelection(indexel, true);
        !          1077:             indexel.className = 'index';
        !          1078:         };
        !          1079:         
        !          1080:         indexel.setAttribute('name', name);
        !          1081:         var sel = this.editor.getSelection();
        !          1082:         sel.collapse(true);
        !          1083:         this.editor.logMessage('Index added');
        !          1084:     };
        !          1085: 
        !          1086:     this.updateIndex = function(event) {
        !          1087:         /* update an existing index */
        !          1088:         var currnode = this.editor.getSelectedNode();
        !          1089:         var indexel = this.editor.getNearestParentOfType(currnode, 'A');
        !          1090:         if (!indexel) {
        !          1091:             return;
        !          1092:         };
        !          1093: 
        !          1094:         if (indexel && indexel.getAttribute('href')) {
        !          1095:             this.editor.logMessage('Can not add an index element inside a link!');
        !          1096:             return;
        !          1097:         };
        !          1098: 
        !          1099:         var name = this.input.value;
        !          1100:         indexel.setAttribute('name', name);
        !          1101:         while (indexel.hasChildNodes()) {
        !          1102:             indexel.removeChild(indexel.firstChild);
        !          1103:         };
        !          1104:         var text = this.editor.getInnerDocument().createTextNode('[' + name + ']')
        !          1105:         indexel.appendChild(text);
        !          1106:         this.editor.logMessage('Index modified');
        !          1107:     };
        !          1108: 
        !          1109:     this.deleteIndex = function() {
        !          1110:         var selNode = this.editor.getSelectedNode();
        !          1111:         var a = this.editor.getNearestParentOfType(selNode, 'a');
        !          1112:         if (!a || a.getAttribute('href')) {
        !          1113:             this.editor.logMessage('Not inside an index element!');
        !          1114:             return;
        !          1115:         };
        !          1116:         a.parentNode.removeChild(a);
        !          1117:         this.editor.logMessage('Index element removed');
        !          1118:     };
        !          1119: 
        !          1120:     this.handleKeyPressOnIndex = function(event) {
        !          1121:         var selNode = this.editor.getSelectedNode();
        !          1122:         var a = this.editor.getNearestParentOfType(selNode, 'a');
        !          1123:         if (!a || a.getAttribute('href')) {
        !          1124:             return;
        !          1125:         };
        !          1126:         var keyCode = event.keyCode;
        !          1127:         if (keyCode == 8 || keyCode == 46) {
        !          1128:             a.parentNode.removeChild(a);
        !          1129:         } else if (keyCode == 9 || keyCode == 39) {
        !          1130:             var next = a.nextSibling;
        !          1131:             while (next && next.nodeName.toLowerCase() == 'br') {
        !          1132:                 next = next.nextSibling;
        !          1133:             };
        !          1134:             if (!next) {
        !          1135:                 var doc = this.editor.getInnerDocument();
        !          1136:                 next = doc.createTextNode('\xa0');
        !          1137:                 a.parentNode.appendChild(next);
        !          1138:             };
        !          1139:             var selection = this.editor.getSelection();
        !          1140:             // XXX I fear I'm working around bugs here... because of a bug in 
        !          1141:             // selection.moveStart() I can't use the same codepath in IE as in Moz
        !          1142:             if (this.editor.getBrowserName() == 'IE') {
        !          1143:                 selection.selectNodeContents(a);
        !          1144:                 // XXX are we depending on a bug here? shouldn't we move the 
        !          1145:                 // selection one place to get out of the anchor? it works,
        !          1146:                 // but seems wrong...
        !          1147:                 selection.collapse(true);
        !          1148:             } else {
        !          1149:                 selection.selectNodeContents(next);
        !          1150:                 selection.collapse();
        !          1151:                 var selection = this.editor.getSelection();
        !          1152:             };
        !          1153:             this.editor.updateState();
        !          1154:         };
        !          1155:         if (event.preventDefault) {
        !          1156:             event.preventDefault();
        !          1157:         } else {
        !          1158:             event.returnValue = false;
        !          1159:         };
        !          1160:         return false;
        !          1161:     };
        !          1162: 
        !          1163:     this.updateState = function(selNode) {
        !          1164:         var indexel = this.editor.getNearestParentOfType(selNode, 'A');
        !          1165:         if (indexel && !indexel.getAttribute('href')) {
        !          1166:             if (this.toolboxel) {
        !          1167:                 this.toolboxel.className = this.activeclass;
        !          1168:             };
        !          1169:             this.input.value = indexel.getAttribute('name');
        !          1170:             this.addbutton.style.display = 'none';
        !          1171:             this.updatebutton.style.display = 'inline';
        !          1172:             this.deletebutton.style.display = 'inline';
        !          1173:         } else {
        !          1174:             if (this.toolboxel) {
        !          1175:                 this.toolboxel.className = this.plainclass;
        !          1176:             };
        !          1177:             this.input.value = '';
        !          1178:             this.updatebutton.style.display = 'none';
        !          1179:             this.deletebutton.style.display = 'none';
        !          1180:             this.addbutton.style.display = 'inline';
        !          1181:         };
        !          1182:     };
        !          1183: 
        !          1184:     this.createContextMenuElements = function(selNode, event) {
        !          1185:         var indexel = this.editor.getNearestParentOfType(selNode, 'A');
        !          1186:         if (indexel && !indexel.getAttribute('href')) {
        !          1187:             return new Array(new ContextMenuElement('Delete index', this.deleteIndex, this));
        !          1188:         } else {
        !          1189:             return new Array();
        !          1190:         };
        !          1191:     };
        !          1192: };
        !          1193: 
        !          1194: SilvaIndexTool.prototype = new KupuTool;
        !          1195: 
        !          1196: function SilvaTocTool(depthselectid, addbuttonid, delbuttonid, toolboxid, plainclass, activeclass) {
        !          1197:     this.depthselect = getFromSelector(depthselectid);
        !          1198:     this.addbutton = getFromSelector(addbuttonid);
        !          1199:     this.delbutton = getFromSelector(delbuttonid);
        !          1200:     this.toolbox = getFromSelector(toolboxid);
        !          1201:     this.plainclass = plainclass;
        !          1202:     this.activeclass = activeclass;
        !          1203:     this._inside_toc = false;
        !          1204: 
        !          1205:     this.initialize = function(editor) {
        !          1206:         this.editor = editor;
        !          1207:         addEventHandler(this.addbutton, 'click', this.addOrUpdateToc, this);
        !          1208:         addEventHandler(this.depthselect, 'change', this.updateToc, this);
        !          1209:         addEventHandler(this.delbutton, 'click', this.deleteToc, this);
        !          1210:         addEventHandler(editor.getInnerDocument(), 'keypress', this.handleKeyPressOnToc, this);
        !          1211:         if (this.editor.getBrowserName() == 'IE') {
        !          1212:             addEventHandler(editor.getInnerDocument(), 'keydown', this.handleKeyPressOnToc, this);
        !          1213:             addEventHandler(editor.getInnerDocument(), 'keyup', this.handleKeyPressOnToc, this);
        !          1214:         };
        !          1215:     };
        !          1216: 
        !          1217:     this.handleKeyPressOnToc = function(event) {
        !          1218:         if (!this._inside_toc) {
        !          1219:             return;
        !          1220:         };
        !          1221:         var keyCode = event.keyCode;
        !          1222:         if (keyCode == 8 || keyCode == 46) {
        !          1223:             var selNode = this.editor.getSelectedNode();
        !          1224:             var toc = this.getNearestToc(selNode);
        !          1225:             toc.parentNode.removeChild(toc);
        !          1226:         };
        !          1227:         if (keyCode == 13 || keyCode == 9 || keyCode == 39) {
        !          1228:             var selNode = this.editor.getSelectedNode();
        !          1229:             var toc = this.getNearestToc(selNode);
        !          1230:             var doc = this.editor.getInnerDocument();
        !          1231:             var selection = this.editor.getSelection();
        !          1232:             if (toc.nextSibling) {
        !          1233:                 var sibling = toc.nextSibling;
        !          1234:                 selection.selectNodeContents(toc.nextSibling);
        !          1235:                 selection.collapse();
        !          1236:             } else {
        !          1237:                 var parent = toc.parentNode;
        !          1238:                 var p = doc.createElement('p');
        !          1239:                 parent.appendChild(p);
        !          1240:                 var text = doc.createTextNode('\xa0');
        !          1241:                 p.appendChild(text);
        !          1242:                 selection.selectNodeContents(p);
        !          1243:             };
        !          1244:             this._inside_toc = false;
        !          1245:         };
        !          1246:         if (event.preventDefault) {
        !          1247:             event.preventDefault();
        !          1248:         } else {
        !          1249:             event.returnValue = false;
        !          1250:         };
        !          1251:     };
        !          1252: 
        !          1253:     this.updateState = function(selNode, event) {
        !          1254:         var toc = this.getNearestToc(selNode);
        !          1255:         if (toc) {
        !          1256:             var depth = toc.getAttribute('toc_depth');
        !          1257:             selectSelectItem(this.depthselect, depth);
        !          1258:             this.addbutton.style.display = 'none';
        !          1259:             this.delbutton.style.display = 'inline';
        !          1260:             this._inside_toc = true;
        !          1261:             if (this.toolbox) {
        !          1262:                 this.toolbox.className = this.activeclass;
        !          1263:             };
        !          1264:         } else {
        !          1265:             this.depthselect.selectedIndex = 0;
        !          1266:             this.delbutton.style.display = 'none';
        !          1267:             this.addbutton.style.display = 'inline';
        !          1268:             this._inside_toc = false;
        !          1269:             if (this.toolbox) {
        !          1270:                 this.toolbox.className = this.plainclass;
        !          1271:             };
        !          1272:         };
        !          1273:     };
        !          1274: 
        !          1275:     this.addOrUpdateToc = function(event, depth) {
        !          1276:         var selNode = this.editor.getSelectedNode();
        !          1277:         var depth = depth ? depth : this.depthselect.options[this.depthselect.selectedIndex].value;
        !          1278:         var toc = this.getNearestToc(selNode);
        !          1279:         var doc = this.editor.getInnerDocument();
        !          1280:         var toctext = this.getTocText(depth);
        !          1281:         if (toc) {
        !          1282:             // there's already a toc, just update the depth
        !          1283:             toc.setAttribute('toc_depth', depth);
        !          1284:             while (toc.hasChildNodes()) {
        !          1285:                 toc.removeChild(toc.firstChild);
        !          1286:             };
        !          1287:             toc.appendChild(doc.createTextNode(toctext));
        !          1288:         } else {
        !          1289:             // create a new toc
        !          1290:             var div = doc.createElement('div');
        !          1291:             div.setAttribute('toc_depth', depth);
        !          1292:             div.setAttribute('is_toc', 1);
        !          1293:             div.className = 'toc';
        !          1294:             var text = doc.createTextNode(toctext);
        !          1295:             div.appendChild(text);
        !          1296:             this.editor.insertNodeAtSelection(div);
        !          1297:         };
        !          1298:     };
        !          1299: 
        !          1300:     this.createDefaultToc = function() {
        !          1301:         // XXX nasty workaround, entering null as the event...
        !          1302:         this.addOrUpdateToc(null, '-1');
        !          1303:     };
        !          1304: 
        !          1305:     this.updateToc = function() {
        !          1306:         var selNode = this.editor.getSelectedNode();
        !          1307:         var toc = this.getNearestToc(selNode);
        !          1308:         if (toc) {
        !          1309:             var depth = this.depthselect.options[this.depthselect.selectedIndex].value;
        !          1310:             var toctext = this.getTocText(depth);
        !          1311:             toc.setAttribute('toc_depth', depth);
        !          1312:             while (toc.hasChildNodes()) {
        !          1313:                 toc.removeChild(toc.firstChild);
        !          1314:             };
        !          1315:             doc = this.editor.getInnerDocument();
        !          1316:             toc.appendChild(doc.createTextNode(toctext));
        !          1317:         };
        !          1318:     };
        !          1319: 
        !          1320:     this.deleteToc = function() {
        !          1321:         var selNode = this.editor.getSelectedNode();
        !          1322:         var toc = this.getNearestToc(selNode);
        !          1323:         if (!toc) {
        !          1324:             this.editor.logMessage('Not inside a toc!', 1);
        !          1325:             return;
        !          1326:         };
        !          1327:         toc.parentNode.removeChild(toc);
        !          1328:     };
        !          1329:     
        !          1330:     this.getNearestToc = function(selNode) {
        !          1331:         var currnode = selNode;
        !          1332:         while (currnode) {
        !          1333:             if (currnode.nodeName.toLowerCase() == 'div' &&
        !          1334:                     currnode.getAttribute('is_toc')) {
        !          1335:                 return currnode;
        !          1336:             };
        !          1337:             currnode = currnode.parentNode;
        !          1338:         };
        !          1339:         return false;
        !          1340:     };
        !          1341:     
        !          1342:     this.createContextMenuElements = function(selNode, event) {
        !          1343:         /* create the 'Delete TOC' menu elements */
        !          1344:         var ret = new Array();
        !          1345:         if (this.getNearestToc(selNode)) {
        !          1346:             ret.push(new ContextMenuElement('Delete TOC', this.deleteToc, this));
        !          1347:         } else {
        !          1348:             ret.push(new ContextMenuElement('Create TOC', this.createDefaultToc, this));
        !          1349:         };
        !          1350:         return ret;
        !          1351:     };
        !          1352: 
        !          1353:     this.getTocText = function(depth) {
        !          1354:         var toctext = 'Table of Contents ';
        !          1355:         switch (depth) {
        !          1356:             case '-1':
        !          1357:                 toctext += '(unlimited levels)';
        !          1358:                 break;
        !          1359:             case '1':
        !          1360:                 toctext += '(1 level)';
        !          1361:                 break;
        !          1362:             default:
        !          1363:                 toctext += '(' + depth + ' levels)';
        !          1364:                 break;
        !          1365:         };
        !          1366:         return toctext;
        !          1367:     };
        !          1368: };
        !          1369: 
        !          1370: SilvaTocTool.prototype = new KupuTool;
        !          1371: 
        !          1372: function SilvaAbbrTool(abbrradioid, acronymradioid, radiocontainerid, titleinputid,
        !          1373:                             addbuttonid, updatebuttonid, delbuttonid,
        !          1374:                             toolboxid, plainclass, activeclass) {
        !          1375:     /* tool to manage citation elements */
        !          1376:     this.abbrradio = getFromSelector(abbrradioid);
        !          1377:     this.acronymradio = getFromSelector(acronymradioid);
        !          1378:     this.radiocontainer = getFromSelector(radiocontainerid);
        !          1379:     this.titleinput = getFromSelector(titleinputid);
        !          1380:     this.addbutton = getFromSelector(addbuttonid);
        !          1381:     this.updatebutton = getFromSelector(updatebuttonid);
        !          1382:     this.delbutton = getFromSelector(delbuttonid);
        !          1383:     this.toolbox = getFromSelector(toolboxid);
        !          1384:     this.plainclass = plainclass;
        !          1385:     this.activeclass = activeclass;
        !          1386:     
        !          1387:     this.initialize = function(editor) {
        !          1388:         this.editor = editor;
        !          1389:         addEventHandler(this.addbutton, 'click', this.addElement, this);
        !          1390:         addEventHandler(this.updatebutton, 'click', this.updateElement, this);
        !          1391:         addEventHandler(this.delbutton, 'click', this.deleteElement, this);
        !          1392:         
        !          1393:         this.updatebutton.style.display = 'none';
        !          1394:         this.delbutton.style.display = 'none';
        !          1395:     };
        !          1396: 
        !          1397:     this.updateState = function(selNode, event) {
        !          1398:         var element = this.getNearestAbbrAcronym(selNode);
        !          1399:         if (element) {
        !          1400:             this.addbutton.style.display = 'none';
        !          1401:             this.updatebutton.style.display = 'inline';
        !          1402:             this.delbutton.style.display = 'inline';
        !          1403:             this.titleinput.value = element.getAttribute('title');
        !          1404:             this.radiocontainer.style.display = 'none';
        !          1405:             if (this.toolbox) {
        !          1406:                 this.toolbox.className = this.activeclass;
        !          1407:             };
        !          1408:         } else {
        !          1409:             this.addbutton.style.display = 'inline';
        !          1410:             this.updatebutton.style.display = 'none';
        !          1411:             this.delbutton.style.display = 'none';
        !          1412:             this.titleinput.value = '';
        !          1413:             if (this.editor.getBrowserName() == 'IE' || this.radiocontainer.nodeName.toLowerCase() != 'tr') {
        !          1414:                 this.radiocontainer.style.display = 'block';
        !          1415:             } else {
        !          1416:                 this.radiocontainer.style.display = 'table-row';
        !          1417:             };
        !          1418:             if (this.toolbox) {
        !          1419:                 this.toolbox.className = this.plainclass;
        !          1420:             };
        !          1421:         };
        !          1422:     };
        !          1423: 
        !          1424:     this.getNearestAbbrAcronym = function(selNode) {
        !          1425:         var current = selNode;
        !          1426:         while (current && current.nodeType != 9) {
        !          1427:             if (current.nodeType == 1) {
        !          1428:                 var nodeName = current.nodeName.toLowerCase();
        !          1429:                 if (nodeName == 'abbr' || nodeName == 'acronym') {
        !          1430:                     return current;
        !          1431:                 };
        !          1432:             };
        !          1433:             current = current.parentNode;
        !          1434:         };
        !          1435:     };
        !          1436: 
        !          1437:     this.addElement = function() {
        !          1438:         var type = this.abbrradio.checked ? 'abbr' : 'acronym';
        !          1439:         var doc = this.editor.getInnerDocument();
        !          1440:         var selNode = this.editor.getSelectedNode();
        !          1441:         if (this.getNearestAbbrAcronym(selNode)) {
        !          1442:             this.editor.logMessage('Can not nest abbr and acronym elements');
        !          1443:             return;
        !          1444:         };
        !          1445:         var element = doc.createElement(type);
        !          1446:         element.setAttribute('title', this.titleinput.value);
        !          1447: 
        !          1448:         var selection = this.editor.getSelection();
        !          1449:         var docfrag = selection.cloneContents();
        !          1450:         var placecursoratend = false;
        !          1451:         if (docfrag.hasChildNodes()) {
        !          1452:             for (var i=0; i < docfrag.childNodes.length; i++) {
        !          1453:                 element.appendChild(docfrag.childNodes[i]);
        !          1454:             };
        !          1455:             placecursoratend = true;
        !          1456:         } else {
        !          1457:             var text = doc.createTextNode('\xa0');
        !          1458:             element.appendChild(text);
        !          1459:         };
        !          1460:         this.editor.insertNodeAtSelection(element, 1);
        !          1461:         var selection = this.editor.getSelection();
        !          1462:         selection.collapse(placecursoratend);
        !          1463:         this.editor.getDocument().getWindow().focus();
        !          1464:         var selNode = selection.getSelectedNode();
        !          1465:         this.editor.updateState(selNode);
        !          1466:         this.editor.logMessage('Element ' + type + ' added');
        !          1467:     };
        !          1468: 
        !          1469:     this.updateElement = function() {
        !          1470:         var selNode = this.editor.getSelectedNode();
        !          1471:         var element = this.getNearestAbbrAcronym(selNode);
        !          1472:         if (!element) {
        !          1473:             this.editor.logMessage('Not inside an abbr or acronym element!', 1);
        !          1474:             return;
        !          1475:         };
        !          1476:         var title = this.titleinput.value;
        !          1477:         element.setAttribute('title', title);
        !          1478:         this.editor.logMessage('Updated ' + element.nodeName.toLowerCase() + ' element');
        !          1479:     };
        !          1480: 
        !          1481:     this.deleteElement = function() {
        !          1482:         var selNode = this.editor.getSelectedNode();
        !          1483:         var element = this.getNearestAbbrAcronym(selNode);
        !          1484:         if (!element) {
        !          1485:             this.editor.logMessage('Not inside an abbr or acronym element!', 1);
        !          1486:             return;
        !          1487:         };
        !          1488:         element.parentNode.removeChild(element);
        !          1489:         this.editor.logMessage('Deleted ' + element.nodeName.toLowerCase() + ' deleted');
        !          1490:     };
        !          1491: };
        !          1492: 
        !          1493: SilvaAbbrTool.prototype = new KupuTool;
        !          1494: 
        !          1495: function SilvaCitationTool(authorinputid, sourceinputid, addbuttonid, updatebuttonid, delbuttonid, 
        !          1496:                             toolboxid, plainclass, activeclass) {
        !          1497:     /* tool to manage citation elements */
        !          1498:     this.authorinput = getFromSelector(authorinputid);
        !          1499:     this.sourceinput = getFromSelector(sourceinputid);
        !          1500:     this.addbutton = getFromSelector(addbuttonid);
        !          1501:     this.updatebutton = getFromSelector(updatebuttonid);
        !          1502:     this.delbutton = getFromSelector(delbuttonid);
        !          1503:     this.toolbox = getFromSelector(toolboxid);
        !          1504:     this.plainclass = plainclass;
        !          1505:     this.activeclass = activeclass;
        !          1506:     this._inside_citation = false;
        !          1507:     
        !          1508:     this.initialize = function(editor) {
        !          1509:         this.editor = editor;
        !          1510:         addEventHandler(this.addbutton, 'click', this.addCitation, this);
        !          1511:         addEventHandler(this.updatebutton, 'click', this.updateCitation, this);
        !          1512:         addEventHandler(this.delbutton, 'click', this.deleteCitation, this);
        !          1513:         if (editor.getBrowserName() == 'IE') {
        !          1514:             addEventHandler(editor.getInnerDocument(), 'keyup', this.cancelEnterPress, this);
        !          1515:             addEventHandler(editor.getInnerDocument(), 'keydown', this.handleKeyPressOnCitation, this);
        !          1516:         } else {
        !          1517:             addEventHandler(editor.getInnerDocument(), 'keypress', this.handleKeyPressOnCitation, this);
        !          1518:         };
        !          1519:         
        !          1520:         this.updatebutton.style.display = 'none';
        !          1521:         this.delbutton.style.display = 'none';
        !          1522:     };
        !          1523: 
        !          1524:     this.cancelEnterPress = function(event) {
        !          1525:         if (!this._inside_citation || (event.keyCode != 13 && event.keyCode != 9)) {
        !          1526:             return;
        !          1527:         };
        !          1528:         if (event.preventDefault) {
        !          1529:             event.preventDefault();
        !          1530:         } else {
        !          1531:             event.returnValue = false;
        !          1532:         };
        !          1533:     };
        !          1534: 
        !          1535:     this.handleKeyPressOnCitation = function(event) {
        !          1536:         if (!this._inside_citation) {
        !          1537:             return;
        !          1538:         };
        !          1539:         var keyCode = event.keyCode;
        !          1540:         var citation = this.getNearestCitation(this.editor.getSelectedNode());
        !          1541:         var doc = this.editor.getInnerDocument();
        !          1542:         var selection = this.editor.getSelection();
        !          1543:         if (keyCode == 13 && this.editor.getBrowserName() == 'IE') {
        !          1544:             var br = doc.createElement('br');
        !          1545:             var currnode = selection.getSelectedNode();
        !          1546:             selection.replaceWithNode(br);
        !          1547:             selection.selectNodeContents(br);
        !          1548:             selection.collapse(true);
        !          1549:             event.returnValue = false;
        !          1550:         } else if (keyCode == 9) {
        !          1551:             var next = citation.nextSibling;
        !          1552:             if (!next) {
        !          1553:                 next = doc.createElement('p');
        !          1554:                 next.appendChild(doc.createTextNode('\xa0'));
        !          1555:                 citation.parentNode.appendChild(next);
        !          1556:             };
        !          1557:             selection.selectNodeContents(next);
        !          1558:             selection.collapse();
        !          1559:             if (event.preventDefault) {
        !          1560:                 event.preventDefault();
        !          1561:             };
        !          1562:             event.returnValue = false;
        !          1563:             this._inside_citation = false;
        !          1564:         };
        !          1565:     };
        !          1566: 
        !          1567:     this.updateState = function(selNode, event) {
        !          1568:         var citation = this.getNearestCitation(selNode);
        !          1569:         if (citation) {
        !          1570:             this.addbutton.style.display = 'none';
        !          1571:             this.updatebutton.style.display = 'inline';
        !          1572:             this.delbutton.style.display = 'inline';
        !          1573:             this.authorinput.value = citation.getAttribute('author');
        !          1574:             this.sourceinput.value = citation.getAttribute('source');
        !          1575:             this._inside_citation = true;
        !          1576:             if (this.toolbox) {
        !          1577:                 this.toolbox.className = this.activeclass;
        !          1578:             };
        !          1579:         } else {
        !          1580:             this.addbutton.style.display = 'inline';
        !          1581:             this.updatebutton.style.display = 'none';
        !          1582:             this.delbutton.style.display = 'none';
        !          1583:             this.authorinput.value = '';
        !          1584:             this.sourceinput.value = '';
        !          1585:             this._inside_citation = false;
        !          1586:             if (this.toolbox) {
        !          1587:                 this.toolbox.className = this.plainclass;
        !          1588:             };
        !          1589:         };
        !          1590:     };
        !          1591: 
        !          1592:     this.addCitation = function() {
        !          1593:         var selNode = this.editor.getSelectedNode();
        !          1594:         var citation = this.getNearestCitation(selNode);
        !          1595:         if (citation) {
        !          1596:             this.editor.logMessage('Nested citations are not allowed!');
        !          1597:             return;
        !          1598:         };
        !          1599:         var author = this.authorinput.value;
        !          1600:         var source = this.sourceinput.value;
        !          1601:         var doc = this.editor.getInnerDocument();
        !          1602:         var div = doc.createElement('div');
        !          1603:         div.className = 'citation';
        !          1604:         div.setAttribute('author', author);
        !          1605:         div.setAttribute('source', source);
        !          1606:         div.setAttribute('is_citation', '1');
        !          1607:         var selection = this.editor.getSelection();
        !          1608:         var docfrag = selection.cloneContents();
        !          1609:         var placecursoratend = false;
        !          1610:         if (docfrag.hasChildNodes()) {
        !          1611:             for (var i=0; i < docfrag.childNodes.length; i++) {
        !          1612:                 div.appendChild(docfrag.childNodes[i]);
        !          1613:             };
        !          1614:             placecursoratend = true;
        !          1615:         } else {
        !          1616:             var text = doc.createTextNode('\xa0');
        !          1617:             div.appendChild(text);
        !          1618:         };
        !          1619:         this.editor.insertNodeAtSelection(div, 1);
        !          1620:         var selection = this.editor.getSelection();
        !          1621:         selection.collapse(placecursoratend);
        !          1622:         this.editor.getDocument().getWindow().focus();
        !          1623:         var selNode = selection.getSelectedNode();
        !          1624:         this.editor.updateState(selNode);
        !          1625:     };
        !          1626: 
        !          1627:     this.updateCitation = function() {
        !          1628:         var selNode = this.editor.getSelectedNode();
        !          1629:         var citation = this.getNearestCitation(selNode);
        !          1630:         if (!citation) {
        !          1631:             this.editor.logMessage('Not inside a citation element!');
        !          1632:             return;
        !          1633:         };
        !          1634:         citation.setAttribute('author', this.authorinput.value);
        !          1635:         citation.setAttribute('source', this.sourceinput.value);
        !          1636:     };
        !          1637: 
        !          1638:     this.deleteCitation = function() {
        !          1639:         var selNode = this.editor.getSelectedNode();
        !          1640:         var citation = this.getNearestCitation(selNode);
        !          1641:         if (!citation) {
        !          1642:             this.editor.logMessage('Not inside citation element!');
        !          1643:             return;
        !          1644:         };
        !          1645:         citation.parentNode.removeChild(citation);
        !          1646:     };
        !          1647: 
        !          1648:     this.getNearestCitation = function(selNode) {
        !          1649:         var currnode = selNode;
        !          1650:         while (currnode) {
        !          1651:             if (currnode.nodeName.toLowerCase() == 'div' &&
        !          1652:                     currnode.getAttribute('is_citation')) {
        !          1653:                 return currnode;
        !          1654:             };
        !          1655:             currnode = currnode.parentNode;
        !          1656:         };
        !          1657:         return false;
        !          1658:     };
        !          1659:     
        !          1660:     this.createContextMenuElements = function(selNode, event) {
        !          1661:         /* create the 'Delete citation' menu elements */
        !          1662:         var ret = new Array();
        !          1663:         if (this.getNearestCitation(selNode)) {
        !          1664:             ret.push(new ContextMenuElement('Delete cite', this.deleteCitation, this));
        !          1665:         };
        !          1666:         return ret;
        !          1667:     };
        !          1668: };
        !          1669: 
        !          1670: SilvaCitationTool.prototype = new KupuTool;
        !          1671: 
        !          1672: function SilvaExternalSourceTool(idselectid, formcontainerid, addbuttonid, cancelbuttonid,
        !          1673:                                     updatebuttonid, delbuttonid, toolboxid, plainclass, activeclass) {
        !          1674:     this.idselect = getFromSelector(idselectid);
        !          1675:     this.formcontainer = getFromSelector(formcontainerid);
        !          1676:     this.addbutton = getFromSelector(addbuttonid);
        !          1677:     this.cancelbutton = getFromSelector(cancelbuttonid);
        !          1678:     this.updatebutton = getFromSelector(updatebuttonid);
        !          1679:     this.delbutton = getFromSelector(delbuttonid);
        !          1680:     this.toolbox = getFromSelector(toolboxid);
        !          1681:     this.plainclass = plainclass;
        !          1682:     this.activeclass = activeclass;
        !          1683: 
        !          1684:     this._editing = false;
        !          1685:     this._url = null;
        !          1686:     this._id = null;
        !          1687:     this._form = null;
        !          1688:     this._insideExternalSource = false;
        !          1689: 
        !          1690:     // store the base url, this will be prepended to the id to form the url to
        !          1691:     // get the codesource from (Zope's acquisition will make sure it ends up on
        !          1692:     // the right object)
        !          1693:     var urlparts = document.location.toString().split('/')
        !          1694:     this._baseurl = urlparts.slice(0, urlparts.length - 2).join('/');
        !          1695: 
        !          1696:     this.initialize = function(editor) {
        !          1697:         this.editor = editor;
        !          1698:         addEventHandler(this.addbutton, 'click', this.startExternalSourceAddEdit, this);
        !          1699:         addEventHandler(this.cancelbutton, 'click', this.resetTool, this);
        !          1700:         addEventHandler(this.updatebutton, 'click', this.startExternalSourceAddEdit, this);
        !          1701:         addEventHandler(this.delbutton, 'click', this.delExternalSource, this);
        !          1702:         addEventHandler(editor.getInnerDocument(), 'keypress', this.handleKeyPressOnExternalSource, this);
        !          1703:         if (this.editor.getBrowserName() == 'IE') {
        !          1704:             addEventHandler(editor.getInnerDocument(), 'keydown', this.handleKeyPressOnExternalSource, this);
        !          1705:             addEventHandler(editor.getInnerDocument(), 'keyup', this.handleKeyPressOnExternalSource, this);
        !          1706:         };
        !          1707:         
        !          1708:         // search for a special serialized identifier of the current document
        !          1709:         // which is used to send to the ExternalSource element when sending
        !          1710:         // requests so the ExternalSources know their context
        !          1711:         this.docref = null;
        !          1712:         var metas = this.editor.getInnerDocument().getElementsByTagName('meta');
        !          1713:         for (var i=0; i < metas.length; i++) {
        !          1714:             var meta = metas[i];
        !          1715:             if (meta.getAttribute('name') == 'docref') {
        !          1716:                 this.docref = meta.getAttribute('content');
        !          1717:             };
        !          1718:         };
        !          1719:          
        !          1720:         this.updatebutton.style.display = 'none';
        !          1721:         this.delbutton.style.display = 'none';
        !          1722:         this.cancelbutton.style.display = 'none';
        !          1723:     };
        !          1724: 
        !          1725:     this.updateState = function(selNode) {
        !          1726:         var extsource = this.getNearestExternalSource(selNode);
        !          1727:         if (extsource) {
        !          1728:             this._insideExternalSource = true;
        !          1729:             selectSelectItem(this.idselect, extsource.getAttribute('source_id'));
        !          1730:             this.addbutton.style.display = 'none';
        !          1731:             this.cancelbutton.style.display = 'none';
        !          1732:             this.updatebutton.style.display = 'inline';
        !          1733:             this.delbutton.style.display = 'inline';
        !          1734:             this.startExternalSourceUpdate(extsource);
        !          1735:             if (this.toolbox) {
        !          1736:                 this.toolbox.className = this.activeclass;
        !          1737:             };
        !          1738:         } else {
        !          1739:             this._insideExternalSource = false;
        !          1740:             this.resetTool();
        !          1741:             if (this.toolbox) {
        !          1742:                 this.toolbox.className = this.plainclass;
        !          1743:             };
        !          1744:         };
        !          1745:     };
        !          1746: 
        !          1747:     this.handleKeyPressOnExternalSource = function(event) {
        !          1748:         if (!this._insideExternalSource) {
        !          1749:             return;
        !          1750:         };
        !          1751:         var keyCode = event.keyCode;
        !          1752:         var selNode = this.editor.getSelectedNode();
        !          1753:         var div = this.getNearestExternalSource(selNode);
        !          1754:         var doc = this.editor.getInnerDocument();
        !          1755:         if (keyCode == 13 || keyCode == 9 || keyCode == 39) {
        !          1756:             if (div.nextSibling) {
        !          1757:                 var selection = this.editor.getSelection();
        !          1758:                 selection.selectNodeContents(div.nextSibling);
        !          1759:                 selection.collapse();
        !          1760:             } else {
        !          1761:                 var p = doc.createElement('p');
        !          1762:                 var nbsp = doc.createTextNode('\xa0');
        !          1763:                 p.appendChild(nbsp);
        !          1764:                 div.parentNode.appendChild(p);
        !          1765:                 var selection = this.editor.getSelection();
        !          1766:                 selection.selectNodeContents(p);
        !          1767:                 selection.collapse();
        !          1768:             };
        !          1769:             this._insideExternalSource = false;
        !          1770:         } else if (keyCode == 8) {
        !          1771:             var selectnode = div.nextSibling;
        !          1772:             if (!selectnode) {
        !          1773:                 selectnode = doc.createElement('p');
        !          1774:                 selectnode.appendChild(doc.createTextNode('\xa0'));
        !          1775:                 doc.appendChild(selectnode);
        !          1776:             };
        !          1777:             var selection = this.editor.getSelection();
        !          1778:             selection.selectNodeContents(selectnode);
        !          1779:             div.parentNode.removeChild(div);
        !          1780:             selection.collapse();
        !          1781:         };
        !          1782:         if (event.preventDefault) {
        !          1783:             event.preventDefault();
        !          1784:         } else {
        !          1785:             event.returnValue = false;
        !          1786:         };
        !          1787:     };
        !          1788: 
        !          1789:     this.getUrlAndContinue = function(id, handler) {
        !          1790:         if (id == this._id) {
        !          1791:             // return cached
        !          1792:             handler.call(this, this._url);
        !          1793:             return;
        !          1794:         };
        !          1795:         var request = new getXMLHttpRequest();
        !          1796:         request.open('GET', 
        !          1797:             this._baseurl + '/edit/get_extsource_url?id=' + id, true);
        !          1798:         var callback = new ContextFixer(function() {
        !          1799:                     if (request.readyState == 4) {
        !          1800:                         var url = request.responseText;
        !          1801:                         this._id = id;
        !          1802:                         this._url = url;
        !          1803:                         handler.call(this, url);
        !          1804:                     };
        !          1805:                 }, this);
        !          1806:         request.onreadystatechange = callback.execute;
        !          1807:         request.send('');
        !          1808:     };
        !          1809: 
        !          1810:     this.startExternalSourceAddEdit = function() {
        !          1811:         // get the appropriate form and display it
        !          1812:         if (!this._editing) {
        !          1813:             var id = this.idselect.options[this.idselect.selectedIndex].value;
        !          1814:             this.getUrlAndContinue(id, this._continueStartExternalSourceEdit);
        !          1815:         } else {
        !          1816:             // validate the data and take further actions
        !          1817:             var formdata = this._gatherFormData();
        !          1818:             var doc = window.document;
        !          1819:             var request = new XMLHttpRequest();
        !          1820:             request.open('POST', this._url + '/validate_form_to_request', true);
        !          1821:             var callback = new ContextFixer(this._addExternalSourceIfValidated, request, this);
        !          1822:             request.onreadystatechange = callback.execute;
        !          1823:             request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        !          1824:             request.send(formdata);
        !          1825:         };
        !          1826:     };
        !          1827: 
        !          1828:     this._continueStartExternalSourceEdit = function(url) {
        !          1829:         url = url + '/get_rendered_form_for_editor?docref=' + this.docref;
        !          1830:         var request = new XMLHttpRequest();
        !          1831:         request.open('GET', url, true);
        !          1832:         var callback = new ContextFixer(this._addFormToTool, request, this);
        !          1833:         request.onreadystatechange = callback.execute;
        !          1834:         request.send(null);
        !          1835:         while (this.formcontainer.hasChildNodes()) {
        !          1836:             this.formcontainer.removeChild(this.formcontainer.firstChild);
        !          1837:         };
        !          1838:         var text = document.createTextNode('Loading...');
        !          1839:         this.formcontainer.appendChild(text);
        !          1840:         this.updatebutton.style.display = 'none';
        !          1841:         this.cancelbutton.style.display = 'inline';
        !          1842:         this.addbutton.style.display = 'inline';
        !          1843:         this._editing = true;
        !          1844:     };
        !          1845: 
        !          1846:     this.startExternalSourceUpdate = function(extsource) {
        !          1847:         var id = extsource.getAttribute('source_id');
        !          1848:         this.getUrlAndContinue(id, this._continueStartExternalSourceUpdate);
        !          1849:     };
        !          1850: 
        !          1851:     this._continueStartExternalSourceUpdate = function(url) {
        !          1852:         url = url + '/get_rendered_form_for_editor';
        !          1853:         var formdata = this._gatherFormDataFromElement();
        !          1854:         formdata += '&docref=' + this.docref;
        !          1855:         var request = new XMLHttpRequest();
        !          1856:         request.open('POST', url, true);
        !          1857:         request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        !          1858:         var callback = new ContextFixer(this._addFormToTool, request, this);
        !          1859:         request.onreadystatechange = callback.execute;
        !          1860:         request.send(formdata);
        !          1861:         this._editing = true;
        !          1862:         while (this.formcontainer.hasChildNodes()) {
        !          1863:             this.formcontainer.removeChild(this.formcontainer.firstChild);
        !          1864:         };
        !          1865:         var text = document.createTextNode('Loading...');
        !          1866:         this.formcontainer.appendChild(text);
        !          1867:     };
        !          1868: 
        !          1869:     this._addFormToTool = function(object) {
        !          1870:         if (this.readyState == 4) {
        !          1871:             while (object.formcontainer.hasChildNodes()) {
        !          1872:                 object.formcontainer.removeChild(object.formcontainer.firstChild);
        !          1873:             };
        !          1874:             // XXX Somehow appending the XML to the form using DOM doesn't 
        !          1875:             // work correctly, it looks like the elements aren't HTMLElements 
        !          1876:             // but XML elements, don't know how to fix now so I'll use string 
        !          1877:             // insertion for now, needless to say it should be changed to DOM
        !          1878:             // manipulation asap...
        !          1879:             // XXX why is this.responseXML.documentElement.xml sometimes 'undefined'?
        !          1880:             object.formcontainer.innerHTML = this.responseText;
        !          1881:             object.idselect.style.display = 'none';
        !          1882:             // the formcontainer will contain a table with a form
        !          1883:             var form = null;
        !          1884:             var iterator = new NodeIterator(object.formcontainer);
        !          1885:             while (form == null) {
        !          1886:                 var next = iterator.next();
        !          1887:                 if (next.nodeName.toLowerCase() == 'form') {
        !          1888:                     form = next;
        !          1889:                 };
        !          1890:             };
        !          1891:             object._form = form;
        !          1892:         };
        !          1893:     };
        !          1894: 
        !          1895:     this._addExternalSourceIfValidated = function(object) {
        !          1896:         if (this.readyState == 4) {
        !          1897:             if (this.status == '200') {
        !          1898:                 // success, add the external source element to the document
        !          1899:                 var selNode = object.editor.getSelectedNode();
        !          1900:                 var currsource = object.getNearestExternalSource(selNode);
        !          1901:                 var doc = object.editor.getInnerDocument();
        !          1902:                 
        !          1903:                 var extsource = doc.createElement('div');
        !          1904:                 extsource.setAttribute('source_id', object._id);
        !          1905:                 var header = doc.createElement('h4');
        !          1906:                 extsource.appendChild(header);
        !          1907:                 extsource.className = 'externalsource';
        !          1908:                 var metatype = 'Silva Code Source'; // a default just in case
        !          1909:                 for (var i=0; i < this.responseXML.documentElement.childNodes.length; i++) {
        !          1910:                     var child = this.responseXML.documentElement.childNodes[i];
        !          1911:                     if (child.nodeName.toLowerCase() == 'parameter') {
        !          1912:                         var key = child.getAttribute('key');
        !          1913:                         var value = '';
        !          1914:                         for (var j=0; j < child.childNodes.length; j++) {
        !          1915:                             value += child.childNodes[j].nodeValue;
        !          1916:                         };
        !          1917:                         if (key == 'metatype') {
        !          1918:                             metatype = value;
        !          1919:                             continue;
        !          1920:                         };
        !          1921:                         extsource.setAttribute(key, value);
        !          1922:                         var textel = doc.createTextNode('Key: ' + key + ', value: ' + value.toString());
        !          1923:                         extsource.appendChild(textel);
        !          1924:                         extsource.appendChild(doc.createElement('br'));
        !          1925:                     };
        !          1926:                 };
        !          1927:                 var htext = doc.createTextNode(metatype + ' \xab' + object._id + '\xbb');
        !          1928:                 header.insertBefore(htext, header.firstChild);
        !          1929:                 extsource.appendChild(doc.createElement('br'));
        !          1930:                 if (!currsource) {
        !          1931:                     object.editor.insertNodeAtSelection(extsource);
        !          1932:                 } else {
        !          1933:                     currsource.parentNode.replaceChild(extsource, currsource);
        !          1934:                     var selection = object.editor.getSelection();
        !          1935:                     selection.selectNodeContents(extsource);
        !          1936:                     selection.collapse(true);
        !          1937:                 };
        !          1938:                 object.resetTool();
        !          1939:                 object.editor.updateState();
        !          1940:             } else if (this.status == '400') {
        !          1941:                 // failure, provide some feedback and return to the form
        !          1942:                 alert('Form could not be validated, error message: ' + this.responseText);
        !          1943:             } else {
        !          1944:                 alert('POST failed with unhandled status ' + this.status);
        !          1945:                 throw('Error handling POST, server returned ' + this.status + ' HTTP status code');
        !          1946:             };
        !          1947:         };
        !          1948:     };
        !          1949: 
        !          1950:     this.delExternalSource = function() {
        !          1951:         var selNode = this.editor.getSelectedNode();
        !          1952:         var source = this.getNearestExternalSource(selNode);
        !          1953:         if (!source) {
        !          1954:             this.editor.logMessage('Not inside external source!', 1);
        !          1955:             return;
        !          1956:         };
        !          1957:         var nextsibling = source.nextSibling;
        !          1958:         source.parentNode.removeChild(source);
        !          1959:         if (nextsibling) {
        !          1960:             var selection = this.editor.getSelection();
        !          1961:             selection.selectNodeContents(nextsibling);
        !          1962:             selection.collapse();
        !          1963:         };
        !          1964:     };
        !          1965: 
        !          1966:     this.resetTool = function() {
        !          1967:         while (this.formcontainer.hasChildNodes()) {
        !          1968:             this.formcontainer.removeChild(this.formcontainer.firstChild);
        !          1969:         };
        !          1970:         this.idselect.style.display = 'inline';
        !          1971:         this.addbutton.style.display = 'inline';
        !          1972:         this.cancelbutton.style.display = 'none';
        !          1973:         this.updatebutton.style.display = 'none';
        !          1974:         this.delbutton.style.display = 'none';
        !          1975:         //this.editor.updateState();
        !          1976:         this._editing = false;
        !          1977:     };
        !          1978: 
        !          1979:     this._gatherFormData = function() {
        !          1980:         /* walks through the form and creates a POST body */
        !          1981:         // XXX we may want to turn this into a helper function, since it's 
        !          1982:         // quite useful outside of this object I reckon
        !          1983:         var form = this._form;
        !          1984:         if (!form) {
        !          1985:             this.editor.logMessage('Not currently editing');
        !          1986:             return;
        !          1987:         };
        !          1988:         // first place all data into a dict, convert to a string later on
        !          1989:         var data = {};
        !          1990:         for (var i=0; i < form.elements.length; i++) {
        !          1991:             var child = form.elements[i];
        !          1992:             var elname = child.nodeName.toLowerCase();
        !          1993:             if (elname == 'input') {
        !          1994:                 var name = child.getAttribute('name');
        !          1995:                 var type = child.getAttribute('type');
        !          1996:                 if (!type || type == 'text' || type == 'hidden' || type == 'password') {
        !          1997:                     data[name] = child.value;
        !          1998:                 } else if (type == 'checkbox' || type == 'radio') {
        !          1999:                     if (child.checked) {
        !          2000:                         if (data[name]) {
        !          2001:                             if (typeof data[name] == typeof('')) {
        !          2002:                                 var value = new Array(data[name]);
        !          2003:                                 value.push(child.value);
        !          2004:                                 data[name] = value;
        !          2005:                             } else {
        !          2006:                                 data[name].push(child.value);
        !          2007:                             };
        !          2008:                         } else {
        !          2009:                             data[name] = child.value;
        !          2010:                         };
        !          2011:                     };
        !          2012:                 };
        !          2013:             } else if (elname == 'textarea') {
        !          2014:                 data[child.getAttribute('name')] = child.value;
        !          2015:             } else if (elname == 'select') {
        !          2016:                 var name = child.getAttribute('name');
        !          2017:                 var multiple = child.getAttribute('multiple');
        !          2018:                 if (!multiple) {
        !          2019:                     data[name] = child.options[child.selectedIndex].value;
        !          2020:                 } else {
        !          2021:                     var value = new Array();
        !          2022:                     for (var i=0; i < child.options.length; i++) {
        !          2023:                         if (child.options[i].checked) {
        !          2024:                             value.push(options[i].value);
        !          2025:                         };
        !          2026:                         if (value.length > 1) {
        !          2027:                             data[name] = value;
        !          2028:                         } else if (value.length) {
        !          2029:                             data[name] = value[0];
        !          2030:                         };
        !          2031:                     };
        !          2032:                 };
        !          2033:             };
        !          2034:         };
        !          2035:         
        !          2036:         // now we should turn it into a query string
        !          2037:         var ret = new Array();
        !          2038:         for (var key in data) {
        !          2039:             var value = data[key];
        !          2040:             // XXX does IE5 support encodeURIComponent?
        !          2041:             ret.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
        !          2042:         };
        !          2043:         
        !          2044:         return ret.join("&");
        !          2045:     };
        !          2046: 
        !          2047:     this._gatherFormDataFromElement = function() {
        !          2048:         var selNode = this.editor.getSelectedNode();
        !          2049:         var source = this.getNearestExternalSource(selNode);
        !          2050:         if (!source) {
        !          2051:             return '';
        !          2052:         };
        !          2053:         var ret = new Array();
        !          2054:         for (var i=0; i < source.attributes.length; i++) {
        !          2055:             var attr = source.attributes[i];
        !          2056:             var name = attr.nodeName;
        !          2057:             var value = attr.nodeValue;
        !          2058:             if (name != 'class' && name != 'source_id' && name != 'id') {
        !          2059:                 ret.push(encodeURIComponent(name) + '=' + encodeURIComponent(value));
        !          2060:             };
        !          2061:         };
        !          2062:         return ret.join('&');
        !          2063:     };
        !          2064: 
        !          2065:     this.getNearestExternalSource = function(selNode) {
        !          2066:     
        !          2067:         var currnode = selNode;
        !          2068:         while (currnode) {
        !          2069:             if (currnode.nodeName.toLowerCase() == 'div' && currnode.className == 'externalsource') {
        !          2070:                 return currnode;
        !          2071:             };
        !          2072:             currnode = currnode.parentNode;
        !          2073:         };
        !          2074:     };
        !          2075: };
        !          2076: 
        !          2077: SilvaExternalSourceTool.prototype = new KupuTool;
        !          2078: 
        !          2079: function SilvaKupuUI(textstyleselectid) {
        !          2080:     this.tsselect = getFromSelector(textstyleselectid);
        !          2081: 
        !          2082:     this.updateState = function(selNode) {
        !          2083:         /* set the text-style pulldown */
        !          2084: 
        !          2085:         // first get the nearest style
        !          2086:         var styles = {}; // use an object here so we can use the 'in' operator later on
        !          2087:         for (var i=0; i < this.tsselect.options.length; i++) {
        !          2088:             // XXX we should cache this
        !          2089:             styles[this.tsselect.options[i].value] = i;
        !          2090:         }
        !          2091:         
        !          2092:         // search the list of nodes like in the original one, break if we encounter a match,
        !          2093:         // this method does some more than the original one since it can handle commands in
        !          2094:         // the form of '<style>|<classname>' next to the plain '<style>' commands
        !          2095:         var currnode = selNode;
        !          2096:         var index = -1;
        !          2097:         while (index==-1 && currnode) {
        !          2098:             var nodename = currnode.nodeName.toLowerCase();
        !          2099:             for (var style in styles) {
        !          2100:                 if (style.indexOf('|') < 0) {
        !          2101:                     // simple command
        !          2102:                     if (nodename == style.toLowerCase() && !currnode.className) {
        !          2103:                         index = styles[style];
        !          2104:                         break;
        !          2105:                     };
        !          2106:                 } else {
        !          2107:                     // command + classname
        !          2108:                     var tuple = style.split('|');
        !          2109:                     if (nodename == tuple[0].toLowerCase() && currnode.className == tuple[1]) {
        !          2110:                         index = styles[style];
        !          2111:                         break;
        !          2112:                     };
        !          2113:                 };
        !          2114:             };
        !          2115:             currnode = currnode.parentNode;
        !          2116:         }
        !          2117:         this.tsselect.selectedIndex = Math.max(index,0);
        !          2118:     };
        !          2119:   
        !          2120:     this.setTextStyle = function(style) {
        !          2121:         /* parse the argument into a type and classname part
        !          2122:         
        !          2123:             generate a block element accordingly 
        !          2124:         */
        !          2125:         // XXX somehow this method always gets called twice... I would
        !          2126:         // really like to know why, but can't find it right now and don't
        !          2127:         // have time for a full investigation, so fiddle-fixed it this
        !          2128:         // way. Needless to say this needs some investigation at some point...
        !          2129:         if (this._cancel_update) {
        !          2130:             this._cancel_update = false;
        !          2131:             return;
        !          2132:         };
        !          2133:         
        !          2134:         var classname = "";
        !          2135:         var eltype = style;
        !          2136:         if (style.indexOf('|') > -1) {
        !          2137:             style = style.split('|');
        !          2138:             eltype = style[0];
        !          2139:             classname = style[1];
        !          2140:         };
        !          2141: 
        !          2142:         var command = eltype;
        !          2143:         // first create the element, then find it and set the classname
        !          2144:         if (this.editor.getBrowserName() == 'IE') {
        !          2145:             command = '<' + eltype + '>';
        !          2146:         };
        !          2147:         this.editor.getDocument().execCommand('formatblock', command);
        !          2148: 
        !          2149:         // now get a reference to the element just added
        !          2150:         var selNode = this.editor.getSelectedNode();
        !          2151:         var el = this.editor.getNearestParentOfType(selNode, eltype);
        !          2152: 
        !          2153:         // now set the classname
        !          2154:         if (classname) {
        !          2155:             el.className = classname;
        !          2156:             el.setAttribute('silva_type', classname);
        !          2157:         };
        !          2158:         this._cancel_update = true;
        !          2159:         this.editor.updateState();
        !          2160:         this.editor.getDocument().getWindow().focus();
        !          2161:     };
        !          2162: };
        !          2163: 
        !          2164: SilvaKupuUI.prototype = new KupuUI;
        !          2165: 
        !          2166: function SilvaPropertyTool(tablerowid) {
        !          2167:     /* a simple tool to edit metadata fields
        !          2168: 
        !          2169:         the fields' contents are stored in Silva's metadata sets
        !          2170:     */
        !          2171:     this.tablerow = document.getElementById(tablerowid);
        !          2172:     this.table = this.tablerow.parentNode;
        !          2173:     while (!this.table.nodeName.toLowerCase() == 'table') {
        !          2174:         this.table = this.table.parentNode;
        !          2175:     };
        !          2176:     // remove current content from the fields
        !          2177:     var tds = this.tablerow.getElementsByTagName('td');
        !          2178:     for (var i=0; i < tds.length; i++) {
        !          2179:         while (tds[i].hasChildNodes()) {
        !          2180:             tds[i].removeChild(tds[i].childNodes[0]);
        !          2181:         };
        !          2182:     };
        !          2183: };
        !          2184: 
        !          2185: SilvaPropertyTool.prototype = new KupuTool;
        !          2186: 
        !          2187: SilvaPropertyTool.prototype.initialize = function(editor) {
        !          2188:     this.editor = editor;
        !          2189:     
        !          2190:     // walk through all metadata fields and expose them to the user
        !          2191:     var metas = this.editor.getInnerDocument().getElementsByTagName('meta');
        !          2192:     for (var i=0; i < metas.length; i++) {
        !          2193:         var meta = metas[i];
        !          2194:         var name = meta.getAttribute('name');
        !          2195:         if (!name) {
        !          2196:             // http-equiv type
        !          2197:             continue;
        !          2198:         };
        !          2199:         var rowcopy = this.tablerow.cloneNode(true);
        !          2200:         var tag = this.parseFormElIntoRow(meta, rowcopy);
        !          2201:         if (tag) {
        !          2202:             this.tablerow.parentNode.appendChild(tag);
        !          2203:         };
        !          2204:     };
        !          2205:     // throw away the original row: we don't need it anymore...
        !          2206:     this.tablerow.parentNode.removeChild(this.tablerow);
        !          2207: };
        !          2208: 
        !          2209: SilvaPropertyTool.prototype.parseFormElIntoRow = function(metatag, tablerow) {
        !          2210:     /* render a field in the properties tool according to a metadata tag
        !          2211: 
        !          2212:         returns some false value if the meta tag should not be editable
        !          2213:     */
        !          2214:     var scheme = metatag.getAttribute('scheme');
        !          2215:     if (!scheme || !(scheme in EDITABLE_METADATA)) {
        !          2216:         return;
        !          2217:     };
        !          2218:     var name = metatag.getAttribute('name');
        !          2219:     var namespace = metatag.getAttribute('scheme');
        !          2220:     var nametypes = EDITABLE_METADATA[scheme];
        !          2221:     var type = 'text';
        !          2222:     var mandatory = false;
        !          2223:     var namefound = false;
        !          2224:     var fieldtitle = '';
        !          2225:     for (var i=0; i < nametypes.length; i++) {
        !          2226:         var nametype = nametypes[i];
        !          2227:         var elname = nametype[0];
        !          2228:         var type = nametype[1];
        !          2229:         var mandatory = nametype[2];
        !          2230:         var fieldtitle = nametype[3];
        !          2231:         if (elname == name) {
        !          2232:             namefound = true;
        !          2233:             break;
        !          2234:         };
        !          2235:     };
        !          2236:     if (!namefound) {
        !          2237:         return;
        !          2238:     };
        !          2239:     
        !          2240:     var titlefield = document.createElement('span');
        !          2241:     var title = document.createTextNode(fieldtitle);
        !          2242:     titlefield.appendChild(title);
        !          2243:     tablerow.getElementsByTagName('td')[0].appendChild(titlefield);
        !          2244:     titlefield.className = 'metadata-field';
        !          2245:     
        !          2246:     var input = null;
        !          2247:     var value = metatag.getAttribute('content');
        !          2248:     var parentvalue = metatag.getAttribute('parentcontent');
        !          2249:     if (type == 'text') {
        !          2250:         input = document.createElement('input');
        !          2251:         input.value = value;
        !          2252:         input.setAttribute('type', 'text');
        !          2253:     } else if (type == 'textarea') {
        !          2254:         input = document.createElement('textarea');
        !          2255:         var content = document.createTextNode(value);
        !          2256:         input.appendChild(content);
        !          2257:     };
        !          2258:     input.setAttribute('name', name);
        !          2259:     input.setAttribute('namespace', namespace);
        !          2260:     input.className = 'metadata-input';
        !          2261:     if (mandatory) {
        !          2262:         input.setAttribute('mandatory', 'true');
        !          2263:     };
        !          2264:     var td = tablerow.getElementsByTagName('td')[1]
        !          2265:     td.appendChild(input);
        !          2266:     if (parentvalue && parentvalue != '') {
        !          2267:         td.appendChild(document.createElement('br'));
        !          2268:         td.appendChild(document.createTextNode('acquired value:'));
        !          2269:         td.appendChild(document.createElement('br'));
        !          2270:         td.appendChild(document.createTextNode(parentvalue));
        !          2271:     };
        !          2272: 
        !          2273:     return tablerow;
        !          2274: };
        !          2275: 
        !          2276: SilvaPropertyTool.prototype.beforeSave = function() {
        !          2277:     /* save the metadata to the document */
        !          2278:     var doc = this.editor.getInnerDocument();
        !          2279:     var inputs = this.table.getElementsByTagName('input');
        !          2280:     var textareas = this.table.getElementsByTagName('textarea');
        !          2281:     var errors = [];
        !          2282:     var okay = [];
        !          2283:     for (var i=0; i < inputs.length; i++) {
        !          2284:         var input = inputs[i];
        !          2285:         if (!input.getAttribute('type') == 'text' || !input.getAttribute('namespace')) {
        !          2286:             continue;
        !          2287:         };
        !          2288:         var name = input.getAttribute('name');
        !          2289:         var scheme = input.getAttribute('namespace');
        !          2290:         var value = input.value;
        !          2291:         if (input.getAttribute('mandatory') && value.strip() == '') {
        !          2292:             errors.push(name);
        !          2293:             continue;
        !          2294:         };
        !          2295:         okay.push([name, scheme, value]);
        !          2296:     };
        !          2297:     for (var i=0; i < textareas.length; i++) {
        !          2298:         var textarea = textareas[i];
        !          2299:         var name = textarea.getAttribute('name');
        !          2300:         var scheme = textarea.getAttribute('namespace');
        !          2301:         var value = textarea.value;
        !          2302:         if (textarea.getAttribute('mandatory') && value.strip() == '') {
        !          2303:             errors.push(name);
        !          2304:             continue;
        !          2305:         };
        !          2306:         okay.push([name, scheme, value]);
        !          2307:     };
        !          2308:     if (errors.length) {
        !          2309:         throw('Error: fields ' + errors.join(', ') + ' are required but not filled in');
        !          2310:     };
        !          2311:     for (var i=0; i < okay.length; i++) {
        !          2312:         this._addMetaTag(doc, okay[i][0], okay[i][1], okay[i][2]);
        !          2313:     };
        !          2314: };
        !          2315: 
        !          2316: SilvaPropertyTool.prototype._addMetaTag = function(doc, name, scheme, value, parentvalue) {
        !          2317:     var head = doc.getElementsByTagName('head')[0];
        !          2318:     if (!head) {
        !          2319:         throw('The editable document *must* have a <head> element!');
        !          2320:     };
        !          2321:     // first find and delete the old one
        !          2322:     // XXX if only we'd have XPath...
        !          2323:     var metas = doc.getElementsByTagName('meta');
        !          2324:     for (var i=0; i < metas.length; i++) {
        !          2325:         var meta = metas[i];
        !          2326:         if (meta.getAttribute('name') == name && 
        !          2327:                 meta.getAttribute('scheme') == scheme) {
        !          2328:             meta.parentNode.removeChild(meta);
        !          2329:         };
        !          2330:     };
        !          2331:     var tag = doc.createElement('meta');
        !          2332:     tag.setAttribute('name', name);
        !          2333:     tag.setAttribute('scheme', scheme);
        !          2334:     tag.setAttribute('content', value);
        !          2335: 
        !          2336:     head.appendChild(tag);
        !          2337: };
        !          2338: 
        !          2339: function SilvaCharactersTool(charselectid) {
        !          2340:     /* a tool to add non-standard characters */
        !          2341:     this._charselect = document.getElementById(charselectid);
        !          2342: };
        !          2343: 
        !          2344: SilvaCharactersTool.prototype = new KupuTool;
        !          2345: 
        !          2346: SilvaCharactersTool.prototype.initialize = function(editor) {
        !          2347:     this.editor = editor;
        !          2348:     addEventHandler(this._charselect, 'change', this.addCharacter, this);
        !          2349:     var chars = this.editor.config.nonstandard_chars.split(' ');
        !          2350:     for (var i=0; i < chars.length; i++) {
        !          2351:         var option = document.createElement('option');
        !          2352:         option.value = chars[i];
        !          2353:         var text = document.createTextNode(chars[i]);
        !          2354:         option.appendChild(text);
        !          2355:         this._charselect.appendChild(option);
        !          2356:     };
        !          2357: };
        !          2358: 
        !          2359: SilvaCharactersTool.prototype.addCharacter = function() {
        !          2360:     var select = this._charselect;
        !          2361:     var c = select.options[select.selectedIndex].value;
        !          2362:     if (!c.strip()) {
        !          2363:         return;
        !          2364:     };
        !          2365:     var selection = this.editor.getSelection();
        !          2366:     var textnode = this.editor.getInnerDocument().createTextNode(c);
        !          2367:     var span = this.editor.getInnerDocument().createElement('span');
        !          2368:     span.appendChild(textnode);
        !          2369:     selection.replaceWithNode(span);
        !          2370:     var selection = this.editor.getSelection();
        !          2371:     selection.selectNodeContents(span);
        !          2372:     selection.moveEnd(1);
        !          2373:     selection.collapse(true);
        !          2374:     this.editor.logMessage('Character ' + c + ' inserted');
        !          2375:     this.editor.getDocument().getWindow().focus();
        !          2376:     select.selectedIndex = 0;
        !          2377: };

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>