Source for "Links Ahoy !"

By Brian Donovan
Has 5 other scripts.


Add Syntax Highlighting (this will take a few seconds, probably freezing your browser while it works)

//--------------- Last Modified  --------------------
// $Date: 2006-08-01 02:25:50 +0800 (Tue, 01 Aug 2006) $
//------------------- Changes -----------------------
// Aug 1, 2006 => v 1.2
// * unsafeWindow.top == unsafeWindow.self check added to exclude sub-frames and iframes
//
// May 6, 2003 => v 1.1
// * Mozilla developers (as of Moz v 1.3+ apparently) changed the 
// target property of the event object generated during mousedown, mouseup events 
// so that it no longer points to the text node within which the Mouse event was
// fired, but instead points to the nearest element node above that text node in
// in the DOM hierarchy, so I'm relying more upon Mozilla's nonstandard Selection
// objects (using the anchorNode and focusNode properties).
//---------------------------------------------------

// ==UserScript==
// @name Links Ahoy !
// @namespace http://projects.briandonovan.info/projects/greasemonkey-user-scripts/
// @description Alt+clicking and dragging across a text fragment will trigger Ahoy and furnish you with an Ahoy link to your selection.
// @include *
// ==/UserScript==

var objSelectedTextRange = null;
var objAhyAnchor = null;
var objAhyAnchorCreatedParentNode = null;

var intThisAhoyVersion = 1.2;

function numberSort(n1,n2) 
{
    if (n1<n2){
        retVal=-1;
    }else if(n1>n2){
        retVal=1;
    }else{
        retVal=0;
    }
    return retVal;
}

function ahy_ScrubQueryString(strScrubbingMode)
{
    //---------------------------------------------------
    // strScrubbingMode is an arg passed to this function to
    // tell it whether to return the query string with Ahoy
    // params removed or the string of Ahoy params itself.
    // Possible values : querystring and ahoyparams
    // Defaults to querystring
    //---------------------------------------------------
    if(arguments.length != 1){
        strScrubbingMode = "querystring";
    } else if((strScrubbingMode != "querystring") && (strScrubbingMode != "ahoyparams")){
        return false;
    }
    var strQueryString = window.location.search;
    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // For our purposes, assume that whomever is using
    // this script hasn't monkeyed with the order or values
    // of the Ahoy parameters.  If they have, they had better
    // rewrite this method anyway.
    //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    //---------------------------------------------------
    // Get the length of the entire query string and find
    // the index of the beginning of the Ahoy parameters
    // string within.
    //---------------------------------------------------
    var intQueryStringLength = strQueryString.length;
    var intIndexBeginAhoyParamsAtBeginning = strQueryString.indexOf("?ahyAnchor");
    var intIndexBeginAhoyParams = strQueryString.indexOf("&ahyAnchor");
    var arrBeginIndices = [intIndexBeginAhoyParamsAtBeginning, intIndexBeginAhoyParams];
    arrBeginIndices.sort(numberSort);
    if(arrBeginIndices[1] != -1){
        //---------------------------------------------------
        // The Ahoy parameters portion of the query string
        // begins with either "?ahyAnchor" or "&ahyAnchor".
        //---------------------------------------------------
        var intIndexAhoySelIndexParam = strQueryString.indexOf("ahySelectionLength", arrBeginIndices[1]);
        if(intIndexAhoySelIndexParam != -1){
            //---------------------------------------------------
            // The final Ahoy parameter, "ahySelectionLength",  
            // is found within the query string.  Below we find how
            // the Ahoy parameters portion of the overall query
            // string is ended.
            //---------------------------------------------------
            var intIndexAmpersand = strQueryString.indexOf("&", intIndexAhoySelIndexParam);
            var intEndAhoyParams = -1;
            if(intIndexAmpersand != -1){
                intEndAhoyParams = intIndexAmpersand;
            }else{
                intEndAhoyParams = intQueryStringLength;
            }
            //---------------------------------------------------
            // Now, we know where the Ahoy params begin and end 
            // within the query string.  Depending on the value of 
            // strScrubbingMode passed as an arg to this function,
            // we will either be returning the query string without the 
            // Ahoy params or just a substring containing them.
            //---------------------------------------------------
            var strQueryForReturn = '';
            if(strScrubbingMode == "querystring"){
                //---------------------------------------------------
                // We will be returning the query string with the Ahoy parameters removed.
                //---------------------------------------------------
                if(intIndexBeginAhoyParamsAtBeginning != -1){
                    if(intIndexAmpersand != -1){
                        //---------------------------------------------------
                        // Ahoy params substring begins with "?" and ends with "&"
                        //---------------------------------------------------
                        strQueryForReturn = strQueryString.substring(0, (intIndexBeginAhoyParamsAtBeginning + 1));
                    }else{
                        //---------------------------------------------------
                        // Ahoy params substring begins with "?" and ends without "&"
                        //---------------------------------------------------
                        strQueryForReturn = strQueryString.substring(0, intIndexBeginAhoyParamsAtBeginning);
                    }
                    strQueryStringBeforeAhoy = strQueryString.substring();
                }else{
                    if(intIndexAmpersand != -1){
                        //---------------------------------------------------
                        // Ahoy params substring begins with "&" and ends with "&"
                        //---------------------------------------------------
                        var strQueryStringBeforeAhoyParams = strQueryString.substring(0, intIndexBeginAhoyParams);
                        var strQueryStringAfterAhoyParams = strQueryString.substring(intIndexAmpersand, (intQueryStringLength + 1));
                        strQueryForReturn = strQueryStringBeforeAhoyParams + strQueryStringAfterAhoyParams;
                    }else{
                        //---------------------------------------------------
                        // Ahoy params substring begins with "&" and ends without "&"
                        //---------------------------------------------------
                        strQueryForReturn = strQueryString.substring(0, intIndexBeginAhoyParams);
                    }
                }
            }else{
                //---------------------------------------------------
                // We will be returning only the Ahoy parameters 
                // substring of the query string.
                //---------------------------------------------------
                var arrEndIndices = [intIndexAmpersand, intQueryStringLength];
                arrEndIndices.sort(numberSort);
                strQueryForReturn = strQueryString.substring(arrBeginIndices[1], arrEndIndices[1]);
            }
            return strQueryForReturn;
        }else{
            return false;
        }
    }else{
        if(strScrubbingMode == "querystring"){
            return strQueryString;
        }else{
            return false;
        }
    }
}


