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().hasAttribute( '_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
354 // The selection may be lost when cloning (due to the splitText() call).
\r
355 cloneContents : function()
\r
357 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
359 if ( !this.collapsed )
\r
360 execContentsAction( this, 2, docFrag );
\r
365 deleteContents : function()
\r
367 if ( this.collapsed )
\r
370 execContentsAction( this, 0 );
\r
373 extractContents : function()
\r
375 var docFrag = new CKEDITOR.dom.documentFragment( this.document );
\r
377 if ( !this.collapsed )
\r
378 execContentsAction( this, 1, docFrag );
\r
384 * Creates a bookmark object, which can be later used to restore the
\r
385 * range by using the moveToBookmark function.
\r
386 * This is an "intrusive" way to create a bookmark. It includes <span> tags
\r
387 * in the range boundaries. The advantage of it is that it is possible to
\r
388 * handle DOM mutations when moving back to the bookmark.
\r
389 * Attention: the inclusion of nodes in the DOM is a design choice and
\r
390 * should not be changed as there are other points in the code that may be
\r
391 * using those nodes to perform operations. See GetBookmarkNode.
\r
392 * @param {Boolean} [serializable] Indicates that the bookmark nodes
\r
393 * must contain ids, which can be used to restore the range even
\r
394 * when these nodes suffer mutations (like a clonation or innerHTML
\r
396 * @returns {Object} And object representing a bookmark.
\r
398 createBookmark : function( serializable )
\r
400 var startNode, endNode;
\r
403 var collapsed = this.collapsed;
\r
405 startNode = this.document.createElement( 'span' );
\r
406 startNode.setAttribute( '_cke_bookmark', 1 );
\r
407 startNode.setStyle( 'display', 'none' );
\r
409 // For IE, it must have something inside, otherwise it may be
\r
410 // removed during DOM operations.
\r
411 startNode.setHtml( ' ' );
\r
413 if ( serializable )
\r
415 baseId = 'cke_bm_' + CKEDITOR.tools.getNextNumber();
\r
416 startNode.setAttribute( 'id', baseId + 'S' );
\r
419 // If collapsed, the endNode will not be created.
\r
422 endNode = startNode.clone();
\r
423 endNode.setHtml( ' ' );
\r
425 if ( serializable )
\r
426 endNode.setAttribute( 'id', baseId + 'E' );
\r
428 clone = this.clone();
\r
430 clone.insertNode( endNode );
\r
433 clone = this.clone();
\r
434 clone.collapse( true );
\r
435 clone.insertNode( startNode );
\r
437 // Update the range position.
\r
440 this.setStartAfter( startNode );
\r
441 this.setEndBefore( endNode );
\r
444 this.moveToPosition( startNode, CKEDITOR.POSITION_AFTER_END );
\r
447 startNode : serializable ? baseId + 'S' : startNode,
\r
448 endNode : serializable ? baseId + 'E' : endNode,
\r
449 serializable : serializable,
\r
450 collapsed : collapsed
\r
455 * Creates a "non intrusive" and "mutation sensible" bookmark. This
\r
456 * kind of bookmark should be used only when the DOM is supposed to
\r
457 * remain stable after its creation.
\r
458 * @param {Boolean} [normalized] Indicates that the bookmark must
\r
459 * normalized. When normalized, the successive text nodes are
\r
460 * considered a single node. To sucessful load a normalized
\r
461 * bookmark, the DOM tree must be also normalized before calling
\r
463 * @returns {Object} An object representing the bookmark.
\r
465 createBookmark2 : function( normalized )
\r
467 var startContainer = this.startContainer,
\r
468 endContainer = this.endContainer;
\r
470 var startOffset = this.startOffset,
\r
471 endOffset = this.endOffset;
\r
473 var collapsed = this.collapsed;
\r
475 var child, previous;
\r
477 // If there is no range then get out of here.
\r
478 // It happens on initial load in Safari #962 and if the editor it's
\r
479 // hidden also in Firefox
\r
480 if ( !startContainer || !endContainer )
\r
481 return { start : 0, end : 0 };
\r
485 // Find out if the start is pointing to a text node that will
\r
487 if ( startContainer.type == CKEDITOR.NODE_ELEMENT )
\r
489 child = startContainer.getChild( startOffset );
\r
491 // In this case, move the start information to that text
\r
493 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
494 && startOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
496 startContainer = child;
\r
501 // Normalize the start.
\r
502 while ( startContainer.type == CKEDITOR.NODE_TEXT
\r
503 && ( previous = startContainer.getPrevious() )
\r
504 && previous.type == CKEDITOR.NODE_TEXT )
\r
506 startContainer = previous;
\r
507 startOffset += previous.getLength();
\r
510 // Process the end only if not normalized.
\r
513 // Find out if the start is pointing to a text node that
\r
514 // will be normalized.
\r
515 if ( endContainer.type == CKEDITOR.NODE_ELEMENT )
\r
517 child = endContainer.getChild( endOffset );
\r
519 // In this case, move the start information to that
\r
521 if ( child && child.type == CKEDITOR.NODE_TEXT
\r
522 && endOffset > 0 && child.getPrevious().type == CKEDITOR.NODE_TEXT )
\r
524 endContainer = child;
\r
529 // Normalize the end.
\r
530 while ( endContainer.type == CKEDITOR.NODE_TEXT
\r
531 && ( previous = endContainer.getPrevious() )
\r
532 && previous.type == CKEDITOR.NODE_TEXT )
\r
534 endContainer = previous;
\r
535 endOffset += previous.getLength();
\r
541 start : startContainer.getAddress( normalized ),
\r
542 end : collapsed ? null : endContainer.getAddress( normalized ),
\r
543 startOffset : startOffset,
\r
544 endOffset : endOffset,
\r
545 normalized : normalized,
\r
546 collapsed : collapsed,
\r
547 is2 : true // It's a createBookmark2 bookmark.
\r
551 moveToBookmark : function( bookmark )
\r
553 if ( bookmark.is2 ) // Created with createBookmark2().
\r
555 // Get the start information.
\r
556 var startContainer = this.document.getByAddress( bookmark.start, bookmark.normalized ),
\r
557 startOffset = bookmark.startOffset;
\r
559 // Get the end information.
\r
560 var endContainer = bookmark.end && this.document.getByAddress( bookmark.end, bookmark.normalized ),
\r
561 endOffset = bookmark.endOffset;
\r
563 // Set the start boundary.
\r
564 this.setStart( startContainer, startOffset );
\r
566 // Set the end boundary. If not available, collapse it.
\r
567 if ( endContainer )
\r
568 this.setEnd( endContainer, endOffset );
\r
570 this.collapse( true );
\r
572 else // Created with createBookmark().
\r
574 var serializable = bookmark.serializable,
\r
575 startNode = serializable ? this.document.getById( bookmark.startNode ) : bookmark.startNode,
\r
576 endNode = serializable ? this.document.getById( bookmark.endNode ) : bookmark.endNode;
\r
578 // Set the range start at the bookmark start node position.
\r
579 this.setStartBefore( startNode );
\r
581 // Remove it, because it may interfere in the setEndBefore call.
\r
582 startNode.remove();
\r
584 // Set the range end at the bookmark end node position, or simply
\r
585 // collapse it if it is not available.
\r
588 this.setEndBefore( endNode );
\r
592 this.collapse( true );
\r
596 getBoundaryNodes : function()
\r
598 var startNode = this.startContainer,
\r
599 endNode = this.endContainer,
\r
600 startOffset = this.startOffset,
\r
601 endOffset = this.endOffset,
\r
604 if ( startNode.type == CKEDITOR.NODE_ELEMENT )
\r
606 childCount = startNode.getChildCount();
\r
607 if ( childCount > startOffset )
\r
608 startNode = startNode.getChild( startOffset );
\r
609 else if ( childCount < 1 )
\r
610 startNode = startNode.getPreviousSourceNode();
\r
611 else // startOffset > childCount but childCount is not 0
\r
613 // Try to take the node just after the current position.
\r
614 startNode = startNode.$;
\r
615 while ( startNode.lastChild )
\r
616 startNode = startNode.lastChild;
\r
617 startNode = new CKEDITOR.dom.node( startNode );
\r
619 // Normally we should take the next node in DFS order. But it
\r
620 // is also possible that we've already reached the end of
\r
622 startNode = startNode.getNextSourceNode() || startNode;
\r
625 if ( endNode.type == CKEDITOR.NODE_ELEMENT )
\r
627 childCount = endNode.getChildCount();
\r
628 if ( childCount > endOffset )
\r
629 endNode = endNode.getChild( endOffset ).getPreviousSourceNode( true );
\r
630 else if ( childCount < 1 )
\r
631 endNode = endNode.getPreviousSourceNode();
\r
632 else // endOffset > childCount but childCount is not 0
\r
634 // Try to take the node just before the current position.
\r
635 endNode = endNode.$;
\r
636 while ( endNode.lastChild )
\r
637 endNode = endNode.lastChild;
\r
638 endNode = new CKEDITOR.dom.node( endNode );
\r
642 // Sometimes the endNode will come right before startNode for collapsed
\r
643 // ranges. Fix it. (#3780)
\r
644 if ( startNode.getPosition( endNode ) & CKEDITOR.POSITION_FOLLOWING )
\r
645 startNode = endNode;
\r
647 return { startNode : startNode, endNode : endNode };
\r
651 * Find the node which fully contains the range.
\r
652 * @param includeSelf
\r
653 * @param {Boolean} ignoreTextNode Whether ignore CKEDITOR.NODE_TEXT type.
\r
655 getCommonAncestor : function( includeSelf , ignoreTextNode )
\r
657 var start = this.startContainer,
\r
658 end = this.endContainer,
\r
661 if ( start.equals( end ) )
\r
664 && start.type == CKEDITOR.NODE_ELEMENT
\r
665 && this.startOffset == this.endOffset - 1 )
\r
666 ancestor = start.getChild( this.startOffset );
\r
671 ancestor = start.getCommonAncestor( end );
\r
673 return ignoreTextNode && !ancestor.is ? ancestor.getParent() : ancestor;
\r
677 * Transforms the startContainer and endContainer properties from text
\r
678 * nodes to element nodes, whenever possible. This is actually possible
\r
679 * if either of the boundary containers point to a text node, and its
\r
680 * offset is set to zero, or after the last char in the node.
\r
682 optimize : function()
\r
684 var container = this.startContainer;
\r
685 var offset = this.startOffset;
\r
687 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
690 this.setStartBefore( container );
\r
691 else if ( offset >= container.getLength() )
\r
692 this.setStartAfter( container );
\r
695 container = this.endContainer;
\r
696 offset = this.endOffset;
\r
698 if ( container.type != CKEDITOR.NODE_ELEMENT )
\r
701 this.setEndBefore( container );
\r
702 else if ( offset >= container.getLength() )
\r
703 this.setEndAfter( container );
\r
708 * Move the range out of bookmark nodes if they'd been the container.
\r
710 optimizeBookmark: function()
\r
712 var startNode = this.startContainer,
\r
713 endNode = this.endContainer;
\r
715 if ( startNode.is && startNode.is( 'span' )
\r
716 && startNode.hasAttribute( '_cke_bookmark' ) )
\r
717 this.setStartAt( startNode, CKEDITOR.POSITION_BEFORE_START );
\r
718 if ( endNode && endNode.is && endNode.is( 'span' )
\r
719 && endNode.hasAttribute( '_cke_bookmark' ) )
\r
720 this.setEndAt( endNode, CKEDITOR.POSITION_AFTER_END );
\r
723 trim : function( ignoreStart, ignoreEnd )
\r
725 var startContainer = this.startContainer,
\r
726 startOffset = this.startOffset,
\r
727 collapsed = this.collapsed;
\r
728 if ( ( !ignoreStart || collapsed )
\r
729 && startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
731 // If the offset is zero, we just insert the new node before
\r
733 if ( !startOffset )
\r
735 startOffset = startContainer.getIndex();
\r
736 startContainer = startContainer.getParent();
\r
738 // If the offset is at the end, we'll insert it after the text
\r
740 else if ( startOffset >= startContainer.getLength() )
\r
742 startOffset = startContainer.getIndex() + 1;
\r
743 startContainer = startContainer.getParent();
\r
745 // In other case, we split the text node and insert the new
\r
746 // node at the split point.
\r
749 var nextText = startContainer.split( startOffset );
\r
751 startOffset = startContainer.getIndex() + 1;
\r
752 startContainer = startContainer.getParent();
\r
754 // Check all necessity of updating the end boundary.
\r
755 if ( this.startContainer.equals( this.endContainer ) )
\r
756 this.setEnd( nextText, this.endOffset - this.startOffset );
\r
757 else if ( startContainer.equals( this.endContainer ) )
\r
758 this.endOffset += 1;
\r
761 this.setStart( startContainer, startOffset );
\r
765 this.collapse( true );
\r
770 var endContainer = this.endContainer;
\r
771 var endOffset = this.endOffset;
\r
773 if ( !( ignoreEnd || collapsed )
\r
774 && endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
776 // If the offset is zero, we just insert the new node before
\r
780 endOffset = endContainer.getIndex();
\r
781 endContainer = endContainer.getParent();
\r
783 // If the offset is at the end, we'll insert it after the text
\r
785 else if ( endOffset >= endContainer.getLength() )
\r
787 endOffset = endContainer.getIndex() + 1;
\r
788 endContainer = endContainer.getParent();
\r
790 // In other case, we split the text node and insert the new
\r
791 // node at the split point.
\r
794 endContainer.split( endOffset );
\r
796 endOffset = endContainer.getIndex() + 1;
\r
797 endContainer = endContainer.getParent();
\r
800 this.setEnd( endContainer, endOffset );
\r
804 enlarge : function( unit )
\r
808 case CKEDITOR.ENLARGE_ELEMENT :
\r
810 if ( this.collapsed )
\r
813 // Get the common ancestor.
\r
814 var commonAncestor = this.getCommonAncestor();
\r
816 var body = this.document.getBody();
\r
818 // For each boundary
\r
819 // a. Depending on its position, find out the first node to be checked (a sibling) or, if not available, to be enlarge.
\r
820 // 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
822 var startTop, endTop;
\r
824 var enlargeable, sibling, commonReached;
\r
826 // Indicates that the node can be added only if whitespace
\r
827 // is available before it.
\r
828 var needsWhiteSpace = false;
\r
832 // Process the start boundary.
\r
834 var container = this.startContainer;
\r
835 var offset = this.startOffset;
\r
837 if ( container.type == CKEDITOR.NODE_TEXT )
\r
841 // Check if there is any non-space text before the
\r
842 // offset. Otherwise, container is null.
\r
843 container = !CKEDITOR.tools.trim( container.substring( 0, offset ) ).length && container;
\r
845 // If we found only whitespace in the node, it
\r
846 // means that we'll need more whitespace to be able
\r
847 // to expand. For example, <i> can be expanded in
\r
848 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
849 needsWhiteSpace = !!container;
\r
854 if ( !( sibling = container.getPrevious() ) )
\r
855 enlargeable = container.getParent();
\r
860 // If we have offset, get the node preceeding it as the
\r
861 // first sibling to be checked.
\r
863 sibling = container.getChild( offset - 1 ) || container.getLast();
\r
865 // If there is no sibling, mark the container to be
\r
868 enlargeable = container;
\r
871 while ( enlargeable || sibling )
\r
873 if ( enlargeable && !sibling )
\r
875 // If we reached the common ancestor, mark the flag
\r
877 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
878 commonReached = true;
\r
880 if ( !body.contains( enlargeable ) )
\r
883 // If we don't need space or this element breaks
\r
884 // the line, then enlarge it.
\r
885 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
887 needsWhiteSpace = false;
\r
889 // If the common ancestor has been reached,
\r
890 // we'll not enlarge it immediately, but just
\r
891 // mark it to be enlarged later if the end
\r
892 // boundary also enlarges it.
\r
893 if ( commonReached )
\r
894 startTop = enlargeable;
\r
896 this.setStartBefore( enlargeable );
\r
899 sibling = enlargeable.getPrevious();
\r
902 // Check all sibling nodes preceeding the enlargeable
\r
903 // node. The node wil lbe enlarged only if none of them
\r
907 // This flag indicates that this node has
\r
908 // whitespaces at the end.
\r
909 isWhiteSpace = false;
\r
911 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
913 siblingText = sibling.getText();
\r
915 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
918 isWhiteSpace = /[\s\ufeff]$/.test( siblingText );
\r
922 // If this is a visible element.
\r
923 // We need to check for the bookmark attribute because IE insists on
\r
924 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
925 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_cke_bookmark' ) )
\r
927 // We'll accept it only if we need
\r
928 // whitespace, and this is an inline
\r
929 // element with whitespace only.
\r
930 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
932 // It must contains spaces and inline elements only.
\r
934 siblingText = sibling.getText();
\r
936 if ( (/[^\s\ufeff]/).test( siblingText ) ) // Spaces + Zero Width No-Break Space (U+FEFF)
\r
940 var allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
941 for ( var i = 0, child ; child = allChildren[ i++ ] ; )
\r
943 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
952 isWhiteSpace = !!siblingText.length;
\r
959 // A node with whitespaces has been found.
\r
960 if ( isWhiteSpace )
\r
962 // Enlarge the last enlargeable node, if we
\r
963 // were waiting for spaces.
\r
964 if ( needsWhiteSpace )
\r
966 if ( commonReached )
\r
967 startTop = enlargeable;
\r
968 else if ( enlargeable )
\r
969 this.setStartBefore( enlargeable );
\r
972 needsWhiteSpace = true;
\r
977 var next = sibling.getPrevious();
\r
979 if ( !enlargeable && !next )
\r
981 // Set the sibling as enlargeable, so it's
\r
982 // parent will be get later outside this while.
\r
983 enlargeable = sibling;
\r
992 // If sibling has been set to null, then we
\r
993 // need to stop enlarging.
\r
994 enlargeable = null;
\r
999 enlargeable = enlargeable.getParent();
\r
1002 // Process the end boundary. This is basically the same
\r
1003 // code used for the start boundary, with small changes to
\r
1004 // make it work in the oposite side (to the right). This
\r
1005 // makes it difficult to reuse the code here. So, fixes to
\r
1006 // the above code are likely to be replicated here.
\r
1008 container = this.endContainer;
\r
1009 offset = this.endOffset;
\r
1011 // Reset the common variables.
\r
1012 enlargeable = sibling = null;
\r
1013 commonReached = needsWhiteSpace = false;
\r
1015 if ( container.type == CKEDITOR.NODE_TEXT )
\r
1017 // Check if there is any non-space text after the
\r
1018 // offset. Otherwise, container is null.
\r
1019 container = !CKEDITOR.tools.trim( container.substring( offset ) ).length && container;
\r
1021 // If we found only whitespace in the node, it
\r
1022 // means that we'll need more whitespace to be able
\r
1023 // to expand. For example, <i> can be expanded in
\r
1024 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
\r
1025 needsWhiteSpace = !( container && container.getLength() );
\r
1029 if ( !( sibling = container.getNext() ) )
\r
1030 enlargeable = container.getParent();
\r
1035 // Get the node right after the boudary to be checked
\r
1037 sibling = container.getChild( offset );
\r
1040 enlargeable = container;
\r
1043 while ( enlargeable || sibling )
\r
1045 if ( enlargeable && !sibling )
\r
1047 if ( !commonReached && enlargeable.equals( commonAncestor ) )
\r
1048 commonReached = true;
\r
1050 if ( !body.contains( enlargeable ) )
\r
1053 if ( !needsWhiteSpace || enlargeable.getComputedStyle( 'display' ) != 'inline' )
\r
1055 needsWhiteSpace = false;
\r
1057 if ( commonReached )
\r
1058 endTop = enlargeable;
\r
1059 else if ( enlargeable )
\r
1060 this.setEndAfter( enlargeable );
\r
1063 sibling = enlargeable.getNext();
\r
1068 isWhiteSpace = false;
\r
1070 if ( sibling.type == CKEDITOR.NODE_TEXT )
\r
1072 siblingText = sibling.getText();
\r
1074 if ( /[^\s\ufeff]/.test( siblingText ) )
\r
1077 isWhiteSpace = /^[\s\ufeff]/.test( siblingText );
\r
1081 // If this is a visible element.
\r
1082 // We need to check for the bookmark attribute because IE insists on
\r
1083 // rendering the display:none nodes we use for bookmarks. (#3363)
\r
1084 if ( sibling.$.offsetWidth > 0 && !sibling.getAttribute( '_cke_bookmark' ) )
\r
1086 // We'll accept it only if we need
\r
1087 // whitespace, and this is an inline
\r
1088 // element with whitespace only.
\r
1089 if ( needsWhiteSpace && CKEDITOR.dtd.$removeEmpty[ sibling.getName() ] )
\r
1091 // It must contains spaces and inline elements only.
\r
1093 siblingText = sibling.getText();
\r
1095 if ( (/[^\s\ufeff]/).test( siblingText ) )
\r
1099 allChildren = sibling.$.all || sibling.$.getElementsByTagName( '*' );
\r
1100 for ( i = 0 ; child = allChildren[ i++ ] ; )
\r
1102 if ( !CKEDITOR.dtd.$removeEmpty[ child.nodeName.toLowerCase() ] )
\r
1111 isWhiteSpace = !!siblingText.length;
\r
1118 if ( isWhiteSpace )
\r
1120 if ( needsWhiteSpace )
\r
1122 if ( commonReached )
\r
1123 endTop = enlargeable;
\r
1125 this.setEndAfter( enlargeable );
\r
1131 next = sibling.getNext();
\r
1133 if ( !enlargeable && !next )
\r
1135 enlargeable = sibling;
\r
1144 // If sibling has been set to null, then we
\r
1145 // need to stop enlarging.
\r
1146 enlargeable = null;
\r
1150 if ( enlargeable )
\r
1151 enlargeable = enlargeable.getParent();
\r
1154 // If the common ancestor can be enlarged by both boundaries, then include it also.
\r
1155 if ( startTop && endTop )
\r
1157 commonAncestor = startTop.contains( endTop ) ? endTop : startTop;
\r
1159 this.setStartBefore( commonAncestor );
\r
1160 this.setEndAfter( commonAncestor );
\r
1164 case CKEDITOR.ENLARGE_BLOCK_CONTENTS:
\r
1165 case CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS:
\r
1167 // Enlarging the start boundary.
\r
1168 var walkerRange = new CKEDITOR.dom.range( this.document );
\r
1170 body = this.document.getBody();
\r
1172 walkerRange.setStartAt( body, CKEDITOR.POSITION_AFTER_START );
\r
1173 walkerRange.setEnd( this.startContainer, this.startOffset );
\r
1175 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1176 blockBoundary, // The node on which the enlarging should stop.
\r
1177 tailBr, // In case BR as block boundary.
\r
1178 notBlockBoundary = CKEDITOR.dom.walker.blockBoundary(
\r
1179 ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ? { br : 1 } : null ),
\r
1180 // Record the encountered 'blockBoundary' for later use.
\r
1181 boundaryGuard = function( node )
\r
1183 var retval = notBlockBoundary( node );
\r
1185 blockBoundary = node;
\r
1188 // Record the encounted 'tailBr' for later use.
\r
1189 tailBrGuard = function( node )
\r
1191 var retval = boundaryGuard( node );
\r
1192 if ( !retval && node.is && node.is( 'br' ) )
\r
1197 walker.guard = boundaryGuard;
\r
1199 enlargeable = walker.lastBackward();
\r
1201 // It's the body which stop the enlarging if no block boundary found.
\r
1202 blockBoundary = blockBoundary || body;
\r
1204 // Start the range either after the end of found block (<p>...</p>[text)
\r
1205 // or at the start of block (<p>[text...), by comparing the document position
\r
1206 // with 'enlargeable' node.
\r
1209 !blockBoundary.is( 'br' ) &&
\r
1210 ( !enlargeable && this.checkStartOfBlock()
\r
1211 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1212 CKEDITOR.POSITION_AFTER_START :
\r
1213 CKEDITOR.POSITION_AFTER_END );
\r
1215 // Enlarging the end boundary.
\r
1216 walkerRange = this.clone();
\r
1217 walkerRange.collapse();
\r
1218 walkerRange.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );
\r
1219 walker = new CKEDITOR.dom.walker( walkerRange );
\r
1221 // tailBrGuard only used for on range end.
\r
1222 walker.guard = ( unit == CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ) ?
\r
1223 tailBrGuard : boundaryGuard;
\r
1224 blockBoundary = null;
\r
1225 // End the range right before the block boundary node.
\r
1227 enlargeable = walker.lastForward();
\r
1229 // It's the body which stop the enlarging if no block boundary found.
\r
1230 blockBoundary = blockBoundary || body;
\r
1232 // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)
\r
1233 // by comparing the document position with 'enlargeable' node.
\r
1236 ( !enlargeable && this.checkEndOfBlock()
\r
1237 || enlargeable && blockBoundary.contains( enlargeable ) ) ?
\r
1238 CKEDITOR.POSITION_BEFORE_END :
\r
1239 CKEDITOR.POSITION_BEFORE_START );
\r
1240 // We must include the <br> at the end of range if there's
\r
1241 // one and we're expanding list item contents
\r
1243 this.setEndAfter( tailBr );
\r
1248 * Descrease the range to make sure that boundaries
\r
1249 * always anchor beside text nodes or innermost element.
\r
1250 * @param {Number} mode ( CKEDITOR.SHRINK_ELEMENT | CKEDITOR.SHRINK_TEXT ) The shrinking mode.
\r
1252 * <dt>CKEDITOR.SHRINK_ELEMENT</dt>
\r
1253 * <dd>Shrink the range boundaries to the edge of the innermost element.</dd>
\r
1254 * <dt>CKEDITOR.SHRINK_TEXT</dt>
\r
1255 * <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
1257 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
\r
1259 shrink : function( mode, selectContents )
\r
1261 // Unable to shrink a collapsed range.
\r
1262 if ( !this.collapsed )
\r
1264 mode = mode || CKEDITOR.SHRINK_TEXT;
\r
1266 var walkerRange = this.clone();
\r
1268 var startContainer = this.startContainer,
\r
1269 endContainer = this.endContainer,
\r
1270 startOffset = this.startOffset,
\r
1271 endOffset = this.endOffset,
\r
1272 collapsed = this.collapsed;
\r
1274 // Whether the start/end boundary is moveable.
\r
1275 var moveStart = 1,
\r
1278 if ( startContainer && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1280 if ( !startOffset )
\r
1281 walkerRange.setStartBefore( startContainer );
\r
1282 else if ( startOffset >= startContainer.getLength( ) )
\r
1283 walkerRange.setStartAfter( startContainer );
\r
1286 // Enlarge the range properly to avoid walker making
\r
1287 // DOM changes caused by triming the text nodes later.
\r
1288 walkerRange.setStartBefore( startContainer );
\r
1293 if ( endContainer && endContainer.type == CKEDITOR.NODE_TEXT )
\r
1296 walkerRange.setEndBefore( endContainer );
\r
1297 else if ( endOffset >= endContainer.getLength( ) )
\r
1298 walkerRange.setEndAfter( endContainer );
\r
1301 walkerRange.setEndAfter( endContainer );
\r
1306 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1307 isBookmark = CKEDITOR.dom.walker.bookmark();
\r
1309 walker.evaluator = function( node )
\r
1311 return node.type == ( mode == CKEDITOR.SHRINK_ELEMENT ?
\r
1312 CKEDITOR.NODE_ELEMENT : CKEDITOR.NODE_TEXT );
\r
1315 var currentElement;
\r
1316 walker.guard = function( node, movingOut )
\r
1318 if ( isBookmark( node ) )
\r
1321 // Stop when we're shrink in element mode while encountering a text node.
\r
1322 if ( mode == CKEDITOR.SHRINK_ELEMENT && node.type == CKEDITOR.NODE_TEXT )
\r
1325 // Stop when we've already walked "through" an element.
\r
1326 if ( movingOut && node.equals( currentElement ) )
\r
1329 if ( !movingOut && node.type == CKEDITOR.NODE_ELEMENT )
\r
1330 currentElement = node;
\r
1337 var textStart = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastForward' : 'next']();
\r
1338 textStart && this.setStartAt( textStart, selectContents ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_START );
\r
1344 var textEnd = walker[ mode == CKEDITOR.SHRINK_ELEMENT ? 'lastBackward' : 'previous']();
\r
1345 textEnd && this.setEndAt( textEnd, selectContents ? CKEDITOR.POSITION_BEFORE_END : CKEDITOR.POSITION_AFTER_END );
\r
1348 return !!( moveStart || moveEnd );
\r
1353 * Inserts a node at the start of the range. The range will be expanded
\r
1354 * the contain the node.
\r
1356 insertNode : function( node )
\r
1358 this.optimizeBookmark();
\r
1359 this.trim( false, true );
\r
1361 var startContainer = this.startContainer;
\r
1362 var startOffset = this.startOffset;
\r
1364 var nextNode = startContainer.getChild( startOffset );
\r
1367 node.insertBefore( nextNode );
\r
1369 startContainer.append( node );
\r
1371 // Check if we need to update the end boundary.
\r
1372 if ( node.getParent().equals( this.endContainer ) )
\r
1375 // Expand the range to embrace the new node.
\r
1376 this.setStartBefore( node );
\r
1379 moveToPosition : function( node, position )
\r
1381 this.setStartAt( node, position );
\r
1382 this.collapse( true );
\r
1385 selectNodeContents : function( node )
\r
1387 this.setStart( node, 0 );
\r
1388 this.setEnd( node, node.type == CKEDITOR.NODE_TEXT ? node.getLength() : node.getChildCount() );
\r
1392 * Sets the start position of a Range.
\r
1393 * @param {CKEDITOR.dom.node} startNode The node to start the range.
\r
1394 * @param {Number} startOffset An integer greater than or equal to zero
\r
1395 * representing the offset for the start of the range from the start
\r
1398 setStart : function( startNode, startOffset )
\r
1400 // W3C requires a check for the new position. If it is after the end
\r
1401 // boundary, the range should be collapsed to the new start. It seams
\r
1402 // we will not need this check for our use of this class so we can
\r
1403 // ignore it for now.
\r
1405 // Fixing invalid range start inside dtd empty elements.
\r
1406 if( startNode.type == CKEDITOR.NODE_ELEMENT
\r
1407 && CKEDITOR.dtd.$empty[ startNode.getName() ] )
\r
1408 startNode = startNode.getParent(), startOffset = startNode.getIndex();
\r
1410 this.startContainer = startNode;
\r
1411 this.startOffset = startOffset;
\r
1413 if ( !this.endContainer )
\r
1415 this.endContainer = startNode;
\r
1416 this.endOffset = startOffset;
\r
1419 updateCollapsed( this );
\r
1423 * Sets the end position of a Range.
\r
1424 * @param {CKEDITOR.dom.node} endNode The node to end the range.
\r
1425 * @param {Number} endOffset An integer greater than or equal to zero
\r
1426 * representing the offset for the end of the range from the start
\r
1429 setEnd : function( endNode, endOffset )
\r
1431 // W3C requires a check for the new position. If it is before the start
\r
1432 // boundary, the range should be collapsed to the new end. It seams we
\r
1433 // will not need this check for our use of this class so we can ignore
\r
1436 // Fixing invalid range end inside dtd empty elements.
\r
1437 if( endNode.type == CKEDITOR.NODE_ELEMENT
\r
1438 && CKEDITOR.dtd.$empty[ endNode.getName() ] )
\r
1439 endOffset = endNode.getIndex() + 1, endNode = endNode.getParent();
\r
1441 this.endContainer = endNode;
\r
1442 this.endOffset = endOffset;
\r
1444 if ( !this.startContainer )
\r
1446 this.startContainer = endNode;
\r
1447 this.startOffset = endOffset;
\r
1450 updateCollapsed( this );
\r
1453 setStartAfter : function( node )
\r
1455 this.setStart( node.getParent(), node.getIndex() + 1 );
\r
1458 setStartBefore : function( node )
\r
1460 this.setStart( node.getParent(), node.getIndex() );
\r
1463 setEndAfter : function( node )
\r
1465 this.setEnd( node.getParent(), node.getIndex() + 1 );
\r
1468 setEndBefore : function( node )
\r
1470 this.setEnd( node.getParent(), node.getIndex() );
\r
1473 setStartAt : function( node, position )
\r
1475 switch( position )
\r
1477 case CKEDITOR.POSITION_AFTER_START :
\r
1478 this.setStart( node, 0 );
\r
1481 case CKEDITOR.POSITION_BEFORE_END :
\r
1482 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1483 this.setStart( node, node.getLength() );
\r
1485 this.setStart( node, node.getChildCount() );
\r
1488 case CKEDITOR.POSITION_BEFORE_START :
\r
1489 this.setStartBefore( node );
\r
1492 case CKEDITOR.POSITION_AFTER_END :
\r
1493 this.setStartAfter( node );
\r
1496 updateCollapsed( this );
\r
1499 setEndAt : function( node, position )
\r
1501 switch( position )
\r
1503 case CKEDITOR.POSITION_AFTER_START :
\r
1504 this.setEnd( node, 0 );
\r
1507 case CKEDITOR.POSITION_BEFORE_END :
\r
1508 if ( node.type == CKEDITOR.NODE_TEXT )
\r
1509 this.setEnd( node, node.getLength() );
\r
1511 this.setEnd( node, node.getChildCount() );
\r
1514 case CKEDITOR.POSITION_BEFORE_START :
\r
1515 this.setEndBefore( node );
\r
1518 case CKEDITOR.POSITION_AFTER_END :
\r
1519 this.setEndAfter( node );
\r
1522 updateCollapsed( this );
\r
1525 fixBlock : function( isStart, blockTag )
\r
1527 var bookmark = this.createBookmark(),
\r
1528 fixedBlock = this.document.createElement( blockTag );
\r
1530 this.collapse( isStart );
\r
1532 this.enlarge( CKEDITOR.ENLARGE_BLOCK_CONTENTS );
\r
1534 this.extractContents().appendTo( fixedBlock );
\r
1535 fixedBlock.trim();
\r
1537 if ( !CKEDITOR.env.ie )
\r
1538 fixedBlock.appendBogus();
\r
1540 this.insertNode( fixedBlock );
\r
1542 this.moveToBookmark( bookmark );
\r
1544 return fixedBlock;
\r
1547 splitBlock : function( blockTag )
\r
1549 var startPath = new CKEDITOR.dom.elementPath( this.startContainer ),
\r
1550 endPath = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1552 var startBlockLimit = startPath.blockLimit,
\r
1553 endBlockLimit = endPath.blockLimit;
\r
1555 var startBlock = startPath.block,
\r
1556 endBlock = endPath.block;
\r
1558 var elementPath = null;
\r
1559 // Do nothing if the boundaries are in different block limits.
\r
1560 if ( !startBlockLimit.equals( endBlockLimit ) )
\r
1563 // Get or fix current blocks.
\r
1564 if ( blockTag != 'br' )
\r
1566 if ( !startBlock )
\r
1568 startBlock = this.fixBlock( true, blockTag );
\r
1569 endBlock = new CKEDITOR.dom.elementPath( this.endContainer ).block;
\r
1573 endBlock = this.fixBlock( false, blockTag );
\r
1576 // Get the range position.
\r
1577 var isStartOfBlock = startBlock && this.checkStartOfBlock(),
\r
1578 isEndOfBlock = endBlock && this.checkEndOfBlock();
\r
1580 // Delete the current contents.
\r
1581 // TODO: Why is 2.x doing CheckIsEmpty()?
\r
1582 this.deleteContents();
\r
1584 if ( startBlock && startBlock.equals( endBlock ) )
\r
1586 if ( isEndOfBlock )
\r
1588 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1589 this.moveToPosition( endBlock, CKEDITOR.POSITION_AFTER_END );
\r
1592 else if ( isStartOfBlock )
\r
1594 elementPath = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1595 this.moveToPosition( startBlock, CKEDITOR.POSITION_BEFORE_START );
\r
1596 startBlock = null;
\r
1600 endBlock = this.splitElement( startBlock );
\r
1602 // In Gecko, the last child node must be a bogus <br>.
\r
1603 // Note: bogus <br> added under <ul> or <ol> would cause
\r
1604 // lists to be incorrectly rendered.
\r
1605 if ( !CKEDITOR.env.ie && !startBlock.is( 'ul', 'ol') )
\r
1606 startBlock.appendBogus() ;
\r
1611 previousBlock : startBlock,
\r
1612 nextBlock : endBlock,
\r
1613 wasStartOfBlock : isStartOfBlock,
\r
1614 wasEndOfBlock : isEndOfBlock,
\r
1615 elementPath : elementPath
\r
1620 * Branch the specified element from the collapsed range position and
\r
1621 * place the caret between the two result branches.
\r
1622 * Note: The range must be collapsed and been enclosed by this element.
\r
1623 * @param {CKEDITOR.dom.element} element
\r
1624 * @return {CKEDITOR.dom.element} Root element of the new branch after the split.
\r
1626 splitElement : function( toSplit )
\r
1628 if ( !this.collapsed )
\r
1631 // Extract the contents of the block from the selection point to the end
\r
1632 // of its contents.
\r
1633 this.setEndAt( toSplit, CKEDITOR.POSITION_BEFORE_END );
\r
1634 var documentFragment = this.extractContents();
\r
1636 // Duplicate the element after it.
\r
1637 var clone = toSplit.clone( false );
\r
1639 // Place the extracted contents into the duplicated element.
\r
1640 documentFragment.appendTo( clone );
\r
1641 clone.insertAfter( toSplit );
\r
1642 this.moveToPosition( toSplit, CKEDITOR.POSITION_AFTER_END );
\r
1647 * Check whether a range boundary is at the inner boundary of a given
\r
1649 * @param {CKEDITOR.dom.element} element The target element to check.
\r
1650 * @param {Number} checkType The boundary to check for both the range
\r
1651 * and the element. It can be CKEDITOR.START or CKEDITOR.END.
\r
1652 * @returns {Boolean} "true" if the range boundary is at the inner
\r
1653 * boundary of the element.
\r
1655 checkBoundaryOfElement : function( element, checkType )
\r
1657 var checkStart = ( checkType == CKEDITOR.START );
\r
1659 // Create a copy of this range, so we can manipulate it for our checks.
\r
1660 var walkerRange = this.clone();
\r
1662 // Collapse the range at the proper size.
\r
1663 walkerRange.collapse( checkStart );
\r
1665 // Expand the range to element boundary.
\r
1666 walkerRange[ checkStart ? 'setStartAt' : 'setEndAt' ]
\r
1667 ( element, checkStart ? CKEDITOR.POSITION_AFTER_START : CKEDITOR.POSITION_BEFORE_END );
\r
1669 // Create the walker, which will check if we have anything useful
\r
1671 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1672 walker.evaluator = elementBoundaryEval;
\r
1674 return walker[ checkStart ? 'checkBackward' : 'checkForward' ]();
\r
1677 // Calls to this function may produce changes to the DOM. The range may
\r
1678 // be updated to reflect such changes.
\r
1679 checkStartOfBlock : function()
\r
1681 var startContainer = this.startContainer,
\r
1682 startOffset = this.startOffset;
\r
1684 // If the starting node is a text node, and non-empty before the offset,
\r
1685 // then we're surely not at the start of block.
\r
1686 if ( startOffset && startContainer.type == CKEDITOR.NODE_TEXT )
\r
1688 var textBefore = CKEDITOR.tools.ltrim( startContainer.substring( 0, startOffset ) );
\r
1689 if ( textBefore.length )
\r
1693 // Antecipate the trim() call here, so the walker will not make
\r
1694 // changes to the DOM, which would not get reflected into this
\r
1695 // range otherwise.
\r
1698 // We need to grab the block element holding the start boundary, so
\r
1699 // let's use an element path for it.
\r
1700 var path = new CKEDITOR.dom.elementPath( this.startContainer );
\r
1702 // Creates a range starting at the block start until the range start.
\r
1703 var walkerRange = this.clone();
\r
1704 walkerRange.collapse( true );
\r
1705 walkerRange.setStartAt( path.block || path.blockLimit, CKEDITOR.POSITION_AFTER_START );
\r
1707 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1708 walker.evaluator = getCheckStartEndBlockEvalFunction( true );
\r
1710 return walker.checkBackward();
\r
1713 checkEndOfBlock : function()
\r
1715 var endContainer = this.endContainer,
\r
1716 endOffset = this.endOffset;
\r
1718 // If the ending node is a text node, and non-empty after the offset,
\r
1719 // then we're surely not at the end of block.
\r
1720 if ( endContainer.type == CKEDITOR.NODE_TEXT )
\r
1722 var textAfter = CKEDITOR.tools.rtrim( endContainer.substring( endOffset ) );
\r
1723 if ( textAfter.length )
\r
1727 // Antecipate the trim() call here, so the walker will not make
\r
1728 // changes to the DOM, which would not get reflected into this
\r
1729 // range otherwise.
\r
1732 // We need to grab the block element holding the start boundary, so
\r
1733 // let's use an element path for it.
\r
1734 var path = new CKEDITOR.dom.elementPath( this.endContainer );
\r
1736 // Creates a range starting at the block start until the range start.
\r
1737 var walkerRange = this.clone();
\r
1738 walkerRange.collapse( false );
\r
1739 walkerRange.setEndAt( path.block || path.blockLimit, CKEDITOR.POSITION_BEFORE_END );
\r
1741 var walker = new CKEDITOR.dom.walker( walkerRange );
\r
1742 walker.evaluator = getCheckStartEndBlockEvalFunction( false );
\r
1744 return walker.checkForward();
\r
1748 * Moves the range boundaries to the first/end editing point inside an
\r
1749 * element. For example, in an element tree like
\r
1750 * "<p><b><i></i></b> Text</p>", the start editing point is
\r
1751 * "<p><b><i>^</i></b> Text</p>" (inside <i>).
\r
1752 * @param {CKEDITOR.dom.element} el The element into which look for the
\r
1754 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
\r
1756 moveToElementEditablePosition : function( el, isMoveToEnd )
\r
1760 // Empty elements are rejected.
\r
1761 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1764 while ( el && el.type == CKEDITOR.NODE_ELEMENT )
\r
1766 isEditable = el.isEditable();
\r
1768 // If an editable element is found, move inside it.
\r
1770 this.moveToPosition( el, isMoveToEnd ?
\r
1771 CKEDITOR.POSITION_BEFORE_END :
\r
1772 CKEDITOR.POSITION_AFTER_START );
\r
1773 // Stop immediately if we've found a non editable inline element (e.g <img>).
\r
1774 else if ( CKEDITOR.dtd.$inline[ el.getName() ] )
\r
1776 this.moveToPosition( el, isMoveToEnd ?
\r
1777 CKEDITOR.POSITION_AFTER_END :
\r
1778 CKEDITOR.POSITION_BEFORE_START );
\r
1782 // Non-editable non-inline elements are to be bypassed, getting the next one.
\r
1783 if ( CKEDITOR.dtd.$empty[ el.getName() ] )
\r
1784 el = el[ isMoveToEnd ? 'getPrevious' : 'getNext' ]( nonWhitespaceOrBookmarkEval );
\r
1786 el = el[ isMoveToEnd ? 'getLast' : 'getFirst' ]( nonWhitespaceOrBookmarkEval );
\r
1788 // Stop immediately if we've found a text node.
\r
1789 if ( el && el.type == CKEDITOR.NODE_TEXT )
\r
1791 this.moveToPosition( el, isMoveToEnd ?
\r
1792 CKEDITOR.POSITION_AFTER_END :
\r
1793 CKEDITOR.POSITION_BEFORE_START );
\r
1798 return isEditable;
\r
1802 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
\r
1804 moveToElementEditStart : function( target )
\r
1806 return this.moveToElementEditablePosition( target );
\r
1810 *@see {CKEDITOR.dom.range.moveToElementEditablePosition}
\r
1812 moveToElementEditEnd : function( target )
\r
1814 return this.moveToElementEditablePosition( target, true );
\r
1818 * Get the single node enclosed within the range if there's one.
\r
1820 getEnclosedNode : function()
\r
1822 var walkerRange = this.clone();
\r
1824 // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)
\r
1825 walkerRange.optimize();
\r
1826 if ( walkerRange.startContainer.type != CKEDITOR.NODE_ELEMENT
\r
1827 || walkerRange.endContainer.type != CKEDITOR.NODE_ELEMENT )
\r
1830 var walker = new CKEDITOR.dom.walker( walkerRange ),
\r
1831 isNotBookmarks = CKEDITOR.dom.walker.bookmark( true ),
\r
1832 isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),
\r
1833 evaluator = function( node )
\r
1835 return isNotWhitespaces( node ) && isNotBookmarks( node );
\r
1837 walkerRange.evaluator = evaluator;
\r
1838 var node = walker.next();
\r
1840 return node && node.equals( walker.previous() ) ? node : null;
\r
1843 getTouchedStartNode : function()
\r
1845 var container = this.startContainer ;
\r
1847 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1848 return container ;
\r
1850 return container.getChild( this.startOffset ) || container ;
\r
1853 getTouchedEndNode : function()
\r
1855 var container = this.endContainer ;
\r
1857 if ( this.collapsed || container.type != CKEDITOR.NODE_ELEMENT )
\r
1858 return container ;
\r
1860 return container.getChild( this.endOffset - 1 ) || container ;
\r
1865 CKEDITOR.POSITION_AFTER_START = 1; // <element>^contents</element> "^text"
\r
1866 CKEDITOR.POSITION_BEFORE_END = 2; // <element>contents^</element> "text^"
\r
1867 CKEDITOR.POSITION_BEFORE_START = 3; // ^<element>contents</element> ^"text"
\r
1868 CKEDITOR.POSITION_AFTER_END = 4; // <element>contents</element>^ "text"
\r
1870 CKEDITOR.ENLARGE_ELEMENT = 1;
\r
1871 CKEDITOR.ENLARGE_BLOCK_CONTENTS = 2;
\r
1872 CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS = 3;
\r
1874 // Check boundary types.
\r
1875 // @see CKEDITOR.dom.range.prototype.checkBoundaryOfElement
\r
1876 CKEDITOR.START = 1;
\r
1878 CKEDITOR.STARTEND = 3;
\r
1880 // Shrink range types.
\r
1881 // @see CKEDITOR.dom.range.prototype.shrink
\r
1882 CKEDITOR.SHRINK_ELEMENT = 1;
\r
1883 CKEDITOR.SHRINK_TEXT = 2;
\r