ttrss/lib/dijit/_editor/range.js

542 lines
15 KiB
JavaScript

/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
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){
root = root||n1.ownerDocument.body;
var getAncestors = function(n){
var as=[];
while(n){
as.unshift(n);
if(n !== root){
n = n.parentNode;
}else{
break;
}
}
return as;
};
var n1as = getAncestors(n1);
var n2as = getAncestors(n2);
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(/^[\s\xA0]+$/.test(node.nodeValue.substr(0,offset))){
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(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){
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(/*Window?*/win){
if(dijit.range._w3c){
return (win || dojo.global).document.createRange();
}else{//IE
return new dijit.range.W3CRange;
}
}
dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){
if(dijit.range._w3c){
return win.getSelection();
}else{//IE
var s = new dijit.range.ie.selection(win);
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 shall 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 == 1 && startOffset == startnode.childNodes.length){
var nextnode=startnode.nextSibling;
if(nextnode && nextnode.nodeType == 3){
startnode = nextnode;
startOffset = 0;
}
}
return [startnode, startOffset];
},
setEndPoint: function(range, container, offset){
//text node
var atmrange = range.duplicate(), node, len;
if(container.nodeType!=3){ //normal node
if(offset > 0){
node = container.childNodes[offset-1];
if(node){
if(node.nodeType == 3){
container = node;
offset = node.length;
//pass through
}else{
if(node.nextSibling && node.nextSibling.nodeType == 3){
container=node.nextSibling;
offset=0;
//pass through
}else{
atmrange.moveToElementText(node.nextSibling?node:container);
var parent = node.parentNode;
var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling);
atmrange.collapse(false);
parent.removeChild(tempNode);
}
}
}
}else{
atmrange.moveToElementText(container);
atmrange.collapse(true);
}
}
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.move('character',offset) != offset){
console.error('Error when moving!');
}
}
}
return atmrange;
},
decomposeTextRange: function(range){
var tmpary = dijit.range.ie.getEndPoint(range);
var startContainer = tmpary[0], startOffset = tmpary[1];
var endContainer = 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);
endContainer = tmpary[0], endOffset = tmpary[1];
// if(startContainer.tagName == "BODY"){
// startContainer = startContainer.firstChild;
// }
}
}
return [startContainer, startOffset, endContainer, endOffset];
},
setRange: function(range, startContainer,
startOffset, endContainer, endOffset, collapsed){
var start=dijit.range.ie.setEndPoint(range, startContainer, startOffset);
range.setEndPoint('StartToStart',start);
if(!collapsed){
var end=dijit.range.ie.setEndPoint(range, endContainer, endOffset);
}
range.setEndPoint('EndToEnd',end || start);
return range;
}
}
dojo.declare("dijit.range.W3CRange",null, {
constructor: function(){
if(arguments.length>0){
this.setStart(arguments[0][0],arguments[0][1]);
this.setEnd(arguments[0][2],arguments[0][3]);
}else{
this.commonAncestorContainer = null;
this.startContainer = null;
this.startOffset = 0;
this.endContainer = null;
this.endOffset = 0;
this.collapsed = true;
}
},
_updateInternal: function(){
if(this.startContainer !== this.endContainer){
this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer);
}else{
this.commonAncestorContainer = this.startContainer;
}
this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
},
setStart: function(node, offset){
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);
}else{
this._updateInternal();
}
},
setEnd: function(node, offset){
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);
}else{
this._updateInternal();
}
},
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, this.collapsed);
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)
}