Subversion Repositories Applications.papyrus

Rev

Blame | Last modification | View Log | RSS feed

if(!dojo._hasResource["dijit._editor.range"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dijit._editor.range"] = true;
dojo.provide("dijit._editor.range");

dijit.range={};

dijit.range.getIndex=function(/*DomNode*/node, /*DomNode*/parent){
//      dojo.profile.start("dijit.range.getIndex");
        var ret=[], retR=[];
        var stop = parent;
        var onode = node;

        while(node != stop){
                var i = 0;
                var pnode = node.parentNode, n;
                while(n=pnode.childNodes[i++]){
                        if(n===node){
                                --i;
                                break;
                        }
                }
                if(i>=pnode.childNodes.length){
                        dojo.debug("Error finding index of a node in dijit.range.getIndex");
                }
                ret.unshift(i);
                retR.unshift(i-pnode.childNodes.length);
                node = pnode;
        }

        //normalized() can not be called so often to prevent
        //invalidating selection/range, so we have to detect
        //here that any text nodes in a row
        if(ret.length>0 && onode.nodeType==3){
                var n = onode.previousSibling;
                while(n && n.nodeType==3){
                        ret[ret.length-1]--;
                        n = n.previousSibling;
                }
                n = onode.nextSibling;
                while(n && n.nodeType==3){
                        retR[retR.length-1]++;
                        n = n.nextSibling;
                }
        }
//      dojo.profile.end("dijit.range.getIndex");
        return {o: ret, r:retR};
}

dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
        if(!dojo.isArray(index) || index.length==0){
                return parent;
        }
        var node = parent;
//      if(!node)debugger
        dojo.every(index, function(i){
                if(i>=0&&i< node.childNodes.length){
                        node = node.childNodes[i];
                }else{
                        node = null;
                        console.debug('Error: can not find node with index',index,'under parent node',parent );
                        return false; //terminate dojo.every
                }
                return true; //carry on the every loop
        });

        return node;
}

dijit.range.getCommonAncestor = function(n1,n2,root){
        var getAncestors = function(n,root){
                var as=[];
                while(n){
                        as.unshift(n);
                        if(n!=root && n.tagName!='BODY'){
                                n = n.parentNode;
                        }else{
                                break;
                        }
                }
                return as;
        };
        var n1as = getAncestors(n1,root);
        var n2as = getAncestors(n2,root);

        var m = Math.min(n1as.length,n2as.length);
        var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
        for(var i=1;i<m;i++){
                if(n1as[i]===n2as[i]){
                        com = n1as[i]
                }else{
                        break;
                }
        }
        return com;
}

dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
        root = root || node.ownerDocument.body;
        while(node && node !== root){
                var name = node.nodeName.toUpperCase() ;
                if(regex.test(name)){
                        return node;
                }

                node = node.parentNode;
        }
        return null;
}

dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
        root = root || node.ownerDocument.body;
        regex = regex || dijit.range.BlockTagNames;
        var block=null, blockContainer;
        while(node && node !== root){
                var name = node.nodeName.toUpperCase() ;
                if(!block && regex.test(name)){
                        block = node;
                }
                if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
                        blockContainer = node;
                }

                node = node.parentNode;
        }
        return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
}

dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
        var atBeginning = false;
        var offsetAtBeginning = (offset == 0);
        if(!offsetAtBeginning && node.nodeType==3){ //if this is a text node, check whether the left part is all space
                if(dojo.trim(node.nodeValue.substr(0,offset))==0){
                        offsetAtBeginning = true;
                }
        }
        if(offsetAtBeginning){
                var cnode = node;
                atBeginning = true;
                while(cnode && cnode !== container){
                        if(cnode.previousSibling){
                                atBeginning = false;
                                break;
                        }
                        cnode = cnode.parentNode;
                }
        }
        return atBeginning;
}

dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
        var atEnd = false;
        var offsetAtEnd = (offset == (node.length || node.childNodes.length));
        if(!offsetAtEnd && node.nodeType==3){ //if this is a text node, check whether the right part is all space
                if(dojo.trim(node.nodeValue.substr(offset))==0){
                        offsetAtEnd = true;
                }
        }
        if(offsetAtEnd){
                var cnode = node;
                atEnd = true;
                while(cnode && cnode !== container){
                        if(cnode.nextSibling){
                                atEnd = false;
                                break;
                        }
                        cnode = cnode.parentNode;
                }
        }
        return atEnd;
}

