]> git.wh0rd.org Git - tt-rss.git/blob - lib/dijit/_editor/range.js.uncompressed.js
update dojo to 1.7.3
[tt-rss.git] / lib / dijit / _editor / range.js.uncompressed.js
1 define("dijit/_editor/range", [
2         "dojo/_base/array", // array.every
3         "dojo/_base/declare", // declare
4         "dojo/_base/lang", // lang.isArray
5         "dojo/_base/window", // win.global
6         ".."    // for exporting symbols to dijit, TODO: remove in 2.0
7 ], function(array, declare, lang, win, dijit){
8
9 // module:
10 //              dijit/_editor/range
11 // summary:
12 //              W3C range API
13
14
15 dijit.range={};
16
17 dijit.range.getIndex = function(/*DomNode*/node, /*DomNode*/parent){
18 //      dojo.profile.start("dijit.range.getIndex");
19         var ret = [], retR = [];
20         var onode = node;
21
22         var pnode, n;
23         while(node != parent){
24                 var i = 0;
25                 pnode = node.parentNode;
26                 while((n = pnode.childNodes[i++])){
27                         if(n === node){
28                                 --i;
29                                 break;
30                         }
31                 }
32                 //if(i>=pnode.childNodes.length){
33                         //dojo.debug("Error finding index of a node in dijit.range.getIndex");
34                 //}
35                 ret.unshift(i);
36                 retR.unshift(i - pnode.childNodes.length);
37                 node = pnode;
38         }
39
40         //normalized() can not be called so often to prevent
41         //invalidating selection/range, so we have to detect
42         //here that any text nodes in a row
43         if(ret.length > 0 && onode.nodeType == 3){
44                 n = onode.previousSibling;
45                 while(n && n.nodeType == 3){
46                         ret[ret.length - 1]--;
47                         n = n.previousSibling;
48                 }
49                 n = onode.nextSibling;
50                 while(n && n.nodeType == 3){
51                         retR[retR.length - 1]++;
52                         n = n.nextSibling;
53                 }
54         }
55 //      dojo.profile.end("dijit.range.getIndex");
56         return {o: ret, r:retR};
57 };
58
59 dijit.range.getNode = function(/*Array*/index, /*DomNode*/parent){
60         if(!lang.isArray(index) || index.length == 0){
61                 return parent;
62         }
63         var node = parent;
64 //      if(!node)debugger
65         array.every(index, function(i){
66                 if(i >= 0 && i < node.childNodes.length){
67                         node = node.childNodes[i];
68                 }else{
69                         node = null;
70                         //console.debug('Error: can not find node with index',index,'under parent node',parent );
71                         return false; //terminate array.every
72                 }
73                 return true; //carry on the every loop
74         });
75
76         return node;
77 };
78
79 dijit.range.getCommonAncestor = function(n1, n2, root){
80         root = root || n1.ownerDocument.body;
81         var getAncestors = function(n){
82                 var as = [];
83                 while(n){
84                         as.unshift(n);
85                         if(n !== root){
86                                 n = n.parentNode;
87                         }else{
88                                 break;
89                         }
90                 }
91                 return as;
92         };
93         var n1as = getAncestors(n1);
94         var n2as = getAncestors(n2);
95
96         var m = Math.min(n1as.length, n2as.length);
97         var com = n1as[0]; //at least, one element should be in the array: the root (BODY by default)
98         for(var i = 1; i < m; i++){
99                 if(n1as[i] === n2as[i]){
100                         com = n1as[i]
101                 }else{
102                         break;
103                 }
104         }
105         return com;
106 };
107
108 dijit.range.getAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
109         root = root || node.ownerDocument.body;
110         while(node && node !== root){
111                 var name = node.nodeName.toUpperCase();
112                 if(regex.test(name)){
113                         return node;
114                 }
115
116                 node = node.parentNode;
117         }
118         return null;
119 };
120
121 dijit.range.BlockTagNames = /^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/;
122 dijit.range.getBlockAncestor = function(/*DomNode*/node, /*RegEx?*/regex, /*DomNode?*/root){
123         root = root || node.ownerDocument.body;
124         regex = regex || dijit.range.BlockTagNames;
125         var block = null, blockContainer;
126         while(node && node !== root){
127                 var name = node.nodeName.toUpperCase();
128                 if(!block && regex.test(name)){
129                         block = node;
130                 }
131                 if(!blockContainer && (/^(?:BODY|TD|TH|CAPTION)$/).test(name)){
132                         blockContainer = node;
133                 }
134
135                 node = node.parentNode;
136         }
137         return {blockNode:block, blockContainer:blockContainer || node.ownerDocument.body};
138 };
139
140 dijit.range.atBeginningOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
141         var atBeginning = false;
142         var offsetAtBeginning = (offset == 0);
143         if(!offsetAtBeginning && node.nodeType == 3){ //if this is a text node, check whether the left part is all space
144                 if(/^[\s\xA0]+$/.test(node.nodeValue.substr(0, offset))){
145                         offsetAtBeginning = true;
146                 }
147         }
148         if(offsetAtBeginning){
149                 var cnode = node;
150                 atBeginning = true;
151                 while(cnode && cnode !== container){
152                         if(cnode.previousSibling){
153                                 atBeginning = false;
154                                 break;
155                         }
156                         cnode = cnode.parentNode;
157                 }
158         }
159         return atBeginning;
160 };
161
162 dijit.range.atEndOfContainer = function(/*DomNode*/container, /*DomNode*/node, /*Int*/offset){
163         var atEnd = false;
164         var offsetAtEnd = (offset == (node.length || node.childNodes.length));
165         if(!offsetAtEnd && node.nodeType == 3){ //if this is a text node, check whether the right part is all space
166                 if(/^[\s\xA0]+$/.test(node.nodeValue.substr(offset))){
167                         offsetAtEnd = true;
168                 }
169         }
170         if(offsetAtEnd){
171                 var cnode = node;
172                 atEnd = true;
173                 while(cnode && cnode !== container){
174                         if(cnode.nextSibling){
175                                 atEnd = false;
176                                 break;
177                         }
178                         cnode = cnode.parentNode;
179                 }
180         }
181         return atEnd;
182 };
183
184 dijit.range.adjacentNoneTextNode = function(startnode, next){
185         var node = startnode;
186         var len = (0 - startnode.length) || 0;
187         var prop = next ? 'nextSibling' : 'previousSibling';
188         while(node){
189                 if(node.nodeType != 3){
190                         break;
191                 }
192                 len += node.length;
193                 node = node[prop];
194         }
195         return [node,len];
196 };
197
198 dijit.range._w3c = Boolean(window['getSelection']);
199 dijit.range.create = function(/*Window?*/window){
200         if(dijit.range._w3c){
201                 return (window || win.global).document.createRange();
202         }else{//IE
203                 return new dijit.range.W3CRange;
204         }
205 };
206
207 dijit.range.getSelection = function(/*Window*/win, /*Boolean?*/ignoreUpdate){
208         if(dijit.range._w3c){
209                 return win.getSelection();
210         }else{//IE
211                 var s = new dijit.range.ie.selection(win);
212                 if(!ignoreUpdate){
213                         s._getCurrentSelection();
214                 }
215                 return s;
216         }
217 };
218
219 if(!dijit.range._w3c){
220         dijit.range.ie = {
221                 cachedSelection: {},
222                 selection: function(win){
223                         this._ranges = [];
224                         this.addRange = function(r, /*boolean*/internal){
225                                 this._ranges.push(r);
226                                 if(!internal){
227                                         r._select();
228                                 }
229                                 this.rangeCount = this._ranges.length;
230                         };
231                         this.removeAllRanges = function(){
232                                 //don't detach, the range may be used later
233 //                              for(var i=0;i<this._ranges.length;i++){
234 //                                      this._ranges[i].detach();
235 //                              }
236                                 this._ranges = [];
237                                 this.rangeCount = 0;
238                         };
239                         var _initCurrentRange = function(){
240                                 var r = win.document.selection.createRange();
241                                 var type = win.document.selection.type.toUpperCase();
242                                 if(type == "CONTROL"){
243                                         //TODO: multiple range selection(?)
244                                         return new dijit.range.W3CRange(dijit.range.ie.decomposeControlRange(r));
245                                 }else{
246                                         return new dijit.range.W3CRange(dijit.range.ie.decomposeTextRange(r));
247                                 }
248                         };
249                         this.getRangeAt = function(i){
250                                 return this._ranges[i];
251                         };
252                         this._getCurrentSelection = function(){
253                                 this.removeAllRanges();
254                                 var r = _initCurrentRange();
255                                 if(r){
256                                         this.addRange(r, true);
257                                         this.isCollapsed = r.collapsed;
258                                 }else{
259                                         this.isCollapsed = true;
260                                 }
261                         };
262                 },
263                 decomposeControlRange: function(range){
264                         var firstnode = range.item(0), lastnode = range.item(range.length - 1);
265                         var startContainer = firstnode.parentNode, endContainer = lastnode.parentNode;
266                         var startOffset = dijit.range.getIndex(firstnode, startContainer).o[0];
267                         var endOffset = dijit.range.getIndex(lastnode, endContainer).o[0] + 1;
268                         return [startContainer, startOffset,endContainer, endOffset];
269                 },
270                 getEndPoint: function(range, end){
271                         var atmrange = range.duplicate();
272                         atmrange.collapse(!end);
273                         var cmpstr = 'EndTo' + (end ? 'End' : 'Start');
274                         var parentNode = atmrange.parentElement();
275
276                         var startnode, startOffset, lastNode;
277                         if(parentNode.childNodes.length > 0){
278                                 array.every(parentNode.childNodes, function(node, i){
279                                         var calOffset;
280                                         if(node.nodeType != 3){
281                                                 atmrange.moveToElementText(node);
282
283                                                 if(atmrange.compareEndPoints(cmpstr, range) > 0){
284                                                         //startnode = node.previousSibling;
285                                                         if(lastNode && lastNode.nodeType == 3){
286                                                                 //where shall we put the start? in the text node or after?
287                                                                 startnode = lastNode;
288                                                                 calOffset = true;
289                                                         }else{
290                                                                 startnode = parentNode;
291                                                                 startOffset = i;
292                                                                 return false;
293                                                         }
294                                                 }else{
295                                                         if(i == parentNode.childNodes.length - 1){
296                                                                 startnode = parentNode;
297                                                                 startOffset = parentNode.childNodes.length;
298                                                                 return false;
299                                                         }
300                                                 }
301                                         }else{
302                                                 if(i == parentNode.childNodes.length - 1){//at the end of this node
303                                                         startnode = node;
304                                                         calOffset = true;
305                                                 }
306                                         }
307                                         //                      try{
308                                         if(calOffset && startnode){
309                                                 var prevnode = dijit.range.adjacentNoneTextNode(startnode)[0];
310                                                 if(prevnode){
311                                                         startnode = prevnode.nextSibling;
312                                                 }else{
313                                                         startnode = parentNode.firstChild; //firstChild must be a text node
314                                                 }
315                                                 var prevnodeobj = dijit.range.adjacentNoneTextNode(startnode);
316                                                 prevnode = prevnodeobj[0];
317                                                 var lenoffset = prevnodeobj[1];
318                                                 if(prevnode){
319                                                         atmrange.moveToElementText(prevnode);
320                                                         atmrange.collapse(false);
321                                                 }else{
322                                                         atmrange.moveToElementText(parentNode);
323                                                 }
324                                                 atmrange.setEndPoint(cmpstr, range);
325                                                 startOffset = atmrange.text.length - lenoffset;
326
327                                                 return false;
328                                         }
329                                         //                      }catch(e){ debugger }
330                                         lastNode = node;
331                                         return true;
332                                 });
333                         }else{
334                                 startnode = parentNode;
335                                 startOffset = 0;
336                         }
337
338                         //if at the end of startnode and we are dealing with start container, then
339                         //move the startnode to nextSibling if it is a text node
340                         //TODO: do this for end container?
341                         if(!end && startnode.nodeType == 1 && startOffset == startnode.childNodes.length){
342                                 var nextnode = startnode.nextSibling;
343                                 if(nextnode && nextnode.nodeType == 3){
344                                         startnode = nextnode;
345                                         startOffset = 0;
346                                 }
347                         }
348                         return [startnode, startOffset];
349                 },
350                 setEndPoint: function(range, container, offset){
351                         //text node
352                         var atmrange = range.duplicate(), node, len;
353                         if(container.nodeType != 3){ //normal node
354                                 if(offset > 0){
355                                         node = container.childNodes[offset - 1];
356                                         if(node){
357                                                 if(node.nodeType == 3){
358                                                         container = node;
359                                                         offset = node.length;
360                                                         //pass through
361                                                 }else{
362                                                         if(node.nextSibling && node.nextSibling.nodeType == 3){
363                                                                 container = node.nextSibling;
364                                                                 offset = 0;
365                                                                 //pass through
366                                                         }else{
367                                                                 atmrange.moveToElementText(node.nextSibling ? node : container);
368                                                                 var parent = node.parentNode;
369                                                                 var tempNode = parent.insertBefore(node.ownerDocument.createTextNode(' '), node.nextSibling);
370                                                                 atmrange.collapse(false);
371                                                                 parent.removeChild(tempNode);
372                                                         }
373                                                 }
374                                         }
375                                 }else{
376                                         atmrange.moveToElementText(container);
377                                         atmrange.collapse(true);
378                                 }
379                         }
380                         if(container.nodeType == 3){
381                                 var prevnodeobj = dijit.range.adjacentNoneTextNode(container);
382                                 var prevnode = prevnodeobj[0];
383                                 len = prevnodeobj[1];
384                                 if(prevnode){
385                                         atmrange.moveToElementText(prevnode);
386                                         atmrange.collapse(false);
387                                         //if contentEditable is not inherit, the above collapse won't make the end point
388                                         //in the correctly position: it always has a -1 offset, so compensate it
389                                         if(prevnode.contentEditable != 'inherit'){
390                                                 len++;
391                                         }
392                                 }else{
393                                         atmrange.moveToElementText(container.parentNode);
394                                         atmrange.collapse(true);
395                                 }
396
397                                 offset += len;
398                                 if(offset > 0){
399                                         if(atmrange.move('character', offset) != offset){
400                                                 console.error('Error when moving!');
401                                         }
402                                 }
403                         }
404
405                         return atmrange;
406                 },
407                 decomposeTextRange: function(range){
408                         var tmpary = dijit.range.ie.getEndPoint(range);
409                         var startContainer = tmpary[0], startOffset = tmpary[1];
410                         var endContainer = tmpary[0], endOffset = tmpary[1];
411
412                         if(range.htmlText.length){
413                                 if(range.htmlText == range.text){ //in the same text node
414                                         endOffset = startOffset + range.text.length;
415                                 }else{
416                                         tmpary = dijit.range.ie.getEndPoint(range, true);
417                                         endContainer = tmpary[0],endOffset = tmpary[1];
418 //                                      if(startContainer.tagName == "BODY"){
419 //                                              startContainer = startContainer.firstChild;
420 //                                      }
421                                 }
422                         }
423                         return [startContainer, startOffset, endContainer, endOffset];
424                 },
425                 setRange: function(range, startContainer, startOffset, endContainer, endOffset, collapsed){
426                         var start = dijit.range.ie.setEndPoint(range, startContainer, startOffset);
427
428                         range.setEndPoint('StartToStart', start);
429                         if(!collapsed){
430                                 var end = dijit.range.ie.setEndPoint(range, endContainer, endOffset);
431                         }
432                         range.setEndPoint('EndToEnd', end || start);
433
434                         return range;
435                 }
436         };
437
438 declare("dijit.range.W3CRange",null, {
439         constructor: function(){
440                 if(arguments.length>0){
441                         this.setStart(arguments[0][0],arguments[0][1]);
442                         this.setEnd(arguments[0][2],arguments[0][3]);
443                 }else{
444                         this.commonAncestorContainer = null;
445                         this.startContainer = null;
446                         this.startOffset = 0;
447                         this.endContainer = null;
448                         this.endOffset = 0;
449                         this.collapsed = true;
450                 }
451         },
452         _updateInternal: function(){
453                 if(this.startContainer !== this.endContainer){
454                         this.commonAncestorContainer = dijit.range.getCommonAncestor(this.startContainer, this.endContainer);
455                 }else{
456                         this.commonAncestorContainer = this.startContainer;
457                 }
458                 this.collapsed = (this.startContainer === this.endContainer) && (this.startOffset == this.endOffset);
459         },
460         setStart: function(node, offset){
461                 offset=parseInt(offset);
462                 if(this.startContainer === node && this.startOffset == offset){
463                         return;
464                 }
465                 delete this._cachedBookmark;
466
467                 this.startContainer = node;
468                 this.startOffset = offset;
469                 if(!this.endContainer){
470                         this.setEnd(node, offset);
471                 }else{
472                         this._updateInternal();
473                 }
474         },
475         setEnd: function(node, offset){
476                 offset=parseInt(offset);
477                 if(this.endContainer === node && this.endOffset == offset){
478                         return;
479                 }
480                 delete this._cachedBookmark;
481
482                 this.endContainer = node;
483                 this.endOffset = offset;
484                 if(!this.startContainer){
485                         this.setStart(node, offset);
486                 }else{
487                         this._updateInternal();
488                 }
489         },
490         setStartAfter: function(node, offset){
491                 this._setPoint('setStart', node, offset, 1);
492         },
493         setStartBefore: function(node, offset){
494                 this._setPoint('setStart', node, offset, 0);
495         },
496         setEndAfter: function(node, offset){
497                 this._setPoint('setEnd', node, offset, 1);
498         },
499         setEndBefore: function(node, offset){
500                 this._setPoint('setEnd', node, offset, 0);
501         },
502         _setPoint: function(what, node, offset, ext){
503                 var index = dijit.range.getIndex(node, node.parentNode).o;
504                 this[what](node.parentNode, index.pop()+ext);
505         },
506         _getIERange: function(){
507                 var r = (this._body || this.endContainer.ownerDocument.body).createTextRange();
508                 dijit.range.ie.setRange(r, this.startContainer, this.startOffset, this.endContainer, this.endOffset, this.collapsed);
509                 return r;
510         },
511         getBookmark: function(){
512                 this._getIERange();
513                 return this._cachedBookmark;
514         },
515         _select: function(){
516                 var r = this._getIERange();
517                 r.select();
518         },
519         deleteContents: function(){
520                 var s = this.startContainer, r = this._getIERange();
521                 if(s.nodeType === 3 && !this.startOffset){
522                         //if the range starts at the beginning of a
523                         //text node, move it to before the textnode
524                         //to make sure the range is still valid
525                         //after deleteContents() finishes
526                         this.setStartBefore(s);
527                 }
528                 r.pasteHTML('');
529                 this.endContainer = this.startContainer;
530                 this.endOffset = this.startOffset;
531                 this.collapsed = true;
532         },
533         cloneRange: function(){
534                 var r = new dijit.range.W3CRange([this.startContainer,this.startOffset,
535                         this.endContainer,this.endOffset]);
536                 r._body = this._body;
537                 return r;
538         },
539         detach: function(){
540                 this._body = null;
541                 this.commonAncestorContainer = null;
542                 this.startContainer = null;
543                 this.startOffset = 0;
544                 this.endContainer = null;
545                 this.endOffset = 0;
546                 this.collapsed = true;
547 }
548 });
549 } //if(!dijit.range._w3c)
550
551
552 return dijit.range;
553 });