Update ckeditor to version 3.2.1
[gopost.git] / ckeditor / _source / core / dom / range.js
blob127f7aaba39ce553c0961f31b471c82436c47d2f
1 /*
2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
6 CKEDITOR.dom.range = function( document )
8 this.startContainer = null;
9 this.startOffset = null;
10 this.endContainer = null;
11 this.endOffset = null;
12 this.collapsed = true;
14 this.document = document;
17 (function()
19 // Updates the "collapsed" property for the given range object.
20 var updateCollapsed = function( range )
22 range.collapsed = (
23 range.startContainer &&
24 range.endContainer &&
25 range.startContainer.equals( range.endContainer ) &&
26 range.startOffset == range.endOffset );
29 // This is a shared function used to delete, extract and clone the range
30 // contents.
31 // V2
32 var execContentsAction = function( range, action, docFrag )
34 range.optimizeBookmark();
36 var startNode = range.startContainer;
37 var endNode = range.endContainer;
39 var startOffset = range.startOffset;
40 var endOffset = range.endOffset;
42 var removeStartNode;
43 var removeEndNode;
45 // For text containers, we must simply split the node and point to the
46 // second part. The removal will be handled by the rest of the code .
47 if ( endNode.type == CKEDITOR.NODE_TEXT )
48 endNode = endNode.split( endOffset );
49 else
51 // If the end container has children and the offset is pointing
52 // to a child, then we should start from it.
53 if ( endNode.getChildCount() > 0 )
55 // If the offset points after the last node.
56 if ( endOffset >= endNode.getChildCount() )
58 // Let's create a temporary node and mark it for removal.
59 endNode = endNode.append( range.document.createText( '' ) );
60 removeEndNode = true;
62 else
63 endNode = endNode.getChild( endOffset );
67 // For text containers, we must simply split the node. The removal will
68 // be handled by the rest of the code .
69 if ( startNode.type == CKEDITOR.NODE_TEXT )
71 startNode.split( startOffset );
73 // In cases the end node is the same as the start node, the above
74 // splitting will also split the end, so me must move the end to
75 // the second part of the split.
76 if ( startNode.equals( endNode ) )
77 endNode = startNode.getNext();
79 else
81 // If the start container has children and the offset is pointing
82 // to a child, then we should start from its previous sibling.
84 // If the offset points to the first node, we don't have a
85 // sibling, so let's use the first one, but mark it for removal.
86 if ( !startOffset )
88 // Let's create a temporary node and mark it for removal.
89 startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
90 removeStartNode = true;
92 else if ( startOffset >= startNode.getChildCount() )
94 // Let's create a temporary node and mark it for removal.
95 startNode = startNode.append( range.document.createText( '' ) );
96 removeStartNode = true;
98 else
99 startNode = startNode.getChild( startOffset ).getPrevious();
102 // Get the parent nodes tree for the start and end boundaries.
103 var startParents = startNode.getParents();
104 var endParents = endNode.getParents();
106 // Compare them, to find the top most siblings.
107 var i, topStart, topEnd;
109 for ( i = 0 ; i < startParents.length ; i++ )
111 topStart = startParents[ i ];
112 topEnd = endParents[ i ];
114 // The compared nodes will match until we find the top most
115 // siblings (different nodes that have the same parent).
116 // "i" will hold the index in the parents array for the top
117 // most element.
118 if ( !topStart.equals( topEnd ) )
119 break;
122 var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
124 // Remove all successive sibling nodes for every node in the
125 // startParents tree.
126 for ( var j = i ; j < startParents.length ; j++ )
128 levelStartNode = startParents[j];
130 // For Extract and Clone, we must clone this level.
131 if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete
132 levelClone = clone.append( levelStartNode.clone() );
134 currentNode = levelStartNode.getNext();
136 while ( currentNode )
138 // Stop processing when the current node matches a node in the
139 // endParents tree or if it is the endNode.
140 if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
141 break;
143 // Cache the next sibling.
144 currentSibling = currentNode.getNext();
146 // If cloning, just clone it.
147 if ( action == 2 ) // 2 = Clone
148 clone.append( currentNode.clone( true ) );
149 else
151 // Both Delete and Extract will remove the node.
152 currentNode.remove();
154 // When Extracting, move the removed node to the docFrag.
155 if ( action == 1 ) // 1 = Extract
156 clone.append( currentNode );
159 currentNode = currentSibling;
162 if ( clone )
163 clone = levelClone;
166 clone = docFrag;
168 // Remove all previous sibling nodes for every node in the
169 // endParents tree.
170 for ( var k = i ; k < endParents.length ; k++ )
172 levelStartNode = endParents[ k ];
174 // For Extract and Clone, we must clone this level.
175 if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete
176 levelClone = clone.append( levelStartNode.clone() );
178 // The processing of siblings may have already been done by the parent.
179 if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
181 currentNode = levelStartNode.getPrevious();
183 while ( currentNode )
185 // Stop processing when the current node matches a node in the
186 // startParents tree or if it is the startNode.
187 if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
188 break;
190 // Cache the next sibling.
191 currentSibling = currentNode.getPrevious();
193 // If cloning, just clone it.
194 if ( action == 2 ) // 2 = Clone
195 clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
196 else
198 // Both Delete and Extract will remove the node.
199 currentNode.remove();
201 // When Extracting, mode the removed node to the docFrag.
202 if ( action == 1 ) // 1 = Extract
203 clone.$.insertBefore( currentNode.$, clone.$.firstChild );
206 currentNode = currentSibling;
210 if ( clone )
211 clone = levelClone;
214 if ( action == 2 ) // 2 = Clone.
216 // No changes in the DOM should be done, so fix the split text (if any).
218 var startTextNode = range.startContainer;
219 if ( startTextNode.type == CKEDITOR.NODE_TEXT )
221 startTextNode.$.data += startTextNode.$.nextSibling.data;
222 startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
225 var endTextNode = range.endContainer;
226 if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
228 endTextNode.$.data += endTextNode.$.nextSibling.data;
229 endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
232 else
234 // Collapse the range.
236 // If a node has been partially selected, collapse the range between
237 // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
238 if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
240 var endIndex = topEnd.getIndex();
242 // If the start node is to be removed, we must correct the
243 // index to reflect the removal.
244 if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
245 endIndex--;
247 range.setStart( topEnd.getParent(), endIndex );
250 // Collapse it to the start.
251 range.collapse( true );
254 // Cleanup any marked node.
255 if ( removeStartNode )
256 startNode.remove();
258 if ( removeEndNode && endNode.$.parentNode )
259 endNode.remove();
262 var inlineChildReqElements = { abbr:1,acronym:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1 };
264 // Creates the appropriate node evaluator for the dom walker used inside
265 // check(Start|End)OfBlock.
266 function getCheckStartEndBlockEvalFunction( isStart )
268 var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
269 return function( node )
271 // First ignore bookmark nodes.
272 if ( bookmarkEvaluator( node ) )
273 return true;
275 if ( node.type == CKEDITOR.NODE_TEXT )
277 // If there's any visible text, then we're not at the start.
278 if ( CKEDITOR.tools.trim( node.getText() ).length )
279 return false;
281 else if ( node.type == CKEDITOR.NODE_ELEMENT )
283 // If there are non-empty inline elements (e.g. <img />), then we're not
284 // at the start.
285 if ( !inlineChildReqElements[ node.getName() ] )
287 // If we're working at the end-of-block, forgive the first <br /> in non-IE
288 // browsers.
289 if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
290 hadBr = true;
291 else
292 return false;
295 return true;
299 // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
300 // text node and non-empty elements unless it's being bookmark text.
301 function elementBoundaryEval( node )
303 // Reject any text node unless it's being bookmark
304 // OR it's spaces. (#3883)
305 return node.type != CKEDITOR.NODE_TEXT
306 && node.getName() in CKEDITOR.dtd.$removeEmpty
307 || !CKEDITOR.tools.trim( node.getText() )
308 || node.getParent().hasAttribute( '_fck_bookmark' );
311 var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
312 bookmarkEval = new CKEDITOR.dom.walker.bookmark();
314 function nonWhitespaceOrBookmarkEval( node )
316 // Whitespaces and bookmark nodes are to be ignored.
317 return !whitespaceEval( node ) && !bookmarkEval( node );
320 CKEDITOR.dom.range.prototype =
322 clone : function()
324 var clone = new CKEDITOR.dom.range( this.document );
326 clone.startContainer = this.startContainer;
327 clone.startOffset = this.startOffset;
328 clone.endContainer = this.endContainer;
329 clone.endOffset = this.endOffset;
330 clone.collapsed = this.collapsed;
332 return clone;
335 collapse : function( toStart )
337 if ( toStart )
339 this.endContainer = this.startContainer;
340 this.endOffset = this.startOffset;
342 else
344 this.startContainer = this.endContainer;
345 this.startOffset = this.endOffset;
348 this.collapsed = true;
351 // The selection may be lost when cloning (due to the splitText() call).
352 cloneContents : function()
354 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
356 if ( !this.collapsed )
357 execContentsAction( this, 2, docFrag );
359 return docFrag;
362 deleteContents : function()
364 if ( this.collapsed )
365 return;
367 execContentsAction( this, 0 );
370 extractContents : function()
372 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
374 if ( !this.collapsed )
375 execContentsAction( this, 1, docFrag );
377 return docFrag;
381 * Creates a bookmark object, which can be later used to restore the
382 * range by using the moveToBookmark function.
383 * This is an "intrusive" way to create a bookmark. It includes <span> tags
384 * in the range boundaries. The advantage of it is that it is possible to
385 * handle DOM mutations when moving back to the bookmark.
386 * Attention: the inclusion of nodes in the DOM is a design choice and
387 * should not be changed as there are other points in the code that may be
388 * using those nodes to perform operations. See GetBookmarkNode.
389 * @param {Boolean} [serializable] Indicates that the bookmark nodes
390 * must contain ids, which can be used to restore the range even
391 * when these nodes suffer mutations (like a clonation or innerHTML
392 * change).
393 * @returns {Object} And object representing a bookmark.
395 createBookmark : function( serializable )
397 var startNode, endNode;
398 var baseId;
399 var clone;
401 startNode = this.document.createElement( 'span' );
402 startNode.setAttribute( '_fck_bookmark', 1 );
403 startNode.setStyle( 'display', 'none' );
405 // For IE, it must have something inside, otherwise it may be
406 // removed during DOM operations.
407 startNode.setHtml( '&nbsp;' );
409 if ( serializable )
411 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
412 startNode.setAttribute( 'id', baseId + 'S' );
415 // If collapsed, the endNode will not be created.
416 if ( !this.collapsed )
418 endNode = startNode.clone();
419 endNode.setHtml( '&nbsp;' );
421 if ( serializable )
422 endNode.setAttribute( 'id', baseId + 'E' );
424 clone = this.clone();
425 clone.collapse();
426 clone.insertNode( endNode );
429 clone = this.clone();
430 clone.collapse( true );
431 clone.insertNode( startNode );
433 // Update the range position.
434 if ( endNode )
436 this.setStartAfter( startNode );
437 this.setEndBefore( endNode );
439 else
440 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
442 return {
443 startNode : serializable ? baseId + 'S' : startNode,
444 endNode : serializable ? baseId + 'E' : endNode,
445 serializable : serializable
450 * Creates a "non intrusive" and "mutation sensible" bookmark. This
451 * kind of bookmark should be used only when the DOM is supposed to
452 * remain stable after its creation.
453 * @param {Boolean} [normalized] Indicates that the bookmark must
454 * normalized. When normalized, the successive text nodes are
455 * considered a single node. To sucessful load a normalized
456 * bookmark, the DOM tree must be also normalized before calling
457 * moveToBookmark.
458 * @returns {Object} An object representing the bookmark.
460 createBookmark2 : function( normalized )
462 var startContainer = this.startContainer,
463 endContainer = this.endContainer;
465 var startOffset = this.startOffset,
466 endOffset = this.endOffset;
468 var child, previous;
470 // If there is no range then get out of here.
471 // It happens on initial load in Safari #962 and if the editor it's
472 // hidden also in Firefox
473 if ( !startContainer || !endContainer )
474 return { start : 0, end : 0 };
476 if ( normalized )
478 // Find out if the start is pointing to a text node that will
479 // be normalized.
480 if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
482 child = startContainer.getChild( startOffset );
484 // In this case, move the start information to that text
485 // node.
486 if ( child && child.type == CKEDITOR.NODE_TEXT
487 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
489 startContainer = child;
490 startOffset = 0;
494 // Normalize the start.
495 while ( startContainer.type == CKEDITOR.NODE_TEXT
496 && ( previous = startContainer.getPrevious() )
497 && previous.type == CKEDITOR.NODE_TEXT )
499 startContainer = previous;
500 startOffset += previous.getLength();
503 // Process the end only if not normalized.
504 if ( !this.isCollapsed )
506 // Find out if the start is pointing to a text node that
507 // will be normalized.
508 if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
510 child = endContainer.getChild( endOffset );
512 // In this case, move the start information to that
513 // text node.
514 if ( child && child.type == CKEDITOR.NODE_TEXT
515 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
517 endContainer = child;
518 endOffset = 0;
522 // Normalize the end.
523 while ( endContainer.type == CKEDITOR.NODE_TEXT
524 && ( previous = endContainer.getPrevious() )
525 && previous.type == CKEDITOR.NODE_TEXT )
527 endContainer = previous;
528 endOffset += previous.getLength();
533 return {
534 start : startContainer.getAddress( normalized ),
535 end : this.isCollapsed ? null : endContainer.getAddress( normalized ),
536 startOffset : startOffset,
537 endOffset : endOffset,
538 normalized : normalized,
539 is2 : true // It's a createBookmark2 bookmark.
543 moveToBookmark : function( bookmark )
545 if ( bookmark.is2 ) // Created with createBookmark2().
547 // Get the start information.
548 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ),
549 startOffset = bookmark.startOffset;
551 // Get the end information.
552 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
553 endOffset = bookmark.endOffset;
555 // Set the start boundary.
556 this.setStart( startContainer, startOffset );
558 // Set the end boundary. If not available, collapse it.
559 if ( endContainer )
560 this.setEnd( endContainer, endOffset );
561 else
562 this.collapse( true );
564 else // Created with createBookmark().
566 var serializable = bookmark.serializable,
567 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
568 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
570 // Set the range start at the bookmark start node position.
571 this.setStartBefore( startNode );
573 // Remove it, because it may interfere in the setEndBefore call.
574 startNode.remove();
576 // Set the range end at the bookmark end node position, or simply
577 // collapse it if it is not available.
578 if ( endNode )
580 this.setEndBefore( endNode );
581 endNode.remove();
583 else
584 this.collapse( true );
588 getBoundaryNodes : function()
590 var startNode = this.startContainer,
591 endNode = this.endContainer,
592 startOffset = this.startOffset,
593 endOffset = this.endOffset,
594 childCount;
596 if ( startNode.type == CKEDITOR.NODE_ELEMENT )
598 childCount = startNode.getChildCount();
599 if ( childCount > startOffset )
600 startNode = startNode.getChild( startOffset );
601 else if ( childCount < 1 )
602 startNode = startNode.getPreviousSourceNode();
603 else // startOffset > childCount but childCount is not 0
605 // Try to take the node just after the current position.
606 startNode = startNode.$;
607 while ( startNode.lastChild )
608 startNode = startNode.lastChild;
609 startNode = new CKEDITOR.dom.node( startNode );
611 // Normally we should take the next node in DFS order. But it
612 // is also possible that we've already reached the end of
613 // document.
614 startNode = startNode.getNextSourceNode() || startNode;
617 if ( endNode.type == CKEDITOR.NODE_ELEMENT )
619 childCount = endNode.getChildCount();
620 if ( childCount > endOffset )
621 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
622 else if ( childCount < 1 )
623 endNode = endNode.getPreviousSourceNode();
624 else // endOffset > childCount but childCount is not 0
626 // Try to take the node just before the current position.
627 endNode = endNode.$;
628 while ( endNode.lastChild )
629 endNode = endNode.lastChild;
630 endNode = new CKEDITOR.dom.node( endNode );
634 // Sometimes the endNode will come right before startNode for collapsed
635 // ranges. Fix it. (#3780)
636 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
637 startNode = endNode;
639 return { startNode : startNode, endNode : endNode };
643 * Find the node which fully contains the range.
644 * @param includeSelf
645 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
647 getCommonAncestor : function( includeSelf , ignoreTextNode )
649 var start = this.startContainer,
650 end = this.endContainer,
651 ancestor;
653 if ( start.equals( end ) )
655 if ( includeSelf
656 && start.type == CKEDITOR.NODE_ELEMENT
657 && this.startOffset == this.endOffset - 1 )
658 ancestor = start.getChild( this.startOffset );
659 else
660 ancestor = start;
662 else
663 ancestor = start.getCommonAncestor( end );
665 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
669 * Transforms the startContainer and endContainer properties from text
670 * nodes to element nodes, whenever possible. This is actually possible
671 * if either of the boundary containers point to a text node, and its
672 * offset is set to zero, or after the last char in the node.
674 optimize : function()
676 var container = this.startContainer;
677 var offset = this.startOffset;
679 if ( container.type != CKEDITOR.NODE_ELEMENT )
681 if ( !offset )
682 this.setStartBefore( container );
683 else if ( offset >= container.getLength() )
684 this.setStartAfter( container );
687 container = this.endContainer;
688 offset = this.endOffset;
690 if ( container.type != CKEDITOR.NODE_ELEMENT )
692 if ( !offset )
693 this.setEndBefore( container );
694 else if ( offset >= container.getLength() )
695 this.setEndAfter( container );
700 * Move the range out of bookmark nodes if they're been the container.
702 optimizeBookmark: function()
704 var startNode = this.startContainer,
705 endNode = this.endContainer;
707 if ( startNode.is && startNode.is( 'span' )
708 && startNode.hasAttribute( '_fck_bookmark' ) )
709 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
710 if ( endNode && endNode.is && endNode.is( 'span' )
711 && endNode.hasAttribute( '_fck_bookmark' ) )
712 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
715 trim : function( ignoreStart, ignoreEnd )
717 var startContainer = this.startContainer,
718 startOffset = this.startOffset,
719 collapsed = this.collapsed;
720 if ( ( !ignoreStart || collapsed )
721 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
723 // If the offset is zero, we just insert the new node before
724 // the start.
725 if ( !startOffset )
727 startOffset = startContainer.getIndex();
728 startContainer = startContainer.getParent();
730 // If the offset is at the end, we'll insert it after the text
731 // node.
732 else if ( startOffset >= startContainer.getLength() )
734 startOffset = startContainer.getIndex() + 1;
735 startContainer = startContainer.getParent();
737 // In other case, we split the text node and insert the new
738 // node at the split point.
739 else
741 var nextText = startContainer.split( startOffset );
743 startOffset = startContainer.getIndex() + 1;
744 startContainer = startContainer.getParent();
746 // Check all necessity of updating the end boundary.
747 if ( this.startContainer.equals( this.endContainer ) )
748 this.setEnd( nextText, this.endOffset - this.startOffset );
749 else if ( startContainer.equals( this.endContainer ) )
750 this.endOffset += 1;
753 this.setStart( startContainer, startOffset );
755 if ( collapsed )
757 this.collapse( true );
758 return;
762 var endContainer = this.endContainer;
763 var endOffset = this.endOffset;
765 if ( !( ignoreEnd || collapsed )
766 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
768 // If the offset is zero, we just insert the new node before
769 // the start.
770 if ( !endOffset )
772 endOffset = endContainer.getIndex();
773 endContainer = endContainer.getParent();
775 // If the offset is at the end, we'll insert it after the text
776 // node.
777 else if ( endOffset >= endContainer.getLength() )
779 endOffset = endContainer.getIndex() + 1;
780 endContainer = endContainer.getParent();
782 // In other case, we split the text node and insert the new
783 // node at the split point.
784 else
786 endContainer.split( endOffset );
788 endOffset = endContainer.getIndex() + 1;
789 endContainer = endContainer.getParent();
792 this.setEnd( endContainer, endOffset );
796 enlarge : function( unit )
798 switch ( unit )
800 case CKEDITOR.ENLARGE_ELEMENT :
802 if ( this.collapsed )
803 return;
805 // Get the common ancestor.
806 var commonAncestor = this.getCommonAncestor();
808 var body = this.document.getBody();
810 // For each boundary
811 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
812 // b. Go ahead checking siblings and enlarging the boundary as much as possible until the common ancestor is not reached. After reaching the common ancestor, just save the enlargeable node to be used later.
814 var startTop, endTop;
816 var enlargeable, sibling, commonReached;
818 // Indicates that the node can be added only if whitespace
819 // is available before it.
820 var needsWhiteSpace = false;
821 var isWhiteSpace;
822 var siblingText;
824 // Process the start boundary.
826 var container = this.startContainer;
827 var offset = this.startOffset;
829 if ( container.type == CKEDITOR.NODE_TEXT )
831 if ( offset )
833 // Check if there is any non-space text before the
834 // offset. Otherwise, container is null.
835 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
837 // If we found only whitespace in the node, it
838 // means that we'll need more whitespace to be able
839 // to expand. For example, <i> can be expanded in
840 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
841 needsWhiteSpace = !!container;
844 if ( container )
846 if ( !( sibling = container.getPrevious() ) )
847 enlargeable = container.getParent();
850 else
852 // If we have offset, get the node preceeding it as the
853 // first sibling to be checked.
854 if ( offset )
855 sibling = container.getChild( offset - 1 ) || container.getLast();
857 // If there is no sibling, mark the container to be
858 // enlarged.
859 if ( !sibling )
860 enlargeable = container;
863 while ( enlargeable || sibling )
865 if ( enlargeable && !sibling )
867 // If we reached the common ancestor, mark the flag
868 // for it.
869 if ( !commonReached && enlargeable.equals( commonAncestor ) )
870 commonReached = true;
872 if ( !body.contains( enlargeable ) )
873 break;
875 // If we don't need space or this element breaks
876 // the line, then enlarge it.
877 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
879 needsWhiteSpace = false;
881 // If the common ancestor has been reached,
882 // we'll not enlarge it immediately, but just
883 // mark it to be enlarged later if the end
884 // boundary also enlarges it.
885 if ( commonReached )
886 startTop = enlargeable;
887 else
888 this.setStartBefore( enlargeable );
891 sibling = enlargeable.getPrevious();
894 // Check all sibling nodes preceeding the enlargeable
895 // node. The node wil lbe enlarged only if none of them
896 // blocks it.
897 while ( sibling )
899 // This flag indicates that this node has
900 // whitespaces at the end.
901 isWhiteSpace = false;
903 if ( sibling.type == CKEDITOR.NODE_TEXT )
905 siblingText = sibling.getText();
907 if ( /[^\s\ufeff]/.test( siblingText ) )
908 sibling = null;
910 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
912 else
914 // If this is a visible element.
915 // We need to check for the bookmark attribute because IE insists on
916 // rendering the display:none nodes we use for bookmarks. (#3363)
917 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
919 // We'll accept it only if we need
920 // whitespace, and this is an inline
921 // element with whitespace only.
922 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
924 // It must contains spaces and inline elements only.
926 siblingText = sibling.getText();
928 if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
929 sibling = null;
930 else
932 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
933 for ( var i = 0, child ; child = allChildren[ i++ ] ; )
935 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
937 sibling = null;
938 break;
943 if ( sibling )
944 isWhiteSpace = !!siblingText.length;
946 else
947 sibling = null;
951 // A node with whitespaces has been found.
952 if ( isWhiteSpace )
954 // Enlarge the last enlargeable node, if we
955 // were waiting for spaces.
956 if ( needsWhiteSpace )
958 if ( commonReached )
959 startTop = enlargeable;
960 else if ( enlargeable )
961 this.setStartBefore( enlargeable );
963 else
964 needsWhiteSpace = true;
967 if ( sibling )
969 var next = sibling.getPrevious();
971 if ( !enlargeable && !next )
973 // Set the sibling as enlargeable, so it's
974 // parent will be get later outside this while.
975 enlargeable = sibling;
976 sibling = null;
977 break;
980 sibling = next;
982 else
984 // If sibling has been set to null, then we
985 // need to stop enlarging.
986 enlargeable = null;
990 if ( enlargeable )
991 enlargeable = enlargeable.getParent();
994 // Process the end boundary. This is basically the same
995 // code used for the start boundary, with small changes to
996 // make it work in the oposite side (to the right). This
997 // makes it difficult to reuse the code here. So, fixes to
998 // the above code are likely to be replicated here.
1000 container = this.endContainer;
1001 offset = this.endOffset;
1003 // Reset the common variables.
1004 enlargeable = sibling = null;
1005 commonReached = needsWhiteSpace = false;
1007 if ( container.type == CKEDITOR.NODE_TEXT )
1009 // Check if there is any non-space text after the
1010 // offset. Otherwise, container is null.
1011 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
1013 // If we found only whitespace in the node, it
1014 // means that we'll need more whitespace to be able
1015 // to expand. For example, <i> can be expanded in
1016 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
1017 needsWhiteSpace = !( container && container.getLength() );
1019 if ( container )
1021 if ( !( sibling = container.getNext() ) )
1022 enlargeable = container.getParent();
1025 else
1027 // Get the node right after the boudary to be checked
1028 // first.
1029 sibling = container.getChild( offset );
1031 if ( !sibling )
1032 enlargeable = container;
1035 while ( enlargeable || sibling )
1037 if ( enlargeable && !sibling )
1039 if ( !commonReached && enlargeable.equals( commonAncestor ) )
1040 commonReached = true;
1042 if ( !body.contains( enlargeable ) )
1043 break;
1045 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
1047 needsWhiteSpace = false;
1049 if ( commonReached )
1050 endTop = enlargeable;
1051 else if ( enlargeable )
1052 this.setEndAfter( enlargeable );
1055 sibling = enlargeable.getNext();
1058 while ( sibling )
1060 isWhiteSpace = false;
1062 if ( sibling.type == CKEDITOR.NODE_TEXT )
1064 siblingText = sibling.getText();
1066 if ( /[^\s\ufeff]/.test( siblingText ) )
1067 sibling = null;
1069 isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
1071 else
1073 // If this is a visible element.
1074 // We need to check for the bookmark attribute because IE insists on
1075 // rendering the display:none nodes we use for bookmarks. (#3363)
1076 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
1078 // We'll accept it only if we need
1079 // whitespace, and this is an inline
1080 // element with whitespace only.
1081 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
1083 // It must contains spaces and inline elements only.
1085 siblingText = sibling.getText();
1087 if ( (/[^\s\ufeff]/).test( siblingText ) )
1088 sibling = null;
1089 else
1091 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
1092 for ( i = 0 ; child = allChildren[ i++ ] ; )
1094 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
1096 sibling = null;
1097 break;
1102 if ( sibling )
1103 isWhiteSpace = !!siblingText.length;
1105 else
1106 sibling = null;
1110 if ( isWhiteSpace )
1112 if ( needsWhiteSpace )
1114 if ( commonReached )
1115 endTop = enlargeable;
1116 else
1117 this.setEndAfter( enlargeable );
1121 if ( sibling )
1123 next = sibling.getNext();
1125 if ( !enlargeable && !next )
1127 enlargeable = sibling;
1128 sibling = null;
1129 break;
1132 sibling = next;
1134 else
1136 // If sibling has been set to null, then we
1137 // need to stop enlarging.
1138 enlargeable = null;
1142 if ( enlargeable )
1143 enlargeable = enlargeable.getParent();
1146 // If the common ancestor can be enlarged by both boundaries, then include it also.
1147 if ( startTop && endTop )
1149 commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
1151 this.setStartBefore( commonAncestor );
1152 this.setEndAfter( commonAncestor );
1154 break;
1156 case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
1157 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
1159 // Enlarging the start boundary.
1160 var walkerRange = new CKEDITOR.dom.range( this.document );
1162 body = this.document.getBody();
1164 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
1165 walkerRange.setEnd( this.startContainer, this.startOffset );
1167 var walker = new CKEDITOR.dom.walker( walkerRange ),
1168 blockBoundary, // The node on which the enlarging should stop.
1169 tailBr, //
1170 defaultGuard = CKEDITOR.dom.walker.blockBoundary(
1171 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
1172 // Record the encountered 'blockBoundary' for later use.
1173 boundaryGuard = function( node )
1175 var retval = defaultGuard( node );
1176 if ( !retval )
1177 blockBoundary = node;
1178 return retval;
1180 // Record the encounted 'tailBr' for later use.
1181 tailBrGuard = function( node )
1183 var retval = boundaryGuard( node );
1184 if ( !retval && node.is && node.is( 'br' ) )
1185 tailBr = node;
1186 return retval;
1189 walker.guard = boundaryGuard;
1191 enlargeable = walker.lastBackward();
1193 // It's the body which stop the enlarging if no block boundary found.
1194 blockBoundary = blockBoundary || body;
1196 // Start the range at different position by comparing
1197 // the document position of it with 'enlargeable' node.
1198 this.setStartAt(
1199 blockBoundary,
1200 !blockBoundary.is( 'br' ) &&
1201 ( !enlargeable && this.checkStartOfBlock()
1202 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1203 CKEDITOR.POSITION_AFTER_START :
1204 CKEDITOR.POSITION_AFTER_END );
1206 // Enlarging the end boundary.
1207 walkerRange = this.clone();
1208 walkerRange.collapse();
1209 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
1210 walker = new CKEDITOR.dom.walker( walkerRange );
1212 // tailBrGuard only used for on range end.
1213 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
1214 tailBrGuard : boundaryGuard;
1215 blockBoundary = null;
1216 // End the range right before the block boundary node.
1218 enlargeable = walker.lastForward();
1220 // It's the body which stop the enlarging if no block boundary found.
1221 blockBoundary = blockBoundary || body;
1223 // Start the range at different position by comparing
1224 // the document position of it with 'enlargeable' node.
1225 this.setEndAt(
1226 blockBoundary,
1227 ( !enlargeable && this.checkEndOfBlock()
1228 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
1229 CKEDITOR.POSITION_BEFORE_END :
1230 CKEDITOR.POSITION_BEFORE_START );
1231 // We must include the <br> at the end of range if there's
1232 // one and we're expanding list item contents
1233 if ( tailBr )
1234 this.setEndAfter( tailBr );
1239 * Descrease the range to make sure that boundaries
1240 * always anchor beside text nodes or innermost element.
1241 * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
1243 shrink : function( mode )
1245 // Unable to shrink a collapsed range.
1246 if ( !this.collapsed )
1248 mode = mode || CKEDITOR.SHRINK_TEXT;
1250 var walkerRange = this.clone();
1252 var startContainer = this.startContainer,
1253 endContainer = this.endContainer,
1254 startOffset = this.startOffset,
1255 endOffset = this.endOffset,
1256 collapsed = this.collapsed;
1258 // Whether the start/end boundary is moveable.
1259 var moveStart = 1,
1260 moveEnd = 1;
1262 if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
1264 if ( !startOffset )
1265 walkerRange.setStartBefore( startContainer );
1266 else if ( startOffset >= startContainer.getLength( ) )
1267 walkerRange.setStartAfter( startContainer );
1268 else
1270 // Enlarge the range properly to avoid walker making
1271 // DOM changes caused by triming the text nodes later.
1272 walkerRange.setStartBefore( startContainer );
1273 moveStart = 0;
1277 if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
1279 if ( !endOffset )
1280 walkerRange.setEndBefore( endContainer );
1281 else if ( endOffset >= endContainer.getLength( ) )
1282 walkerRange.setEndAfter( endContainer );
1283 else
1285 walkerRange.setEndAfter( endContainer );
1286 moveEnd = 0;
1290 var walker = new CKEDITOR.dom.walker( walkerRange );
1292 walker.evaluator = function( node )
1294 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
1295 CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
1298 var currentElement;
1299 walker.guard = function( node, movingOut )
1301 // Stop when we're shrink in element mode while encountering a text node.
1302 if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
1303 return false;
1305 // Stop when we've already walked "through" an element.
1306 if ( movingOut && node.equals( currentElement ) )
1307 return false;
1309 if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
1310 currentElement = node;
1312 return true;
1315 if ( moveStart )
1317 var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
1318 textStart && this.setStartBefore( textStart );
1321 if ( moveEnd )
1323 walker.reset();
1324 var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
1325 textEnd && this.setEndAfter( textEnd );
1328 return !!( moveStart || moveEnd );
1333 * Inserts a node at the start of the range. The range will be expanded
1334 * the contain the node.
1336 insertNode : function( node )
1338 this.optimizeBookmark();
1339 this.trim( false, true );
1341 var startContainer = this.startContainer;
1342 var startOffset = this.startOffset;
1344 var nextNode = startContainer.getChild( startOffset );
1346 if ( nextNode )
1347 node.insertBefore( nextNode );
1348 else
1349 startContainer.append( node );
1351 // Check if we need to update the end boundary.
1352 if ( node.getParent().equals( this.endContainer ) )
1353 this.endOffset++;
1355 // Expand the range to embrace the new node.
1356 this.setStartBefore( node );
1359 moveToPosition : function( node, position )
1361 this.setStartAt( node, position );
1362 this.collapse( true );
1365 selectNodeContents : function( node )
1367 this.setStart( node, 0 );
1368 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
1372 * Sets the start position of a Range.
1373 * @param {CKEDITOR.dom.node} startNode The node to start the range.
1374 * @param {Number} startOffset An integer greater than or equal to zero
1375 * representing the offset for the start of the range from the start
1376 * of startNode.
1378 setStart : function( startNode, startOffset )
1380 // W3C requires a check for the new position. If it is after the end
1381 // boundary, the range should be collapsed to the new start. It seams
1382 // we will not need this check for our use of this class so we can
1383 // ignore it for now.
1385 this.startContainer = startNode;
1386 this.startOffset = startOffset;
1388 if ( !this.endContainer )
1390 this.endContainer = startNode;
1391 this.endOffset = startOffset;
1394 updateCollapsed( this );
1398 * Sets the end position of a Range.
1399 * @param {CKEDITOR.dom.node} endNode The node to end the range.
1400 * @param {Number} endOffset An integer greater than or equal to zero
1401 * representing the offset for the end of the range from the start
1402 * of endNode.
1404 setEnd : function( endNode, endOffset )
1406 // W3C requires a check for the new position. If it is before the start
1407 // boundary, the range should be collapsed to the new end. It seams we
1408 // will not need this check for our use of this class so we can ignore
1409 // it for now.
1411 this.endContainer = endNode;
1412 this.endOffset = endOffset;
1414 if ( !this.startContainer )
1416 this.startContainer = endNode;
1417 this.startOffset = endOffset;
1420 updateCollapsed( this );
1423 setStartAfter : function( node )
1425 this.setStart( node.getParent(), node.getIndex() + 1 );
1428 setStartBefore : function( node )
1430 this.setStart( node.getParent(), node.getIndex() );
1433 setEndAfter : function( node )
1435 this.setEnd( node.getParent(), node.getIndex() + 1 );
1438 setEndBefore : function( node )
1440 this.setEnd( node.getParent(), node.getIndex() );
1443 setStartAt : function( node, position )
1445 switch( position )
1447 case CKEDITOR.POSITION_AFTER_START :
1448 this.setStart( node, 0 );
1449 break;
1451 case CKEDITOR.POSITION_BEFORE_END :
1452 if ( node.type == CKEDITOR.NODE_TEXT )
1453 this.setStart( node, node.getLength() );
1454 else
1455 this.setStart( node, node.getChildCount() );
1456 break;
1458 case CKEDITOR.POSITION_BEFORE_START :
1459 this.setStartBefore( node );
1460 break;
1462 case CKEDITOR.POSITION_AFTER_END :
1463 this.setStartAfter( node );
1466 updateCollapsed( this );
1469 setEndAt : function( node, position )
1471 switch( position )
1473 case CKEDITOR.POSITION_AFTER_START :
1474 this.setEnd( node, 0 );
1475 break;
1477 case CKEDITOR.POSITION_BEFORE_END :
1478 if ( node.type == CKEDITOR.NODE_TEXT )
1479 this.setEnd( node, node.getLength() );
1480 else
1481 this.setEnd( node, node.getChildCount() );
1482 break;
1484 case CKEDITOR.POSITION_BEFORE_START :
1485 this.setEndBefore( node );
1486 break;
1488 case CKEDITOR.POSITION_AFTER_END :
1489 this.setEndAfter( node );
1492 updateCollapsed( this );
1495 fixBlock : function( isStart, blockTag )
1497 var bookmark = this.createBookmark(),
1498 fixedBlock = this.document.createElement( blockTag );
1500 this.collapse( isStart );
1502 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
1504 this.extractContents().appendTo( fixedBlock );
1505 fixedBlock.trim();
1507 if ( !CKEDITOR.env.ie )
1508 fixedBlock.appendBogus();
1510 this.insertNode( fixedBlock );
1512 this.moveToBookmark( bookmark );
1514 return fixedBlock;
1517 splitBlock : function( blockTag )
1519 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ),
1520 endPath = new CKEDITOR.dom.elementPath( this.endContainer );
1522 var startBlockLimit = startPath.blockLimit,
1523 endBlockLimit = endPath.blockLimit;
1525 var startBlock = startPath.block,
1526 endBlock = endPath.block;
1528 var elementPath = null;
1529 // Do nothing if the boundaries are in different block limits.
1530 if ( !startBlockLimit.equals( endBlockLimit ) )
1531 return null;
1533 // Get or fix current blocks.
1534 if ( blockTag != 'br' )
1536 if ( !startBlock )
1538 startBlock = this.fixBlock( true, blockTag );
1539 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
1542 if ( !endBlock )
1543 endBlock = this.fixBlock( false, blockTag );
1546 // Get the range position.
1547 var isStartOfBlock = startBlock && this.checkStartOfBlock(),
1548 isEndOfBlock = endBlock && this.checkEndOfBlock();
1550 // Delete the current contents.
1551 // TODO: Why is 2.x doing CheckIsEmpty()?
1552 this.deleteContents();
1554 if ( startBlock && startBlock.equals( endBlock ) )
1556 if ( isEndOfBlock )
1558 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1559 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
1560 endBlock = null;
1562 else if ( isStartOfBlock )
1564 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
1565 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
1566 startBlock = null;
1568 else
1570 endBlock = this.splitElement( startBlock );
1572 // In Gecko, the last child node must be a bogus <br>.
1573 // Note: bogus <br> added under <ul> or <ol> would cause
1574 // lists to be incorrectly rendered.
1575 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
1576 startBlock.appendBogus() ;
1580 return {
1581 previousBlock : startBlock,
1582 nextBlock : endBlock,
1583 wasStartOfBlock : isStartOfBlock,
1584 wasEndOfBlock : isEndOfBlock,
1585 elementPath : elementPath
1590 * Branch the specified element from the collapsed range position and
1591 * place the caret between the two result branches.
1592 * Note: The range must be collapsed and been enclosed by this element.
1593 * @param {CKEDITOR.dom.element} element
1594 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
1596 splitElement : function( toSplit )
1598 if ( !this.collapsed )
1599 return null;
1601 // Extract the contents of the block from the selection point to the end
1602 // of its contents.
1603 this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
1604 var documentFragment = this.extractContents();
1606 // Duplicate the element after it.
1607 var clone = toSplit.clone( false );
1609 // Place the extracted contents into the duplicated element.
1610 documentFragment.appendTo( clone );
1611 clone.insertAfter( toSplit );
1612 this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
1613 return clone;
1617 * Check whether current range is on the inner edge of the specified element.
1618 * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
1619 * @param {CKEDITOR.dom.element} element The target element to check.
1621 checkBoundaryOfElement : function( element, checkType )
1623 var walkerRange = this.clone();
1624 // Expand the range to element boundary.
1625 walkerRange[ checkType == CKEDITOR.START ?
1626 'setStartAt' : 'setEndAt' ]
1627 ( element, checkType == CKEDITOR.START ?
1628 CKEDITOR.POSITION_AFTER_START
1629 : CKEDITOR.POSITION_BEFORE_END );
1631 var walker = new CKEDITOR.dom.walker( walkerRange ),
1632 retval = false;
1633 walker.evaluator = elementBoundaryEval;
1634 return walker[ checkType == CKEDITOR.START ?
1635 'checkBackward' : 'checkForward' ]();
1637 // Calls to this function may produce changes to the DOM. The range may
1638 // be updated to reflect such changes.
1639 checkStartOfBlock : function()
1641 var startContainer = this.startContainer,
1642 startOffset = this.startOffset;
1644 // If the starting node is a text node, and non-empty before the offset,
1645 // then we're surely not at the start of block.
1646 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
1648 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
1649 if ( textBefore.length )
1650 return false;
1653 // Antecipate the trim() call here, so the walker will not make
1654 // changes to the DOM, which would not get reflected into this
1655 // range otherwise.
1656 this.trim();
1658 // We need to grab the block element holding the start boundary, so
1659 // let's use an element path for it.
1660 var path = new CKEDITOR.dom.elementPath( this.startContainer );
1662 // Creates a range starting at the block start until the range start.
1663 var walkerRange = this.clone();
1664 walkerRange.collapse( true );
1665 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
1667 var walker = new CKEDITOR.dom.walker( walkerRange );
1668 walker.evaluator = getCheckStartEndBlockEvalFunction( true );
1670 return walker.checkBackward();
1673 checkEndOfBlock : function()
1675 var endContainer = this.endContainer,
1676 endOffset = this.endOffset;
1678 // If the ending node is a text node, and non-empty after the offset,
1679 // then we're surely not at the end of block.
1680 if ( endContainer.type == CKEDITOR.NODE_TEXT )
1682 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
1683 if ( textAfter.length )
1684 return false;
1687 // Antecipate the trim() call here, so the walker will not make
1688 // changes to the DOM, which would not get reflected into this
1689 // range otherwise.
1690 this.trim();
1692 // We need to grab the block element holding the start boundary, so
1693 // let's use an element path for it.
1694 var path = new CKEDITOR.dom.elementPath( this.endContainer );
1696 // Creates a range starting at the block start until the range start.
1697 var walkerRange = this.clone();
1698 walkerRange.collapse( false );
1699 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
1701 var walker = new CKEDITOR.dom.walker( walkerRange );
1702 walker.evaluator = getCheckStartEndBlockEvalFunction( false );
1704 return walker.checkForward();
1708 * Moves the range boundaries to the first/end editing point inside an
1709 * element. For example, in an element tree like
1710 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;", the start editing point is
1711 * "&lt;p&gt;&lt;b&gt;&lt;i&gt;^&lt;/i&gt;&lt;/b&gt; Text&lt;/p&gt;" (inside &lt;i&gt;).
1712 * @param {CKEDITOR.dom.element} el The element into which look for the
1713 * editing spot.
1714 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
1716 moveToElementEditablePosition : function( el, isMoveToEnd )
1718 var isEditable;
1720 // Empty elements are rejected.
1721 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
1722 return false;
1724 while ( el && el.type == CKEDITOR.NODE_ELEMENT )
1726 isEditable = el.isEditable();
1728 // If an editable element is found, move inside it.
1729 if ( isEditable )
1730 this.moveToPosition( el, isMoveToEnd ?
1731 CKEDITOR.POSITION_BEFORE_END :
1732 CKEDITOR.POSITION_AFTER_START );
1733 // Stop immediately if we've found a non editable inline element (e.g <img>).
1734 else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
1736 this.moveToPosition( el, isMoveToEnd ?
1737 CKEDITOR.POSITION_AFTER_END :
1738 CKEDITOR.POSITION_BEFORE_START );
1739 return true;
1742 // Non-editable non-inline elements are to be bypassed, getting the next one.
1743 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
1744 el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
1745 else
1746 el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
1748 // Stop immediately if we've found a text node.
1749 if ( el && el.type == CKEDITOR.NODE_TEXT )
1751 this.moveToPosition( el, isMoveToEnd ?
1752 CKEDITOR.POSITION_AFTER_END :
1753 CKEDITOR.POSITION_BEFORE_START );
1754 return true;
1758 return isEditable;
1762 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
1764 moveToElementEditStart : function( target )
1766 return this.moveToElementEditablePosition( target );
1770 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
1772 moveToElementEditEnd : function( target )
1774 return this.moveToElementEditablePosition( target, true );
1778 * Get the single node enclosed within the range if there's one.
1780 getEnclosedNode : function()
1782 var walkerRange = this.clone(),
1783 walker = new CKEDITOR.dom.walker( walkerRange ),
1784 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
1785 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
1786 evaluator = function( node )
1788 return isNotWhitespaces( node ) && isNotBookmarks( node );
1790 walkerRange.evaluator = evaluator;
1791 var node = walker.next();
1792 walker.reset();
1793 return node && node.equals( walker.previous() ) ? node : null;
1796 getTouchedStartNode : function()
1798 var container = this.startContainer ;
1800 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1801 return container ;
1803 return container.getChild( this.startOffset ) || container ;
1806 getTouchedEndNode : function()
1808 var container = this.endContainer ;
1810 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
1811 return container ;
1813 return container.getChild( this.endOffset - 1 ) || container ;
1816 })();
1818 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text"
1819 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^"
1820 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text"
1821 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text"
1823 CKEDITOR.ENLARGE_ELEMENT = 1;
1824 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
1825 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
1828 * Check boundary types.
1829 * @see CKEDITOR.dom.range::checkBoundaryOfElement
1831 CKEDITOR.START = 1;
1832 CKEDITOR.END = 2;
1833 CKEDITOR.STARTEND = 3;
1835 CKEDITOR.SHRINK_ELEMENT = 1;
1836 CKEDITOR.SHRINK_TEXT = 2;