dijit.range.adjacentNoneTextNode=function(startnode, next){
        var node = startnode;
        var len = (0-startnode.length) || 0;
        var prop = next?'nextSibling':'previousSibling';
        while(node){
                if(node.nodeType!=3){
                        break;
                }
                len += node.length
                node = node[prop];
        }
        return [node,len];
}

dijit.range._w3c = Boolean(window['getSelection']);
dijit.range.create = function(){
        if(dijit.range._w3c){
                return document.createRange();
        }else{//IE
                return new dijit.range.W3CRange;
        }
}

dijit.range.getSelection = function(win, /*Boolean?*/ignoreUpdate){
        if(dijit.range._w3c){
                return win.getSelection();
        }else{//IE
                var id=win.__W3CRange;
                if(!id || !dijit.range.ie.cachedSelection[id]){
                        var s = new dijit.range.ie.selection(win);
                        //use win as the key in an object is not reliable, which
                        //can leads to quite odd behaviors. thus we generate a
                        //string and use it as a key in the cache
                        id=(new Date).getTime();
                        while(id in dijit.range.ie.cachedSelection){
                                id=id+1;
                        }
                        id=String(id);
                        dijit.range.ie.cachedSelection[id] = s;
                }else{
                        var s = dijit.range.ie.cachedSelection[id];
                }
                if(!ignoreUpdate){
                        s._getCurrentSelection();
                }
                return s;
        }
}

