2 Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.
\r
3 For licensing, see LICENSE.html or http://ckeditor.com/license
\r
9 CKEDITOR.dom.range = function( document )
\r
11 this.startContainer = null;
\r
12 this.startOffset = null;
\r
13 this.endContainer = null;
\r
14 this.endOffset = null;
\r
15 this.collapsed = true;
\r
17 this.document = document;
\r
22 // Updates the "collapsed" property for the given range object.
\r
23 var updateCollapsed = function( range )
\r
26 range.startContainer &&
\r
27 range.endContainer &&
\r
28 range.startContainer.equals( range.endContainer ) &&
\r
29 range.startOffset == range.endOffset );
\r
32 // This is a shared function used to delete, extract and clone the range
\r
35 var execContentsAction = function( range, action, docFrag )
\r
37 range.optimizeBookmark();
\r
39 var startNode = range.startContainer;
\r
40 var endNode = range.endContainer;
\r
42 var startOffset = range.startOffset;
\r
43 var endOffset = range.endOffset;
\r
45 var removeStartNode;
\r
48 // For text containers, we must simply split the node and point to the
\r
49 // second part. The removal will be handled by the rest of the code .
\r
50 if ( endNode.type == CKEDITOR.NODE_TEXT )
\r
51 endNode = endNode.split( endOffset );
\r
54 // If the end container has children and the offset is pointing
\r
55 // to a child, then we should start from it.
\r
56 if ( endNode.getChildCount() > 0 )
\r
58 // If the offset points after the last node.
\r
59 if ( endOffset >= endNode.getChildCount() )
\r
61 // Let's create a temporary node and mark it for removal.
\r
62 endNode = endNode.append( range.document.createText( '' ) );
\r
63 removeEndNode = true;
\r
66 endNode = endNode.getChild( endOffset );
\r
70 // For text containers, we must simply split the node. The removal will
\r
71 // be handled by the rest of the code .
\r
72 if ( startNode.type == CKEDITOR.NODE_TEXT )
\r
74 startNode.split( startOffset );
\r
76 // In cases the end node is the same as the start node, the above
\r
77 // splitting will also split the end, so me must move the end to
\r
78 // the second part of the split.
\r
79 if ( startNode.equals( endNode ) )
\r
80 endNode = startNode.getNext();
\r
84 // If the start container has children and the offset is pointing
\r
85 // to a child, then we should start from its previous sibling.
\r
87 // If the offset points to the first node, we don't have a
\r
88 // sibling, so let's use the first one, but mark it for removal.
\r
91 // Let's create a temporary node and mark it for removal.
\r
92 startNode = startNode.getFirst().insertBeforeMe( range.document.createText( '' ) );
\r
93 removeStartNode = true;
\r
95 else if ( startOffset >= startNode.getChildCount() )
\r
97 // Let's create a temporary node and mark it for removal.
\r
98 startNode = startNode.append( range.document.createText( '' ) );
\r
99 removeStartNode = true;
\r
102 startNode = startNode.getChild( startOffset ).getPrevious();
\r
105 // Get the parent nodes tree for the start and end boundaries.
\r
106 var startParents = startNode.getParents();
\r
107 var endParents = endNode.getParents();
\r
109 // Compare them, to find the top most siblings.
\r
110 var i, topStart, topEnd;
\r
112 for ( i = 0 ; i < startParents.length ; i++ )
\r
114 topStart = startParents[ i ];
\r
115 topEnd = endParents[ i ];
\r
117 // The compared nodes will match until we find the top most
\r
118 // siblings (different nodes that have the same parent).
\r
119 // "i" will hold the index in the parents array for the top
\r
121 if ( !topStart.equals( topEnd ) )
\r
125 var clone = docFrag, levelStartNode, levelClone, currentNode, currentSibling;
\r
127 // Remove all successive sibling nodes for every node in the
\r
128 // startParents tree.
\r
129 for ( var j = i ; j < startParents.length ; j++ )
\r
131 levelStartNode = startParents[j];
\r
133 // For Extract and Clone, we must clone this level.
\r
134 if ( clone && !levelStartNode.equals( startNode ) ) // action = 0 = Delete
\r
135 levelClone = clone.append( levelStartNode.clone() );
\r
137 currentNode = levelStartNode.getNext();
\r
139 while ( currentNode )
\r
141 // Stop processing when the current node matches a node in the
\r
142 // endParents tree or if it is the endNode.
\r
143 if ( currentNode.equals( endParents[ j ] ) || currentNode.equals( endNode ) )
\r
146 // Cache the next sibling.
\r
147 currentSibling = currentNode.getNext();
\r
149 // If cloning, just clone it.
\r
150 if ( action == 2 ) // 2 = Clone
\r
151 clone.append( currentNode.clone( true ) );
\r
154 // Both Delete and Extract will remove the node.
\r
155 currentNode.remove();
\r
157 // When Extracting, move the removed node to the docFrag.
\r
158 if ( action == 1 ) // 1 = Extract
\r
159 clone.append( currentNode );
\r
162 currentNode = currentSibling;
\r
166 clone = levelClone;
\r
171 // Remove all previous sibling nodes for every node in the
\r
172 // endParents tree.
\r
173 for ( var k = i ; k < endParents.length ; k++ )
\r
175 levelStartNode = endParents[ k ];
\r
177 // For Extract and Clone, we must clone this level.
\r
178 if ( action > 0 && !levelStartNode.equals( endNode ) ) // action = 0 = Delete
\r
179 levelClone = clone.append( levelStartNode.clone() );
\r
181 // The processing of siblings may have already been done by the parent.
\r
182 if ( !startParents[ k ] || levelStartNode.$.parentNode != startParents[ k ].$.parentNode )
\r
184 currentNode = levelStartNode.getPrevious();
\r
186 while ( currentNode )
\r
188 // Stop processing when the current node matches a node in the
\r
189 // startParents tree or if it is the startNode.
\r
190 if ( currentNode.equals( startParents[ k ] ) || currentNode.equals( startNode ) )
\r
193 // Cache the next sibling.
\r
194 currentSibling = currentNode.getPrevious();
\r
196 // If cloning, just clone it.
\r
197 if ( action == 2 ) // 2 = Clone
\r
198 clone.$.insertBefore( currentNode.$.cloneNode( true ), clone.$.firstChild ) ;
\r
201 // Both Delete and Extract will remove the node.
\r
202 currentNode.remove();
\r
204 // When Extracting, mode the removed node to the docFrag.
\r
205 if ( action == 1 ) // 1 = Extract
\r
206 clone.$.insertBefore( currentNode.$, clone.$.firstChild );
\r
209 currentNode = currentSibling;
\r
214 clone = levelClone;
\r
217 if ( action == 2 ) // 2 = Clone.
\r
219 // No changes in the DOM should be done, so fix the split text (if any).
\r
221 var startTextNode = range.startContainer;
\r
222 if ( startTextNode.type == CKEDITOR.NODE_TEXT )
\r
224 startTextNode.$.data += startTextNode.$.nextSibling.data;
\r
225 startTextNode.$.parentNode.removeChild( startTextNode.$.nextSibling );
\r
228 var endTextNode = range.endContainer;
\r
229 if ( endTextNode.type == CKEDITOR.NODE_TEXT && endTextNode.$.nextSibling )
\r
231 endTextNode.$.data += endTextNode.$.nextSibling.data;
\r
232 endTextNode.$.parentNode.removeChild( endTextNode.$.nextSibling );
\r
237 // Collapse the range.
\r
239 // If a node has been partially selected, collapse the range between
\r
240 // topStart and topEnd. Otherwise, simply collapse it to the start. (W3C specs).
\r
241 if ( topStart && topEnd && ( startNode.$.parentNode != topStart.$.parentNode || endNode.$.parentNode != topEnd.$.parentNode ) )
\r
243 var endIndex = topEnd.getIndex();
\r
245 // If the start node is to be removed, we must correct the
\r
246 // index to reflect the removal.
\r
247 if ( removeStartNode && topEnd.$.parentNode == startNode.$.parentNode )
\r
250 range.setStart( topEnd.getParent(), endIndex );
\r
253 // Collapse it to the start.
\r
254 range.collapse( true );
\r
257 // Cleanup any marked node.
\r
258 if ( removeStartNode )
\r
259 startNode.remove();
\r
261 if ( removeEndNode && endNode.$.parentNode )
\r
265 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
267 // Creates the appropriate node evaluator for the dom walker used inside
\r
268 // check(Start|End)OfBlock.
\r
269 function getCheckStartEndBlockEvalFunction( isStart )
\r
271 var hadBr = false, bookmarkEvaluator = CKEDITOR.dom.walker.bookmark( true );
\r
272 return function( node )
\r
274 // First ignore bookmark nodes.
\r
275 if ( bookmarkEvaluator( node ) )
\r
278 if ( node.type == CKEDITOR.NODE_TEXT )
\r
280 // If there's any visible text, then we're not at the start.
\r
281 if ( CKEDITOR.tools.trim( node.getText() ).length )
\r
284 else if ( node.type == CKEDITOR.NODE_ELEMENT )
\r
286 // If there are non-empty inline elements (e.g. <img />), then we're not
\r
288 if ( !inlineChildReqElements[ node.getName() ] )
\r
290 // If we're working at the end-of-block, forgive the first <br /> in non-IE
\r
292 if ( !isStart && !CKEDITOR.env.ie && node.getName() == 'br' && !hadBr )
\r
302 // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
\r
303 // text node and non-empty elements unless it's being bookmark text.
\r
304 function elementBoundaryEval( node )
\r
306 // Reject any text node unless it's being bookmark
\r
307 // OR it's spaces. (#3883)
\r
308 return node.type != CKEDITOR.NODE_TEXT
\r
309 && node.getName() in CKEDITOR.dtd.$removeEmpty
\r
310 || !CKEDITOR.tools.trim( node.getText() )
\r
311 || !!node.getParent().data( 'cke-bookmark' );
\r
314 var whitespaceEval = new CKEDITOR.dom.walker.whitespaces(),
\r
315 bookmarkEval = new CKEDITOR.dom.walker.bookmark();
\r
317 function nonWhitespaceOrBookmarkEval( node )
\r
319 // Whitespaces and bookmark nodes are to be ignored.
\r
320 return !whitespaceEval( node ) && !bookmarkEval( node );
\r
323 CKEDITOR.dom.range.prototype =
\r
327 var clone = new CKEDITOR.dom.range( this.document );
\r
329 clone.startContainer = this.startContainer;
\r
330 clone.startOffset = this.startOffset;
\r
331 clone.endContainer = this.endContainer;
\r
332 clone.endOffset = this.endOffset;
\r
333 clone.collapsed = this.collapsed;
\r
338 collapse : function( toStart )
\r
342 this.endContainer = this.startContainer;
\r
343 this.endOffset = this.startOffset;
\r
347 this.startContainer = this.endContainer;
\r
348 this.startOffset = this.endOffset;
\r
351 this.collapsed = true;
\r
355 * The content nodes of the range are cloned and added to a document fragment, which is returned.
\r
356 * <strong> Note: </strong> Text selection may lost after invoking this method. (caused by text node splitting).
\r
358 cloneContents : function()
\r
360 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
362 if ( !this.collapsed )
\r
363 execContentsAction( this, 2, docFrag );
\r
369 * Deletes the content nodes of the range permanently from the DOM tree.
\r
371 deleteContents : function()
\r
373 if ( this.collapsed )
\r
376 execContentsAction( this, 0 );
\r
380 * The content nodes of the range are cloned and added to a document fragment,
\r
381 * meanwhile they're removed permanently from the DOM tree.
\r
383 extractContents : function()
\r
385 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
387 if ( !this.collapsed )
\r
388 execContentsAction( this, 1, docFrag );
\r
394 * Creates a bookmark object, which can be later used to restore the
\r
395 * range by using the moveToBookmark function.
\r
396 * This is an "intrusive" way to create a bookmark. It includes <span> tags
\r
397 * in the range boundaries. The advantage of it is that it is possible to
\r
398 * handle DOM mutations when moving back to the bookmark.
\r
399 * Attention: the inclusion of nodes in the DOM is a design choice and
\r
400 * should not be changed as there are other points in the code that may be
\r
401 * using those nodes to perform operations. See GetBookmarkNode.
\r
402 * @param {Boolean} [serializable] Indicates that the bookmark nodes
\r
403 * must contain ids, which can be used to restore the range even
\r
404 * when these nodes suffer mutations (like a clonation or innerHTML
\r
406 * @returns {Object} And object representing a bookmark.
\r
408 createBookmark : function( serializable )
\r
410 var startNode, endNode;
\r
413 var collapsed = this.collapsed;
\r
415 startNode = this.document.createElement( 'span' );
\r
416 startNode.data( 'cke-bookmark', 1 );
\r
417 startNode.setStyle( 'display', 'none' );
\r
419 // For IE, it must have something inside, otherwise it may be
\r
420 // removed during DOM operations.
\r
421 startNode.setHtml( ' ' );
\r
423 if ( serializable )
\r
425 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
\r
426 startNode.setAttribute( 'id', baseId + 'S' );
\r
429 // If collapsed, the endNode will not be created.
\r
432 endNode = startNode.clone();
\r
433 endNode.setHtml( ' ' );
\r
435 if ( serializable )
\r
436 endNode.setAttribute( 'id', baseId + 'E' );
\r
438 clone = this.clone();
\r
440 clone.insertNode( endNode );
\r
443 clone = this.clone();
\r
444 clone.collapse( true );
\r
445 clone.insertNode( startNode );
\r
447 // Update the range position.
\r
450 this.setStartAfter( startNode );
\r
451 this.setEndBefore( endNode );
\r
454 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
\r
457 startNode : serializable ? baseId + 'S' : startNode,
\r
458 endNode : serializable ? baseId + 'E' : endNode,
\r
459 serializable : serializable,
\r
460 collapsed : collapsed
\r
465 * Creates a "non intrusive" and "mutation sensible" bookmark. This
\r
466 * kind of bookmark should be used only when the DOM is supposed to
\r
467 * remain stable after its creation.
\r
468 * @param {Boolean} [normalized] Indicates that the bookmark must
\r
469 * normalized. When normalized, the successive text nodes are
\r
470 * considered a single node. To sucessful load a normalized
\r
471 * bookmark, the DOM tree must be also normalized before calling
\r
473 * @returns {Object} An object representing the bookmark.
\r
475 createBookmark2 : function( normalized )
\r
477 var startContainer = this.startContainer,
\r
478 endContainer = this.endContainer;
\r
480 var startOffset = this.startOffset,
\r
481 endOffset = this.endOffset;
\r
483 var collapsed = this.collapsed;
\r
485 var child, previous;
\r
487 // If there is no range then get out of here.
\r
488 // It happens on initial load in Safari #962 and if the editor it's
\r
489 // hidden also in Firefox
\r
490 if ( !startContainer || !endContainer )
\r
491 return { start : 0, end : 0 };
\r
495 // Find out if the start is pointing to a text node that will
\r
497 if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
\r
499 child = startContainer.getChild( startOffset );
\r
501 // In this case, move the start information to that text
\r
503 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
504 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
506 startContainer = child;
\r
511 // Normalize the start.
\r
512 while ( startContainer.type == CKEDITOR.NODE_TEXT
\r
513 && ( previous = startContainer.getPrevious() )
\r
514 && previous.type == CKEDITOR.NODE_TEXT )
\r
516 startContainer = previous;
\r
517 startOffset += previous.getLength();
\r
520 // Process the end only if not normalized.
\r
523 // Find out if the start is pointing to a text node that
\r
524 // will be normalized.
\r
525 if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
\r
527 child = endContainer.getChild( endOffset );
\r
529 // In this case, move the start information to that
\r
531 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
532 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
534 endContainer = child;
\r
539 // Normalize the end.
\r
540 while ( endContainer.type == CKEDITOR.NODE_TEXT
\r
541 && ( previous = endContainer.getPrevious() )
\r
542 && previous.type == CKEDITOR.NODE_TEXT )
\r
544 endContainer = previous;
\r
545 endOffset += previous.getLength();
\r
551 start : startContainer.getAddress( normalized ),
\r
552 end : collapsed ? null : endContainer.getAddress( normalized ),
\r
553 startOffset : startOffset,
\r
554 endOffset : endOffset,
\r
555 normalized : normalized,
\r
556 collapsed : collapsed,
\r
557 is2 : true // It's a createBookmark2 bookmark.
\r
561 moveToBookmark : function( bookmark )
\r
563 if ( bookmark.is2 ) // Created with createBookmark2().
\r
565 // Get the start information.
\r
566 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ),
\r
567 startOffset = bookmark.startOffset;
\r
569 // Get the end information.
\r
570 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
\r
571 endOffset = bookmark.endOffset;
\r
573 // Set the start boundary.
\r
574 this.setStart( startContainer, startOffset );
\r
576 // Set the end boundary. If not available, collapse it.
\r
577 if ( endContainer )
\r
578 this.setEnd( endContainer, endOffset );
\r
580 this.collapse( true );
\r
582 else // Created with createBookmark().
\r
584 var serializable = bookmark.serializable,
\r
585 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
\r
586 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
\r
588 // Set the range start at the bookmark start node position.
\r
589 this.setStartBefore( startNode );
\r
591 // Remove it, because it may interfere in the setEndBefore call.
\r
592 startNode.remove();
\r
594 // Set the range end at the bookmark end node position, or simply
\r
595 // collapse it if it is not available.
\r
598 this.setEndBefore( endNode );
\r
602 this.collapse( true );
\r
606 getBoundaryNodes : function()
\r
608 var startNode = this.startContainer,
\r
609 endNode = this.endContainer,
\r
610 startOffset = this.startOffset,
\r
611 endOffset = this.endOffset,
\r
614 if ( startNode.type == CKEDITOR.NODE_ELEMENT )
\r
616 childCount = startNode.getChildCount();
\r
617 if ( childCount > startOffset )
\r
618 startNode = startNode.getChild( startOffset );
\r
619 else if ( childCount < 1 )
\r
620 startNode = startNode.getPreviousSourceNode();
\r
621 else // startOffset > childCount but childCount is not 0
\r
623 // Try to take the node just after the current position.
\r
624 startNode = startNode.$;
\r
625 while ( startNode.lastChild )
\r
626 startNode = startNode.lastChild;
\r
627 startNode = new CKEDITOR.dom.node( startNode );
\r
629 // Normally we should take the next node in DFS order. But it
\r
630 // is also possible that we've already reached the end of
\r
632 startNode = startNode.getNextSourceNode() || startNode;
\r
635 if ( endNode.type == CKEDITOR.NODE_ELEMENT )
\r
637 childCount = endNode.getChildCount();
\r
638 if ( childCount > endOffset )
\r
639 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
\r
640 else if ( childCount < 1 )
\r
641 endNode = endNode.getPreviousSourceNode();
\r
642 else // endOffset > childCount but childCount is not 0
\r
644 // Try to take the node just before the current position.
\r
645 endNode = endNode.$;
\r
646 while ( endNode.lastChild )
\r
647 endNode = endNode.lastChild;
\r
648 endNode = new CKEDITOR.dom.node( endNode );
\r
652 // Sometimes the endNode will come right before startNode for collapsed
\r
653 // ranges. Fix it. (#3780)
\r
654 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
\r
655 startNode = endNode;
\r
657 return { startNode : startNode, endNode : endNode };
\r
661 * Find the node which fully contains the range.
\r
662 * @param includeSelf
\r
663 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
\r
665 getCommonAncestor : function( includeSelf , ignoreTextNode )
\r
667 var start = this.startContainer,
\r
668 end = this.endContainer,
\r
671 if ( start.equals( end ) )
\r
674 && start.type == CKEDITOR.NODE_ELEMENT
\r
675 && this.startOffset == this.endOffset - 1 )
\r
676 ancestor = start.getChild( this.startOffset );
\r
681 ancestor = start.getCommonAncestor( end );
\r
683 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
\r
687 * Transforms the startContainer and endContainer properties from text
\r
688 * nodes to element nodes, whenever possible. This is actually possible
\r
689 * if either of the boundary containers point to a text node, and its
\r
690 * offset is set to zero, or after the last char in the node.
\r
692 optimize : function()
\r
694 var container = this.startContainer;
\r
695 var offset = this.startOffset;
\r
697 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
700 this.setStartBefore( container );
\r
701 else if ( offset >= container.getLength() )
\r
702 this.setStartAfter( container );
\r
705 container = this.endContainer;
\r
706 offset = this.endOffset;
\r
708 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
711 this.setEndBefore( container );
\r
712 else if ( offset >= container.getLength() )
\r
713 this.setEndAfter( container );
\r
718 * Move the range out of bookmark nodes if they'd been the container.
\r
720 optimizeBookmark: function()
\r
722 var startNode = this.startContainer,
\r
723 endNode = this.endContainer;
\r
725 if ( startNode.is && startNode.is( 'span' )
\r
726 && startNode.data( 'cke-bookmark' ) )
\r
727 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
\r
728 if ( endNode && endNode.is && endNode.is( 'span' )
\r
729 && endNode.data( 'cke-bookmark' ) )
\r
730 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
\r
733 trim : function( ignoreStart, ignoreEnd )
\r
735 var startContainer = this.startContainer,
\r
736 startOffset = this.startOffset,
\r
737 collapsed = this.collapsed;
\r
738 if ( ( !ignoreStart || collapsed )
\r
739 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
741 // If the offset is zero, we just insert the new node before
\r
743 if ( !startOffset )
\r
745 startOffset = startContainer.getIndex();
\r
746 startContainer = startContainer.getParent();
\r
748 // If the offset is at the end, we'll insert it after the text
\r
750 else if ( startOffset >= startContainer.getLength() )
\r
752 startOffset = startContainer.getIndex() + 1;
\r
753 startContainer = startContainer.getParent();
\r
755 // In other case, we split the text node and insert the new
\r
756 // node at the split point.
\r
759 var nextText = startContainer.split( startOffset );
\r
761 startOffset = startContainer.getIndex() + 1;
\r
762 startContainer = startContainer.getParent();
\r
764 // Check all necessity of updating the end boundary.
\r
765 if ( this.startContainer.equals( this.endContainer ) )
\r
766 this.setEnd( nextText, this.endOffset - this.startOffset );
\r
767 else if ( startContainer.equals( this.endContainer ) )
\r
768 this.endOffset += 1;
\r
771 this.setStart( startContainer, startOffset );
\r
775 this.collapse( true );
\r
780 var endContainer = this.endContainer;
\r
781 var endOffset = this.endOffset;
\r
783 if ( !( ignoreEnd || collapsed )
\r
784 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
786 // If the offset is zero, we just insert the new node before
\r
790 endOffset = endContainer.getIndex();
\r
791 endContainer = endContainer.getParent();
\r
793 // If the offset is at the end, we'll insert it after the text
\r
795 else if ( endOffset >= endContainer.getLength() )
\r
797 endOffset = endContainer.getIndex() + 1;
\r
798 endContainer = endContainer.getParent();
\r
800 // In other case, we split the text node and insert the new
\r
801 // node at the split point.
\r
804 endContainer.split( endOffset );
\r
806 endOffset = endContainer.getIndex() + 1;
\r
807 endContainer = endContainer.getParent();
\r
810 this.setEnd( endContainer, endOffset );
\r
814 enlarge : function( unit )
\r
818 case CKEDITOR.ENLARGE_ELEMENT :
\r
820 if ( this.collapsed )
\r
823 // Get the common ancestor.
\r
824 var commonAncestor = this.getCommonAncestor();
\r
826 var body = this.document.getBody();
\r
828 // For each boundary
\r
829 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
\r
830 // 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
832 var startTop, endTop;
\r
834 var enlargeable, sibling, commonReached;
\r
836 // Indicates that the node can be added only if whitespace
\r
837 // is available before it.
\r
838 var needsWhiteSpace = false;
\r
842 // Process the start boundary.
\r
844 var container = this.startContainer;
\r
845 var offset = this.startOffset;
\r
847 if ( container.type == CKEDITOR.NODE_TEXT )
\r
851 // Check if there is any non-space text before the
\r
852 // offset. Otherwise, container is null.
\r
853 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
\r
855 // If we found only whitespace in the node, it
\r
856 // means that we'll need more whitespace to be able
\r
857 // to expand. For example, <i> can be expanded in
\r
858 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
859 needsWhiteSpace = !!container;
\r
864 if ( !( sibling = container.getPrevious() ) )
\r
865 enlargeable = container.getParent();
\r
870 // If we have offset, get the node preceeding it as the
\r
871 // first sibling to be checked.
\r
873 sibling = container.getChild( offset - 1 ) || container.getLast();
\r
875 // If there is no sibling, mark the container to be
\r
878 enlargeable = container;
\r
881 while ( enlargeable || sibling )
\r
883 if ( enlargeable && !sibling )
\r
885 // If we reached the common ancestor, mark the flag
\r
887 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
888 commonReached = true;
\r
890 if ( !body.contains( enlargeable ) )
\r
893 // If we don't need space or this element breaks
\r
894 // the line, then enlarge it.
\r
895 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
897 needsWhiteSpace = false;
\r
899 // If the common ancestor has been reached,
\r
900 // we'll not enlarge it immediately, but just
\r
901 // mark it to be enlarged later if the end
\r
902 // boundary also enlarges it.
\r
903 if ( commonReached )
\r
904 startTop = enlargeable;
\r
906 this.setStartBefore( enlargeable );
\r
909 sibling = enlargeable.getPrevious();
\r
912 // Check all sibling nodes preceeding the enlargeable
\r
913 // node. The node wil lbe enlarged only if none of them
\r
917 // This flag indicates that this node has
\r
918 // whitespaces at the end.
\r
919 isWhiteSpace = false;
\r
921 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
923 siblingText = sibling.getText();
\r
925 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
928 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
\r
932 // If this is a visible element.
\r
933 // We need to check for the bookmark attribute because IE insists on
\r
934 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
935 if ( sibling.$.offsetWidth > 0 && !sibling.data( 'cke-bookmark' ) )
\r
937 // We'll accept it only if we need
\r
938 // whitespace, and this is an inline
\r
939 // element with whitespace only.
\r
940 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
942 // It must contains spaces and inline elements only.
\r
944 siblingText = sibling.getText();
\r
946 if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
\r
950 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
951 for ( var i = 0, child ; child = allChildren[ i++ ] ; )
\r
953 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
962 isWhiteSpace = !!siblingText.length;
\r
969 // A node with whitespaces has been found.
\r
970 if ( isWhiteSpace )
\r
972 // Enlarge the last enlargeable node, if we
\r
973 // were waiting for spaces.
\r
974 if ( needsWhiteSpace )
\r
976 if ( commonReached )
\r
977 startTop = enlargeable;
\r
978 else if ( enlargeable )
\r
979 this.setStartBefore( enlargeable );
\r
982 needsWhiteSpace = true;
\r
987 var next = sibling.getPrevious();
\r
989 if ( !enlargeable && !next )
\r
991 // Set the sibling as enlargeable, so it's
\r
992 // parent will be get later outside this while.
\r
993 enlargeable = sibling;
\r
1002 // If sibling has been set to null, then we
\r
1003 // need to stop enlarging.
\r
1004 enlargeable = null;
\r
1008 if ( enlargeable )
\r
1009 enlargeable = enlargeable.getParent();
\r
1012 // Process the end boundary. This is basically the same
\r
1013 // code used for the start boundary, with small changes to
\r
1014 // make it work in the oposite side (to the right). This
\r
1015 // makes it difficult to reuse the code here. So, fixes to
\r
1016 // the above code are likely to be replicated here.
\r
1018 container = this.endContainer;
\r
1019 offset = this.endOffset;
\r
1021 // Reset the common variables.
\r
1022 enlargeable = sibling = null;
\r
1023 commonReached = needsWhiteSpace = false;
\r
1025 if ( container.type == CKEDITOR.NODE_TEXT )
\r
1027 // Check if there is any non-space text after the
\r
1028 // offset. Otherwise, container is null.
\r
1029 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
\r
1031 // If we found only whitespace in the node, it
\r
1032 // means that we'll need more whitespace to be able
\r
1033 // to expand. For example, <i> can be expanded in
\r
1034 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
1035 needsWhiteSpace = !( container && container.getLength() );
\r
1039 if ( !( sibling = container.getNext() ) )
\r
1040 enlargeable = container.getParent();
\r
1045 // Get the node right after the boudary to be checked
\r
1047 sibling = container.getChild( offset );
\r
1050 enlargeable = container;
\r
1053 while ( enlargeable || sibling )
\r
1055 if ( enlargeable && !sibling )
\r
1057 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
1058 commonReached = true;
\r
1060 if ( !body.contains( enlargeable ) )
\r
1063 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
1065 needsWhiteSpace = false;
\r
1067 if ( commonReached )
\r
1068 endTop = enlargeable;
\r
1069 else if ( enlargeable )
\r
1070 this.setEndAfter( enlargeable );
\r
1073 sibling = enlargeable.getNext();
\r
1078 isWhiteSpace = false;
\r
1080 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
1082 siblingText = sibling.getText();
\r
1084 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
1087 isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
\r
1091 // If this is a visible element.
\r
1092 // We need to check for the bookmark attribute because IE insists on
\r
1093 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
1094 if ( sibling.$.offsetWidth > 0 && !sibling.data( 'cke-bookmark' ) )
\r
1096 // We'll accept it only if we need
\r
1097 // whitespace, and this is an inline
\r
1098 // element with whitespace only.
\r
1099 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
1101 // It must contains spaces and inline elements only.
\r
1103 siblingText = sibling.getText();
\r
1105 if ( (/[^\s\ufeff]/).test( siblingText ) )
\r
1109 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
1110 for ( i = 0 ; child = allChildren[ i++ ] ; )
\r
1112 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
1121 isWhiteSpace = !!siblingText.length;
\r
1128 if ( isWhiteSpace )
\r
1130 if ( needsWhiteSpace )
\r
1132 if ( commonReached )
\r
1133 endTop = enlargeable;
\r
1135 this.setEndAfter( enlargeable );
\r
1141 next = sibling.getNext();
\r
1143 if ( !enlargeable && !next )
\r
1145 enlargeable = sibling;
\r
1154 // If sibling has been set to null, then we
\r
1155 // need to stop enlarging.
\r
1156 enlargeable = null;
\r
1160 if ( enlargeable )
\r
1161 enlargeable = enlargeable.getParent();
\r
1164 // If the common ancestor can be enlarged by both boundaries, then include it also.
\r
1165 if ( startTop && endTop )
\r
1167 commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
\r
1169 this.setStartBefore( commonAncestor );
\r
1170 this.setEndAfter( commonAncestor );
\r
1174 case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
\r
1175 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
\r
1177 // Enlarging the start boundary.
\r
1178 var walkerRange = new CKEDITOR.dom.range( this.document );
\r
1180 body = this.document.getBody();
\r
1182 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
\r
1183 walkerRange.setEnd( this.startContainer, this.startOffset );
\r
1185 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1186 blockBoundary, // The node on which the enlarging should stop.
\r
1187 tailBr, // In case BR as block boundary.
\r
1188 notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(
\r
1189 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
\r
1190 // Record the encountered 'blockBoundary' for later use.
\r
1191 boundaryGuard = function( node )
\r
1193 var retval = notBlockBoundary( node );
\r
1195 blockBoundary = node;
\r
1198 // Record the encounted 'tailBr' for later use.
\r
1199 tailBrGuard = function( node )
\r
1201 var retval = boundaryGuard( node );
\r
1202 if ( !retval && node.is && node.is( 'br' ) )
\r
1207 walker.guard = boundaryGuard;
\r
1209 enlargeable = walker.lastBackward();
\r
1211 // It's the body which stop the enlarging if no block boundary found.
\r
1212 blockBoundary = blockBoundary || body;
\r
1214 // Start the range either after the end of found block (<p>...</p>[text)
\r
1215 // or at the start of block (<p>[text...), by comparing the document position
\r
1216 // with 'enlargeable' node.
\r
1219 !blockBoundary.is( 'br' ) &&
\r
1220 ( !enlargeable && this.checkStartOfBlock()
\r
1221 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1222 CKEDITOR.POSITION_AFTER_START :
\r
1223 CKEDITOR.POSITION_AFTER_END );
\r
1225 // Enlarging the end boundary.
\r
1226 walkerRange = this.clone();
\r
1227 walkerRange.collapse();
\r
1228 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
\r
1229 walker = new CKEDITOR.dom.walker( walkerRange );
\r
1231 // tailBrGuard only used for on range end.
\r
1232 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
\r
1233 tailBrGuard : boundaryGuard;
\r
1234 blockBoundary = null;
\r
1235 // End the range right before the block boundary node.
\r
1237 enlargeable = walker.lastForward();
\r
1239 // It's the body which stop the enlarging if no block boundary found.
\r
1240 blockBoundary = blockBoundary || body;
\r
1242 // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)
\r
1243 // by comparing the document position with 'enlargeable' node.
\r
1246 ( !enlargeable && this.checkEndOfBlock()
\r
1247 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1248 CKEDITOR.POSITION_BEFORE_END :
\r
1249 CKEDITOR.POSITION_BEFORE_START );
\r
1250 // We must include the <br> at the end of range if there's
\r
1251 // one and we're expanding list item contents
\r
1253 this.setEndAfter( tailBr );
\r
1258 * Descrease the range to make sure that boundaries
\r
1259 * always anchor beside text nodes or innermost element.
\r
1260 * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
\r
1262 * <dt>CKEDITOR.SHRINK_ELEMENT</dt>
\r
1263 * <dd>Shrink the range boundaries to the edge of the innermost element.</dd>
\r
1264 * <dt>CKEDITOR.SHRINK_TEXT</dt>
\r
1265 * <dd>Shrink the range boudaries to anchor by the side of enclosed text node, range remains if there's no text nodes on boundaries at all.</dd>
\r
1267 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
\r
1269 shrink : function( mode, selectContents )
\r
1271 // Unable to shrink a collapsed range.
\r
1272 if ( !this.collapsed )
\r
1274 mode = mode || CKEDITOR.SHRINK_TEXT;
\r
1276 var walkerRange = this.clone();
\r
1278 var startContainer = this.startContainer,
\r
1279 endContainer = this.endContainer,
\r
1280 startOffset = this.startOffset,
\r
1281 endOffset = this.endOffset,
\r
1282 collapsed = this.collapsed;
\r
1284 // Whether the start/end boundary is moveable.
\r
1285 var moveStart = 1,
\r
1288 if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1290 if ( !startOffset )
\r
1291 walkerRange.setStartBefore( startContainer );
\r
1292 else if ( startOffset >= startContainer.getLength( ) )
\r
1293 walkerRange.setStartAfter( startContainer );
\r
1296 // Enlarge the range properly to avoid walker making
\r
1297 // DOM changes caused by triming the text nodes later.
\r
1298 walkerRange.setStartBefore( startContainer );
\r
1303 if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
1306 walkerRange.setEndBefore( endContainer );
\r
1307 else if ( endOffset >= endContainer.getLength( ) )
\r
1308 walkerRange.setEndAfter( endContainer );
\r
1311 walkerRange.setEndAfter( endContainer );
\r
1316 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1317 isBookmark = CKEDITOR.dom.walker.bookmark();
\r
1319 walker.evaluator = function( node )
\r
1321 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
\r
1322 CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
\r
1325 var currentElement;
\r
1326 walker.guard = function( node, movingOut )
\r
1328 if ( isBookmark( node ) )
\r
1331 // Stop when we're shrink in element mode while encountering a text node.
\r
1332 if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
\r
1335 // Stop when we've already walked "through" an element.
\r
1336 if ( movingOut && node.equals( currentElement ) )
\r
1339 if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
\r
1340 currentElement = node;
\r
1347 var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
\r
1348 textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );
\r
1354 var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
\r
1355 textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );
\r
1358 return !!( moveStart || moveEnd );
\r
1363 * Inserts a node at the start of the range. The range will be expanded
\r
1364 * the contain the node.
\r
1366 insertNode : function( node )
\r
1368 this.optimizeBookmark();
\r
1369 this.trim( false, true );
\r
1371 var startContainer = this.startContainer;
\r
1372 var startOffset = this.startOffset;
\r
1374 var nextNode = startContainer.getChild( startOffset );
\r
1377 node.insertBefore( nextNode );
\r
1379 startContainer.append( node );
\r
1381 // Check if we need to update the end boundary.
\r
1382 if ( node.getParent().equals( this.endContainer ) )
\r
1385 // Expand the range to embrace the new node.
\r
1386 this.setStartBefore( node );
\r
1389 moveToPosition : function( node, position )
\r
1391 this.setStartAt( node, position );
\r
1392 this.collapse( true );
\r
1395 selectNodeContents : function( node )
\r
1397 this.setStart( node, 0 );
\r
1398 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
\r
1402 * Sets the start position of a Range.
\r
1403 * @param {CKEDITOR.dom.node} startNode The node to start the range.
\r
1404 * @param {Number} startOffset An integer greater than or equal to zero
\r
1405 * representing the offset for the start of the range from the start
\r
1408 setStart : function( startNode, startOffset )
\r
1410 // W3C requires a check for the new position. If it is after the end
\r
1411 // boundary, the range should be collapsed to the new start. It seams
\r
1412 // we will not need this check for our use of this class so we can
\r
1413 // ignore it for now.
\r
1415 // Fixing invalid range start inside dtd empty elements.
\r
1416 if( startNode.type == CKEDITOR.NODE_ELEMENT
\r
1417 && CKEDITOR.dtd.$empty[ startNode.getName() ] )
\r
1418 startOffset = startNode.getIndex(), startNode = startNode.getParent();
\r
1420 this.startContainer = startNode;
\r
1421 this.startOffset = startOffset;
\r
1423 if ( !this.endContainer )
\r
1425 this.endContainer = startNode;
\r
1426 this.endOffset = startOffset;
\r
1429 updateCollapsed( this );
\r
1433 * Sets the end position of a Range.
\r
1434 * @param {CKEDITOR.dom.node} endNode The node to end the range.
\r
1435 * @param {Number} endOffset An integer greater than or equal to zero
\r
1436 * representing the offset for the end of the range from the start
\r
1439 setEnd : function( endNode, endOffset )
\r
1441 // W3C requires a check for the new position. If it is before the start
\r
1442 // boundary, the range should be collapsed to the new end. It seams we
\r
1443 // will not need this check for our use of this class so we can ignore
\r
1446 // Fixing invalid range end inside dtd empty elements.
\r
1447 if( endNode.type == CKEDITOR.NODE_ELEMENT
\r
1448 && CKEDITOR.dtd.$empty[ endNode.getName() ] )
\r
1449 endOffset = endNode.getIndex() + 1, endNode = endNode.getParent();
\r
1451 this.endContainer = endNode;
\r
1452 this.endOffset = endOffset;
\r
1454 if ( !this.startContainer )
\r
1456 this.startContainer = endNode;
\r
1457 this.startOffset = endOffset;
\r
1460 updateCollapsed( this );
\r
1463 setStartAfter : function( node )
\r
1465 this.setStart( node.getParent(), node.getIndex() + 1 );
\r
1468 setStartBefore : function( node )
\r
1470 this.setStart( node.getParent(), node.getIndex() );
\r
1473 setEndAfter : function( node )
\r
1475 this.setEnd( node.getParent(), node.getIndex() + 1 );
\r
1478 setEndBefore : function( node )
\r
1480 this.setEnd( node.getParent(), node.getIndex() );
\r
1483 setStartAt : function( node, position )
\r
1485 switch( position )
\r
1487 case CKEDITOR.POSITION_AFTER_START :
\r
1488 this.setStart( node, 0 );
\r
1491 case CKEDITOR.POSITION_BEFORE_END :
\r
1492 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1493 this.setStart( node, node.getLength() );
\r
1495 this.setStart( node, node.getChildCount() );
\r
1498 case CKEDITOR.POSITION_BEFORE_START :
\r
1499 this.setStartBefore( node );
\r
1502 case CKEDITOR.POSITION_AFTER_END :
\r
1503 this.setStartAfter( node );
\r
1506 updateCollapsed( this );
\r
1509 setEndAt : function( node, position )
\r
1511 switch( position )
\r
1513 case CKEDITOR.POSITION_AFTER_START :
\r
1514 this.setEnd( node, 0 );
\r
1517 case CKEDITOR.POSITION_BEFORE_END :
\r
1518 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1519 this.setEnd( node, node.getLength() );
\r
1521 this.setEnd( node, node.getChildCount() );
\r
1524 case CKEDITOR.POSITION_BEFORE_START :
\r
1525 this.setEndBefore( node );
\r
1528 case CKEDITOR.POSITION_AFTER_END :
\r
1529 this.setEndAfter( node );
\r
1532 updateCollapsed( this );
\r
1535 fixBlock : function( isStart, blockTag )
\r
1537 var bookmark = this.createBookmark(),
\r
1538 fixedBlock = this.document.createElement( blockTag );
\r
1540 this.collapse( isStart );
\r
1542 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
\r
1544 this.extractContents().appendTo( fixedBlock );
\r
1545 fixedBlock.trim();
\r
1547 if ( !CKEDITOR.env.ie )
\r
1548 fixedBlock.appendBogus();
\r
1550 this.insertNode( fixedBlock );
\r
1552 this.moveToBookmark( bookmark );
\r
1554 return fixedBlock;
\r
1557 splitBlock : function( blockTag )
\r
1559 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ),
\r
1560 endPath = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1562 var startBlockLimit = startPath.blockLimit,
\r
1563 endBlockLimit = endPath.blockLimit;
\r
1565 var startBlock = startPath.block,
\r
1566 endBlock = endPath.block;
\r
1568 var elementPath = null;
\r
1569 // Do nothing if the boundaries are in different block limits.
\r
1570 if ( !startBlockLimit.equals( endBlockLimit ) )
\r
1573 // Get or fix current blocks.
\r
1574 if ( blockTag != 'br' )
\r
1576 if ( !startBlock )
\r
1578 startBlock = this.fixBlock( true, blockTag );
\r
1579 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
\r
1583 endBlock = this.fixBlock( false, blockTag );
\r
1586 // Get the range position.
\r
1587 var isStartOfBlock = startBlock && this.checkStartOfBlock(),
\r
1588 isEndOfBlock = endBlock && this.checkEndOfBlock();
\r
1590 // Delete the current contents.
\r
1591 // TODO: Why is 2.x doing CheckIsEmpty()?
\r
1592 this.deleteContents();
\r
1594 if ( startBlock && startBlock.equals( endBlock ) )
\r
1596 if ( isEndOfBlock )
\r
1598 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1599 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
\r
1602 else if ( isStartOfBlock )
\r
1604 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1605 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
\r
1606 startBlock = null;
\r
1610 endBlock = this.splitElement( startBlock );
\r
1612 // In Gecko, the last child node must be a bogus <br>.
\r
1613 // Note: bogus <br> added under <ul> or <ol> would cause
\r
1614 // lists to be incorrectly rendered.
\r
1615 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
\r
1616 startBlock.appendBogus() ;
\r
1621 previousBlock : startBlock,
\r
1622 nextBlock : endBlock,
\r
1623 wasStartOfBlock : isStartOfBlock,
\r
1624 wasEndOfBlock : isEndOfBlock,
\r
1625 elementPath : elementPath
\r
1630 * Branch the specified element from the collapsed range position and
\r
1631 * place the caret between the two result branches.
\r
1632 * Note: The range must be collapsed and been enclosed by this element.
\r
1633 * @param {CKEDITOR.dom.element} element
\r
1634 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
\r
1636 splitElement : function( toSplit )
\r
1638 if ( !this.collapsed )
\r
1641 // Extract the contents of the block from the selection point to the end
\r
1642 // of its contents.
\r
1643 this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
\r
1644 var documentFragment = this.extractContents();
\r
1646 // Duplicate the element after it.
\r
1647 var clone = toSplit.clone( false );
\r
1649 // Place the extracted contents into the duplicated element.
\r
1650 documentFragment.appendTo( clone );
\r
1651 clone.insertAfter( toSplit );
\r
1652 this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
\r
1657 * Check whether a range boundary is at the inner boundary of a given
\r
1659 * @param {CKEDITOR.dom.element} element The target element to check.
\r
1660 * @param {Number} checkType The boundary to check for both the range
\r
1661 * and the element. It can be CKEDITOR.START or CKEDITOR.END.
\r
1662 * @returns {Boolean} "true" if the range boundary is at the inner
\r
1663 * boundary of the element.
\r
1665 checkBoundaryOfElement : function( element, checkType )
\r
1667 var checkStart = ( checkType == CKEDITOR.START );
\r
1669 // Create a copy of this range, so we can manipulate it for our checks.
\r
1670 var walkerRange = this.clone();
\r
1672 // Collapse the range at the proper size.
\r
1673 walkerRange.collapse( checkStart );
\r
1675 // Expand the range to element boundary.
\r
1676 walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]
\r
1677 ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
\r
1679 // Create the walker, which will check if we have anything useful
\r
1681 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1682 walker.evaluator = elementBoundaryEval;
\r
1684 return walker[ checkStart ? 'checkBackward' : 'checkForward' ]();
\r
1687 // Calls to this function may produce changes to the DOM. The range may
\r
1688 // be updated to reflect such changes.
\r
1689 checkStartOfBlock : function()
\r
1691 var startContainer = this.startContainer,
\r
1692 startOffset = this.startOffset;
\r
1694 // If the starting node is a text node, and non-empty before the offset,
\r
1695 // then we're surely not at the start of block.
\r
1696 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1698 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
\r
1699 if ( textBefore.length )
\r
1703 // Antecipate the trim() call here, so the walker will not make
\r
1704 // changes to the DOM, which would not get reflected into this
\r
1705 // range otherwise.
\r
1708 // We need to grab the block element holding the start boundary, so
\r
1709 // let's use an element path for it.
\r
1710 var path = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1712 // Creates a range starting at the block start until the range start.
\r
1713 var walkerRange = this.clone();
\r
1714 walkerRange.collapse( true );
\r
1715 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
\r
1717 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1718 walker.evaluator = getCheckStartEndBlockEvalFunction( true );
\r
1720 return walker.checkBackward();
\r
1723 checkEndOfBlock : function()
\r
1725 var endContainer = this.endContainer,
\r
1726 endOffset = this.endOffset;
\r
1728 // If the ending node is a text node, and non-empty after the offset,
\r
1729 // then we're surely not at the end of block.
\r
1730 if ( endContainer.type == CKEDITOR.NODE_TEXT )
\r
1732 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
\r
1733 if ( textAfter.length )
\r
1737 // Antecipate the trim() call here, so the walker will not make
\r
1738 // changes to the DOM, which would not get reflected into this
\r
1739 // range otherwise.
\r
1742 // We need to grab the block element holding the start boundary, so
\r
1743 // let's use an element path for it.
\r
1744 var path = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1746 // Creates a range starting at the block start until the range start.
\r
1747 var walkerRange = this.clone();
\r
1748 walkerRange.collapse( false );
\r
1749 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
\r
1751 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1752 walker.evaluator = getCheckStartEndBlockEvalFunction( false );
\r
1754 return walker.checkForward();
\r
1758 * Moves the range boundaries to the first/end editing point inside an
\r
1759 * element. For example, in an element tree like
\r
1760 * "<p><b><i></i></b> Text</p>", the start editing point is
\r
1761 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
\r
1762 * @param {CKEDITOR.dom.element} el The element into which look for the
\r
1764 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
\r
1766 moveToElementEditablePosition : function( el, isMoveToEnd )
\r
1770 // Empty elements are rejected.
\r
1771 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1774 while ( el && el.type == CKEDITOR.NODE_ELEMENT )
\r
1776 isEditable = el.isEditable();
\r
1778 // If an editable element is found, move inside it.
\r
1780 this.moveToPosition( el, isMoveToEnd ?
\r
1781 CKEDITOR.POSITION_BEFORE_END :
\r
1782 CKEDITOR.POSITION_AFTER_START );
\r
1783 // Stop immediately if we've found a non editable inline element (e.g <img>).
\r
1784 else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
\r
1786 this.moveToPosition( el, isMoveToEnd ?
\r
1787 CKEDITOR.POSITION_AFTER_END :
\r
1788 CKEDITOR.POSITION_BEFORE_START );
\r
1792 // Non-editable non-inline elements are to be bypassed, getting the next one.
\r
1793 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1794 el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
\r
1796 el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
\r
1798 // Stop immediately if we've found a text node.
\r
1799 if ( el && el.type == CKEDITOR.NODE_TEXT )
\r
1801 this.moveToPosition( el, isMoveToEnd ?
\r
1802 CKEDITOR.POSITION_AFTER_END :
\r
1803 CKEDITOR.POSITION_BEFORE_START );
\r
1808 return isEditable;
\r
1812 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
\r
1814 moveToElementEditStart : function( target )
\r
1816 return this.moveToElementEditablePosition( target );
\r
1820 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
\r
1822 moveToElementEditEnd : function( target )
\r
1824 return this.moveToElementEditablePosition( target, true );
\r
1828 * Get the single node enclosed within the range if there's one.
\r
1830 getEnclosedNode : function()
\r
1832 var walkerRange = this.clone();
\r
1834 // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)
\r
1835 walkerRange.optimize();
\r
1836 if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT
\r
1837 || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
\r
1840 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1841 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
\r
1842 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
\r
1843 evaluator = function( node )
\r
1845 return isNotWhitespaces( node ) && isNotBookmarks( node );
\r
1847 walkerRange.evaluator = evaluator;
\r
1848 var node = walker.next();
\r
1850 return node && node.equals( walker.previous() ) ? node : null;
\r
1853 getTouchedStartNode : function()
\r
1855 var container = this.startContainer ;
\r
1857 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1858 return container ;
\r
1860 return container.getChild( this.startOffset ) || container ;
\r
1863 getTouchedEndNode : function()
\r
1865 var container = this.endContainer ;
\r
1867 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1868 return container ;
\r
1870 return container.getChild( this.endOffset - 1 ) || container ;
\r
1875 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text"
\r
1876 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^"
\r
1877 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text"
\r
1878 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text"
\r
1880 CKEDITOR.ENLARGE_ELEMENT = 1;
\r
1881 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
\r
1882 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
\r
1884 // Check boundary types.
\r
1885 // @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement
\r
1886 CKEDITOR.START = 1;
\r
1888 CKEDITOR.STARTEND = 3;
\r
1890 // Shrink range types.
\r
1891 // @see CKEDITOR.dom.range.prototype.shrink
\r
1892 CKEDITOR.SHRINK_ELEMENT = 1;
\r
1893 CKEDITOR.SHRINK_TEXT = 2;
\r