function ahyObjSelectedTextRange(objExistingAnchorParentNode)
{
    //---------------------------------------------------
    // properties
    //---------------------------------------------------
    ahyObjSelectedTextRange.prototype.nodeExistingAnchorParentNode = objExistingAnchorParentNode;
    ahyObjSelectedTextRange.prototype.ahyMousedownContainer = null;
    ahyObjSelectedTextRange.prototype.ahyMouseupContainer = null;
    ahyObjSelectedTextRange.prototype.ahySelection = null;
    ahyObjSelectedTextRange.prototype.ahySelectionString = null;
    ahyObjSelectedTextRange.prototype.ahySelectionLength = null;
    ahyObjSelectedTextRange.prototype.ahySelectionStartOffset = null;
    ahyObjSelectedTextRange.prototype.ahyParentNodeForAnchor = null;
    ahyObjSelectedTextRange.prototype.ahyChildNodeOfParentNodeForAnchorToSeek = null;
    ahyObjSelectedTextRange.prototype.ahyParentNodeTagName = null;
    ahyObjSelectedTextRange.prototype.ahySelectionParentNodeElementID = null;
    ahyObjSelectedTextRange.prototype.ahyParentNodeIndex = null;
    ahyObjSelectedTextRange.prototype.ahySelectionThisNodeIndex = null;
    ahyObjSelectedTextRange.prototype.ahyQueryStringFragment = null;
    //---------------------------------------------------
    // assign methods to functions 
    //---------------------------------------------------
    ahyObjSelectedTextRange.prototype.ahy_EndSelect = ahy_EndSelect;
    ahyObjSelectedTextRange.prototype.ahy_ScrubQueryString = ahy_ScrubQueryString;

    //---------------------------------------------------
    // functions (ahy_ScrubQueryString used by both, shown above)
    //---------------------------------------------------

    function ahy_EndSelect(event)
    {
        if (event.altKey){
            //---------------------------------------------------
            // In Mozilla, getSelection returns a Selection object whose
            // properties are undocumented. See http://www.pbwizard.com/Articles/Moz_Range_Object_Article.htm
            // for more information.
            //---------------------------------------------------
            //=====================================
            // proprietary Moz dom stuff
            //=====================================
            this.ahySelection = window.getSelection();

            this.ahyMousedownContainer = this.ahySelection.anchorNode;
            this.ahyMouseupContainer = this.ahySelection.focusNode;

            this.ahySelectionString = this.ahySelection.toString();
            this.ahySelectionLength = this.ahySelectionString.length;
            var intSelectionAnchorOffset = this.ahySelection.anchorOffset;
            var intSelectionFocusOffset = this.ahySelection.focusOffset;
            //=====================================
            if(this.ahySelectionLength == 0){
                alert("Zero Characters Selected");
                return false;
            }
            strMouseupContainerNodeValue = this.ahyMouseupContainer.nodeValue;
            //---------------------------------------------------
            // If we are already on a page with an Ahoy-created
            // anchor, we have to check to make sure that the
            // user hasn't made a selection within a text node child
            // of the parent node containing the text node within
            // which the previous selection and anchor insertion was made.
            // Why?  because it may not be possible to pin down
            // the location correctly.
            //---------------------------------------------------
            if(this.ahyMouseupContainer.nodeType == 3){
                //---------------------------------------------------
                // The container node within which the selection begins
                // is the same as the ocontainer in which it ends.
                //---------------------------------------------------
                if (this.ahyMouseupContainer.parentNode.nodeType == 1){
                    //---------------------------------------------------
                    // If they're highlighting text, then the containing nodes of 
                    // selections will always be a text nodes.  The parent nodes 
                    //of the containers are likely to be some html element, like
                    // a P of H1,H2, etc.
                    //---------------------------------------------------
                    var objPreExistingAnchorSpan = document.getElementById("ahoypreanchorspan");
                    var objExistingAnchorSpan = document.getElementById("ahoyanchorid");
                    var objPostExistingAnchorSpan = document.getElementById("ahoypostanchorspan");
                    //---------------------------------------------------
                    //if(objExistingAnchorParentNode == this.ahyMouseupContainer.parentNode.parentNode){
                    if(((this.ahyMouseupContainer.parentNode == objPreExistingAnchorSpan)||
                        (this.ahyMouseupContainer.parentNode == objExistingAnchorSpan)||
                        (this.ahyMouseupContainer.parentNode == objPostExistingAnchorSpan)
                        )&&((this.ahyMousedownContainer.parentNode == objPreExistingAnchorSpan)||
                        (this.ahyMousedownContainer.parentNode == objExistingAnchorSpan)||
                        (this.ahyMousedownContainer.parentNode == objPostExistingAnchorSpan)
                        )){
                        //---------------------------------------------------
                        // There is an existing anchor and the grandparent node 
                        // of the mouseup container is not the parent of the 
                        // existing anchor node.
                        //---------------------------------------------------
                        this.ahyParentNodeForAnchor = this.ahyMouseupContainer.parentNode.parentNode;
                        //---------------------------------------------------
                        // Each of the ahoy-generated  elements has only one 
                        // text node as a child.  We know this because it's how 
                        // we created them.
                        //---------------------------------------------------
                        var strPreExistingAnchorSpanTextNodeVal = '';
                        var strExistingAnchorSpanTextNodeVal = '';
                        var strPostExistingAnchorSpanTextNodeVal = '';
                        //---------------------------------------------------
                        // Set the flags for the Mousedown and Mouseup Container locations
                        //---------------------------------------------------
                        var intMouseDownTextNodeParentFlag = 0;
                        var intMouseUpTextNodeParentFlag = 0;
                        if(objPreExistingAnchorSpan != null){
                            //---------------------------------------------------
                            // Get the text out of the single text node within this SPAN
                            //---------------------------------------------------
                            strPreExistingAnchorSpanTextNodeVal = objPreExistingAnchorSpan.childNodes[0].nodeValue;
                            //---------------------------------------------------
                            // Check for the mouseup and mousedown containers
                            //---------------------------------------------------
                            if(this.ahyMousedownContainer == objPreExistingAnchorSpan.childNodes[0]){
                                intMouseDownTextNodeParentFlag = 1;
                            }
                            if(this.ahyMouseupContainer  == objPreExistingAnchorSpan.childNodes[0]){
                                intMouseUpTextNodeParentFlag = 1;
                            }
                        }
                        if(objExistingAnchorSpan != null){
                            strExistingAnchorSpanTextNodeVal = objExistingAnchorSpan.childNodes[0].nodeValue;
                            //---------------------------------------------------
                            // Check for the mouseup and mousedown containers
                            //---------------------------------------------------
                            if(this.ahyMousedownContainer == objExistingAnchorSpan.childNodes[0]){
                                intMouseDownTextNodeParentFlag = 2;
                            }
                            if(this.ahyMouseupContainer  == objExistingAnchorSpan.childNodes[0]){
                                intMouseUpTextNodeParentFlag = 2;
                            }
                        }
                        if(objPostExistingAnchorSpan != null){
                            //---------------------------------------------------
                            // Get the text out of the single text node within this SPAN
                            //---------------------------------------------------
                            strPostExistingAnchorSpanTextNodeVal = objPostExistingAnchorSpan.childNodes[0].nodeValue;
                            //---------------------------------------------------
                            // Check for the mouseup and mousedown containers
                            //---------------------------------------------------
                            if(this.ahyMousedownContainer == objPostExistingAnchorSpan.childNodes[0]){
                                intMouseDownTextNodeParentFlag = 3;
                            }
                            if(this.ahyMouseupContainer  == objPostExistingAnchorSpan.childNodes[0]){
                                intMouseUpTextNodeParentFlag = 3;
                            }
                        }
                        if(intMouseDownTextNodeParentFlag == intMouseUpTextNodeParentFlag){
                            //---------------------------------------------------
                            // Accomodating left-right and right-left text selection.
                            //---------------------------------------------------
                            var arrSelectionOffsets = [intSelectionAnchorOffset, intSelectionFocusOffset]
                            arrSelectionOffsets.sort(numberSort);
                            this.ahySelectionStartOffset = arrSelectionOffsets[0];
                        }
                        if((intMouseDownTextNodeParentFlag == 1)&&(intMouseUpTextNodeParentFlag == 1)){
                            //---------------------------------------------------
                            // This section of code addresses the problems caused
                            // by returns or other whitespace preceeding a selection
                            // when that selection includes the opening of the text node.
                            //---------------------------------------------------
                            intNumCharBeginSelectionInNodeValue = strMouseupContainerNodeValue.indexOf(this.ahySelectionString);
                            if(intNumCharBeginSelectionInNodeValue > this.ahySelectionStartOffset){
                                this.ahySelectionStartOffset = intNumCharBeginSelectionInNodeValue;
                            }
                        }
                        if((intMouseDownTextNodeParentFlag == 2)&&(intMouseUpTextNodeParentFlag == 2)){
                            //---------------------------------------------------
                            // This section of code addresses the problems caused
                            // by returns or other whitespace preceeding a selection
                            // when that selection includes the opening of the text node.
                            //---------------------------------------------------
                            intNumCharBeginSelectionInNodeValue = strMouseupContainerNodeValue.indexOf(this.ahySelectionString);
                            if(intNumCharBeginSelectionInNodeValue > this.ahySelectionStartOffset){
                                this.ahySelectionStartOffset = intNumCharBeginSelectionInNodeValue;
                            }
                            this.ahySelectionStartOffset = this.ahySelectionStartOffset + strPreExistingAnchorSpanTextNodeVal.length;
                        }
                        if((intMouseDownTextNodeParentFlag == 3)&&(intMouseUpTextNodeParentFlag == 3)){
                            //---------------------------------------------------
                            // This section of code addresses the problems caused
                            // by returns or other whitespace preceeding a selection
                            // when that selection includes the opening of the text node.
                            //---------------------------------------------------
                            intNumCharBeginSelectionInNodeValue = strMouseupContainerNodeValue.indexOf(this.ahySelectionString);
                            if(intNumCharBeginSelectionInNodeValue > this.ahySelectionStartOffset){
                                this.ahySelectionStartOffset = intNumCharBeginSelectionInNodeValue;
                            }
                            this.ahySelectionStartOffset = this.ahySelectionStartOffset + strPreExistingAnchorSpanTextNodeVal.length + strExistingAnchorSpanTextNodeVal.length;
                        }
                        if((intMouseDownTextNodeParentFlag == 1) && ((intMouseUpTextNodeParentFlag == 2) || (intMouseUpTextNodeParentFlag == 3))){
                            this.ahySelectionStartOffset = intSelectionAnchorOffset;
                        }
                        if((intMouseDownTextNodeParentFlag == 2) && (intMouseUpTextNodeParentFlag == 3)){
                            this.ahySelectionStartOffset =  strPreExistingAnchorSpanTextNodeVal.length + intSelectionAnchorOffset;
                        }
                        if((intMouseDownTextNodeParentFlag == 2) && (intMouseUpTextNodeParentFlag == 1)){
                            this.ahySelectionStartOffset =  intSelectionFocusOffset;
                        }
                        if((intMouseDownTextNodeParentFlag == 3) && (intMouseUpTextNodeParentFlag == 2)){
                            this.ahySelectionStartOffset =  strPreExistingAnchorSpanTextNodeVal.length + intSelectionFocusOffset;
                        }
                        if((intMouseDownTextNodeParentFlag == 3) && (intMouseUpTextNodeParentFlag == 1)){
                            this.ahySelectionStartOffset =  intSelectionFocusOffset;
                        }
                        //---------------------------------------------------
                        if(objPreExistingAnchorSpan != null){
                            this.ahyChildNodeOfParentNodeForAnchorToSeek = objPreExistingAnchorSpan;
                        }else{
                            this.ahyChildNodeOfParentNodeForAnchorToSeek = objExistingAnchorSpan;
                        }
                        //---------------------------------------------------
                    }else if((this.ahyMousedownContainer == this.ahyMouseupContainer)&&
                                    (
                                        ((objExistingAnchorParentNode == this.ahyMouseupContainer.parentNode)) &&
                                        ((objExistingAnchorParentNode == this.ahyMousedownContainer.parentNode))
                                    )
                                ){
                        var intNumAhoyNodes = 0;
                        var intIndexMouseEventsNode=0;
                        for(var i=0; i<this.ahyMouseupContainer.parentNode.childNodes.length; i++){
                            if((this.ahyMouseupContainer.parentNode.childNodes[i] == objPreExistingAnchorSpan)||
                                (this.ahyMouseupContainer.parentNode.childNodes[i] == objExistingAnchorSpan)||
                                (this.ahyMouseupContainer.parentNode.childNodes[i] == objPostExistingAnchorSpan)){
                                intNumAhoyNodes++;
                             }else if(this.ahyMousedownContainer == this.ahyMouseupContainer.parentNode.childNodes[i]){
                                if(intNumAhoyNodes > 0){
                                    intIndexMouseEventsNode = i - (intNumAhoyNodes - 1);
                                }else{
                                    intIndexMouseEventsNode = i;
                                }
                                break;
                            }
                        }
                        //---------------------------------------------------
                        var arrSelectionOffsets = [intSelectionAnchorOffset, intSelectionFocusOffset]
                        arrSelectionOffsets.sort(numberSort);
                        this.ahySelectionStartOffset = arrSelectionOffsets[0];
                        //---------------------------------------------------
                        // There is no existing anchor and/or if one exists, the 
                        // grandparent node of the mouseup container is not
                        // the parent of the existing anchor node
                        //---------------------------------------------------
                        this.ahyParentNodeForAnchor = this.ahyMouseupContainer.parentNode;
                        //---------------------------------------------------
                        this.ahyChildNodeOfParentNodeForAnchorToSeek = this.ahyMouseupContainer;
                        //---------------------------------------------------
                    }else if(this.ahyMousedownContainer == this.ahyMouseupContainer){
                        //---------------------------------------------------
                        // There is no existing anchor and/or if one exists, the 
                        // grandparent node of the mouseup container is not
                        // the parent of the existing anchor node
                        //---------------------------------------------------
                        this.ahyParentNodeForAnchor = this.ahyMouseupContainer.parentNode;
                        var arrSelectionOffsets = [intSelectionAnchorOffset, intSelectionFocusOffset]
                        arrSelectionOffsets.sort(numberSort);
                        this.ahySelectionStartOffset = arrSelectionOffsets[0];
                        //---------------------------------------------------
                        // This section of code addresses the problems caused
                        // by returns or other whitespace preceeding a selection
                        // when that selection includes the opening of the text node.
                        //---------------------------------------------------
                        intNumCharBeginSelectionInNodeValue = strMouseupContainerNodeValue.indexOf(this.ahySelectionString);
                        if(intNumCharBeginSelectionInNodeValue > this.ahySelectionStartOffset){
                            this.ahySelectionStartOffset = intNumCharBeginSelectionInNodeValue;
                        }
                        this.ahyChildNodeOfParentNodeForAnchorToSeek = this.ahyMouseupContainer;
                        //---------------------------------------------------
                    }else{
                        alert("start and end in different nodes ! ");
                        return false;
                    }
                    //---------------------------------------------------
                    if(this.ahyParentNodeForAnchor.hasAttribute("ID")){
                        //---------------------------------------------------
                        // The parent node has an ID attribute
                        //---------------------------------------------------
                        this.ahySelectionParentNodeElementID = this.ahyParentNodeForAnchor.getAttribute("ID");
                    }else{
                        //---------------------------------------------------
                        // Element has no id attribute.  Use getElementsByTagName()
                        //---------------------------------------------------
                        this.ahyParentNodeTagName = this.ahyParentNodeForAnchor.nodeName;
                        var arrDocumentGetElementsByTagName = document.getElementsByTagName(this.ahyParentNodeTagName);
                        for(var i=0; i< arrDocumentGetElementsByTagName.length; i++){
                            if(arrDocumentGetElementsByTagName[i] == this.ahyParentNodeForAnchor){
                                this.ahyParentNodeIndex = i;
                                break;
                            }
                        }
                    }
                    for(var i=0; i < this.ahyParentNodeForAnchor.childNodes.length; i++){
                        if(this.ahyParentNodeForAnchor.childNodes[i] == this.ahyChildNodeOfParentNodeForAnchorToSeek){
                            if(intIndexMouseEventsNode > 0){
                                this.ahySelectionThisNodeIndex = intIndexMouseEventsNode;
                            } else{
                                this.ahySelectionThisNodeIndex = i;
                            }
                            break;
                        }
                    }
                    //---------------------------------------------------
                    // Prepare the query string.
                    //---------------------------------------------------
                    if( this.ahySelectionParentNodeElementID == null){
                        //---------------------------------------------------
                        // The parent element doesn't have an ID attribute, so
                        // we're using the tag name and node index instead.
                        //---------------------------------------------------
                        this.ahyQueryStringFragment = "ahyAnchor="+intThisAhoyVersion+"&ahyParentNodeTagName="+this.ahyParentNodeTagName+"&ahyParentNodeIndex="+this.ahyParentNodeIndex+"&ahyChildIndex="+this.ahySelectionThisNodeIndex+"&ahySelectionStart="+this.ahySelectionStartOffset+"&ahySelectionLength="+this.ahySelectionLength;
                    } else{
                        //---------------------------------------------------
                        // The parent element has an id attribute.
                        //---------------------------------------------------
                        this.ahyQueryStringFragment = "ahyAnchor="+intThisAhoyVersion+"&ahyParentNodeID="+this.ahySelectionParentNodeElementID+"&ahyChildIndex="+this.ahySelectionThisNodeIndex+"&ahySelectionStart="+this.ahySelectionStartOffset+"&ahySelectionLength="+this.ahySelectionLength;
                    }
                    var strNewHref = location.protocol+"//"+location.host+location.pathname;
                    if(location.search == ''){
                        location.href = strNewHref + "?"+this.ahyQueryStringFragment+"#ahoyanchor";
                    }else{
                        //---------------------------------------------------
                        // There is a query string, but some or all of the 
                        // parameters could be Ahoy params.  They must be 
                        // removed.
                        //---------------------------------------------------
                        var strScrubbedQueryString = ahy_ScrubQueryString();
                        if(strScrubbedQueryString != ''){
                            strNewHref  += strScrubbedQueryString + "&" + this.ahyQueryStringFragment+"#ahoyanchor";
                        }else{
                            strNewHref  += "?" + this.ahyQueryStringFragment+"#ahoyanchor";
                        }
                        //---------------------------------------------------
                        // Send the browser to the new location.
                        //---------------------------------------------------
                        location.href = strNewHref;
                    }
                }else{
                    alert("Parent node is not an element node \n nodeType : " + this.ahyMouseupContainer.parentNode.nodeType  + "\n nodeName : " + this.ahyMouseupContainer.parentNode.nodeName);
                    return false;
                }
            }else{
                alert("Not a text node ! ");
                return false;
            }
        }
    }

}

