571 lines
16 KiB
JavaScript
571 lines
16 KiB
JavaScript
|
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;
|
||
|
|
||
|
var pnode, n;
|
||
|
while(node != stop){
|
||
|
var i = 0;
|
||
|
pnode = node.parentNode;
|
||
|
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){
|
||
|
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 dojo.doc.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,s;
|
||
|
if(!id || !dijit.range.ie.cachedSelection[id]){
|
||
|
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{
|
||
|
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(), node, len;
|
||
|
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)
|
||
|
node = container.lastChild;
|
||
|
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){
|
||
|
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){
|
||
|
offset=parseInt(offset);
|
||
|
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){
|
||
|
offset=parseInt(offset);
|
||
|
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)
|
||
|
|
||
|
}
|