Changeset 70

Show
Ignore:
Timestamp:
12/20/06 19:53:12 (2 years ago)
Author:
rgrp
Message:

Some old (last week) changes to marginalia js consisting of (a) hacks to get stuff working (b) config changes.

  • marginalia/config.js: turn on debugging and annotation service url
  • marginalia/rest-annotate.js:
    • minor change to debug message
    • createAnnotation: change to put data in url string on POST as paste does *not* support x-www-form-urlencoded
  • marginalia/annotation.js
    • comment out note repositiong (this.repositionNotes( noteElement.nextSibling );) as it does not seem to work and breaks the system
  • marginalia/index.html:
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • sandbox/annotater/marginalia/annotation.js

    Revision 67 Revision 70
    1/* 1/* 
    2 * annotation.js 2 * annotation.js 
    3 * 3 * 
    4 * Web Annotation is being developed for Moodle with funding from BC Campus  4 * Web Annotation is being developed for Moodle with funding from BC Campus  
    5 * and support from Simon Fraser University and SFU's Applied Communication 5 * and support from Simon Fraser University and SFU's Applied Communication 
    6 * Technologies Group and the e-Learning Innovation Centre of the 6 * Technologies Group and the e-Learning Innovation Centre of the 
    7 * Learning Instructional Development Centre at SFU 7 * Learning Instructional Development Centre at SFU 
    8 * Copyright (C) 2005 Geoffrey Glass www.geof.net 8 * Copyright (C) 2005 Geoffrey Glass www.geof.net 
    9 *  9 *  
    10 * This program is free software; you can redistribute it and/or 10 * This program is free software; you can redistribute it and/or 
    11 * modify it under the terms of the GNU General Public License 11 * modify it under the terms of the GNU General Public License 
    12 * as published by the Free Software Foundation; either version 2 12 * as published by the Free Software Foundation; either version 2 
    13 * of the License, or (at your option) any later version. 13 * of the License, or (at your option) any later version. 
    14 * 14 * 
    15 * This program is distributed in the hope that it will be useful, 15 * This program is distributed in the hope that it will be useful, 
    16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
    17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    18 * GNU General Public License for more details. 18 * GNU General Public License for more details. 
    19 * 19 * 
    20 * You should have received a copy of the GNU General Public License 20 * You should have received a copy of the GNU General Public License 
    21 * along with this program; if not, write to the Free Software 21 * along with this program; if not, write to the Free Software 
    22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
    23 */ 23 */ 
    24 24 
    25// namespaces 25// namespaces 
    26NS_PTR = 'http://www.geof.net/code/annotation/'; 26NS_PTR = 'http://www.geof.net/code/annotation/'; 
    27NS_ATOM = 'http://www.w3.org/2005/Atom'; 27NS_ATOM = 'http://www.w3.org/2005/Atom'; 
    28 28 
    29// The names of HTML/CSS classes used by the annotation code. 29// The names of HTML/CSS classes used by the annotation code. 
    30AN_NOTES_CLASS = 'notes';                       // the notes portion of a fragment 30AN_NOTES_CLASS = 'notes';                       // the notes portion of a fragment 
    31AN_HIGHLIGHT_CLASS = 'annotation';// class given to em nodes for highlighting 31AN_HIGHLIGHT_CLASS = 'annotation';// class given to em nodes for highlighting 
    32AN_HOVER_CLASS = 'hover';                       // assigned to highlights and notes when the mouse is over the other 32AN_HOVER_CLASS = 'hover';                       // assigned to highlights and notes when the mouse is over the other 
    33AN_ANNOTATED_CLASS = 'annotated';       // class added to fragment when annotation is on 33AN_ANNOTATED_CLASS = 'annotated';       // class added to fragment when annotation is on 
    34AN_SELFANNOTATED_CLASS = 'self-annotated';  // annotations are by the current user (and therefore editable) 34AN_SELFANNOTATED_CLASS = 'self-annotated';  // annotations are by the current user (and therefore editable) 
    35AN_DUMMY_CLASS = 'dummy';                       // used for dummy item in note list 35AN_DUMMY_CLASS = 'dummy';                       // used for dummy item in note list 
    36AN_RANGEMISMATCH_ERROR_CLASS = 'annotation-range-mismatch';     // one or more annotations don't match the current state of the document 36AN_RANGEMISMATCH_ERROR_CLASS = 'annotation-range-mismatch';     // one or more annotations don't match the current state of the document 
    37AN_ID_PREFIX = 'a';                                     // prefix for annotation IDs in element classes and IDs 37AN_ID_PREFIX = 'a';                                     // prefix for annotation IDs in element classes and IDs 
    38AN_SUN_SYMBOL = '\u25cb'; //'\u263c'; 38AN_SUN_SYMBOL = '\u25cb'; //'\u263c'; 
    39AN_MOON_SYMBOL = '\u25c6'; //'\u2641'; 39AN_MOON_SYMBOL = '\u25c6'; //'\u2641'; 
    40 40 
    41// Length limits 41// Length limits 
    42MAX_QUOTE_LENGTH = 1000; 42MAX_QUOTE_LENGTH = 1000; 
    43MAX_NOTE_LENGTH = 250; 43MAX_NOTE_LENGTH = 250; 
    44 44 
    45/* 45/* 
    46 * Must be called before any other annotation functions 46 * Must be called before any other annotation functions 
    47 * wwwroot - the root of the annotation service 47 * wwwroot - the root of the annotation service 
    48 * anuser - the user whose annotations are to be shown 48 * anuser - the user whose annotations are to be shown 
    49 * thisuser - the current user (may differ from anuser) 49 * thisuser - the current user (may differ from anuser) 
    50 * urlBase - if null, annotation URLs are used as normal.  Otherwise, they are searched for this 50 * urlBase - if null, annotation URLs are used as normal.  Otherwise, they are searched for this 
    51 * string and anything preceeding it is chopped off.  This is necessary because IE lies about 51 * string and anything preceeding it is chopped off.  This is necessary because IE lies about 
    52 * hrefs:  it provides an absolute URL for that attribute, rather than the actual text.  In some 52 * hrefs:  it provides an absolute URL for that attribute, rather than the actual text.  In some 
    53 * cases, absolute URLs aren't desirable (e.g. because the annotated resources might be moved 53 * cases, absolute URLs aren't desirable (e.g. because the annotated resources might be moved 
    54 * to another host, in which case the URLs would all break). 54 * to another host, in which case the URLs would all break). 
    55 */ 55 */ 
    56function annotationInit( wwwroot, thisuser, anuser, urlBase ) 56function annotationInit( wwwroot, thisuser, anuser, urlBase ) 
    57{ 57{ 
    58        window.annotationUrlBase = urlBase; 58        window.annotationUrlBase = urlBase; 
    59        window.annotationService = new AnnotationService( wwwroot, anuser ); 59        window.annotationService = new AnnotationService( wwwroot, anuser ); 
    60        window.annotationThisUser = thisuser; 60        window.annotationThisUser = thisuser; 
    61 61 
    62        // Event handlers 62        // Event handlers 
    63        if ( document.addEventListener ) 63        if ( document.addEventListener ) 
    64                document.addEventListener( 'keyup', _keyupCreateAnnotation, false ); 64                document.addEventListener( 'keyup', _keyupCreateAnnotation, false ); 
    65        else  // for IE: 65        else  // for IE: 
    66        { 66        { 
    67                if ( document.onkeyup ) 67                if ( document.onkeyup ) 
    68                        document.onkeyup = function( event ) { _keyupCreateAnnotation(event); document.onkeyup; } 68                        document.onkeyup = function( event ) { _keyupCreateAnnotation(event); document.onkeyup; } 
    69                else 69                else 
    70                        document.onkeyup = _keyupCreateAnnotation; 70                        document.onkeyup = _keyupCreateAnnotation; 
    71        } 71        } 
    72} 72} 
    73 73 
    74/* 74/* 
    75 * Get the list of notes.  It is a DOM element containing children, 75 * Get the list of notes.  It is a DOM element containing children, 
    76 * each of which has an annotation field referencing an annotation. 76 * each of which has an annotation field referencing an annotation. 
    77 * There is a dummy first child because of spacing problems in IE. 77 * There is a dummy first child because of spacing problems in IE. 
    78 */ 78 */ 
    79PostMicro.prototype.getNotesElement = function( ) 79PostMicro.prototype.getNotesElement = function( ) 
    80{ 80{ 
    81        // Make sure it has the additional annotation properties added 81        // Make sure it has the additional annotation properties added 
    82        if ( ! this.notesElement ) 82        if ( ! this.notesElement ) 
    83        { 83        { 
    84                var t = getChildByTagClass( this.element, null, AN_NOTES_CLASS, _skipPostContent ); 84                var t = getChildByTagClass( this.element, null, AN_NOTES_CLASS, _skipPostContent ); 
    85                this.notesElement = t.getElementsByTagName( 'ol' )[ 0 ]; 85                this.notesElement = t.getElementsByTagName( 'ol' )[ 0 ]; 
    86        } 86        } 
    87        return this.notesElement; 87        return this.notesElement; 
    88} 88} 
    89 89 
    90 90 
    91/* 91/* 
    92 * Parse Atom containing annotation info and return an array of annotation objects 92 * Parse Atom containing annotation info and return an array of annotation objects 
    93 */ 93 */ 
    94function parseAnnotationXml( xmlDoc ) 94function parseAnnotationXml( xmlDoc ) 
    95{ 95{ 
    96        var annotations = new Array( ); 96        var annotations = new Array( ); 
    97         97         
    98        if ( xmlDoc.documentElement.tagName == "error" ) 98        if ( xmlDoc.documentElement.tagName == "error" ) 
    99        { 99        { 
    100                logError( "parseAnnotationXML Error: " + xmlDoc.documentElement.textValue() ); 100                logError( "parseAnnotationXML Error: " + xmlDoc.documentElement.textValue() ); 
    101                alert( getLocalized( 'corrupt XML from service' ) ); 101                alert( getLocalized( 'corrupt XML from service' ) ); 
    102                return null; 102                return null; 
    103        } 103        } 
    104        else 104        else 
    105        { 105        { 
    106                for ( var i = 0;  i < xmlDoc.documentElement.childNodes.length;  ++i ) { 106                for ( var i = 0;  i < xmlDoc.documentElement.childNodes.length;  ++i ) { 
    107                        child = xmlDoc.documentElement.childNodes[ i ]; 107                        child = xmlDoc.documentElement.childNodes[ i ]; 
    108                        // obliged to use tagName here rather than localName due to IE 108                        // obliged to use tagName here rather than localName due to IE 
    109                        if ( child.namespaceURI == NS_ATOM && getLocalName( child ) == 'entry' ) 109                        if ( child.namespaceURI == NS_ATOM && getLocalName( child ) == 'entry' ) 
    110                        { 110                        { 
    111                                var hOffset, hLength, text, url, id; 111                                var hOffset, hLength, text, url, id; 
    112                                var annotation = new Annotation( ); 112                                var annotation = new Annotation( ); 
    113                                var rangeStr = null; 113                                var rangeStr = null; 
    114                                for ( var field = child.firstChild;  field != null;  field = field.nextSibling ) 114                                for ( var field = child.firstChild;  field != null;  field = field.nextSibling ) 
    115                                { 115                                { 
    116                                        if ( field.namespaceURI == NS_ATOM && getLocalName( field ) == 'link' ) 116                                        if ( field.namespaceURI == NS_ATOM && getLocalName( field ) == 'link' ) 
    117                                        { 117                                        { 
    118                                                var rel = field.getAttribute( 'rel' ); 118                                                var rel = field.getAttribute( 'rel' ); 
    119                                                var href = field.getAttribute( 'href' ); 119                                                var href = field.getAttribute( 'href' ); 
    120                                                // What is the role of this link element?  (there are several links in each entry) 120                                                // What is the role of this link element?  (there are several links in each entry) 
    121                                                if ( 'self' == rel ) 121                                                if ( 'self' == rel ) 
    122                                                        annotation.id = href.substring( href.lastIndexOf( '/' ) + 1 ); 122                                                        annotation.id = href.substring( href.lastIndexOf( '/' ) + 1 ); 
    123                                                else if ( 'related' == rel ) 123                                                else if ( 'related' == rel ) 
    124                                                { 124                                                { 
    125                                                        if ( null != window.annotationUrlBase 125                                                        if ( null != window.annotationUrlBase 
    126                                                                && href.substring( 0, window.annotationUrlBase.length ) == window.annotationUrlBase ) 126                                                                && href.substring( 0, window.annotationUrlBase.length ) == window.annotationUrlBase ) 
    127                                                        { 127                                                        { 
    128                                                                href = href.substring( window.annotationUrlBase.length ); 128                                                                href = href.substring( window.annotationUrlBase.length ); 
    129                                                        } 129                                                        } 
    130                                                        annotation.post = findPostByUrl( href ); 130                                                        annotation.post = findPostByUrl( href ); 
    131                                                } 131                                                } 
    132                                        } 132                                        } 
    133                                        else if ( NS_ATOM == field.namespaceURI && 'author' == getLocalName( field ) ) 133                                        else if ( NS_ATOM == field.namespaceURI && 'author' == getLocalName( field ) ) 
    134                                        { 134                                        { 
    135                                                for ( var nameElement = field.firstChild;  null != nameElement;  nameElement = nameElement.nextSibling ) 135                                                for ( var nameElement = field.firstChild;  null != nameElement;  nameElement = nameElement.nextSibling ) 
    136                                                { 136                                                { 
    137                                                        if ( NS_ATOM == nameElement.namespaceURI && 'name' == getLocalName( nameElement ) ) 137                                                        if ( NS_ATOM == nameElement.namespaceURI && 'name' == getLocalName( nameElement ) ) 
    138                                                                annotation.userid = nameElement.firstChild ? nameElement.firstChild.nodeValue : null; 138                                                                annotation.userid = nameElement.firstChild ? nameElement.firstChild.nodeValue : null; 
    139                                                } 139                                                } 
    140                                        } 140                                        } 
    141                                        else if ( field.namespaceURI == NS_ATOM && getLocalName( field ) == 'title' ) 141                                        else if ( field.namespaceURI == NS_ATOM && getLocalName( field ) == 'title' ) 
    142                                                annotation.note = null == field.firstChild ? '' : field.firstChild.nodeValue; 142                                                annotation.note = null == field.firstChild ? '' : field.firstChild.nodeValue; 
    143                                        else if ( field.namespaceURI == NS_ATOM && getLocalName( field ) == 'summary' ) 143                                        else if ( field.namespaceURI == NS_ATOM && getLocalName( field ) == 'summary' ) 
    144                                                annotation.quote = null == field.firstChild ? null : field.firstChild.nodeValue; 144                                                annotation.quote = null == field.firstChild ? null : field.firstChild.nodeValue; 
    145                                        else if ( field.namespaceURI == NS_PTR && getLocalName( field ) == 'range' ) 145                                        else if ( field.namespaceURI == NS_PTR && getLocalName( field ) == 'range' ) 
    146                                                rangeStr = field.firstChild.nodeValue; 146                                                rangeStr = field.firstChild.nodeValue; 
    147                                        else if ( field.namespaceURI == NS_PTR && getLocalName( field ) == 'access' ) 147                                        else if ( field.namespaceURI == NS_PTR && getLocalName( field ) == 'access' ) 
    148                                        { 148                                        { 
    149                                                if ( field.firstChild ) 149                                                if ( field.firstChild ) 
    150                                                        annotation.access = field.firstChild.nodeValue; 150                                                        annotation.access = field.firstChild.nodeValue; 
    151                                                else 151                                                else 
    152                                                        annotation.access = 'private'; 152                                                        annotation.access = 'private'; 
    153                                        } 153                                        } 
    154                                } 154                                } 
    155                                // This should really check the validity of the whole annotation.  Most important 155                                // This should really check the validity of the whole annotation.  Most important 
    156                                // though is that the ID not be zero, otherwise this would interfere with the 156                                // though is that the ID not be zero, otherwise this would interfere with the 
    157                                // creation of new annotations. 157                                // creation of new annotations. 
    158                                if ( 0 != annotation.id && null != annotation.post ) 158                                if ( 0 != annotation.id && null != annotation.post ) 
    159                                { 159                                { 
    160                                        annotation.range = new WordRange( ); 160                                        annotation.range = new WordRange( ); 
    161                                        annotation.range.fromString( rangeStr, annotation.post.contentElement ); 161                                        annotation.range.fromString( rangeStr, annotation.post.contentElement ); 
    162                                        annotations[ annotations.length ] = annotation; 162                                        annotations[ annotations.length ] = annotation; 
    163                                } 163                                } 
    164                        } 164                        } 
    165                } 165                } 
    166                annotations.sort( compareAnnotationRanges ); 166                annotations.sort( compareAnnotationRanges ); 
    167                return annotations; 167                return annotations; 
    168        } 168        } 
    169} 169} 
    170 170 
    171 171 
    172/* ************************ Add/Show Functions ************************ */ 172/* ************************ Add/Show Functions ************************ */ 
    173/* These are for adding an annotation to the post and display. 173/* These are for adding an annotation to the post and display. 
    174 * addAnnotation calls the other three in order: 174 * addAnnotation calls the other three in order: 
    175 * showNote, highlightRange, positionNote 175 * showNote, highlightRange, positionNote 
    176 * None of these do anything with the server, but they do create interface 176 * None of these do anything with the server, but they do create interface 
    177 * elements which when activated call server functions. 177 * elements which when activated call server functions. 
    178 * 178 * 
    179 * In order to achieve a single point of truth, the only annotation list 179 * In order to achieve a single point of truth, the only annotation list 
    180 * is the list of annotation notes attached to each post in the DOM. 180 * is the list of annotation notes attached to each post in the DOM. 
    181 * On the one hand, the two can't vary independently.  But I can't think 181 * On the one hand, the two can't vary independently.  But I can't think 
    182 * why they would need to.  This way, they can't get out of sync. 182 * why they would need to.  This way, they can't get out of sync. 
    183 */ 183 */ 
    184 184 
    185/* 185/* 
    186 * Get the index where an annotation is or where it would display 186 * Get the index where an annotation is or where it would display 
    187 */ 187 */ 
    188PostMicro.prototype.getAnnotationIndex = function( annotation ) 188PostMicro.prototype.getAnnotationIndex = function( annotation ) 
    189{ 189{ 
    190        var notesElement = this.getNotesElement( ); 190        var notesElement = this.getNotesElement( ); 
    191        // Go from last to first, on the assumption that this function will be called repeatedly 191        // Go from last to first, on the assumption that this function will be called repeatedly 
    192        // in order.  Calling in reverse order gives worst-case scenario O(n^2) behavior. 192        // in order.  Calling in reverse order gives worst-case scenario O(n^2) behavior. 
    193        // Don't forget the first node in the list is a dummy with no annotation. 193        // Don't forget the first node in the list is a dummy with no annotation. 
    194        var pos = notesElement.childNodes.length; 194        var pos = notesElement.childNodes.length; 
    195        for ( var note = notesElement.lastChild;  null != note;  note = note.previousSibling ) 195        for ( var note = notesElement.lastChild;  null != note;  note = note.previousSibling ) 
    196        { 196        { 
    197                --pos; 197                --pos; 
    198                if ( null != note.annotation ) 198                if ( null != note.annotation ) 
    199                { 199                { 
    200                        if ( note.annotation.id == annotation.id ) 200                        if ( note.annotation.id == annotation.id ) 
    201                                return pos; 201                                return pos; 
    202                        else if ( compareAnnotationRanges( note.annotation, annotation ) < 0 ) 202                        else if ( compareAnnotationRanges( note.annotation, annotation ) < 0 ) 
    203                                break; 203                                break; 
    204                } 204                } 
    205        } 205        } 
    206        return pos; 206        return pos; 
    207} 207} 
    208 208 
    209/* 209/* 
    210 * Add an annotation to the local annotation list and display. 210 * Add an annotation to the local annotation list and display. 
    211 */ 211 */ 
    212PostMicro.prototype.addAnnotation = function( annotation ) 212PostMicro.prototype.addAnnotation = function( annotation ) 
    213{ 213{ 
    214        var pos = this.getAnnotationIndex( annotation ); 214        var pos = this.getAnnotationIndex( annotation ); 
    215        if ( ! this.showHighlight( annotation ) ) 215        if ( ! this.showHighlight( annotation ) ) 
    216                return -1; 216                return -1; 
    217        this.showNote( pos, annotation ); 217        this.showNote( pos, annotation ); 
    218        return pos; 218        return pos; 
    219} 219} 
    220 220 
    221/* 221/* 
    222 * Create an item in the notes list 222 * Create an item in the notes list 
    223 * pos - the position in the list 223 * pos - the position in the list 
    224 * annotation - the annotation 224 * annotation - the annotation 
    225 */ 225 */ 
    226PostMicro.prototype.showNote = function( pos, annotation ) 226PostMicro.prototype.showNote = function( pos, annotation ) 
    227{ 227{ 
    228        var noteList = this.getNotesElement(); 228        var noteList = this.getNotesElement(); 
    229 229 
    230        // Ensure we have a dummy first sibling 230        // Ensure we have a dummy first sibling 
    231        if ( null == noteList.firstChild ) 231        if ( null == noteList.firstChild ) 
    232        { 232        { 
    233                var dummy = document.createElement( 'li' ); 233                var dummy = document.createElement( 'li' ); 
    234                dummy.setAttribute( 'class', AN_DUMMY_CLASS ); 234                dummy.setAttribute( 'class', AN_DUMMY_CLASS ); 
    235                noteList.appendChild( dummy ); 235                noteList.appendChild( dummy ); 
    236        } 236        } 
    237         237         
    238        // Find the notes that will precede and follow this one 238        // Find the notes that will precede and follow this one 
    239        var prevNode = noteList.firstChild; // the dummy first node 239        var prevNode = noteList.firstChild; // the dummy first node 
    240        var nextNode = noteList.firstChild.nextSibling; // skip dummy first node 240        var nextNode = noteList.firstChild.nextSibling; // skip dummy first node 
    241        for ( var j = 0;  j < pos && null != nextNode;  ++j ) 241        for ( var j = 0;  j < pos && null != nextNode;  ++j ) 
    242        { 242        { 
    243                prevNode = nextNode; 243                prevNode = nextNode; 
    244                nextNode = nextNode.nextSibling; 244                nextNode = nextNode.nextSibling; 
    245        } 245        } 
    246 246 
    247        // Create the list item 247        // Create the list item 
    248        var postMicro = this; 248        var postMicro = this; 
    249        var noteElement = document.createElement( 'li' ); 249        var noteElement = document.createElement( 'li' ); 
    250        noteElement.id = AN_ID_PREFIX + annotation.id; 250        noteElement.id = AN_ID_PREFIX + annotation.id; 
    251        noteElement.annotationId = annotation.id; 251        noteElement.annotationId = annotation.id; 
    252        noteElement.annotation = annotation; 252        noteElement.annotation = annotation; 
    253         253         
    254        // Create its contents 254        // Create its contents 
    255        if ( annotation.isEditing ) 255        if ( annotation.isEditing ) 
    256        { 256        { 
    257                // Create the edit box 257                // Create the edit box 
    258                var editNode = document.createElement( "textarea" ); 258                var editNode = document.createElement( "textarea" ); 
    259                editNode.rows = 3; 259                editNode.rows = 3; 
    260                editNode.appendChild( document.createTextNode( annotation.note ) ); 260                editNode.appendChild( document.createTextNode( annotation.note ) ); 
    261                noteElement.appendChild( editNode ); 261                noteElement.appendChild( editNode ); 
    262                // Set focus after making visible later (IE requirement; it would be OK to do it here for Gecko) 262                // Set focus after making visible later (IE requirement; it would be OK to do it here for Gecko) 
    263                //editNode.onkeyup = function( event ) { event = getEvent( event ); return postMicro.editKeyUp( event, this ); }; 263                //editNode.onkeyup = function( event ) { event = getEvent( event ); return postMicro.editKeyUp( event, this ); }; 
    264                editNode.annotationId = annotation.id; 264                editNode.annotationId = annotation.id; 
    265                editNode.onkeypress = _editKeyDown; 265                editNode.onkeypress = _editKeyDown; 
    266                editNode.onblur = _saveAnnotation; 266                editNode.onblur = _saveAnnotation; 
    267        } 267        } 
    268        else 268        else 
    269        { 269        { 
    270                if ( annotation.userid == window.annotationThisUser ) 270                if ( annotation.userid == window.annotationThisUser ) 
    271                { 271                { 
    272                        // add the delete button 272                        // add the delete button 
    273                        var buttonNode = document.createElement( "button" ); 273                        var buttonNode = document.createElement( "button" ); 
    274                        buttonNode.setAttribute( 'type', "button" ); 274                        buttonNode.setAttribute( 'type', "button" ); 
    275                        buttonNode.className = 'annotation-delete'; 275                        buttonNode.className = 'annotation-delete'; 
    276                        buttonNode.setAttribute( 'title', getLocalized( 'delete annotation button' ) ); 276                        buttonNode.setAttribute( 'title', getLocalized( 'delete annotation button' ) ); 
    277                        buttonNode.appendChild( document.createTextNode( "x" ) ); 277                        buttonNode.appendChild( document.createTextNode( "x" ) ); 
    278                        buttonNode.annotationId = annotation.id; 278                        buttonNode.annotationId = annotation.id; 
    279                        buttonNode.onclick = _deleteAnnotation; 279                        buttonNode.onclick = _deleteAnnotation; 
    280                        noteElement.appendChild( buttonNode ); 280                        noteElement.appendChild( buttonNode ); 
    281                        /* access button removed because of new access values and haven't come 281                        /* access button removed because of new access values and haven't come 
    282                         * up with a clear user interface to choosing among them 282                         * up with a clear user interface to choosing among them 
    283                        // add the access button 283                        // add the access button 
    284                        buttonNode = document.createElement( "button" ); 284                        buttonNode = document.createElement( "button" ); 
    285                        buttonNode.type = "button"; 285                        buttonNode.type = "button"; 
    286                        buttonNode.className = 'annotation-access'; 286                        buttonNode.className = 'annotation-access'; 
    287                        buttonNode.setAttribute( 'title', annotation.access == 'public' ? 287                        buttonNode.setAttribute( 'title', annotation.access == 'public' ? 
    288                                getLocalized( 'public annotation' ) : getLocalized( 'private annotation' ) ); 288                                getLocalized( 'public annotation' ) : getLocalized( 'private annotation' ) ); 
    289                        buttonNode.appendChild( document.createTextNode( annotation.access == 'public' ? AN_SUN_SYMBOL : AN_MOON_SYMBOL ) ); 289                        buttonNode.appendChild( document.createTextNode( annotation.access == 'public' ? AN_SUN_SYMBOL : AN_MOON_SYMBOL ) ); 
    290                        buttonNode.annotation = annotation; 290                        buttonNode.annotation = annotation; 
    291                        buttonNode.onclick = _toggleAnnotationAccess; 291                        buttonNode.onclick = _toggleAnnotationAccess; 
    292                        noteElement.appendChild( buttonNode ); 292                        noteElement.appendChild( buttonNode ); 
    293                        */ 293                        */ 
    294                        // Add edit and hover behaviors 294                        // Add edit and hover behaviors 
    295                        noteElement.onmouseover = _hoverAnnotation; 295                        noteElement.onmouseover = _hoverAnnotation; 
    296                        noteElement.onmouseout = _unhoverAnnotation; 296                        noteElement.onmouseout = _unhoverAnnotation; 
    297                        noteElement.onclick = _editAnnotation; 297                        noteElement.onclick = _editAnnotation; 
    298                } 298                } 
    299                // add the text content 299                // add the text content 
    300                noteElement.appendChild( document.createTextNode( annotation.note ) ); 300                noteElement.appendChild( document.createTextNode( annotation.note ) ); 
    301        } 301        } 
    302 302 
    303        var highlightElement = getChildByTagClass( this.contentElement, 'em', AN_ID_PREFIX + annotation.id, null ); 303        var highlightElement = getChildByTagClass( this.contentElement, 'em', AN_ID_PREFIX + annotation.id, null ); 
    304        noteElement.style.marginTop = '' + this.calculateNotePushdown( prevNode, highlightElement ) + 'px'; 304        noteElement.style.marginTop = '' + this.calculateNotePushdown( prevNode, highlightElement ) + 'px'; 
    305         305         
    306        // Insert the note in the list 306        // Insert the note in the list 
    307        noteList.insertBefore( noteElement, nextNode ); 307        noteList.insertBefore( noteElement, nextNode ); 
    308         308         
    309        return noteElement; 309        return noteElement; 
    310} 310} 
    311 311 
    312/* 312/* 
    313 * Display a single highlighted range 313 * Display a single highlighted range 
    314 * Inserts em tags of class annotation were appropriate 314 * Inserts em tags of class annotation were appropriate 
    315 */ 315 */ 
    316PostMicro.prototype.showHighlight = function( annotation ) 316PostMicro.prototype.showHighlight = function( annotation ) 
    317{ 317{ 
    318        var textRange = wordRangeToTextRange( annotation.range, annotation.post.contentElement, _skipSmartcopy ); 318        var textRange = wordRangeToTextRange( annotation.range, annotation.post.contentElement, _skipSmartcopy ); 
    319        // Check whether the content of the text range matches what the annotation expects 319        // Check whether the content of the text range matches what the annotation expects 
    320        if ( null == textRange ) 320        if ( null == textRange ) 
    321        { 321        { 
    322                trace( 'find-quote', 'Annotation ' + annotation.id + ' not within the content area.' ); 322                trace( 'find-quote', 'Annotation ' + annotation.id + ' not within the content area.' ); 
    323                return false; 323                return false; 
    324        } 324        } 
    325        var actual = getTextRangeContent( textRange, _skipSmartcopy ); 325        var actual = getTextRangeContent( textRange, _skipSmartcopy ); 
    326        var quote = annotation.quote; 326        var quote = annotation.quote; 
    327        actual = actual.replace( /\s|\u00a0\s*/g, ' ' ); 327        actual = actual.replace( /\s|\u00a0\s*/g, ' ' ); 
    328        quote = quote.replace( /\s|\u00a0\s*/g, ' ' ); 328        quote = quote.replace( /\s|\u00a0\s*/g, ' ' ); 
    329        if ( actual != quote ) 329        if ( actual != quote ) 
    330        { 330        { 
    331                trace( 'find-quote', 'Annotation ' + annotation.id + ' range \"' + actual + '\" doesn\'t match "' + quote + '"' ); 331                trace( 'find-quote', 'Annotation ' + annotation.id + ' range \"' + actual + '\" doesn\'t match "' + quote + '"' ); 
    332                return false; 332                return false; 
    333        } 333        } 
    334        var nrange = NormalizedRange( textRange, this.contentElement, _skipSmartcopy ); 334        var nrange = NormalizedRange( textRange, this.contentElement, _skipSmartcopy ); 
    335        this.showHighlight_Recurse( annotation, this.contentElement, nrange, 0 ); 335        this.showHighlight_Recurse( annotation, this.contentElement, nrange, 0 ); 
    336        trace( 'find-quote', 'Annotation ' + annotation.id + ' range found.' ); 336        trace( 'find-quote', 'Annotation ' + annotation.id + ' range found.' ); 
    337        return true; 337        return true; 
    338} 338} 
    339 339 
    340PostMicro.prototype.showHighlight_Recurse = function( annotation, node, textRange, position ) 340PostMicro.prototype.showHighlight_Recurse = function( annotation, node, textRange, position ) 
    341{ 341{ 
    342        var start = textRange.offset; 342        var start = textRange.offset; 
    343        var end = textRange.offset + textRange.length; 343        var end = textRange.offset + textRange.length; 
    344 344 
    345        // if we've completed all our markup, finish 345        // if we've completed all our markup, finish 
    346        if ( position > end ) 346        if ( position > end ) 
    347                return 0;               // what a hack!  usually returns the length, but this time returns a node! 347                return 0;               // what a hack!  usually returns the length, but this time returns a node! 
    348         348         
    349        if ( node.nodeType == ELEMENT_NODE ) 349        if ( node.nodeType == ELEMENT_NODE ) 
    350        { 350        { 
    351                if ( hasClass( node, 'smart-copy' ) ) 351                if ( hasClass( node, 'smart-copy' ) ) 
    352                        return 0;       // don't include temporary smartcopy content in count 352                        return 0;       // don't include temporary smartcopy content in count 
    353                else 353                else 
    354                { 354                { 
    355                        var children = new Array(); 355                        var children = new Array(); 
    356                        var length = 0; 356                        var length = 0; 
    357                        for ( var i = 0;  i < node.childNodes.length;  ++i ) 357                        for ( var i = 0;  i < node.childNodes.length;  ++i ) 
    358                                children[ i ] = node.childNodes[ i ]; 358                                children[ i ] = node.childNodes[ i ]; 
    359                        for ( var i = 0;  i < children.length;  ++i ) 359                        for ( var i = 0;  i < children.length;  ++i ) 
    360                                length += this.showHighlight_Recurse( annotation, children[ i ], textRange, position + length ); 360                                length += this.showHighlight_Recurse( annotation, children[ i ], textRange, position + length ); 
    361                        return length; 361                        return length; 
    362                } 362                } 
    363        } 363        } 
    364        else if ( node.nodeType == TEXT_NODE || node.nodeType == CDATA_SECTION_NODE ) 364        else if ( node.nodeType == TEXT_NODE || node.nodeType == CDATA_SECTION_NODE ) 
    365        { 365        { 
    366                var length = node.length; 366                var length = node.length; 
    367                var newNode; 367                var newNode; 
    368                if ( start < position + length ) 368                if ( start < position + length ) 
    369                { 369                { 
    370                        // Is <em> valid in this position in the document?  (It might well not be if 370                        // Is <em> valid in this position in the document?  (It might well not be if 
    371                        // this is a script or style element, or if this is whitespace text in 371                        // this is a script or style element, or if this is whitespace text in 
    372                        // certain other nodes (ul, ol, table, tr, etc.)) 372                        // certain other nodes (ul, ol, table, tr, etc.)) 
    373                        if ( isValidHtmlContent( node.parentNode.tagName, 'em' ) ) 373                        if ( isValidHtmlContent( node.parentNode.tagName, 'em' ) ) 
    374                        { 374                        { 
    375                                var a = start < position ? 0 : start - position; 375                                var a = start < position ? 0 : start - position; 
    376                                var b = end > position + length ? length : end - position; 376                                var b = end > position + length ? length : end - position; 
    377                                var text = node.nodeValue + ""; 377                                var text = node.nodeValue + ""; 
    378                                // break the portion of the node before the annotation off and insert it 378                                // break the portion of the node before the annotation off and insert it 
    379                                if ( a > 0 ) 379                                if ( a > 0 ) 
    380                                { 380                                { 
    381                                        newNode = document.createTextNode( text.substring( 0, a ) ); 381                                        newNode = document.createTextNode( text.substring( 0, a ) ); 
    382                                        node.parentNode.insertBefore( newNode, node ); 382                                        node.parentNode.insertBefore( newNode, node ); 
    383                                } 383                                } 
    384                                // replace node content with annotation 384                                // replace node content with annotation 
    385                                newNode = document.createElement( 'em' ); 385                                newNode = document.createElement( 'em' ); 
    386                                newNode.className = AN_HIGHLIGHT_CLASS + ' ' + AN_ID_PREFIX + annotation.id; 386                                newNode.className = AN_HIGHLIGHT_CLASS + ' ' + AN_ID_PREFIX + annotation.id; 
    387                                newNode.onmouseover = _hoverAnnotation; 387                                newNode.onmouseover = _hoverAnnotation; 
    388                                newNode.onmouseout = _unhoverAnnotation; 388                                newNode.onmouseout = _unhoverAnnotation; 
    389                                newNode.annotation = annotation; 389                                newNode.annotation = annotation; 
    390                                node.parentNode.replaceChild( newNode, node ); 390                                node.parentNode.replaceChild( newNode, node ); 
    391                                newNode.appendChild( node ); 391                                newNode.appendChild( node ); 
    392                                node.nodeValue = text.substring( a, b ); 392                                node.nodeValue = text.substring( a, b ); 
    393                                node = newNode; // necessary for the next bit to work right 393                                node = newNode; // necessary for the next bit to work right 
    394                                // break the portion of the node after the annotation off and insert it 394                                // break the portion of the node after the annotation off and insert it 
    395                                if ( b < length ) 395                                if ( b < length ) 
    396                                { 396                                { 
    397                                        newNode = document.createTextNode( text.substring( b ) ); 397                                        newNode = document.createTextNode( text.substring( b ) ); 
    398                                        if ( node.nextSibling ) 398                                        if ( node.nextSibling ) 
    399                                                node.parentNode.insertBefore( newNode, node.nextSibling ); 399                                                node.parentNode.insertBefore( newNode, node.nextSibling ); 
    400                                        else 400                                        else 
    401                                                node.parentNode.appendChild( newNode ); 401                                                node.parentNode.appendChild( newNode ); 
    402                                } 402                                } 
    403                        } 403                        } 
    404                } 404                } 
    405                else 405                else 
    406                { 406                { 
    407                        trace( 'highlighting', "Don't draw <em> within <" + node.parentNode.tagName + ">: " + node.nodeValue ); 407                        trace( 'highlighting', "Don't draw <em> within <" + node.parentNode.tagName + ">: " + node.nodeValue ); 
    408                } 408                } 
    409                return length; 409                return length; 
    410        } 410        } 
    411        else 411        else 
    412                return 0; 412                return 0; 
    413} 413} 
    414 414 
    415/* 415/* 
    416 * Position the notes for an annotation next to the highlight 416 * Position the notes for an annotation next to the highlight 
    417 * It is not necessary to call this method when creating notes, only when the positions of 417 * It is not necessary to call this method when creating notes, only when the positions of 
    418 * existing notes are changing 418 * existing notes are changing 
    419 */ 419 */ 
    420PostMicro.prototype.positionNote = function( annotation ) 420PostMicro.prototype.positionNote = function( annotation ) 
    421{ 421{ 
    422        var note = document.getElementById( AN_ID_PREFIX + annotation.id ); 422        var note = document.getElementById( AN_ID_PREFIX + annotation.id ); 
    423        while ( null != note ) 423        while ( null != note ) 
    424        { 424        {