Annotation of kupuMPIWG/silva/kupusilvatools.js, revision 1.1.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>