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
281 else if( node.type == CKEDITOR.NODE_ELEMENT )
\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 var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
\r
312 bookmarkEval = new CKEDITOR.dom.walker.bookmark();
\r
314 function nonWhitespaceOrBookmarkEval( node )
\r
316 // Whitespaces and bookmark nodes are to be ignored.
\r
317 return !whitespaceEval( node ) && !bookmarkEval( node );
\r
320 CKEDITOR.dom.range.prototype =
\r
324 var clone = new CKEDITOR.dom.range( this.document );
\r
326 clone.startContainer = this.startContainer;
\r
327 clone.startOffset = this.startOffset;
\r
328 clone.endContainer = this.endContainer;
\r
329 clone.endOffset = this.endOffset;
\r
330 clone.collapsed = this.collapsed;
\r
335 collapse : function( toStart )
\r
339 this.endContainer = this.startContainer;
\r
340 this.endOffset = this.startOffset;
\r
344 this.startContainer = this.endContainer;
\r
345 this.startOffset = this.endOffset;
\r
348 this.collapsed = true;
\r
351 // The selection may be lost when cloning (due to the splitText() call).
\r
352 cloneContents : function()
\r
354 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
356 if ( !this.collapsed )
\r
357 execContentsAction( this, 2, docFrag );
\r
362 deleteContents : function()
\r
364 if ( this.collapsed )
\r
367 execContentsAction( this, 0 );
\r
370 extractContents : function()
\r
372 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
374 if ( !this.collapsed )
\r
375 execContentsAction( this, 1, docFrag );
\r
381 * Creates a bookmark object, which can be later used to restore the
\r
382 * range by using the moveToBookmark function.
\r
383 * This is an "intrusive" way to create a bookmark. It includes <span> tags
\r
384 * in the range boundaries. The advantage of it is that it is possible to
\r
385 * handle DOM mutations when moving back to the bookmark.
\r
386 * Attention: the inclusion of nodes in the DOM is a design choice and
\r
387 * should not be changed as there are other points in the code that may be
\r
388 * using those nodes to perform operations. See GetBookmarkNode.
\r
389 * @param {Boolean} [serializable] Indicates that the bookmark nodes
\r
390 * must contain ids, which can be used to restore the range even
\r
391 * when these nodes suffer mutations (like a clonation or innerHTML
\r
393 * @returns {Object} And object representing a bookmark.
\r
395 createBookmark : function( serializable )
\r
397 var startNode, endNode;
\r
401 startNode = this.document.createElement( 'span' );
\r
402 startNode.setAttribute( '_fck_bookmark', 1 );
\r
403 startNode.setStyle( 'display', 'none' );
\r
405 // For IE, it must have something inside, otherwise it may be
\r
406 // removed during DOM operations.
\r
407 startNode.setHtml( ' ' );
\r
409 if ( serializable )
\r
411 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
\r
412 startNode.setAttribute( 'id', baseId + 'S' );
\r
415 // If collapsed, the endNode will not be created.
\r
416 if ( !this.collapsed )
\r
418 endNode = startNode.clone();
\r
419 endNode.setHtml( ' ' );
\r
421 if ( serializable )
\r
422 endNode.setAttribute( 'id', baseId + 'E' );
\r
424 clone = this.clone();
\r
426 clone.insertNode( endNode );
\r
429 clone = this.clone();
\r
430 clone.collapse( true );
\r
431 clone.insertNode( startNode );
\r
433 // Update the range position.
\r
436 this.setStartAfter( startNode );
\r
437 this.setEndBefore( endNode );
\r
440 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
\r
443 startNode : serializable ? baseId + 'S' : startNode,
\r
444 endNode : serializable ? baseId + 'E' : endNode,
\r
445 serializable : serializable
\r
450 * Creates a "non intrusive" and "mutation sensible" bookmark. This
\r
451 * kind of bookmark should be used only when the DOM is supposed to
\r
452 * remain stable after its creation.
\r
453 * @param {Boolean} [normalized] Indicates that the bookmark must
\r
454 * normalized. When normalized, the successive text nodes are
\r
455 * considered a single node. To sucessful load a normalized
\r
456 * bookmark, the DOM tree must be also normalized before calling
\r
458 * @returns {Object} An object representing the bookmark.
\r
460 createBookmark2 : function( normalized )
\r
462 var startContainer = this.startContainer,
\r
463 endContainer = this.endContainer;
\r
465 var startOffset = this.startOffset,
\r
466 endOffset = this.endOffset;
\r
468 var child, previous;
\r
470 // If there is no range then get out of here.
\r
471 // It happens on initial load in Safari #962 and if the editor it's
\r
472 // hidden also in Firefox
\r
473 if ( !startContainer || !endContainer )
\r
474 return { start : 0, end : 0 };
\r
478 // Find out if the start is pointing to a text node that will
\r
480 if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
\r
482 child = startContainer.getChild( startOffset );
\r
484 // In this case, move the start information to that text
\r
486 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
487 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
489 startContainer = child;
\r
494 // Normalize the start.
\r
495 while ( startContainer.type == CKEDITOR.NODE_TEXT
\r
496 && ( previous = startContainer.getPrevious() )
\r
497 && previous.type == CKEDITOR.NODE_TEXT )
\r
499 startContainer = previous;
\r
500 startOffset += previous.getLength();
\r
503 // Process the end only if not normalized.
\r
504 if ( !this.isCollapsed )
\r
506 // Find out if the start is pointing to a text node that
\r
507 // will be normalized.
\r
508 if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
\r
510 child = endContainer.getChild( endOffset );
\r
512 // In this case, move the start information to that
\r
514 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
515 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
517 endContainer = child;
\r
522 // Normalize the end.
\r
523 while ( endContainer.type == CKEDITOR.NODE_TEXT
\r
524 && ( previous = endContainer.getPrevious() )
\r
525 && previous.type == CKEDITOR.NODE_TEXT )
\r
527 endContainer = previous;
\r
528 endOffset += previous.getLength();
\r
534 start : startContainer.getAddress( normalized ),
\r
535 end : this.isCollapsed ? null : endContainer.getAddress( normalized ),
\r
536 startOffset : startOffset,
\r
537 endOffset : endOffset,
\r
538 normalized : normalized,
\r
539 is2 : true // It's a createBookmark2 bookmark.
\r
543 moveToBookmark : function( bookmark )
\r
545 if ( bookmark.is2 ) // Created with createBookmark2().
\r
547 // Get the start information.
\r
548 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ),
\r
549 startOffset = bookmark.startOffset;
\r
551 // Get the end information.
\r
552 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
\r
553 endOffset = bookmark.endOffset;
\r
555 // Set the start boundary.
\r
556 this.setStart( startContainer, startOffset );
\r
558 // Set the end boundary. If not available, collapse it.
\r
559 if ( endContainer )
\r
560 this.setEnd( endContainer, endOffset );
\r
562 this.collapse( true );
\r
564 else // Created with createBookmark().
\r
566 var serializable = bookmark.serializable,
\r
567 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
\r
568 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
\r
570 // Set the range start at the bookmark start node position.
\r
571 this.setStartBefore( startNode );
\r
573 // Remove it, because it may interfere in the setEndBefore call.
\r
574 startNode.remove();
\r
576 // Set the range end at the bookmark end node position, or simply
\r
577 // collapse it if it is not available.
\r
580 this.setEndBefore( endNode );
\r
584 this.collapse( true );
\r
588 getBoundaryNodes : function()
\r
590 var startNode = this.startContainer,
\r
591 endNode = this.endContainer,
\r
592 startOffset = this.startOffset,
\r
593 endOffset = this.endOffset,
\r
596 if ( startNode.type == CKEDITOR.NODE_ELEMENT )
\r
598 childCount = startNode.getChildCount();
\r
599 if ( childCount > startOffset )
\r
600 startNode = startNode.getChild( startOffset );
\r
601 else if ( childCount < 1 )
\r
602 startNode = startNode.getPreviousSourceNode();
\r
603 else // startOffset > childCount but childCount is not 0
\r
605 // Try to take the node just after the current position.
\r
606 startNode = startNode.$;
\r
607 while ( startNode.lastChild )
\r
608 startNode = startNode.lastChild;
\r
609 startNode = new CKEDITOR.dom.node( startNode );
\r
611 // Normally we should take the next node in DFS order. But it
\r
612 // is also possible that we've already reached the end of
\r
614 startNode = startNode.getNextSourceNode() || startNode;
\r
617 if ( endNode.type == CKEDITOR.NODE_ELEMENT )
\r
619 childCount = endNode.getChildCount();
\r
620 if ( childCount > endOffset )
\r
621 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
\r
622 else if ( childCount < 1 )
\r
623 endNode = endNode.getPreviousSourceNode();
\r
624 else // endOffset > childCount but childCount is not 0
\r
626 // Try to take the node just before the current position.
\r
627 endNode = endNode.$;
\r
628 while ( endNode.lastChild )
\r
629 endNode = endNode.lastChild;
\r
630 endNode = new CKEDITOR.dom.node( endNode );
\r
634 // Sometimes the endNode will come right before startNode for collapsed
\r
635 // ranges. Fix it. (#3780)
\r
636 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
\r
637 startNode = endNode;
\r
639 return { startNode : startNode, endNode : endNode };
\r
643 * Find the node which fully contains the range.
\r
644 * @param includeSelf
\r
645 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
\r
647 getCommonAncestor : function( includeSelf , ignoreTextNode )
\r
649 var start = this.startContainer,
\r
650 end = this.endContainer,
\r
653 if ( start.equals( end ) )
\r
656 && start.type == CKEDITOR.NODE_ELEMENT
\r
657 && this.startOffset == this.endOffset - 1 )
\r
658 ancestor = start.getChild( this.startOffset );
\r
663 ancestor = start.getCommonAncestor( end );
\r
665 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
\r
669 * Transforms the startContainer and endContainer properties from text
\r
670 * nodes to element nodes, whenever possible. This is actually possible
\r
671 * if either of the boundary containers point to a text node, and its
\r
672 * offset is set to zero, or after the last char in the node.
\r
674 optimize : function()
\r
676 var container = this.startContainer;
\r
677 var offset = this.startOffset;
\r
679 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
682 this.setStartBefore( container );
\r
683 else if ( offset >= container.getLength() )
\r
684 this.setStartAfter( container );
\r
687 container = this.endContainer;
\r
688 offset = this.endOffset;
\r
690 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
693 this.setEndBefore( container );
\r
694 else if ( offset >= container.getLength() )
\r
695 this.setEndAfter( container );
\r
700 * Move the range out of bookmark nodes if they're been the container.
\r
702 optimizeBookmark: function()
\r
704 var startNode = this.startContainer,
\r
705 endNode = this.endContainer;
\r
707 if ( startNode.is && startNode.is( 'span' )
\r
708 && startNode.hasAttribute( '_fck_bookmark' ) )
\r
709 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
\r
710 if ( endNode && endNode.is && endNode.is( 'span' )
\r
711 && endNode.hasAttribute( '_fck_bookmark' ) )
\r
712 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
\r
715 trim : function( ignoreStart, ignoreEnd )
\r
717 var startContainer = this.startContainer,
\r
718 startOffset = this.startOffset,
\r
719 collapsed = this.collapsed;
\r
720 if ( ( !ignoreStart || collapsed )
\r
721 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
723 // If the offset is zero, we just insert the new node before
\r
725 if ( !startOffset )
\r
727 startOffset = startContainer.getIndex();
\r
728 startContainer = startContainer.getParent();
\r
730 // If the offset is at the end, we'll insert it after the text
\r
732 else if ( startOffset >= startContainer.getLength() )
\r
734 startOffset = startContainer.getIndex() + 1;
\r
735 startContainer = startContainer.getParent();
\r
737 // In other case, we split the text node and insert the new
\r
738 // node at the split point.
\r
741 var nextText = startContainer.split( startOffset );
\r
743 startOffset = startContainer.getIndex() + 1;
\r
744 startContainer = startContainer.getParent();
\r
745 // Check if it is necessary to update the end boundary.
\r
746 if ( !collapsed && this.startContainer.equals( this.endContainer ) )
\r
747 this.setEnd( nextText, this.endOffset - this.startOffset );
\r
750 this.setStart( startContainer, startOffset );
\r
753 this.collapse( true );
\r
756 var endContainer = this.endContainer;
\r
757 var endOffset = this.endOffset;
\r
759 if ( !( ignoreEnd || collapsed )
\r
760 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
762 // If the offset is zero, we just insert the new node before
\r
766 endOffset = endContainer.getIndex();
\r
767 endContainer = endContainer.getParent();
\r
769 // If the offset is at the end, we'll insert it after the text
\r
771 else if ( endOffset >= endContainer.getLength() )
\r
773 endOffset = endContainer.getIndex() + 1;
\r
774 endContainer = endContainer.getParent();
\r
776 // In other case, we split the text node and insert the new
\r
777 // node at the split point.
\r
780 endContainer.split( endOffset );
\r
782 endOffset = endContainer.getIndex() + 1;
\r
783 endContainer = endContainer.getParent();
\r
786 this.setEnd( endContainer, endOffset );
\r
790 enlarge : function( unit )
\r
794 case CKEDITOR.ENLARGE_ELEMENT :
\r
796 if ( this.collapsed )
\r
799 // Get the common ancestor.
\r
800 var commonAncestor = this.getCommonAncestor();
\r
802 var body = this.document.getBody();
\r
804 // For each boundary
\r
805 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
\r
806 // 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
808 var startTop, endTop;
\r
810 var enlargeable, sibling, commonReached;
\r
812 // Indicates that the node can be added only if whitespace
\r
813 // is available before it.
\r
814 var needsWhiteSpace = false;
\r
818 // Process the start boundary.
\r
820 var container = this.startContainer;
\r
821 var offset = this.startOffset;
\r
823 if ( container.type == CKEDITOR.NODE_TEXT )
\r
827 // Check if there is any non-space text before the
\r
828 // offset. Otherwise, container is null.
\r
829 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
\r
831 // If we found only whitespace in the node, it
\r
832 // means that we'll need more whitespace to be able
\r
833 // to expand. For example, <i> can be expanded in
\r
834 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
835 needsWhiteSpace = !!container;
\r
840 if ( !( sibling = container.getPrevious() ) )
\r
841 enlargeable = container.getParent();
\r
846 // If we have offset, get the node preceeding it as the
\r
847 // first sibling to be checked.
\r
849 sibling = container.getChild( offset - 1 ) || container.getLast();
\r
851 // If there is no sibling, mark the container to be
\r
854 enlargeable = container;
\r
857 while ( enlargeable || sibling )
\r
859 if ( enlargeable && !sibling )
\r
861 // If we reached the common ancestor, mark the flag
\r
863 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
864 commonReached = true;
\r
866 if ( !body.contains( enlargeable ) )
\r
869 // If we don't need space or this element breaks
\r
870 // the line, then enlarge it.
\r
871 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
873 needsWhiteSpace = false;
\r
875 // If the common ancestor has been reached,
\r
876 // we'll not enlarge it immediately, but just
\r
877 // mark it to be enlarged later if the end
\r
878 // boundary also enlarges it.
\r
879 if ( commonReached )
\r
880 startTop = enlargeable;
\r
882 this.setStartBefore( enlargeable );
\r
885 sibling = enlargeable.getPrevious();
\r
888 // Check all sibling nodes preceeding the enlargeable
\r
889 // node. The node wil lbe enlarged only if none of them
\r
893 // This flag indicates that this node has
\r
894 // whitespaces at the end.
\r
895 isWhiteSpace = false;
\r
897 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
899 siblingText = sibling.getText();
\r
901 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
904 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
\r
908 // If this is a visible element.
\r
909 // We need to check for the bookmark attribute because IE insists on
\r
910 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
911 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
\r
913 // We'll accept it only if we need
\r
914 // whitespace, and this is an inline
\r
915 // element with whitespace only.
\r
916 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
918 // It must contains spaces and inline elements only.
\r
920 siblingText = sibling.getText();
\r
922 if ( !(/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
\r
926 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
927 for ( var i = 0, child ; child = allChildren[ i++ ] ; )
\r
929 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
938 isWhiteSpace = !!siblingText.length;
\r
945 // A node with whitespaces has been found.
\r
946 if ( isWhiteSpace )
\r
948 // Enlarge the last enlargeable node, if we
\r
949 // were waiting for spaces.
\r
950 if ( needsWhiteSpace )
\r
952 if ( commonReached )
\r
953 startTop = enlargeable;
\r
954 else if ( enlargeable )
\r
955 this.setStartBefore( enlargeable );
\r
958 needsWhiteSpace = true;
\r
963 var next = sibling.getPrevious();
\r
965 if ( !enlargeable && !next )
\r
967 // Set the sibling as enlargeable, so it's
\r
968 // parent will be get later outside this while.
\r
969 enlargeable = sibling;
\r
978 // If sibling has been set to null, then we
\r
979 // need to stop enlarging.
\r
980 enlargeable = null;
\r
985 enlargeable = enlargeable.getParent();
\r
988 // Process the end boundary. This is basically the same
\r
989 // code used for the start boundary, with small changes to
\r
990 // make it work in the oposite side (to the right). This
\r
991 // makes it difficult to reuse the code here. So, fixes to
\r
992 // the above code are likely to be replicated here.
\r
994 container = this.endContainer;
\r
995 offset = this.endOffset;
\r
997 // Reset the common variables.
\r
998 enlargeable = sibling = null;
\r
999 commonReached = needsWhiteSpace = false;
\r
1001 if ( container.type == CKEDITOR.NODE_TEXT )
\r
1003 // Check if there is any non-space text after the
\r
1004 // offset. Otherwise, container is null.
\r
1005 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
\r
1007 // If we found only whitespace in the node, it
\r
1008 // means that we'll need more whitespace to be able
\r
1009 // to expand. For example, <i> can be expanded in
\r
1010 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
1011 needsWhiteSpace = !( container && container.getLength() );
\r
1015 if ( !( sibling = container.getNext() ) )
\r
1016 enlargeable = container.getParent();
\r
1021 // Get the node right after the boudary to be checked
\r
1023 sibling = container.getChild( offset );
\r
1026 enlargeable = container;
\r
1029 while ( enlargeable || sibling )
\r
1031 if ( enlargeable && !sibling )
\r
1033 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
1034 commonReached = true;
\r
1036 if ( !body.contains( enlargeable ) )
\r
1039 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
1041 needsWhiteSpace = false;
\r
1043 if ( commonReached )
\r
1044 endTop = enlargeable;
\r
1045 else if ( enlargeable )
\r
1046 this.setEndAfter( enlargeable );
\r
1049 sibling = enlargeable.getNext();
\r
1054 isWhiteSpace = false;
\r
1056 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
1058 siblingText = sibling.getText();
\r
1060 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
1063 isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
\r
1067 // If this is a visible element.
\r
1068 // We need to check for the bookmark attribute because IE insists on
\r
1069 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
1070 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_fck_bookmark' ) )
\r
1072 // We'll accept it only if we need
\r
1073 // whitespace, and this is an inline
\r
1074 // element with whitespace only.
\r
1075 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
1077 // It must contains spaces and inline elements only.
\r
1079 siblingText = sibling.getText();
\r
1081 if ( !(/[^\s\ufeff]/).test( siblingText ) )
\r
1085 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
1086 for ( i = 0 ; child = allChildren[ i++ ] ; )
\r
1088 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
1097 isWhiteSpace = !!siblingText.length;
\r
1104 if ( isWhiteSpace )
\r
1106 if ( needsWhiteSpace )
\r
1108 if ( commonReached )
\r
1109 endTop = enlargeable;
\r
1111 this.setEndAfter( enlargeable );
\r
1117 next = sibling.getNext();
\r
1119 if ( !enlargeable && !next )
\r
1121 enlargeable = sibling;
\r
1130 // If sibling has been set to null, then we
\r
1131 // need to stop enlarging.
\r
1132 enlargeable = null;
\r
1136 if ( enlargeable )
\r
1137 enlargeable = enlargeable.getParent();
\r
1140 // If the common ancestor can be enlarged by both boundaries, then include it also.
\r
1141 if ( startTop && endTop )
\r
1143 commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
\r
1145 this.setStartBefore( commonAncestor );
\r
1146 this.setEndAfter( commonAncestor );
\r
1150 case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
\r
1151 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
\r
1153 // Enlarging the start boundary.
\r
1154 var walkerRange = new CKEDITOR.dom.range( this.document );
\r
1156 body = this.document.getBody();
\r
1158 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
\r
1159 walkerRange.setEnd( this.startContainer, this.startOffset );
\r
1161 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1162 blockBoundary, // The node on which the enlarging should stop.
\r
1164 defaultGuard = CKEDITOR.dom.walker.blockBoundary(
\r
1165 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
\r
1166 // Record the encountered 'blockBoundary' for later use.
\r
1167 boundaryGuard = function( node )
\r
1169 var retval = defaultGuard( node );
\r
1171 blockBoundary = node;
\r
1174 // Record the encounted 'tailBr' for later use.
\r
1175 tailBrGuard = function( node )
\r
1177 var retval = boundaryGuard( node );
\r
1178 if ( !retval && node.is && node.is( 'br' ) )
\r
1183 walker.guard = boundaryGuard;
\r
1185 enlargeable = walker.lastBackward();
\r
1187 // It's the body which stop the enlarging if no block boundary found.
\r
1188 blockBoundary = blockBoundary || body;
\r
1190 // Start the range at different position by comparing
\r
1191 // the document position of it with 'enlargeable' node.
\r
1194 !blockBoundary.is( 'br' ) &&
\r
1195 ( !enlargeable && this.checkStartOfBlock()
\r
1196 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1197 CKEDITOR.POSITION_AFTER_START :
\r
1198 CKEDITOR.POSITION_AFTER_END );
\r
1200 // Enlarging the end boundary.
\r
1201 walkerRange = this.clone();
\r
1202 walkerRange.collapse();
\r
1203 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
\r
1204 walker = new CKEDITOR.dom.walker( walkerRange );
\r
1206 // tailBrGuard only used for on range end.
\r
1207 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
\r
1208 tailBrGuard : boundaryGuard;
\r
1209 blockBoundary = null;
\r
1210 // End the range right before the block boundary node.
\r
1212 enlargeable = walker.lastForward();
\r
1214 // It's the body which stop the enlarging if no block boundary found.
\r
1215 blockBoundary = blockBoundary || body;
\r
1217 // Start the range at different position by comparing
\r
1218 // the document position of it with 'enlargeable' node.
\r
1221 ( !enlargeable && this.checkEndOfBlock()
\r
1222 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1223 CKEDITOR.POSITION_BEFORE_END :
\r
1224 CKEDITOR.POSITION_BEFORE_START );
\r
1225 // We must include the <br> at the end of range if there's
\r
1226 // one and we're expanding list item contents
\r
1228 this.setEndAfter( tailBr );
\r
1233 * Inserts a node at the start of the range. The range will be expanded
\r
1234 * the contain the node.
\r
1236 insertNode : function( node )
\r
1238 this.optimizeBookmark();
\r
1239 this.trim( false, true );
\r
1241 var startContainer = this.startContainer;
\r
1242 var startOffset = this.startOffset;
\r
1244 var nextNode = startContainer.getChild( startOffset );
\r
1247 node.insertBefore( nextNode );
\r
1249 startContainer.append( node );
\r
1251 // Check if we need to update the end boundary.
\r
1252 if ( node.getParent().equals( this.endContainer ) )
\r
1255 // Expand the range to embrace the new node.
\r
1256 this.setStartBefore( node );
\r
1259 moveToPosition : function( node, position )
\r
1261 this.setStartAt( node, position );
\r
1262 this.collapse( true );
\r
1265 selectNodeContents : function( node )
\r
1267 this.setStart( node, 0 );
\r
1268 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
\r
1272 * Sets the start position of a Range.
\r
1273 * @param {CKEDITOR.dom.node} startNode The node to start the range.
\r
1274 * @param {Number} startOffset An integer greater than or equal to zero
\r
1275 * representing the offset for the start of the range from the start
\r
1278 setStart : function( startNode, startOffset )
\r
1280 // W3C requires a check for the new position. If it is after the end
\r
1281 // boundary, the range should be collapsed to the new start. It seams
\r
1282 // we will not need this check for our use of this class so we can
\r
1283 // ignore it for now.
\r
1285 this.startContainer = startNode;
\r
1286 this.startOffset = startOffset;
\r
1288 if ( !this.endContainer )
\r
1290 this.endContainer = startNode;
\r
1291 this.endOffset = startOffset;
\r
1294 updateCollapsed( this );
\r
1298 * Sets the end position of a Range.
\r
1299 * @param {CKEDITOR.dom.node} endNode The node to end the range.
\r
1300 * @param {Number} endOffset An integer greater than or equal to zero
\r
1301 * representing the offset for the end of the range from the start
\r
1304 setEnd : function( endNode, endOffset )
\r
1306 // W3C requires a check for the new position. If it is before the start
\r
1307 // boundary, the range should be collapsed to the new end. It seams we
\r
1308 // will not need this check for our use of this class so we can ignore
\r
1311 this.endContainer = endNode;
\r
1312 this.endOffset = endOffset;
\r
1314 if ( !this.startContainer )
\r
1316 this.startContainer = endNode;
\r
1317 this.startOffset = endOffset;
\r
1320 updateCollapsed( this );
\r
1323 setStartAfter : function( node )
\r
1325 this.setStart( node.getParent(), node.getIndex() + 1 );
\r
1328 setStartBefore : function( node )
\r
1330 this.setStart( node.getParent(), node.getIndex() );
\r
1333 setEndAfter : function( node )
\r
1335 this.setEnd( node.getParent(), node.getIndex() + 1 );
\r
1338 setEndBefore : function( node )
\r
1340 this.setEnd( node.getParent(), node.getIndex() );
\r
1343 setStartAt : function( node, position )
\r
1345 switch( position )
\r
1347 case CKEDITOR.POSITION_AFTER_START :
\r
1348 this.setStart( node, 0 );
\r
1351 case CKEDITOR.POSITION_BEFORE_END :
\r
1352 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1353 this.setStart( node, node.getLength() );
\r
1355 this.setStart( node, node.getChildCount() );
\r
1358 case CKEDITOR.POSITION_BEFORE_START :
\r
1359 this.setStartBefore( node );
\r
1362 case CKEDITOR.POSITION_AFTER_END :
\r
1363 this.setStartAfter( node );
\r
1366 updateCollapsed( this );
\r
1369 setEndAt : function( node, position )
\r
1371 switch( position )
\r
1373 case CKEDITOR.POSITION_AFTER_START :
\r
1374 this.setEnd( node, 0 );
\r
1377 case CKEDITOR.POSITION_BEFORE_END :
\r
1378 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1379 this.setEnd( node, node.getLength() );
\r
1381 this.setEnd( node, node.getChildCount() );
\r
1384 case CKEDITOR.POSITION_BEFORE_START :
\r
1385 this.setEndBefore( node );
\r
1388 case CKEDITOR.POSITION_AFTER_END :
\r
1389 this.setEndAfter( node );
\r
1392 updateCollapsed( this );
\r
1395 fixBlock : function( isStart, blockTag )
\r
1397 var bookmark = this.createBookmark(),
\r
1398 fixedBlock = this.document.createElement( blockTag );
\r
1400 this.collapse( isStart );
\r
1402 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
\r
1404 this.extractContents().appendTo( fixedBlock );
\r
1405 fixedBlock.trim();
\r
1407 if ( !CKEDITOR.env.ie )
\r
1408 fixedBlock.appendBogus();
\r
1410 this.insertNode( fixedBlock );
\r
1412 this.moveToBookmark( bookmark );
\r
1414 return fixedBlock;
\r
1417 splitBlock : function( blockTag )
\r
1419 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ),
\r
1420 endPath = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1422 var startBlockLimit = startPath.blockLimit,
\r
1423 endBlockLimit = endPath.blockLimit;
\r
1425 var startBlock = startPath.block,
\r
1426 endBlock = endPath.block;
\r
1428 var elementPath = null;
\r
1429 // Do nothing if the boundaries are in different block limits.
\r
1430 if ( !startBlockLimit.equals( endBlockLimit ) )
\r
1433 // Get or fix current blocks.
\r
1434 if ( blockTag != 'br' )
\r
1436 if ( !startBlock )
\r
1438 startBlock = this.fixBlock( true, blockTag );
\r
1439 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
\r
1443 endBlock = this.fixBlock( false, blockTag );
\r
1446 // Get the range position.
\r
1447 var isStartOfBlock = startBlock && this.checkStartOfBlock(),
\r
1448 isEndOfBlock = endBlock && this.checkEndOfBlock();
\r
1450 // Delete the current contents.
\r
1451 // TODO: Why is 2.x doing CheckIsEmpty()?
\r
1452 this.deleteContents();
\r
1454 if ( startBlock && startBlock.equals( endBlock ) )
\r
1456 if ( isEndOfBlock )
\r
1458 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1459 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
\r
1462 else if ( isStartOfBlock )
\r
1464 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1465 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
\r
1466 startBlock = null;
\r
1470 endBlock = this.splitElement( startBlock );
\r
1471 // In Gecko, the last child node must be a bogus <br>.
\r
1472 // Note: bogus <br> added under <ul> or <ol> would cause
\r
1473 // lists to be incorrectly rendered.
\r
1474 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
\r
1475 startBlock.appendBogus() ;
\r
1480 previousBlock : startBlock,
\r
1481 nextBlock : endBlock,
\r
1482 wasStartOfBlock : isStartOfBlock,
\r
1483 wasEndOfBlock : isEndOfBlock,
\r
1484 elementPath : elementPath
\r
1489 * Branch the specified element from the collapsed range position and
\r
1490 * place the caret between the two result branches.
\r
1491 * Note: The range must be collapsed and been enclosed by this element.
\r
1492 * @param {CKEDITOR.dom.element} element
\r
1493 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
\r
1495 splitElement : function( toSplit )
\r
1497 if ( !this.collapsed )
\r
1500 // Extract the contents of the block from the selection point to the end
\r
1501 // of its contents.
\r
1502 this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
\r
1503 var documentFragment = this.extractContents();
\r
1505 // Duplicate the element after it.
\r
1506 var clone = toSplit.clone( false );
\r
1508 // Place the extracted contents into the duplicated element.
\r
1509 documentFragment.appendTo( clone );
\r
1510 clone.insertAfter( toSplit );
\r
1511 this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
\r
1516 * Check whether current range is on the inner edge of the specified element.
\r
1517 * @param {Number} checkType ( CKEDITOR.START | CKEDITOR.END ) The checking side.
\r
1518 * @param {CKEDITOR.dom.element} element The target element to check.
\r
1520 checkBoundaryOfElement : function( element, checkType )
\r
1522 var walkerRange = this.clone();
\r
1523 // Expand the range to element boundary.
\r
1524 walkerRange[ checkType == CKEDITOR.START ?
\r
1525 'setStartAt' : 'setEndAt' ]
\r
1526 ( element, checkType == CKEDITOR.START ?
\r
1527 CKEDITOR.POSITION_AFTER_START
\r
1528 : CKEDITOR.POSITION_BEFORE_END );
\r
1530 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1532 walker.evaluator = elementBoundaryEval;
\r
1533 return walker[ checkType == CKEDITOR.START ?
\r
1534 'checkBackward' : 'checkForward' ]();
\r
1536 // Calls to this function may produce changes to the DOM. The range may
\r
1537 // be updated to reflect such changes.
\r
1538 checkStartOfBlock : function()
\r
1540 var startContainer = this.startContainer,
\r
1541 startOffset = this.startOffset;
\r
1543 // If the starting node is a text node, and non-empty before the offset,
\r
1544 // then we're surely not at the start of block.
\r
1545 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1547 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
\r
1548 if ( textBefore.length )
\r
1552 // Antecipate the trim() call here, so the walker will not make
\r
1553 // changes to the DOM, which would not get reflected into this
\r
1554 // range otherwise.
\r
1557 // We need to grab the block element holding the start boundary, so
\r
1558 // let's use an element path for it.
\r
1559 var path = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1561 // Creates a range starting at the block start until the range start.
\r
1562 var walkerRange = this.clone();
\r
1563 walkerRange.collapse( true );
\r
1564 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
\r
1566 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1567 walker.evaluator = getCheckStartEndBlockEvalFunction( true );
\r
1569 return walker.checkBackward();
\r
1572 checkEndOfBlock : function()
\r
1574 var endContainer = this.endContainer,
\r
1575 endOffset = this.endOffset;
\r
1577 // If the ending node is a text node, and non-empty after the offset,
\r
1578 // then we're surely not at the end of block.
\r
1579 if ( endContainer.type == CKEDITOR.NODE_TEXT )
\r
1581 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
\r
1582 if ( textAfter.length )
\r
1586 // Antecipate the trim() call here, so the walker will not make
\r
1587 // changes to the DOM, which would not get reflected into this
\r
1588 // range otherwise.
\r
1591 // We need to grab the block element holding the start boundary, so
\r
1592 // let's use an element path for it.
\r
1593 var path = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1595 // Creates a range starting at the block start until the range start.
\r
1596 var walkerRange = this.clone();
\r
1597 walkerRange.collapse( false );
\r
1598 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
\r
1600 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1601 walker.evaluator = getCheckStartEndBlockEvalFunction( false );
\r
1603 return walker.checkForward();
\r
1607 * Moves the range boundaries to the first editing point inside an
\r
1608 * element. For example, in an element tree like
\r
1609 * "<p><b><i></i></b> Text</p>", the start editing point is
\r
1610 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
\r
1611 * @param {CKEDITOR.dom.element} el The element into which look for the
\r
1614 moveToElementEditStart : function( el )
\r
1618 while ( el && el.type == CKEDITOR.NODE_ELEMENT )
\r
1620 isEditable = el.isEditable();
\r
1622 // If an editable element is found, move inside it.
\r
1624 this.moveToPosition( el, CKEDITOR.POSITION_AFTER_START );
\r
1625 // Stop immediately if we've found a non editable inline element (e.g <img>).
\r
1626 else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
\r
1628 this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );
\r
1632 // Non-editable non-inline elements are to be bypassed, getting the next one.
\r
1633 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1634 el = el.getNext( nonWhitespaceOrBookmarkEval );
\r
1636 el = el.getFirst( nonWhitespaceOrBookmarkEval );
\r
1638 // Stop immediately if we've found a text node.
\r
1639 if ( el && el.type == CKEDITOR.NODE_TEXT )
\r
1641 this.moveToPosition( el, CKEDITOR.POSITION_BEFORE_START );
\r
1646 return isEditable;
\r
1650 * Get the single node enclosed within the range if there's one.
\r
1652 getEnclosedNode : function()
\r
1654 var walkerRange = this.clone(),
\r
1655 walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1656 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
\r
1657 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
\r
1658 evaluator = function( node )
\r
1660 return isNotWhitespaces( node ) && isNotBookmarks( node );
\r
1662 walkerRange.evaluator = evaluator;
\r
1663 var node = walker.next();
\r
1665 return node && node.equals( walker.previous() ) ? node : null;
\r
1668 getTouchedStartNode : function()
\r
1670 var container = this.startContainer ;
\r
1672 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1673 return container ;
\r
1675 return container.getChild( this.startOffset ) || container ;
\r
1678 getTouchedEndNode : function()
\r
1680 var container = this.endContainer ;
\r
1682 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1683 return container ;
\r
1685 return container.getChild( this.endOffset - 1 ) || container ;
\r
1690 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text"
\r
1691 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^"
\r
1692 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text"
\r
1693 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text"
\r
1695 CKEDITOR.ENLARGE_ELEMENT = 1;
\r
1696 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
\r
1697 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
\r
1700 * Check boundary types.
\r
1701 * @see CKEDITOR.dom.range::checkBoundaryOfElement
\r
1703 CKEDITOR.START = 1;
\r
1705 CKEDITOR.STARTEND = 3;
\r