if(!dijit.range._w3c){
        dijit.range.ie={
                cachedSelection: {},
                selection: function(win){
                        this._ranges = [];
                        this.addRange = function(r, /*boolean*/internal){
                                this._ranges.push(r);
                                if(!internal){
                                        r._select();
                                }
                                this.rangeCount = this._ranges.length;
                        };
                        this.removeAllRanges = function(){
                                //don't detach, the range may be used later
//                              for(var i=0;i<this._ranges.length;i++){
//                                      this._ranges[i].detach();
//                              }
                                this._ranges = [];
                                this.rangeCount = 0;
                        };
                        var _initCurrentRange = function(){
                                var r = win.document.selection.createRange();
                                var type=win.document.selection.type.toUpperCase();
                                if(type == "CONTROL"){
                                        //TODO: multiple range selection(?)
                                        return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
                                }else{
                                        return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
                                }
                        };
                        this.getRangeAt = function(i){
                                return this._ranges[i];
                        };
                        this._getCurrentSelection = function(){
                                this.removeAllRanges();
                                var r=_initCurrentRange();
                                if(r){
                                        this.addRange(r, true);
                                }
                        };
                },
                decomposeControlRange: function(range){
                        var firstnode = range.item(0), lastnode = range.item(range.length-1)
                        var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
                        var startOffset = dijit.range.getIndex(firstnode, startContainer).o;
                        var endOffset = dijit.range.getIndex(lastnode, endContainer).o+1;
                        return [[startContainer, startOffset],[endContainer, endOffset]];
                },
                getEndPoint: function(range, end){
                        var atmrange = range.duplicate();
                        atmrange.collapse(!end);
                        var cmpstr = 'EndTo' + (end?'End':'Start');
                        var parentNode = atmrange.parentElement();

                        var startnode, startOffset, lastNode;
                        if(parentNode.childNodes.length>0){
                                dojo.every(parentNode.childNodes, function(node,i){
                                        var calOffset;
                                        if(node.nodeType != 3){
                                                atmrange.moveToElementText(node);

                                                if(atmrange.compareEndPoints(cmpstr,range) > 0){
                                                        startnode = node.previousSibling;
                                                        if(lastNode && lastNode.nodeType == 3){
                                                                //where share we put the start? in the text node or after?
                                                                startnode = lastNode;
                                                                calOffset = true;
                                                        }else{
                                                                startnode = parentNode;
                                                                startOffset = i;
                                                                return false;
                                                        }
                                                }else{
                                                        if(i==parentNode.childNodes.length-1){
                                                                startnode = parentNode;
                                                                startOffset = parentNode.childNodes.length;
                                                                return false;
                                                        }
                                                }
                                        }else{
                                                if(i==parentNode.childNodes.length-1){//at the end of this node
                                                        startnode = node;
                                                        calOffset = true;
                                                }
                                        }
                //                      try{
                                                if(calOffset && startnode){
                                                        var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
                                                        if(prevnode){
                                                                startnode = prevnode.nextSibling;
                                                        }else{
                                                                startnode = parentNode.firstChild; //firstChild must be a text node
                                                        }
                                                        var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
                                                        prevnode = prevnodeobj[0];
                                                        var lenoffset = prevnodeobj[1];
                                                        if(prevnode){
                                                                atmrange.moveToElementText(prevnode);
                                                                atmrange.collapse(false);
                                                        }else{
                                                                atmrange.moveToElementText(parentNode);
                                                        }
                                                        atmrange.setEndPoint(cmpstr, range);
                                                        startOffset = atmrange.text.length-lenoffset;

                                                        return false;
                                                }
                //                      }catch(e){ debugger }
                                        lastNode = node;
                                        return true;
                                });
                        }else{
                                startnode = parentNode;
                                startOffset = 0;
                        }

                        //if at the end of startnode and we are dealing with start container, then
                        //move the startnode to nextSibling if it is a text node
                        //TODO: do this for end container?
                        if(!end && startnode.nodeType!=3 && startOffset == startnode.childNodes.length){
                                if(startnode.nextSibling && startnode.nextSibling.nodeType==3){
                                        startnode = startnode.nextSibling;
                                        startOffset = 0;
                                }
                        }
                        return [startnode, startOffset];
                },
                setEndPoint: function(range, container, offset){
                        //text node
                        var atmrange = range.duplicate();
                        if(container.nodeType!=3){ //normal node
                                atmrange.moveToElementText(container);
                                atmrange.collapse(true);
                                if(offset == container.childNodes.length){
                                        if(offset > 0){
                                                //a simple atmrange.collapse(false); won't work here:
                                                //although moveToElementText(node) is supposed to encompass the content of the node,
                                                //but when collapse to end, it is in fact after the ending tag of node (collapse to start
                                                //is after the begining tag of node as expected)
                                                var node = container.lastChild;
                                                var len = 0;
                                                while(node && node.nodeType == 3){
                                                        len += node.length;
                                                        container = node; //pass through
                                                        node = node.previousSibling;
                                                }
                                                if(node){
                                                        atmrange.moveToElementText(node);
                                                }
                                                atmrange.collapse(false);
                                                offset = len; //pass through
                                        }else{ //no childNodes
                                                atmrange.moveToElementText(container);
                                                atmrange.collapse(true);
                                        }
                                }else{
                                        if(offset > 0){
                                                var node = container.childNodes[offset-1];
                                                if(node.nodeType==3){
                                                        container = node;
                                                        offset = node.length;
                                                        //pass through
                                                }else{
                                                        atmrange.moveToElementText(node);
                                                        atmrange.collapse(false);
                                                }
                                        }
                                }
                        }
                        if(container.nodeType==3){
                                var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
                                var prevnode = prevnodeobj[0], len = prevnodeobj[1];
                                if(prevnode){
                                        atmrange.moveToElementText(prevnode);
                                        atmrange.collapse(false);
                                        //if contentEditable is not inherit, the above collapse won't make the end point
                                        //in the correctly position: it always has a -1 offset, so compensate it
                                        if(prevnode.contentEditable!='inherit'){
                                                len++;
                                        }
                                }else{
                                        atmrange.moveToElementText(container.parentNode);
                                        atmrange.collapse(true);
                                }

                                offset += len;
                                if(offset>0){
                                        if(atmrange.moveEnd('character',offset) != offset){
                                                alert('Error when moving!');
                                        }
                                        atmrange.collapse(false);
                                }
                        }

                        return atmrange;
                },
                decomposeTextRange: function(range){
                        var tmpary = dijit.range.ie.getEndPoint(range);
                        var startContainter = tmpary[0], startOffset = tmpary[1];
                        var endContainter = tmpary[0], endOffset = tmpary[1];

                        if(range.htmlText.length){
                                if(range.htmlText == range.text){ //in the same text node
                                        endOffset = startOffset+range.text.length;
                                }else{
                                        tmpary = dijit.range.ie.getEndPoint(range,true);
                                        endContainter = tmpary[0], endOffset = tmpary[1];
                                }
                        }
                        return [[startContainter, startOffset],[endContainter, endOffset], range.parentElement()];
                },
                setRange: function(range, startContainter,
                        startOffset, endContainter, endOffset, check){
                        var startrange = dijit.range.ie.setEndPoint(range, startContainter, startOffset);
                        range.setEndPoint('StartToStart', startrange);
                        if(!this.collapsed){
                                var endrange = dijit.range.ie.setEndPoint(range, endContainter, endOffset);
                                range.setEndPoint('EndToEnd', endrange);
                        }

                        return range;
                }
        }

dojo.declare("dijit.range.W3CRange",null, {
        constructor: function(){
                if(arguments.length>0){
                        this.setStart(arguments[0][0][0],arguments[0][0][1]);
                        this.setEnd(arguments[0][1][0],arguments[0][1][1],arguments[0][2]);
                }else{
                        this.commonAncestorContainer = null;
                        this.startContainer = null;
                        this.startOffset = 0;
                        this.endContainer = null;
                        this.endOffset = 0;
                        this.collapsed = true;
                }
        },
        _simpleSetEndPoint: function(node, range, end){
                var r = (this._body||node.ownerDocument.body).createTextRange();
                if(node.nodeType!=1){
                        r.moveToElementText(node.parentNode);
                }else{
                        r.moveToElementText(node);
                }
                r.collapse(true);
                range.setEndPoint(end?'EndToEnd':'StartToStart',r);
        },
        _updateInternal: function(__internal_common){
                if(this.startContainer !== this.endContainer){
                        if(!__internal_common){
                                var r = (this._body||this.startContainer.ownerDocument.body).createTextRange();
                                this._simpleSetEndPoint(this.startContainer,r);
                                this._simpleSetEndPoint(this.endContainer,r,true);
                                __internal_common = r.parentElement();
                        }
                        this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer, __internal_common);
                }else{
                        this.commonAncestorContainer = this.startContainer;
                }
                this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
        },
        setStart: function(node, offset, __internal_common){
                if(this.startContainer === node && this.startOffset == offset){
                        return;
                }
                delete this._cachedBookmark;

                this.startContainer = node;
                this.startOffset = offset;
                if(!this.endContainer){
                        this.setEnd(node, offset, __internal_common);
                }else{
                        this._updateInternal(__internal_common);
                }
        },
        setEnd: function(node, offset, __internal_common){
                if(this.endContainer === node && this.endOffset == offset){
                        return;
                }
                delete this._cachedBookmark;

                this.endContainer = node;
                this.endOffset = offset;
                if(!this.startContainer){
                        this.setStart(node, offset, __internal_common);
                }else{
                        this._updateInternal(__internal_common);
                }
        },
        setStartAfter: function(node, offset){
                this._setPoint('setStart', node, offset, 1);
        },
        setStartBefore: function(node, offset){
                this._setPoint('setStart', node, offset, 0);
        },
        setEndAfter: function(node, offset){
                this._setPoint('setEnd', node, offset, 1);
        },
        setEndBefore: function(node, offset){
                this._setPoint('setEnd', node, offset, 0);
        },
        _setPoint: function(what, node, offset, ext){
                var index = dijit.range.getIndex(node, node.parentNode).o;
                this[what](node.parentNode, index.pop()+ext);
        },
        _getIERange: function(){
                var r=(this._body||this.endContainer.ownerDocument.body).createTextRange();
                dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset);
                return r;
        },
        getBookmark: function(body){
                this._getIERange();
                return this._cachedBookmark;
        },
        _select: function(){
                var r = this._getIERange();
                r.select();
        },
        deleteContents: function(){
                var r = this._getIERange();
                r.pasteHTML('');
                this.endContainer = this.startContainer;
                this.endOffset = this.startOffset;
                this.collapsed = true;
        },
        cloneRange: function(){
                var r = new dijit.range.W3CRange([[this.startContainer,this.startOffset],
                        [this.endContainer,this.endOffset]]);
                r._body = this._body;
                return r;
        },
        detach: function(){
                this._body = null;
                this.commonAncestorContainer = null;
                this.startContainer = null;
                this.startOffset = 0;
                this.endContainer = null;
                this.endOffset = 0;
                this.collapsed = true;
}
});
} //if(!dijit.range._w3c)

}