function ahyObjAnchor()
{
    //---------------------------------------------------
    // properties
    //---------------------------------------------------
    ahyObjAnchor.prototype.ahyQueryStringParamsString = null;
    ahyObjAnchor.prototype.ahyAnchorPropertiesArr = null;
    ahyObjAnchor.prototype.ahySelectionParentNode = null;
    ahyObjAnchor.prototype.ahySelectionChildNode = null;
    ahyObjAnchor.prototype.ahyRangeObj = null;
    ahyObjAnchor.prototype.ahyRangeContentsBeforeAnchor = null;
    ahyObjAnchor.prototype.ahyDocumentFragmentToInsert = null;
    ahyObjAnchor.prototype.ahyInsertedAnchorNode = null;
    //---------------------------------------------------
    // assign methods to functions 
    //---------------------------------------------------
    ahyObjAnchor.prototype.ahy_FetchAhyQueryParamsString = ahy_ScrubQueryString;
    ahyObjAnchor.prototype.ahy_ParseAhyQueryParamsString = ahy_ParseAhyQueryParamsString;
    ahyObjAnchor.prototype.ahy_ParseOneAhyParam = ahy_ParseOneAhyParam;
    ahyObjAnchor.prototype.ahy_CreateAnchor = ahy_CreateAnchor;

    //---------------------------------------------------
    //  functions (ahy_ScrubQueryString used by both, shown above)
    //---------------------------------------------------

    function ahy_ParseOneAhyParam(strAhyParamName)
    {
        var intIndexAhyParamValuePairStart = this.ahyQueryStringParamsString.indexOf(strAhyParamName);
        if(intIndexAhyParamValuePairStart!= -1){
            //---------------------------------------------------
            // The Ahoy parameter name was found in the query string.
            //---------------------------------------------------
            var intIndexAhyParamValuePairStop = this.ahyQueryStringParamsString.indexOf("&",intIndexAhyParamValuePairStart);
            if(intIndexAhyParamValuePairStop == -1){
                intIndexAhyParamValuePairStop = this.ahyQueryStringParamsString.length;
            }
            var strAhyParamValuePair = this.ahyQueryStringParamsString.substring(intIndexAhyParamValuePairStart, intIndexAhyParamValuePairStop);
            var arrAhyParamValuePair = strAhyParamValuePair.split("=");
            var intLengthArrAhyParamVauePair = arrAhyParamValuePair.length;
            if(intLengthArrAhyParamVauePair == 2){
                //---------------------------------------------------
                // The array produced by splitting the param value 
                // pair on "=" is 2 elements in length.  Return the value.
                //---------------------------------------------------
                return arrAhyParamValuePair[1];
            } else{
                return -1;
            }
        }else{
            return -1;
        }
    }

    function ahy_ParseAhyQueryParamsString()
    {
        this.ahyAnchorPropertiesArr = new Array();
        if(this.ahyQueryStringParamsString.indexOf("ahyParentNodeID") != -1){
            //---------------------------------------------------
            // "ahyParentNodeID" param can be used to identify
            // the parent node using document.getElementById()
            //---------------------------------------------------
            this.ahyAnchorPropertiesArr["parentnodeid"] = this.ahy_ParseOneAhyParam("ahyParentNodeID");
            this.ahyAnchorPropertiesArr["parentnodetagname"] = null;
            this.ahyAnchorPropertiesArr["parentnodeindex"] = null;
            if(this.ahyAnchorPropertiesArr["parentnodeid"] == -1){
                return false;
            }
        }else if((this.ahyQueryStringParamsString.indexOf("ahyParentNodeTagName") != -1)&&(this.ahyQueryStringParamsString.indexOf("ahyParentNodeIndex") != -1)){
            //---------------------------------------------------
            // "ahyParentNodeTagName" and "ahyParentNodeIndex" 
            // params can be used to identify the parent node using 
            // document.getElementsByTagName()
            //---------------------------------------------------
            this.ahyAnchorPropertiesArr["parentnodeid"] = null;
            this.ahyAnchorPropertiesArr["parentnodetagname"] = this.ahy_ParseOneAhyParam("ahyParentNodeTagName");
            this.ahyAnchorPropertiesArr["parentnodeindex"] = parseInt(this.ahy_ParseOneAhyParam("ahyParentNodeIndex"));
            if((this.ahyAnchorPropertiesArr["parentnodetagname"] == -1) || (this.ahyAnchorPropertiesArr["parentnodeindex"] == -1)){
                return false;
            }
        }else{
            //---------------------------------------------------
            // Parameters necessary for identification of the parent
            // node to the child node containing the selection have
            // not been provided.
            //---------------------------------------------------
            return false;
        }
        this.ahyAnchorPropertiesArr["childnodeindex"] = parseInt(this.ahy_ParseOneAhyParam("ahyChildIndex"));
        this.ahyAnchorPropertiesArr["selectionstart"] = parseInt(this.ahy_ParseOneAhyParam("ahySelectionStart"));
        this.ahyAnchorPropertiesArr["selectionlength"] = parseInt(this.ahy_ParseOneAhyParam("ahySelectionLength"));
        if((this.ahyAnchorPropertiesArr["childnodeindex"] == -1) || 
            (this.ahyAnchorPropertiesArr["selectionstart"] == -1) ||
            (this.ahyAnchorPropertiesArr["selectionlength"] == -1)){
            return false;
        }else{
            return true;
        }
    }

    function ahy_CreateAnchor()
    {
        //---------------------------------------------------
        // First, Get the parent node.  The method depends
        // on whether the Ahoy query string parameters
        // include a parent element id OR a parent element
        // tagname and index in getElementsByTagName array.
        //---------------------------------------------------
        if(this.ahyAnchorPropertiesArr["parentnodeid"] != null){
            this.ahySelectionParentNode = document.getElementById(this.ahyAnchorPropertiesArr["parentnodeid"]);
        }else if((this.ahyAnchorPropertiesArr["parentnodetagname"] != null)&&(this.ahyAnchorPropertiesArr["parentnodeindex"] != null)){
            var arrElementsWithSameTagNameAsParentNode = document.getElementsByTagName(this.ahyAnchorPropertiesArr["parentnodetagname"]);
            this.ahySelectionParentNode = arrElementsWithSameTagNameAsParentNode[this.ahyAnchorPropertiesArr["parentnodeindex"]];
        }else{
            alert("One or more Ahoy parameters necessary to resolve the location of the selection are missing.  Exiting.");
            return false;
        }
        //---------------------------------------------------
        // Next, get the Child Node of interest
        //---------------------------------------------------
        this.ahySelectionChildNode = this.ahySelectionParentNode.childNodes[this.ahyAnchorPropertiesArr["childnodeindex"]];
        //---------------------------------------------------
        // Create Range objects and set their starts and ends 
        // using data from the Ahoy parameters.
        //---------------------------------------------------
        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        // Create Range object before Selection
        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        var objPreSelectionRange = document.createRange();
        objPreSelectionRange.setStart(this.ahySelectionChildNode, 0);
        objPreSelectionRange.setEnd(this.ahySelectionChildNode, this.ahyAnchorPropertiesArr["selectionstart"]);
        var objPreSelectionRangeContentsString = objPreSelectionRange.toString();
        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        // Create Range object for Selection itself
        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        this.ahyRangeObj = document.createRange();
        this.ahyRangeObj.setStart(this.ahySelectionChildNode, this.ahyAnchorPropertiesArr["selectionstart"]);
        var intIndexSelectionEnd = this.ahyAnchorPropertiesArr["selectionstart"] + this.ahyAnchorPropertiesArr["selectionlength"];
        this.ahyRangeObj.setEnd(this.ahySelectionChildNode, intIndexSelectionEnd);
        var strRangeSelectionForAnchor = this.ahyRangeObj.toString();
        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        // Create Range object after Selection
        //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        var objPostSelectionRange = document.createRange();
        objPostSelectionRange.setStart(this.ahySelectionChildNode, intIndexSelectionEnd);
        objPostSelectionRange.setEnd(this.ahySelectionChildNode, this.ahySelectionChildNode.nodeValue.length);
        var objPostSelectionRangeContentsString = objPreSelectionRange.toString();
        //---------------------------------------------------
        // We will be creating a document fragment, appending 
        // nodes constructed from the ranges as children, and
        // then replacing the original node with out fragment.
        //---------------------------------------------------
        this.ahyDocumentFragmentToInsert  = document.createDocumentFragment();
        //---------------------------------------------------
        // Create some strings to contain the snips of the
        // original text node on either side of the selection.
        //---------------------------------------------------
        var strPreSelectionText = objPreSelectionRange.toString();
        var strPostSelectionText = objPostSelectionRange.toString();
        //---------------------------------------------------
        // Create our anchor element, give it a name and an id
        // and fill it with a text node containing the selected text.
        //---------------------------------------------------
        var nodeAnchorElement = document.createElement("A");
        var nodeAnchorElementContents = document.createTextNode(strRangeSelectionForAnchor);
        nodeAnchorElement.setAttribute("name", "ahoyanchor" );
        nodeAnchorElement.setAttribute("id", "ahoyanchorid" );
        nodeAnchorElement.appendChild(nodeAnchorElementContents);
        //---------------------------------------------------
        // Create span elements that will be placed adjacent to 
        // the anchor element to contain the text on either side 
        // of the selection in the original text node.
        //---------------------------------------------------
        //---------------------------------------------------
        // Append the child nodes to the document fragment in
        // consecutive order
        //---------------------------------------------------
        if(strPreSelectionText.length > 0){
            var nodePreAnchorSpanElement = document.createElement("SPAN");
            var nodePreSelectionTextNode = document.createTextNode(strPreSelectionText);
            nodePreAnchorSpanElement.setAttribute("id", "ahoypreanchorspan" );
            nodePreAnchorSpanElement.appendChild(nodePreSelectionTextNode);
            this.ahyDocumentFragmentToInsert .appendChild(nodePreAnchorSpanElement);
        }
        this.ahyDocumentFragmentToInsert .appendChild(nodeAnchorElement);
        if(strPostSelectionText.length > 0){
            var nodePostAnchorSpanElement = document.createElement("SPAN");
            var nodePostSelectionTextNode = document.createTextNode(strPostSelectionText);
            nodePostAnchorSpanElement.setAttribute("id", "ahoypostanchorspan" );
            nodePostAnchorSpanElement.appendChild(nodePostSelectionTextNode);
            this.ahyDocumentFragmentToInsert .appendChild(nodePostAnchorSpanElement);
        }
        //---------------------------------------------------
        // Replace the text node containing the selection with
        // the document fragment that we've prepared.
        //---------------------------------------------------
        this.ahySelectionParentNode.replaceChild(this.ahyDocumentFragmentToInsert, this.ahySelectionParentNode.childNodes[this.ahyAnchorPropertiesArr["childnodeindex"]]);
        //---------------------------------------------------
        // Get a handle on the ahyanchor A element so that we
        // can manipulate its style properties.
        //---------------------------------------------------
        this.ahyInsertedAnchorNode = document.getElementById("ahoyanchorid");
        this.ahyInsertedAnchorNode.style.backgroundColor="rgb(220,220,220)";
        return true;
    }

}

