2 Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved.
\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license
\r
6 CKEDITOR.dom.range = function( document )
\r
8 this.startContainer = null;
\r
9 this.startOffset = null;
\r
10 this.endContainer = null;
\r
11 this.endOffset = null;
\r
12 this.collapsed = true;
\r
14 this.document = document;
\r
19 // Updates the "collapsed" property for the given range object.
\r
20 var updateCollapsed = function( range )
\r
23 range.startContainer &&
\r
24 range.endContainer &&
\r
25 range.startContainer.equals( range.endContainer ) &&
\r
26 range.startOffset == range.endOffset );
\r
29 // This is a shared function used to delete, extract and clone the range
\r
32 var execContentsAction = function( range, action, docFrag )
\r
34 range.optimizeBookmark();
\r
36 var startNode = range.startContainer;
\r
37 var endNode = range.endContainer;
\r
39 var startOffset = range.startOffset;
\r
40 var endOffset = range.endOffset;
\r
42 var removeStartNode;
\r
45 // For text containers, we must simply split the node and point to the
\r
46 // second part. The removal will be handled by the rest of the code .
\r
47 if ( endNode.type == CKEDITOR.NODE_TEXT )
\r
48 endNode = endNode.split( endOffset );
\r
51 // If the end container has children and the offset is pointing
\r
52 // to a child, then we should start from it.
\r
53 if ( endNode.getChildCount() > 0 )
\r
55 // If the offset points after the last node.
\r
56 if ( endOffset >= endNode.getChildCount() )
\r
58 // Let's create a temporary node and mark it for removal.
\r
59 endNode = endNode.append( range.document.createText( '' ) );
\r
60 removeEndNode = true;
\r
63 endNode = endNode.getChild( endOffset );
\r
67 // For text containers, we must simply split the node. The removal will
\r
68 // be handled by the rest of the code .
\r
69 if ( startNode.type == CKEDITOR.NODE_TEXT )
\r
71 startNode.split( startOffset );
\r
73 // In cases the end node is the same as the start node, the above
\r
74 // splitting will also split the end, so me must move the end to
\r
75 // the second part of the split.
\r
76 if ( startNode.equals( endNode ) )
\r
77 endNode = startNode.getNext();
\r
81 // If the start container has children and the offset is pointing
\r
82 // to a child, then we should start from its previous sibling.
\r
84 // If the offset points to the first node, we don't have a
\r
85 // sibling, so let's use the first one, but mark it for removal.
\r
88 // Let's create a temporary node and mark it for removal.
\r
89 startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
\r
90 removeStartNode = true;
\r
92 else if ( startOffset >= startNode.getChildCount() )
\r
94 // Let's create a temporary node and mark it for removal.
\r
95 startNode = startNode.append( range.document.createText( '' ) );
\r
96 removeStartNode = true;
\r
99 startNode = startNode.getChild( startOffset ).getPrevious();
\r
102 // Get the parent nodes tree for the start and end boundaries.
\r
103 var startParents = startNode.getParents();
\r
104 var endParents = endNode.getParents();
\r
106 // Compare them, to find the top most siblings.
\r
107 var i, topStart, topEnd;
\r
109 for ( i = 0 ; i < startParents.length ; i++ )
\r
111 topStart = startParents[ i ];
\r
112 topEnd = endParents[ i ];
\r
114 // The compared nodes will match until we find the top most
\r
115 // siblings (different nodes that have the same parent).
\r
116 // "i" will hold the index in the parents array for the top
\r
118 if ( !topStart.equals( topEnd ) )
\r
122 var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
\r
124 // Remove all successive sibling nodes for every node in the
\r
125 // startParents tree.
\r
126 for ( var j = i ; j < startParents.length ; j++ )
\r
128 levelStartNode = startParents[j];
\r
130 // For Extract and Clone, we must clone this level.
\r
131 if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete
\r
132 levelClone = clone.append( levelStartNode.clone() );
\r
134 currentNode = levelStartNode.getNext();
\r
136 while( currentNode )
\r
138 // Stop processing when the current node matches a node in the
\r
139 // endParents tree or if it is the endNode.
\r
140 if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
\r
143 // Cache the next sibling.
\r
144 currentSibling = currentNode.getNext();
\r
146 // If cloning, just clone it.
\r
147 if ( action == 2 ) // 2 = Clone
\r
148 clone.append( currentNode.clone( true ) );
\r
151 // Both Delete and Extract will remove the node.
\r
152 currentNode.remove();
\r
154 // When Extracting, move the removed node to the docFrag.
\r
155 if ( action == 1 ) // 1 = Extract
\r
156 clone.append( currentNode );
\r
159 currentNode = currentSibling;
\r
163 clone = levelClone;
\r
168 // Remove all previous sibling nodes for every node in the
\r
169 // endParents tree.
\r
170 for ( var k = i ; k < endParents.length ; k++ )
\r
172 levelStartNode = endParents[ k ];
\r
174 // For Extract and Clone, we must clone this level.
\r
175 if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete
\r
176 levelClone = clone.append( levelStartNode.clone() );
\r
178 // The processing of siblings may have already been done by the parent.
\r
179 if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
\r
181 currentNode = levelStartNode.getPrevious();
\r
183 while( currentNode )
\r
185 // Stop processing when the current node matches a node in the
\r
186 // startParents tree or if it is the startNode.
\r
187 if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
\r
190 // Cache the next sibling.
\r
191 currentSibling = currentNode.getPrevious();
\r
193 // If cloning, just clone it.
\r
194 if ( action == 2 ) // 2 = Clone
\r
195 clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
\r
198 // Both Delete and Extract will remove the node.
\r
199 currentNode.remove();
\r
201 // When Extracting, mode the removed node to the docFrag.
\r
202 if ( action == 1 ) // 1 = Extract
\r
203 clone.$.insertBefore( currentNode.$, clone.$.firstChild );
\r
206 currentNode = currentSibling;
\r
211 clone = levelClone;
\r
214 if ( action == 2 ) // 2 = Clone.
\r
216 // No changes in the DOM should be done, so fix the split text (if any).
\r
218 var startTextNode = range.startContainer;
\r
219 if ( startTextNode.type == CKEDITOR.NODE_TEXT )
\r
221 startTextNode.$.data += startTextNode.$.nextSibling.data;
\r
222 startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
\r
225 var endTextNode = range.endContainer;
\r
226 if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
\r
228 endTextNode.$.data += endTextNode.$.nextSibling.data;
\r
229 endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
\r
234 // Collapse the range.
\r
236 // If a node has been partially selected, collapse the range between
\r
237 // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
\r
238 if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
\r
240 var endIndex = topEnd.getIndex();
\r
242 // If the start node is to be removed, we must correct the
\r
243 // index to reflect the removal.
\r
244 if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
\r
247 range.setStart( topEnd.getParent(), endIndex );
\r
250 // Collapse it to the start.
\r
251 range.collapse( true );
\r
254 // Cleanup any marked node.
\r
255 if( removeStartNode )
\r
256 startNode.remove();
\r
258 if( removeEndNode && endNode.$.parentNode )
\r
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 };
\r
264 // Creates the appropriate node evaluator for the dom walker used inside
\r
265 // check(Start|End)OfBlock.
\r
266 function getCheckStartEndBlockEvalFunction( isStart )
\r
268 var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
\r
269 return function( node )
\r
271 // First ignore bookmark nodes.
\r
272 if ( bookmarkEvaluator( node ) )
\r
275 if ( node.type == CKEDITOR.NODE_TEXT )
\r
277 // If there's any visible text, then we're not at the start.
\r
278 if ( CKEDITOR.tools.trim( node.getText() ).length )
\r
283 // If there are non-empty inline elements (e.g. <img />), then we're not
\r
285 if ( !inlineChildReqElements[ node.getName() ] )
\r
287 // If we're working at the end-of-block, forgive the first <br /> in non-IE
\r
289 if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
\r
299 // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
\r
300 // text node and non-empty elements unless it's being bookmark text.
\r
301 function elementBoundaryEval( node )
\r
303 // Reject any text node unless it's being bookmark
\r
304 // OR it's spaces. (#3883)
\r
305 return node.type != CKEDITOR.NODE_TEXT
\r
306 && node.getName() in CKEDITOR.dtd.$removeEmpty
\r
307 || !CKEDITOR.tools.trim( node.getText() )
\r
308 || node.getParent().hasAttribute( '_fck_bookmark' );
\r
311 CKEDITOR.dom.range.prototype =
\r
315 var clone = new CKEDITOR.dom.range( this.document );
\r
317 clone.startContainer = this.startContainer;
\r
318 clone.startOffset = this.startOffset;
\r
319 clone.endContainer = this.endContainer;
\r
320 clone.endOffset = this.endOffset;
\r
321 clone.collapsed = this.collapsed;
\r
326 collapse : function( toStart )
\r
330 this.endContainer = this.startContainer;
\r
331 this.endOffset = this.startOffset;
\r
335 this.startContainer = this.endContainer;
\r
336 this.startOffset = this.endOffset;
\r
339 this.collapsed = true;
\r
342 // The selection may be lost when cloning (due to the splitText() call).
\r
343 cloneContents : function()
\r
345 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
347 if ( !this.collapsed )
\r
348 execContentsAction( this, 2, docFrag );
\r
353 deleteContents : function()
\r
355 if ( this.collapsed )
\r
358 execContentsAction( this, 0 );
\r
361 extractContents : function()
\r
363 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
365 if ( !this.collapsed )
\r
366 execContentsAction( this, 1, docFrag );
\r
372 * Creates a bookmark object, which can be later used to restore the
\r
373 * range by using the moveToBookmark function.
\r
374 * This is an "intrusive" way to create a bookmark. It includes <span> tags
\r
375 * in the range boundaries. The advantage of it is that it is possible to
\r
376 * handle DOM mutations when moving back to the bookmark.
\r
377 * Attention: the inclusion of nodes in the DOM is a design choice and
\r
378 * should not be changed as there are other points in the code that may be
\r
379 * using those nodes to perform operations. See GetBookmarkNode.
\r
380 * @param {Boolean} [serializable] Indicates that the bookmark nodes
\r
381 * must contain ids, which can be used to restore the range even
\r
382 * when these nodes suffer mutations (like a clonation or innerHTML
\r
384 * @returns {Object} And object representing a bookmark.
\r
386 createBookmark : function( serializable )
\r
388 var startNode, endNode;
\r
392 startNode = this.document.createElement( 'span' );
\r
393 startNode.setAttribute( '_fck_bookmark', 1 );
\r
394 startNode.setStyle( 'display', 'none' );
\r
396 // For IE, it must have something inside, otherwise it may be
\r
397 // removed during DOM operations.
\r
398 startNode.setHtml( ' ' );
\r
400 if ( serializable )
\r
402 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
\r
403 startNode.setAttribute( 'id', baseId + 'S' );
\r
406 // If collapsed, the endNode will not be created.
\r
407 if ( !this.collapsed )
\r
409 endNode = startNode.clone();
\r
410 endNode.setHtml( ' ' );
\r
412 if ( serializable )
\r
413 endNode.setAttribute( 'id', baseId + 'E' );
\r
415 clone = this.clone();
\r
417 clone.insertNode( endNode );
\r
420 clone = this.clone();
\r
421 clone.collapse( true );
\r
422 clone.insertNode( startNode );
\r
424 // Update the range position.
\r
427 this.setStartAfter( startNode );
\r
428 this.setEndBefore( endNode );
\r
431 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
\r
434 startNode : serializable ? baseId + 'S' : startNode,
\r
435 endNode : serializable ? baseId + 'E' : endNode,
\r
436 serializable : serializable
\r
441 * Creates a "non intrusive" and "mutation sensible" bookmark. This
\r
442 * kind of bookmark should be used only when the DOM is supposed to
\r
443 * remain stable after its creation.
\r
444 * @param {Boolean} [normalized] Indicates that the bookmark must
\r
445 * normalized. When normalized, the successive text nodes are
\r
446 * considered a single node. To sucessful load a normalized
\r
447 * bookmark, the DOM tree must be also normalized before calling
\r
449 * @returns {Object} An object representing the bookmark.
\r
451 createBookmark2 : function( normalized )
\r
453 var startContainer = this.startContainer,
\r
454 endContainer = this.endContainer;
\r
456 var startOffset = this.startOffset,
\r
457 endOffset = this.endOffset;
\r
459 var child, previous;
\r
461 // If there is no range then get out of here.
\r
462 // It happens on initial load in Safari #962 and if the editor it's
\r
463 // hidden also in Firefox
\r
464 if ( !startContainer || !endContainer )
\r
465 return { start : 0, end : 0 };
\r
469 // Find out if the start is pointing to a text node that will
\r
471 if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
\r
473 child = startContainer.getChild( startOffset );
\r
475 // In this case, move the start information to that text
\r
477 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
478 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
480 startContainer = child;
\r
485 // Normalize the start.
\r
486 while ( startContainer.type == CKEDITOR.NODE_TEXT
\r
487 && ( previous = startContainer.getPrevious() )
\r
488 && previous.type == CKEDITOR.NODE_TEXT )
\r
490 startContainer = previous;
\r
491 startOffset += previous.getLength();
\r
494 // Process the end only if not normalized.
\r
495 if ( !this.isCollapsed )
\r
497 // Find out if the start is pointing to a text node that
\r
498 // will be normalized.
\r
499 if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
\r
501 child = endContainer.getChild( endOffset );
\r
503 // In this case, move the start information to that
\r
505 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
506 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
508 endContainer = child;
\r
513 // Normalize the end.
\r
514 while ( endContainer.type == CKEDITOR.NODE_TEXT
\r
515 && ( previous = endContainer.getPrevious() )
\r
516 && previous.type == CKEDITOR.NODE_TEXT )
\r
518 endContainer = previous;
\r
519 endOffset += previous.getLength();
\r
525 start : startContainer.getAddress( normalized ),
\r
526 end : this.isCollapsed ? null : endContainer.getAddress( normalized ),
\r
527 startOffset : startOffset,
\r
528 endOffset : endOffset,
\r
529 normalized : normalized,
\r
530 is2 : true // It's a createBookmark2 bookmark.
\r
534 moveToBookmark : function( bookmark )
\r
536 if ( bookmark.is2 ) // Created with createBookmark2().
\r
538 // Get the start information.
\r
539 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ),
\r
540 startOffset = bookmark.startOffset;
\r
542 // Get the end information.
\r
543 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
\r
544 endOffset = bookmark.endOffset;
\r
546 // Set the start boundary.
\r
547 this.setStart( startContainer, startOffset );
\r
549 // Set the end boundary. If not available, collapse it.
\r
550 if ( endContainer )
\r
551 this.setEnd( endContainer, endOffset );
\r
553 this.collapse( true );
\r
555 else // Created with createBookmark().
\r
557 var serializable = bookmark.serializable,
\r
558 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
\r
559 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
\r
561 // Set the range start at the bookmark start node position.
\r
562 this.setStartBefore( startNode );
\r
564 // Remove it, because it may interfere in the setEndBefore call.
\r
565 startNode.remove();
\r
567 // Set the range end at the bookmark end node position, or simply
\r
568 // collapse it if it is not available.
\r
571 this.setEndBefore( endNode );
\r
575 this.collapse( true );
\r
579 getBoundaryNodes : function()
\r
581 var startNode = this.startContainer,
\r
582 endNode = this.endContainer,
\r
583 startOffset = this.startOffset,
\r
584 endOffset = this.endOffset,
\r
587 if ( startNode.type == CKEDITOR.NODE_ELEMENT )
\r
589 childCount = startNode.getChildCount();
\r
590 if ( childCount > startOffset )
\r
591 startNode = startNode.getChild( startOffset );
\r
592 else if ( childCount < 1 )
\r
593 startNode = startNode.getPreviousSourceNode();
\r
594 else // startOffset > childCount but childCount is not 0
\r
596 // Try to take the node just after the current position.
\r
597 startNode = startNode.$;
\r
598 while ( startNode.lastChild )
\r
599 startNode = startNode.lastChild;
\r
600 startNode = new CKEDITOR.dom.node( startNode );
\r
602 // Normally we should take the next node in DFS order. But it
\r
603 // is also possible that we've already reached the end of
\r
605 startNode = startNode.getNextSourceNode() || startNode;
\r
608 if ( endNode.type == CKEDITOR.NODE_ELEMENT )
\r
610 childCount = endNode.getChildCount();
\r
611 if ( childCount > endOffset )
\r
612 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
\r
613 else if ( childCount < 1 )
\r
614 endNode = endNode.getPreviousSourceNode();
\r
615 else // endOffset > childCount but childCount is not 0
\r
617 // Try to take the node just before the current position.
\r
618 endNode = endNode.$;
\r
619 while ( endNode.lastChild )
\r
620 endNode = endNode.lastChild;
\r
621 endNode = new CKEDITOR.dom.node( endNode );
\r
625 // Sometimes the endNode will come right before startNode for collapsed
\r
626 // ranges. Fix it. (#3780)
\r
627 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
\r
628 startNode = endNode;
\r
630 return { startNode : startNode, endNode : endNode };
\r
634 * Find the node which fully contains the range.
\r
635 * @param includeSelf
\r
636 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
\r
638 getCommonAncestor : function( includeSelf , ignoreTextNode )
\r
640 var start = this.startContainer,
\r
641 end = this.endContainer,
\r
644 if ( start.equals( end ) )
\r
647 && start.type == CKEDITOR.NODE_ELEMENT
\r
648 && this.startOffset == this.endOffset - 1 )
\r
649 ancestor = start.getChild( this.startOffset );
\r
654 ancestor = start.getCommonAncestor( end );
\r
656 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
\r
660 * Transforms the startContainer and endContainer properties from text
\r
661 * nodes to element nodes, whenever possible. This is actually possible
\r
662 * if either of the boundary containers point to a text node, and its
\r
663 * offset is set to zero, or after the last char in the node.
\r
665 optimize : function()
\r
667 var container = this.startContainer;
\r
668 var offset = this.startOffset;
\r
670 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
673 this.setStartBefore( container );
\r
674 else if ( offset >= container.getLength() )
\r
675 this.setStartAfter( container );
\r
678 container = this.endContainer;
\r
679 offset = this.endOffset;
\r
681 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
684 this.setEndBefore( container );
\r
685 else if ( offset >= container.getLength() )
\r
686 this.setEndAfter( container );
\r
691 * Move the range out of bookmark nodes if they're been the container.
\r
693 optimizeBookmark: function()
\r
695 var startNode = this.startContainer,
\r
696 endNode = this.endContainer;
\r
698 if ( startNode.is && startNode.is( 'span' )
\r
699 && startNode.hasAttribute( '_fck_bookmark' ) )
\r
700 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
\r
701 if ( endNode && endNode.is && endNode.is( 'span' )
\r
702 && endNode.hasAttribute( '_fck_bookmark' ) )
\r
703 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
\r
706 trim : function( ignoreStart, ignoreEnd )
\r
708 var startContainer = this.startContainer,
\r
709 startOffset = this.startOffset,
\r
710 collapsed = this.collapsed;
\r
711 if ( ( !ignoreStart || collapsed )
\r
712 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
714 // If the offset is zero, we just insert the new node before
\r
716 if ( !startOffset )
\r
718 startOffset = startContainer.getIndex();
\r
719 startContainer = startContainer.getParent();
\r
721 // If the offset is at the end, we'll insert it after the text
\r
723 else if ( startOffset >= startContainer.getLength() )
\r
725 startOffset = startContainer.getIndex() + 1;
\r
726 startContainer = startContainer.getParent();
\r
728 // In other case, we split the text node and insert the new
\r
729 // node at the split point.
\r
732 var nextText = startContainer.split( startOffset );
\r
734 startOffset = startContainer.getIndex() + 1;
\r
735 startContainer = startContainer.getParent();
\r
736 // Check if it is necessary to update the end boundary.
\r
737 if ( !collapsed && this.startContainer.equals( this.endContainer ) )
\r
738 this.setEnd( nextText, this.endOffset - this.startOffset );
\r
741 this.setStart( startContainer, startOffset );
\r
744 this.collapse( true );
\r
747 var endContainer = this.endContainer;
\r
748 var endOffset = this.endOffset;
\r
750 if ( !( ignoreEnd || collapsed )
\r
751 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
753 // If the offset is zero, we just insert the new node before
\r
757 endOffset = endContainer.getIndex();
\r
758 endContainer = endContainer.getParent();
\r
760 // If the offset is at the end, we'll insert it after the text
\r
762 else if ( endOffset >= endContainer.getLength() )
\r
764 endOffset = endContainer.getIndex() + 1;
\r
765 endContainer = endContainer.getParent();
\r
767 // In other case, we split the text node and insert the new
\r
768 // node at the split point.
\r
771 endContainer.split( endOffset );
\r
773 endOffset = endContainer.getIndex() + 1;
\r
774 endContainer = endContainer.getParent();
\r
777 this.setEnd( endContainer, endOffset );
\r
781 enlarge : function( unit )
\r
785 case CKEDITOR.ENLARGE_ELEMENT :
\r
787 if ( this.collapsed )
\r
790 // Get the common ancestor.
\r
791 var commonAncestor = this.getCommonAncestor();
\r
793 var body = this.document.getBody();
\r
795 // For each boundary
\r
796 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
\r
797 // 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.
\r
799 var startTop, endTop;
\r
801 var enlargeable, sibling, commonReached;
\r
803 // Indicates that the node can be added only if whitespace
\r
804 // is available before it.
\r
805 var needsWhiteSpace = false;
\r
809 // Process the start boundary.
\r
811 var container = this.startContainer;
\r
812 var offset = this.startOffset;
\r
814 if ( container.type == CKEDITOR.NODE_TEXT )
\r
818 // Check if there is any non-space text before the
\r
819 // offset. Otherwise, container is null.
\r
820 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
\r
822 // If we found only whitespace in the node, it
\r
823 // means that we'll need more whitespace to be able
\r
824 // to expand. For example, <i> can be expanded in
\r
825 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
826 needsWhiteSpace = !!container;
\r
831 if ( !( sibling = container.getPrevious() ) )
\r
832 enlargeable = container.getParent();
\r
837 // If we have offset, get the node preceeding it as the
\r
838 // first sibling to be checked.
\r
840 sibling = container.getChild( offset - 1 ) || container.getLast();
\r
842 // If there is no sibling, mark the container to be
\r
845 enlargeable = container;
\r
848 while ( enlargeable || sibling )
\r
850 if ( enlargeable && !sibling )
\r
852 // If we reached the common ancestor, mark the flag
\r
854 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
855 commonReached = true;
\r
857 if ( !body.contains( enlargeable ) )
\r
860 // If we don't need space or this element breaks
\r
861 // the line, then enlarge it.
\r
862 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
864 needsWhiteSpace = false;
\r
866 // If the common ancestor has been reached,
\r
867 // we'll not enlarge it immediately, but just
\r
868 // mark it to be enlarged later if the end
\r
869 // boundary also enlarges it.
\r
870 if ( commonReached )
\r
871 startTop = enlargeable;
\r
873 this.setStartBefore( enlargeable );
\r
876 sibling = enlargeable.getPrevious();
\r
879 // Check all sibling nodes preceeding the enlargeable
\r
880 // node. The node wil lbe enlarged only if none of them
\r
884 // This flag indicates that this node has
\r
885 // whitespaces at the end.
\r
886 isWhiteSpace = false;
\r
888 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
890 siblingText = sibling.getText();
\r
892 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
895 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
\r
899 // If this is a visible element.
\r
900 // We need to check for the bookmark attribute because IE insists on
\r
901 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
902 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
\r
904 // We'll accept it only if we need
\r
905 // whitespace, and this is an inline
\r
906 // element with whitespace only.
\r
907 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
909 // It must contains spaces and inline elements only.
\r
911 siblingText = sibling.getText();
\r
913 if ( !(/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
\r
917 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
918 for ( var i = 0, child ; child = allChildren[ i++ ] ; )
\r
920 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
929 isWhiteSpace = !!siblingText.length;
\r
936 // A node with whitespaces has been found.
\r
937 if ( isWhiteSpace )
\r
939 // Enlarge the last enlargeable node, if we
\r
940 // were waiting for spaces.
\r
941 if ( needsWhiteSpace )
\r
943 if ( commonReached )
\r
944 startTop = enlargeable;
\r
945 else if ( enlargeable )
\r
946 this.setStartBefore( enlargeable );
\r
949 needsWhiteSpace = true;
\r
954 var next = sibling.getPrevious();
\r
956 if ( !enlargeable && !next )
\r
958 // Set the sibling as enlargeable, so it's
\r
959 // parent will be get later outside this while.
\r
960 enlargeable = sibling;
\r
969 // If sibling has been set to null, then we
\r
970 // need to stop enlarging.
\r
971 enlargeable = null;
\r
976 enlargeable = enlargeable.getParent();
\r
979 // Process the end boundary. This is basically the same
\r
980 // code used for the start boundary, with small changes to
\r
981 // make it work in the oposite side (to the right). This
\r
982 // makes it difficult to reuse the code here. So, fixes to
\r
983 // the above code are likely to be replicated here.
\r
985 container = this.endContainer;
\r
986 offset = this.endOffset;
\r
988 // Reset the common variables.
\r
989 enlargeable = sibling = null;
\r
990 commonReached = needsWhiteSpace = false;
\r
992 if ( container.type == CKEDITOR.NODE_TEXT )
\r
994 // Check if there is any non-space text after the
\r
995 // offset. Otherwise, container is null.
\r
996 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
\r
998 // If we found only whitespace in the node, it
\r
999 // means that we'll need more whitespace to be able
\r
1000 // to expand. For example, <i> can be expanded in
\r
1001 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
1002 needsWhiteSpace = !( container && container.getLength() );
\r
1006 if ( !( sibling = container.getNext() ) )
\r
1007 enlargeable = container.getParent();
\r
1012 // Get the node right after the boudary to be checked
\r
1014 sibling = container.getChild( offset );
\r
1017 enlargeable = container;
\r
1020 while ( enlargeable || sibling )
\r
1022 if ( enlargeable && !sibling )
\r
1024 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
1025 commonReached = true;
\r
1027 if ( !body.contains( enlargeable ) )
\r
1030 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
1032 needsWhiteSpace = false;
\r
1034 if ( commonReached )
\r
1035 endTop = enlargeable;
\r
1036 else if ( enlargeable )
\r
1037 this.setEndAfter( enlargeable );
\r
1040 sibling = enlargeable.getNext();
\r
1045 isWhiteSpace = false;
\r
1047 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
1049 siblingText = sibling.getText();
\r
1051 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
1054 isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
\r
1058 // If this is a visible element.
\r
1059 // We need to check for the bookmark attribute because IE insists on
\r
1060 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
1061 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
\r
1063 // We'll accept it only if we need
\r
1064 // whitespace, and this is an inline
\r
1065 // element with whitespace only.
\r
1066 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
1068 // It must contains spaces and inline elements only.
\r
1070 siblingText = sibling.getText();
\r
1072 if ( !(/[^\s\ufeff]/).test( siblingText ) )
\r
1076 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
1077 for ( i = 0 ; child = allChildren[ i++ ] ; )
\r
1079 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
1088 isWhiteSpace = !!siblingText.length;
\r
1095 if ( isWhiteSpace )
\r
1097 if ( needsWhiteSpace )
\r
1099 if ( commonReached )
\r
1100 endTop = enlargeable;
\r
1102 this.setEndAfter( enlargeable );
\r
1108 next = sibling.getNext();
\r
1110 if ( !enlargeable && !next )
\r
1112 enlargeable = sibling;
\r
1121 // If sibling has been set to null, then we
\r
1122 // need to stop enlarging.
\r
1123 enlargeable = null;
\r
1127 if ( enlargeable )
\r
1128 enlargeable = enlargeable.getParent();
\r
1131 // If the common ancestor can be enlarged by both boundaries, then include it also.
\r
1132 if ( startTop && endTop )
\r
1134 commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
\r
1136 this.setStartBefore( commonAncestor );
\r
1137 this.setEndAfter( commonAncestor );
\r
1141 case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
\r
1142 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
\r
1144 // Enlarging the start boundary.
\r
1145 var walkerRange = new CKEDITOR.dom.range( this.document );
\r
1147 body = this.document.getBody();
\r
1149 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
\r
1150 walkerRange.setEnd( this.startContainer, this.startOffset );
\r
1152 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1153 blockBoundary, // The node on which the enlarging should stop.
\r
1155 defaultGuard = CKEDITOR.dom.walker.blockBoundary(
\r
1156 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
\r
1157 // Record the encountered 'blockBoundary' for later use.
\r
1158 boundaryGuard = function( node )
\r
1160 var retval = defaultGuard( node );
\r
1162 blockBoundary = node;
\r
1165 // Record the encounted 'tailBr' for later use.
\r
1166 tailBrGuard = function( node )
\r
1168 var retval = boundaryGuard( node );
\r
1169 if ( !retval && node.is && node.is( 'br' ) )
\r
1174 walker.guard = boundaryGuard;
\r
1176 enlargeable = walker.lastBackward();
\r
1178 // It's the body which stop the enlarging if no block boundary found.
\r
1179 blockBoundary = blockBoundary || body;
\r
1181 // Start the range at different position by comparing
\r
1182 // the document position of it with 'enlargeable' node.
\r
1185 !blockBoundary.is( 'br' ) &&
\r
1186 ( !enlargeable || blockBoundary.contains( enlargeable ) ) ?
\r
1187 CKEDITOR.POSITION_AFTER_START :
\r
1188 CKEDITOR.POSITION_AFTER_END );
\r
1190 // Enlarging the end boundary.
\r
1191 walkerRange = this.clone();
\r
1192 walkerRange.collapse();
\r
1193 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
\r
1194 walker = new CKEDITOR.dom.walker( walkerRange );
\r
1196 // tailBrGuard only used for on range end.
\r
1197 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
\r
1198 tailBrGuard : boundaryGuard;
\r
1199 blockBoundary = null;
\r
1200 // End the range right before the block boundary node.
\r
1202 enlargeable = walker.lastForward();
\r
1204 // It's the body which stop the enlarging if no block boundary found.
\r
1205 blockBoundary = blockBoundary || body;
\r
1207 // Start the range at different position by comparing
\r
1208 // the document position of it with 'enlargeable' node.
\r
1211 !blockBoundary.is( 'br' ) &&
\r
1212 ( !enlargeable || blockBoundary.contains( enlargeable ) ) ?
\r
1213 CKEDITOR.POSITION_BEFORE_END :
\r
1214 CKEDITOR.POSITION_BEFORE_START );
\r
1215 // We must include the <br> at the end of range if there's
\r
1216 // one and we're expanding list item contents
\r
1218 this.setEndAfter( tailBr );
\r
1223 * Inserts a node at the start of the range. The range will be expanded
\r
1224 * the contain the node.
\r
1226 insertNode : function( node )
\r
1228 this.optimizeBookmark();
\r
1229 this.trim( false, true );
\r
1231 var startContainer = this.startContainer;
\r
1232 var startOffset = this.startOffset;
\r
1234 var nextNode = startContainer.getChild( startOffset );
\r
1237 node.insertBefore( nextNode );
\r
1239 startContainer.append( node );
\r
1241 // Check if we need to update the end boundary.
\r
1242 if ( node.getParent().equals( this.endContainer ) )
\r
1245 // Expand the range to embrace the new node.
\r
1246 this.setStartBefore( node );
\r
1249 moveToPosition : function( node, position )
\r
1251 this.setStartAt( node, position );
\r
1252 this.collapse( true );
\r
1255 selectNodeContents : function( node )
\r
1257 this.setStart( node, 0 );
\r
1258 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
\r
1262 * Sets the start position of a Range.
\r
1263 * @param {CKEDITOR.dom.node} startNode The node to start the range.
\r
1264 * @param {Number} startOffset An integer greater than or equal to zero
\r
1265 * representing the offset for the start of the range from the start
\r
1268 setStart : function( startNode, startOffset )
\r
1270 // W3C requires a check for the new position. If it is after the end
\r
1271 // boundary, the range should be collapsed to the new start. It seams
\r
1272 // we will not need this check for our use of this class so we can
\r
1273 // ignore it for now.
\r
1275 this.startContainer = startNode;
\r
1276 this.startOffset = startOffset;
\r
1278 if ( !this.endContainer )
\r
1280 this.endContainer = startNode;
\r
1281 this.endOffset = startOffset;
\r
1284 updateCollapsed( this );
\r
1288 * Sets the end position of a Range.
\r
1289 * @param {CKEDITOR.dom.node} endNode The node to end the range.
\r
1290 * @param {Number} endOffset An integer greater than or equal to zero
\r
1291 * representing the offset for the end of the range from the start
\r
1294 setEnd : function( endNode, endOffset )
\r
1296 // W3C requires a check for the new position. If it is before the start
\r
1297 // boundary, the range should be collapsed to the new end. It seams we
\r
1298 // will not need this check for our use of this class so we can ignore
\r
1301 this.endContainer = endNode;
\r
1302 this.endOffset = endOffset;
\r
1304 if ( !this.startContainer )
\r
1306 this.startContainer = endNode;
\r
1307 this.startOffset = endOffset;
\r
1310 updateCollapsed( this );
\r
1313 setStartAfter : function( node )
\r
1315 this.setStart( node.getParent(), node.getIndex() + 1 );
\r
1318 setStartBefore : function( node )
\r
1320 this.setStart( node.getParent(), node.getIndex() );
\r
1323 setEndAfter : function( node )
\r
1325 this.setEnd( node.getParent(), node.getIndex() + 1 );
\r
1328 setEndBefore : function( node )
\r
1330 this.setEnd( node.getParent(), node.getIndex() );
\r
1333 setStartAt : function( node, position )
\r
1335 switch( position )
\r
1337 case CKEDITOR.POSITION_AFTER_START :
\r
1338 this.setStart( node, 0 );
\r
1341 case CKEDITOR.POSITION_BEFORE_END :
\r
1342 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1343 this.setStart( node, node.getLength() );
\r
1345 this.setStart( node, node.getChildCount() );
\r
1348 case CKEDITOR.POSITION_BEFORE_START :
\r
1349 this.setStartBefore( node );
\r
1352 case CKEDITOR.POSITION_AFTER_END :
\r
1353 this.setStartAfter( node );
\r
1356 updateCollapsed( this );
\r
1359 setEndAt : function( node, position )
\r
1361 switch( position )
\r
1363 case CKEDITOR.POSITION_AFTER_START :
\r
1364 this.setEnd( node, 0 );
\r
1367 case CKEDITOR.POSITION_BEFORE_END :
\r
1368 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1369 this.setEnd( node, node.getLength() );
\r
1371 this.setEnd( node, node.getChildCount() );
\r
1374 case CKEDITOR.POSITION_BEFORE_START :
\r
1375 this.setEndBefore( node );
\r
1378 case CKEDITOR.POSITION_AFTER_END :
\r
1379 this.setEndAfter( node );
\r
1382 updateCollapsed( this );
\r
1385 fixBlock : function( isStart, blockTag )
\r
1387 var bookmark = this.createBookmark(),
\r
1388 fixedBlock = this.document.createElement( blockTag );
\r
1390 this.collapse( isStart );
\r
1392 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
\r
1394 this.extractContents().appendTo( fixedBlock );
\r
1395 fixedBlock.trim();
\r
1397 if ( !CKEDITOR.env.ie )
\r
1398 fixedBlock.appendBogus();
\r
1400 this.insertNode( fixedBlock );
\r
1402 this.moveToBookmark( bookmark );
\r
1404 return fixedBlock;
\r
1407 splitBlock : function( blockTag )
\r
1409 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ),
\r
1410 endPath = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1412 var startBlockLimit = startPath.blockLimit,
\r
1413 endBlockLimit = endPath.blockLimit;
\r
1415 var startBlock = startPath.block,
\r
1416 endBlock = endPath.block;
\r
1418 var elementPath = null;
\r
1419 // Do nothing if the boundaries are in different block limits.
\r
1420 if ( !startBlockLimit.equals( endBlockLimit ) )
\r
1423 // Get or fix current blocks.
\r
1424 if ( blockTag != 'br' )
\r
1426 if ( !startBlock )
\r
1428 startBlock = this.fixBlock( true, blockTag );
\r
1429 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
\r
1433 endBlock = this.fixBlock( false, blockTag );
\r
1436 // Get the range position.
\r
1437 var isStartOfBlock = startBlock && this.checkStartOfBlock(),
\r
1438 isEndOfBlock = endBlock && this.checkEndOfBlock();
\r
1440 // Delete the current contents.
\r
1441 // TODO: Why is 2.x doing CheckIsEmpty()?
\r
1442 this.deleteContents();
\r
1444 if ( startBlock && startBlock.equals( endBlock ) )
\r
1446 if ( isEndOfBlock )
\r
1448 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1449 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
\r
1452 else if ( isStartOfBlock )
\r
1454 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1455 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
\r
1456 startBlock = null;
\r
1460 // Extract the contents of the block from the selection point to the end
\r
1461 // of its contents.
\r
1462 this.setEndAt( startBlock, CKEDITOR.POSITION_BEFORE_END );
\r
1463 var documentFragment = this.extractContents();
\r
1465 // Duplicate the block element after it.
\r
1466 endBlock = startBlock.clone( false );
\r
1468 // Place the extracted contents into the duplicated block.
\r
1469 documentFragment.appendTo( endBlock );
\r
1470 endBlock.insertAfter( startBlock );
\r
1471 this.moveToPosition( startBlock, CKEDITOR.POSITION_AFTER_END );
\r
1473 // In Gecko, the last child node must be a bogus <br>.
\r
1474 // Note: bogus <br> added under <ul> or <ol> would cause
\r
1475 // lists to be incorrectly rendered.
\r
1476 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
\r
1477 startBlock.appendBogus() ;
\r
1482 previousBlock : startBlock,
\r
1483 nextBlock : endBlock,
\r
1484 wasStartOfBlock : isStartOfBlock,
\r
1485 wasEndOfBlock : isEndOfBlock,
\r
1486 elementPath : elementPath
\r
1491 * Check whether current range is on the inner edge of the specified element.
\r
1492 * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
\r
1493 * @param {CKEDITOR.dom.element} element The target element to check.
\r
1495 checkBoundaryOfElement : function( element, checkType )
\r
1497 var walkerRange = this.clone();
\r
1498 // Expand the range to element boundary.
\r
1499 walkerRange[ checkType == CKEDITOR.START ?
\r
1500 'setStartAt' : 'setEndAt' ]
\r
1501 ( element, checkType == CKEDITOR.START ?
\r
1502 CKEDITOR.POSITION_AFTER_START
\r
1503 : CKEDITOR.POSITION_BEFORE_END );
\r
1505 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1507 walker.evaluator = elementBoundaryEval;
\r
1508 return walker[ checkType == CKEDITOR.START ?
\r
1509 'checkBackward' : 'checkForward' ]();
\r
1511 // Calls to this function may produce changes to the DOM. The range may
\r
1512 // be updated to reflect such changes.
\r
1513 checkStartOfBlock : function()
\r
1515 var startContainer = this.startContainer,
\r
1516 startOffset = this.startOffset;
\r
1518 // If the starting node is a text node, and non-empty before the offset,
\r
1519 // then we're surely not at the start of block.
\r
1520 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1522 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
\r
1523 if ( textBefore.length )
\r
1527 // Antecipate the trim() call here, so the walker will not make
\r
1528 // changes to the DOM, which would not get reflected into this
\r
1529 // range otherwise.
\r
1532 // We need to grab the block element holding the start boundary, so
\r
1533 // let's use an element path for it.
\r
1534 var path = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1536 // Creates a range starting at the block start until the range start.
\r
1537 var walkerRange = this.clone();
\r
1538 walkerRange.collapse( true );
\r
1539 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
\r
1541 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1542 walker.evaluator = getCheckStartEndBlockEvalFunction( true );
\r
1544 return walker.checkBackward();
\r
1547 checkEndOfBlock : function()
\r
1549 var endContainer = this.endContainer,
\r
1550 endOffset = this.endOffset;
\r
1552 // If the ending node is a text node, and non-empty after the offset,
\r
1553 // then we're surely not at the end of block.
\r
1554 if ( endContainer.type == CKEDITOR.NODE_TEXT )
\r
1556 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
\r
1557 if ( textAfter.length )
\r
1561 // Antecipate the trim() call here, so the walker will not make
\r
1562 // changes to the DOM, which would not get reflected into this
\r
1563 // range otherwise.
\r
1566 // We need to grab the block element holding the start boundary, so
\r
1567 // let's use an element path for it.
\r
1568 var path = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1570 // Creates a range starting at the block start until the range start.
\r
1571 var walkerRange = this.clone();
\r
1572 walkerRange.collapse( false );
\r
1573 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
\r
1575 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1576 walker.evaluator = getCheckStartEndBlockEvalFunction( false );
\r
1578 return walker.checkForward();
\r
1582 * Moves the range boundaries to the first editing point inside an
\r
1583 * element. For example, in an element tree like
\r
1584 * "<p><b><i></i></b> Text</p>", the start editing point is
\r
1585 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
\r
1586 * @param {CKEDITOR.dom.element} targetElement The element into which
\r
1587 * look for the editing spot.
\r
1589 moveToElementEditStart : function( targetElement )
\r
1591 var editableElement;
\r
1593 while ( targetElement && targetElement.type == CKEDITOR.NODE_ELEMENT )
\r
1595 if ( targetElement.isEditable() )
\r
1596 editableElement = targetElement;
\r
1597 else if ( editableElement )
\r
1598 break ; // If we already found an editable element, stop the loop.
\r
1600 targetElement = targetElement.getFirst();
\r
1603 if ( editableElement )
\r
1605 this.moveToPosition(editableElement, CKEDITOR.POSITION_AFTER_START);
\r
1613 * Get the single node enclosed within the range if there's one.
\r
1615 getEnclosedNode : function()
\r
1617 var walkerRange = this.clone(),
\r
1618 walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1619 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
\r
1620 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
\r
1621 evaluator = function( node )
\r
1623 return isNotWhitespaces( node ) && isNotBookmarks( node );
\r
1625 walkerRange.evaluator = evaluator;
\r
1626 var node = walker.next();
\r
1628 return node && node.equals( walker.previous() ) ? node : null;
\r
1631 getTouchedStartNode : function()
\r
1633 var container = this.startContainer ;
\r
1635 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1636 return container ;
\r
1638 return container.getChild( this.startOffset ) || container ;
\r
1641 getTouchedEndNode : function()
\r
1643 var container = this.endContainer ;
\r
1645 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1646 return container ;
\r
1648 return container.getChild( this.endOffset - 1 ) || container ;
\r
1653 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text"
\r
1654 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^"
\r
1655 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text"
\r
1656 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text"
\r
1658 CKEDITOR.ENLARGE_ELEMENT = 1;
\r
1659 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
\r
1660 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
\r
1663 * Check boundary types.
\r
1664 * @see CKEDITOR.dom.range::checkBoundaryOfElement
\r
1666 CKEDITOR.START = 1;
\r
1668 CKEDITOR.STARTEND = 3;
\r