function ahy_DoOnLoad()
{
    //---------------------------------------------------
    // Test to see if the browser claims to have implemented
    // the W3C DOM Range API 
    //---------------------------------------------------
    if(window.getSelection && document.implementation.hasFeature){
        var blnRangeImplemented = document.implementation.hasFeature("Range", "2.0");
        if(blnRangeImplemented){
            if(location.search.indexOf("ahyAnchor") != -1){
                //---------------------------------------------------
                // There's an "ahyAnchor" parameter in the query string,
                // so we may need to create an anchor.  Instantiate an
                // Ahoy Anchor object, parse the query string, and create
                // the anchor.
                //---------------------------------------------------
                objAhyAnchor = new ahyObjAnchor();
                var blnORstrExtractAhoyParamsSubstring = objAhyAnchor.ahy_FetchAhyQueryParamsString("ahoyparams");
                if(blnORstrExtractAhoyParamsSubstring){
                    objAhyAnchor.ahyQueryStringParamsString = blnORstrExtractAhoyParamsSubstring;
                }else{
                    alert("Problems were encountered while obtaining the set of Ahoy parameters from the query string.  Cannot create anchor.");
                    return false;
                }
                var blnparseAhoyParams = objAhyAnchor.ahy_ParseAhyQueryParamsString();
                if(blnparseAhoyParams){
                    var blnAhyAnchorCreated = objAhyAnchor.ahy_CreateAnchor();
                    if(blnAhyAnchorCreated){
                        objAhyAnchorCreatedParentNode = objAhyAnchor.ahySelectionParentNode;
                    }
                    //---------------------------------------------------
                    // We add a delay before jumping to the anchor that's
                    // been created.  Without the delay, Mozilla seems to
                    // slightly undershoot or overshoot the scroll to the anchor.
                    //---------------------------------------------------
                    setTimeout("location.href = '#ahoyanchor'", 100);
                }else{
                    alert("Problems were encountered while parsing the Ahoy parameters in query string.  Cannot create anchor.");
                    return false;
                }
            }
            //---------------------------------------------------
            // Instantiate an Ahoy Selected Text Range object and
            // attach ahyObjSelectedTextRange methods as handlers
            // for the mousedown and mouseup events.
            //---------------------------------------------------
            objSelectedTextRange = new ahyObjSelectedTextRange(objAhyAnchorCreatedParentNode);
            document.addEventListener("mouseup", objSelectedTextRange.ahy_EndSelect, false);
        }
    }
}

if (unsafeWindow.top == unsafeWindow.self) {
    if(window.addEventListener){
        window.addEventListener("load", ahy_DoOnLoad, false);
    }else{
        if(location.search.indexOf("ahyAnchor") != -1){
            alert("You've attempted to access an AHOY bookmark within this page, but your browser doesn't support the addEventListener method of the W3C DOM Level 2 Events API for the window object.  You're being redirected to the url of the page sans the AHOY query string parameters ...");
           //---------------------------------------------------
            // There is a query string, but some or all of the 
            // parameters could be Ahoy params.  They must be 
            // removed.
            //---------------------------------------------------
            var strLocationSansQueryString = location.protocol+"//"+location.host+location.pathname;
            var strScrubbedQueryString = ahy_ScrubQueryString();
            if(strScrubbedQueryString != ''){
                location.replace(strLocationSansQueryString+strScrubbedQueryString);
            }else{
                location.replace(strLocationSansQueryString);
            }
        }
    }
}