2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
7 * Represents a delimited piece of content in a DOM Document.
8 * It is contiguous in the sense that it can be characterized as selecting all
9 * of the content between a pair of boundary-points.
11 * This class shares much of the W3C
12 * [Document Object Model Range](http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html)
13 * ideas and features, adding several range manipulation tools to it, but it's
14 * not intended to be compatible with it.
16 * // Create a range for the entire contents of the editor document body.
17 * var range = new CKEDITOR.dom.range( editor.document );
18 * range.selectNodeContents( editor.document.getBody() );
19 * // Delete the contents.
20 * range.deleteContents();
22 * Usually you will want to work on a ranges rooted in the editor's {@link CKEDITOR.editable editable}
23 * element. Such ranges can be created with a shorthand method – {@link CKEDITOR.editor#createRange editor.createRange}.
25 * var range = editor.createRange();
26 * range.root.equals( editor.editable() ); // -> true
28 * Note that the {@link #root} of a range is an important property, which limits many
29 * algorithms implemented in range's methods. Therefore it is crucial, especially
30 * when using ranges inside inline editors, to specify correct root, so using
31 * the {@link CKEDITOR.editor#createRange} method is highly recommended.
35 * Range is only a logical representation of a piece of content in a DOM. It should not
36 * be confused with a {@link CKEDITOR.dom.selection selection} which represents "physically
37 * marked" content. It is possible to create unlimited number of various ranges, when
38 * only one real selection may exist at a time in a document. Ranges are used to read position
39 * of selection in the DOM and to move selection to new positions.
41 * The editor selection may be retrieved using the {@link CKEDITOR.editor#getSelection} method:
43 * var sel = editor.getSelection(),
44 * ranges = sel.getRanges(); // CKEDITOR.dom.rangeList instance.
46 * var range = ranges[ 0 ];
47 * range.root; // -> editor's editable element.
49 * A range can also be selected:
51 * var range = editor.createRange();
52 * range.selectNodeContents( editor.editable() );
53 * sel.selectRanges( [ range ] );
56 * @constructor Creates a {@link CKEDITOR.dom.range} instance that can be used inside a specific DOM Document.
57 * @param {CKEDITOR.dom.document/CKEDITOR.dom.element} root The document or element
58 * within which the range will be scoped.
59 * @todo global "TODO" - precise algorithms descriptions needed for the most complex methods like #enlarge.
61 CKEDITOR
.dom
.range = function( root
) {
63 * Node within which the range begins.
65 * var range = new CKEDITOR.dom.range( editor.document );
66 * range.selectNodeContents( editor.document.getBody() );
67 * alert( range.startContainer.getName() ); // 'body'
70 * @property {CKEDITOR.dom.element/CKEDITOR.dom.text}
72 this.startContainer
= null;
75 * Offset within the starting node of the range.
77 * var range = new CKEDITOR.dom.range( editor.document );
78 * range.selectNodeContents( editor.document.getBody() );
79 * alert( range.startOffset ); // 0
84 this.startOffset
= null;
87 * Node within which the range ends.
89 * var range = new CKEDITOR.dom.range( editor.document );
90 * range.selectNodeContents( editor.document.getBody() );
91 * alert( range.endContainer.getName() ); // 'body'
94 * @property {CKEDITOR.dom.element/CKEDITOR.dom.text}
96 this.endContainer
= null;
99 * Offset within the ending node of the range.
101 * var range = new CKEDITOR.dom.range( editor.document );
102 * range.selectNodeContents( editor.document.getBody() );
103 * alert( range.endOffset ); // == editor.document.getBody().getChildCount()
108 this.endOffset
= null;
111 * Indicates that this is a collapsed range. A collapsed range has its
112 * start and end boundaries at the very same point so nothing is contained
115 * var range = new CKEDITOR.dom.range( editor.document );
116 * range.selectNodeContents( editor.document.getBody() );
117 * alert( range.collapsed ); // false
119 * alert( range.collapsed ); // true
123 this.collapsed
= true;
125 var isDocRoot
= root
instanceof CKEDITOR
.dom
.document
;
127 * The document within which the range can be used.
129 * // Selects the body contents of the range document.
130 * range.selectNodeContents( range.document.getBody() );
133 * @property {CKEDITOR.dom.document}
135 this.document
= isDocRoot
? root : root
.getDocument();
138 * The ancestor DOM element within which the range manipulation are limited.
141 * @property {CKEDITOR.dom.element}
143 this.root
= isDocRoot
? root
.getBody() : root
;
147 // Updates the "collapsed" property for the given range object.
148 function updateCollapsed( range
) {
149 range
.collapsed
= ( range
.startContainer
&& range
.endContainer
&& range
.startContainer
.equals( range
.endContainer
) && range
.startOffset
== range
.endOffset
);
152 // This is a shared function used to delete, extract and clone the range content.
154 // The outline of the algorithm:
156 // 1. Normalization. We handle special cases, split text nodes if we can, find boundary nodes (startNode and endNode).
157 // 2. Gathering data.
158 // * We start by creating two arrays of boundary nodes parents. You can imagine these arrays as lines limiting
159 // the tree from the left and right and thus marking the part which is selected by the range. The both lines
160 // start in the same node which is the range.root and end in startNode and endNode.
161 // * Then we find min level and max levels. Level represents all nodes which are equally far from the range.root.
162 // Min level is the level at which the left and right boundaries diverged (the first diverged level). And max levels
163 // are how deep the start and end nodes are nested.
164 // 3. Cloning/extraction.
165 // * We start iterating over start node parents (left branch) from min level and clone the parent (usually shallow clone,
166 // because we know that it's not fully selected) and its right siblings (deep clone, because they are fully selected).
167 // We iterate over siblings up to meeting end node parent or end of the siblings chain.
168 // * We clone level after level down to the startNode.
169 // * Then we do the same with end node parents (right branch), because it may contains notes we omit during the previous
170 // step, for example if the right branch is deeper then left branch. Things are more complicated here because we have to
171 // watch out for nodes that were already cloned.
172 // * ***Note:** Setting `cloneId` option to `false` for **extraction** works for partially selected elements only.
173 // See range.extractContents to know more.
175 // * There are two things we need to do - updating the range position and perform the action of the "mergeThen"
176 // param (see range.deleteContents or range.extractContents).
177 // See comments in mergeAndUpdate because this is lots of fun too.
178 function execContentsAction( range
, action
, docFrag
, mergeThen
, cloneId
) {
181 range
.optimizeBookmark();
183 var isDelete
= action
=== 0;
184 var isExtract
= action
== 1;
185 var isClone
= action
== 2;
186 var doClone
= isClone
|| isExtract
;
188 var startNode
= range
.startContainer
;
189 var endNode
= range
.endContainer
;
191 var startOffset
= range
.startOffset
;
192 var endOffset
= range
.endOffset
;
197 var doNotRemoveStartNode
;
198 var doNotRemoveEndNode
;
203 // Handle here an edge case where we clone a range which is located in one text node.
204 // This allows us to not think about startNode == endNode case later on.
205 // We do that only when cloning, because in other cases we can safely split this text node
206 // and hence we can easily handle this case as many others.
207 if ( isClone
&& endNode
.type
== CKEDITOR
.NODE_TEXT
&& startNode
.equals( endNode
) ) {
208 startNode
= range
.document
.createText( startNode
.substring( startOffset
, endOffset
) );
209 docFrag
.append( startNode
);
213 // For text containers, we must simply split the node and point to the
214 // second part. The removal will be handled by the rest of the code.
215 if ( endNode
.type
== CKEDITOR
.NODE_TEXT
) {
216 // If Extract or Delete we can split the text node,
217 // but if Clone (2), then we cannot modify the DOM (#11586) so we mark the text node for cloning.
219 endNode
= endNode
.split( endOffset
);
224 // If there's no node after the range boundary we set endNode to the previous node
225 // and mark it to be cloned.
226 if ( endNode
.getChildCount() > 0 ) {
227 // If the offset points after the last node.
228 if ( endOffset
>= endNode
.getChildCount() ) {
229 endNode
= endNode
.getChild( endOffset
- 1 );
232 endNode
= endNode
.getChild( endOffset
);
235 // The end container is empty (<h1>]</h1>), but we want to clone it, although not remove.
238 doNotRemoveEndNode
= true;
242 // For text containers, we must simply split the node. The removal will
243 // be handled by the rest of the code .
244 if ( startNode
.type
== CKEDITOR
.NODE_TEXT
) {
245 // If Extract or Delete we can split the text node,
246 // but if Clone (2), then we cannot modify the DOM (#11586) so we mark
247 // the text node for cloning.
249 startNode
.split( startOffset
);
251 cloneStartText
= true;
254 // If there's no node before the range boundary we set startNode to the next node
255 // and mark it to be cloned.
256 if ( startNode
.getChildCount() > 0 ) {
257 if ( startOffset
=== 0 ) {
258 startNode
= startNode
.getChild( startOffset
);
259 cloneStartNode
= true;
261 startNode
= startNode
.getChild( startOffset
- 1 );
264 // The start container is empty (<h1>[</h1>), but we want to clone it, although not remove.
266 cloneStartNode
= true;
267 doNotRemoveStartNode
= true;
271 // Get the parent nodes tree for the start and end boundaries.
272 var startParents
= startNode
.getParents(),
273 endParents
= endNode
.getParents(),
274 // Level at which start and end boundaries diverged.
275 minLevel
= findMinLevel(),
276 maxLevelLeft
= startParents
.length
- 1,
277 maxLevelRight
= endParents
.length
- 1,
278 // Keeps the frag/element which is parent of the level that we are currently cloning.
279 levelParent
= docFrag
,
284 // Keeps track of the last connected level (on which left and right branches are connected)
285 // Usually this is minLevel, but not always.
286 lastConnectedLevel
= -1;
289 for ( var level
= minLevel
; level
<= maxLevelLeft
; level
++ ) {
290 leftNode
= startParents
[ level
];
291 nextSibling
= leftNode
.getNext();
294 // The first step is to handle partial selection of the left branch.
296 // Max depth of the left branch. It means that ( leftSibling == endNode ).
297 // We also check if the leftNode isn't only partially selected, because in this case
298 // we want to make a shallow clone of it (the else part).
299 if ( level
== maxLevelLeft
&& !( leftNode
.equals( endParents
[ level
] ) && maxLevelLeft
< maxLevelRight
) ) {
300 if ( cloneStartNode
) {
301 consume( leftNode
, levelParent
, false, doNotRemoveStartNode
);
302 } else if ( cloneStartText
) {
303 levelParent
.append( range
.document
.createText( leftNode
.substring( startOffset
) ) );
305 } else if ( doClone
) {
306 nextLevelParent
= levelParent
.append( leftNode
.clone( 0, cloneId
) );
310 // The second step is to handle full selection of the content between the left branch and the right branch.
312 while ( nextSibling
) {
313 // We can't clone entire endParent just like we can't clone entire startParent -
314 // - they are not fully selected with the range. Partial endParent selection
315 // will be cloned in the next loop.
316 if ( nextSibling
.equals( endParents
[ level
] ) ) {
317 lastConnectedLevel
= level
;
321 nextSibling
= consume( nextSibling
, levelParent
);
324 levelParent
= nextLevelParent
;
327 // Reset levelParent, because we reset the level.
328 levelParent
= docFrag
;
331 for ( level
= minLevel
; level
<= maxLevelRight
; level
++ ) {
332 rightNode
= endParents
[ level
];
333 nextSibling
= rightNode
.getPrevious();
335 // Do not process this node if it is shared with the left branch
336 // because it was already processed.
338 // Note: Don't worry about text nodes selection - if the entire range was placed in a single text node
339 // it was handled as a special case at the beginning. In other cases when startNode == endNode
340 // or when on this level leftNode == rightNode (so rightNode.equals( startParents[ level ] ))
341 // this node was handled by the previous loop.
342 if ( !rightNode
.equals( startParents
[ level
] ) ) {
344 // The first step is to handle partial selection of the right branch.
346 // Max depth of the right branch. It means that ( rightNode == endNode ).
347 // We also check if the rightNode isn't only partially selected, because in this case
348 // we want to make a shallow clone of it (the else part).
349 if ( level
== maxLevelRight
&& !( rightNode
.equals( startParents
[ level
] ) && maxLevelRight
< maxLevelLeft
) ) {
350 if ( cloneEndNode
) {
351 consume( rightNode
, levelParent
, false, doNotRemoveEndNode
);
352 } else if ( cloneEndText
) {
353 levelParent
.append( range
.document
.createText( rightNode
.substring( 0, endOffset
) ) );
355 } else if ( doClone
) {
356 nextLevelParent
= levelParent
.append( rightNode
.clone( 0, cloneId
) );
360 // The second step is to handle all left (selected) siblings of the rightNode which
361 // have not yet been handled. If the level branches were connected, the previous loop
362 // already copied all siblings (except the current rightNode).
363 if ( level
> lastConnectedLevel
) {
364 while ( nextSibling
) {
365 nextSibling
= consume( nextSibling
, levelParent
, true );
369 levelParent
= nextLevelParent
;
370 } else if ( doClone
) {
371 // If this is "shared" node and we are in cloning mode we have to update levelParent to
372 // reflect that we visited the node (even though we didn't process it).
373 // If we don't do that, in next iterations nodes will be appended to wrong parent.
375 // We can just take first child because the algorithm guarantees
376 // that this will be the only child on this level. (#13568)
377 levelParent
= levelParent
.getChild( 0 );
381 // Delete or Extract.
382 // We need to update the range and if mergeThen was passed do it.
387 // Depending on an action:
388 // * clones node and adds to new parent,
390 // * moves node to the new parent.
391 function consume( node
, newParent
, toStart
, forceClone
) {
392 var nextSibling
= toStart
? node
.getPrevious() : node
.getNext();
394 // We do not clone if we are only deleting, so do nothing.
395 if ( forceClone
&& isDelete
) {
399 // If cloning, just clone it.
400 if ( isClone
|| forceClone
) {
401 newParent
.append( node
.clone( true, cloneId
), toStart
);
403 // Both Delete and Extract will remove the node.
406 // When Extracting, move the removed node to the docFrag.
408 newParent
.append( node
);
415 // Finds a level number on which both branches starts diverging.
416 // If such level does not exist, return the last on which both branches have nodes.
417 function findMinLevel() {
418 // Compare them, to find the top most siblings.
419 var i
, topStart
, topEnd
,
420 maxLevel
= Math
.min( startParents
.length
, endParents
.length
);
422 for ( i
= 0; i
< maxLevel
; i
++ ) {
423 topStart
= startParents
[ i
];
424 topEnd
= endParents
[ i
];
426 // The compared nodes will match until we find the top most siblings (different nodes that have the same parent).
427 // "i" will hold the index in the parents array for the top most element.
428 if ( !topStart
.equals( topEnd
) ) {
433 // When startNode == endNode.
437 // Executed only when deleting or extracting to update range position
438 // and perform the merge operation.
439 function mergeAndUpdate() {
440 var commonLevel
= minLevel
- 1,
441 boundariesInEmptyNode
= doNotRemoveStartNode
&& doNotRemoveEndNode
&& !startNode
.equals( endNode
);
443 // If a node has been partially selected, collapse the range between
444 // startParents[ minLevel + 1 ] and endParents[ minLevel + 1 ] (the first diverged elements).
445 // Otherwise, simply collapse it to the start. (W3C specs).
449 // It took me few hours to truly understand a previous version of this condition.
450 // Mine seems to be more straightforward (even if it doesn't look so) and I could leave you here
451 // without additional comments, but I'm not that mean so here goes the explanation.
453 // We want to know if both ends of the range are anchored in the same element. Really. It's this simple.
454 // But why? Because we need to differentiate situations like:
456 // <p>foo[<b>x</b>bar]y</p> (commonLevel = p, maxLL = "foo", maxLR = "y")
458 // <p>foo<b>x[</b>bar]y</p> (commonLevel = p, maxLL = "x", maxLR = "y")
460 // In the first case we can collapse the range to the left, because simply everything between range's
461 // boundaries was removed.
462 // In the second case we must place the range after </b>, because <b> was only **partially selected**.
464 // * <b> is our startParents[ commonLevel + 1 ]
465 // * "y" is our endParents[ commonLevel + 1 ].
467 // By now "bar" is removed from the DOM so <b> is a direct sibling of "y":
468 // <p>foo<b>x</b>y</p>
470 // Therefore it's enough to place the range between <b> and "y".
472 // Now, what does the comparison mean? Why not just taking startNode and endNode and checking
473 // their parents? Because the tree is already changed and they may be gone. Plus, thanks to
474 // cloneStartNode and cloneEndNode, that would be reaaaaly tricky.
476 // So we play with levels which can give us the same information:
477 // * commonLevel - the level of common ancestor,
478 // * maxLevel - 1 - the level of range boundary parent (range boundary is here like a bookmark span).
479 // * commonLevel < maxLevel - 1 - whether the range boundary is not a child of common ancestor.
481 // There's also an edge case in which both range boundaries were placed in empty nodes like:
483 // Those boundaries were not removed, but in this case start and end nodes are child of the common ancestor.
484 // We handle this edge case separately.
485 if ( commonLevel
< ( maxLevelLeft
- 1 ) || commonLevel
< ( maxLevelRight
- 1 ) || boundariesInEmptyNode
) {
486 if ( boundariesInEmptyNode
) {
487 range
.moveToPosition( endNode
, CKEDITOR
.POSITION_BEFORE_START
);
488 } else if ( ( maxLevelRight
== commonLevel
+ 1 ) && cloneEndNode
) {
489 // The maxLevelRight + 1 element could be already removed so we use the fact that
490 // we know that it was the last element in its parent.
491 range
.moveToPosition( endParents
[ commonLevel
], CKEDITOR
.POSITION_BEFORE_END
);
493 range
.moveToPosition( endParents
[ commonLevel
+ 1 ], CKEDITOR
.POSITION_BEFORE_START
);
496 // Merge split parents.
498 // Find the first diverged node in the left branch.
499 var topLeft
= startParents
[ commonLevel
+ 1 ];
501 // TopLeft may simply not exist if commonLevel == maxLevel or may be a text node.
502 if ( topLeft
&& topLeft
.type
== CKEDITOR
.NODE_ELEMENT
) {
503 var span
= CKEDITOR
.dom
.element
.createFromHtml( '<span ' +
504 'data-cke-bookmark="1" style="display:none"> </span>', range
.document
);
505 span
.insertAfter( topLeft
);
506 topLeft
.mergeSiblings( false );
507 range
.moveToBookmark( { startNode: span
} );
511 // Collapse it to the start.
512 range
.collapse( true );
517 var inlineChildReqElements
= {
518 abbr: 1, acronym: 1, b: 1, bdo: 1, big: 1, cite: 1, code: 1, del: 1,
519 dfn: 1, em: 1, font: 1, i: 1, ins: 1, label: 1, kbd: 1, q: 1, samp: 1, small: 1, span: 1, strike: 1,
520 strong: 1, sub: 1, sup: 1, tt: 1, u: 1, 'var': 1
523 // Creates the appropriate node evaluator for the dom walker used inside
524 // check(Start|End)OfBlock.
525 function getCheckStartEndBlockEvalFunction() {
526 var skipBogus
= false,
527 whitespaces
= CKEDITOR
.dom
.walker
.whitespaces(),
528 bookmarkEvaluator
= CKEDITOR
.dom
.walker
.bookmark( true ),
529 isBogus
= CKEDITOR
.dom
.walker
.bogus();
531 return function( node
) {
532 // First skip empty nodes
533 if ( bookmarkEvaluator( node
) || whitespaces( node
) )
536 // Skip the bogus node at the end of block.
537 if ( isBogus( node
) && !skipBogus
) {
542 // If there's any visible text, then we're not at the start.
543 if ( node
.type
== CKEDITOR
.NODE_TEXT
&&
544 ( node
.hasAscendant( 'pre' ) ||
545 CKEDITOR
.tools
.trim( node
.getText() ).length
) ) {
549 // If there are non-empty inline elements (e.g. <img />), then we're not
551 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
&& !node
.is( inlineChildReqElements
) )
558 var isBogus
= CKEDITOR
.dom
.walker
.bogus(),
559 nbspRegExp
= /^[\t\r\n ]*(?: 
;|\xa0)$/,
560 editableEval
= CKEDITOR
.dom
.walker
.editable(),
561 notIgnoredEval
= CKEDITOR
.dom
.walker
.ignored( true );
563 // Evaluator for CKEDITOR.dom.element::checkBoundaryOfElement, reject any
564 // text node and non-empty elements unless it's being bookmark text.
565 function elementBoundaryEval( checkStart
) {
566 var whitespaces
= CKEDITOR
.dom
.walker
.whitespaces(),
567 bookmark
= CKEDITOR
.dom
.walker
.bookmark( 1 );
569 return function( node
) {
570 // First skip empty nodes.
571 if ( bookmark( node
) || whitespaces( node
) )
574 // Tolerant bogus br when checking at the end of block.
575 // Reject any text node unless it's being bookmark
577 // Reject any element unless it's being invisible empty. (#3883)
578 return !checkStart
&& isBogus( node
) ||
579 node
.type
== CKEDITOR
.NODE_ELEMENT
&&
580 node
.is( CKEDITOR
.dtd
.$removeEmpty
);
584 function getNextEditableNode( isPrevious
) {
588 return this[ isPrevious
? 'getPreviousNode' : 'getNextNode' ]( function( node
) {
589 // Cache first not ignorable node.
590 if ( !first
&& notIgnoredEval( node
) )
593 // Return true if found editable node, but not a bogus next to start of our lookup (first != bogus).
594 return editableEval( node
) && !( isBogus( node
) && node
.equals( first
) );
599 CKEDITOR
.dom
.range
.prototype = {
603 * @returns {CKEDITOR.dom.range}
606 var clone
= new CKEDITOR
.dom
.range( this.root
);
608 clone
._setStartContainer( this.startContainer
);
609 clone
.startOffset
= this.startOffset
;
610 clone
._setEndContainer( this.endContainer
);
611 clone
.endOffset
= this.endOffset
;
612 clone
.collapsed
= this.collapsed
;
618 * Makes the range collapsed by moving its start point (or end point if `toStart==true`)
621 * @param {Boolean} toStart Collapse range "to start".
623 collapse: function( toStart
) {
625 this._setEndContainer( this.startContainer
);
626 this.endOffset
= this.startOffset
;
628 this._setStartContainer( this.endContainer
);
629 this.startOffset
= this.endOffset
;
632 this.collapsed
= true;
636 * Clones content nodes of the range and adds them to a document fragment, which is returned.
638 * @param {Boolean} [cloneId=true] Whether to preserve ID attributes in the clone.
639 * @returns {CKEDITOR.dom.documentFragment} Document fragment containing a clone of range's content.
641 cloneContents: function( cloneId
) {
642 var docFrag
= new CKEDITOR
.dom
.documentFragment( this.document
);
644 cloneId
= typeof cloneId
== 'undefined' ? true : cloneId
;
646 if ( !this.collapsed
)
647 execContentsAction( this, 2, docFrag
, false, cloneId
);
653 * Deletes the content nodes of the range permanently from the DOM tree.
655 * @param {Boolean} [mergeThen] Merge any split elements result in DOM true due to partial selection.
657 deleteContents: function( mergeThen
) {
658 if ( this.collapsed
)
661 execContentsAction( this, 0, null, mergeThen
);
665 * The content nodes of the range are cloned and added to a document fragment,
666 * meanwhile they are removed permanently from the DOM tree.
668 * **Note:** Setting the `cloneId` parameter to `false` works for **partially** selected elements only.
669 * If an element with an ID attribute is **fully enclosed** in a range, it will keep the ID attribute
670 * regardless of the `cloneId` parameter value, because it is not cloned — it is moved to the returned
673 * @param {Boolean} [mergeThen] Merge any split elements result in DOM true due to partial selection.
674 * @param {Boolean} [cloneId=true] Whether to preserve ID attributes in the extracted content.
675 * @returns {CKEDITOR.dom.documentFragment} Document fragment containing extracted content.
677 extractContents: function( mergeThen
, cloneId
) {
678 var docFrag
= new CKEDITOR
.dom
.documentFragment( this.document
);
680 cloneId
= typeof cloneId
== 'undefined' ? true : cloneId
;
682 if ( !this.collapsed
)
683 execContentsAction( this, 1, docFrag
, mergeThen
, cloneId
);
689 * Creates a bookmark object, which can be later used to restore the
690 * range by using the {@link #moveToBookmark} function.
692 * This is an "intrusive" way to create a bookmark. It includes `<span>` tags
693 * in the range boundaries. The advantage of it is that it is possible to
694 * handle DOM mutations when moving back to the bookmark.
696 * **Note:** The inclusion of nodes in the DOM is a design choice and
697 * should not be changed as there are other points in the code that may be
698 * using those nodes to perform operations.
700 * @param {Boolean} [serializable] Indicates that the bookmark nodes
701 * must contain IDs, which can be used to restore the range even
702 * when these nodes suffer mutations (like cloning or `innerHTML` change).
703 * @returns {Object} And object representing a bookmark.
704 * @returns {CKEDITOR.dom.node/String} return.startNode Node or element ID.
705 * @returns {CKEDITOR.dom.node/String} return.endNode Node or element ID.
706 * @returns {Boolean} return.serializable
707 * @returns {Boolean} return.collapsed
709 createBookmark: function( serializable
) {
710 var startNode
, endNode
;
713 var collapsed
= this.collapsed
;
715 startNode
= this.document
.createElement( 'span' );
716 startNode
.data( 'cke-bookmark', 1 );
717 startNode
.setStyle( 'display', 'none' );
719 // For IE, it must have something inside, otherwise it may be
720 // removed during DOM operations.
721 startNode
.setHtml( ' ' );
723 if ( serializable
) {
724 baseId
= 'cke_bm_' + CKEDITOR
.tools
.getNextNumber();
725 startNode
.setAttribute( 'id', baseId
+ ( collapsed
? 'C' : 'S' ) );
728 // If collapsed, the endNode will not be created.
730 endNode
= startNode
.clone();
731 endNode
.setHtml( ' ' );
734 endNode
.setAttribute( 'id', baseId
+ 'E' );
736 clone
= this.clone();
738 clone
.insertNode( endNode
);
741 clone
= this.clone();
742 clone
.collapse( true );
743 clone
.insertNode( startNode
);
745 // Update the range position.
747 this.setStartAfter( startNode
);
748 this.setEndBefore( endNode
);
750 this.moveToPosition( startNode
, CKEDITOR
.POSITION_AFTER_END
);
754 startNode: serializable
? baseId
+ ( collapsed
? 'C' : 'S' ) : startNode
,
755 endNode: serializable
? baseId
+ 'E' : endNode
,
756 serializable: serializable
,
762 * Creates a "non intrusive" and "mutation sensible" bookmark. This
763 * kind of bookmark should be used only when the DOM is supposed to
764 * remain stable after its creation.
766 * @param {Boolean} [normalized] Indicates that the bookmark must
767 * be normalized. When normalized, the successive text nodes are
768 * considered a single node. To successfully load a normalized
769 * bookmark, the DOM tree must also be normalized before calling
770 * {@link #moveToBookmark}.
771 * @returns {Object} An object representing the bookmark.
772 * @returns {Array} return.start Start container's address (see {@link CKEDITOR.dom.node#getAddress}).
773 * @returns {Array} return.end Start container's address.
774 * @returns {Number} return.startOffset
775 * @returns {Number} return.endOffset
776 * @returns {Boolean} return.collapsed
777 * @returns {Boolean} return.normalized
778 * @returns {Boolean} return.is2 This is "bookmark2".
780 createBookmark2: ( function() {
781 var isNotText
= CKEDITOR
.dom
.walker
.nodeType( CKEDITOR
.NODE_TEXT
, true );
783 // Returns true for limit anchored in element and placed between text nodes.
786 // <p>[text node] [text node]</p> -> true
789 // <p> [text node]</p> -> false
792 // <p>[text node][text node]</p> -> false (limit is anchored in text node)
793 function betweenTextNodes( container
, offset
) {
794 // Not anchored in element or limit is on the edge.
795 if ( container
.type
!= CKEDITOR
.NODE_ELEMENT
|| offset
=== 0 || offset
== container
.getChildCount() )
798 return container
.getChild( offset
- 1 ).type
== CKEDITOR
.NODE_TEXT
&&
799 container
.getChild( offset
).type
== CKEDITOR
.NODE_TEXT
;
802 // Sums lengths of all preceding text nodes.
803 function getLengthOfPrecedingTextNodes( node
) {
806 while ( ( node
= node
.getPrevious() ) && node
.type
== CKEDITOR
.NODE_TEXT
)
807 sum
+= node
.getText().replace( CKEDITOR
.dom
.selection
.FILLING_CHAR_SEQUENCE
, '' ).length
;
812 function normalizeTextNodes( limit
) {
813 var container
= limit
.container
,
814 offset
= limit
.offset
;
816 // If limit is between text nodes move it to the end of preceding one,
817 // because they will be merged.
818 if ( betweenTextNodes( container
, offset
) ) {
819 container
= container
.getChild( offset
- 1 );
820 offset
= container
.getLength();
823 // Now, if limit is anchored in element and has at least one node before it,
824 // it may happen that some of them will be merged. Normalize the offset
825 // by setting it to normalized index of its preceding, safe node.
826 // (safe == one for which getIndex(true) does not return -1, so one which won't disappear).
827 if ( container
.type
== CKEDITOR
.NODE_ELEMENT
&& offset
> 0 ) {
828 offset
= getPrecedingSafeNodeIndex( container
, offset
) + 1;
831 // The last step - fix the offset inside text node by adding
832 // lengths of preceding text nodes which will be merged with container.
833 if ( container
.type
== CKEDITOR
.NODE_TEXT
) {
834 var precedingLength
= getLengthOfPrecedingTextNodes( container
);
836 // Normal case - text node is not empty.
837 if ( container
.getText() ) {
838 offset
+= precedingLength
;
840 // Awful case - the text node is empty and thus will be totally lost.
841 // In this case we are trying to normalize the limit to the left:
842 // * either to the preceding text node,
843 // * or to the "gap" after the preceding element.
845 // Find the closest non-text sibling.
846 var precedingContainer
= container
.getPrevious( isNotText
);
848 // If there are any characters on the left, that means that we can anchor
849 // there, because this text node will not be lost.
850 if ( precedingLength
) {
851 offset
= precedingLength
;
853 if ( precedingContainer
) {
854 // The text node is the first node after the closest non-text sibling.
855 container
= precedingContainer
.getNext();
857 // But if there was no non-text sibling, then the text node is the first child.
858 container
= container
.getParent().getFirst();
861 // If there are no characters on the left, then anchor after the previous non-text node.
862 // E.g. (see tests for a legend :D):
863 // <b>x</b>(foo)({}bar) -> <b>x</b>[](foo)(bar)
865 container
= container
.getParent();
866 offset
= precedingContainer
? ( precedingContainer
.getIndex( true ) + 1 ) : 0;
871 limit
.container
= container
;
872 limit
.offset
= offset
;
875 function normalizeFCSeq( limit
, root
) {
876 var fcseq
= root
.getCustomData( 'cke-fillingChar' );
882 var container
= limit
.container
;
884 if ( fcseq
.equals( container
) ) {
885 limit
.offset
-= CKEDITOR
.dom
.selection
.FILLING_CHAR_SEQUENCE
.length
;
887 // == 0 handles case when limit was at the end of FCS.
888 // < 0 handles all cases where limit was somewhere in the middle or at the beginning.
889 // > 0 (the "else" case) means cases where there are some more characters in the FCS node (FCSabc^def).
890 if ( limit
.offset
<= 0 ) {
891 limit
.offset
= container
.getIndex();
892 limit
.container
= container
.getParent();
897 // And here goes the funny part - all other cases are handled inside node.getAddress() and getIndex() thanks to
898 // node.getIndex() being aware of FCS (handling it as an empty node).
901 // Finds a normalized index of a safe node preceding this one.
902 // Safe == one that will not disappear, so one for which getIndex( true ) does not return -1.
903 // Return -1 if there's no safe preceding node.
904 function getPrecedingSafeNodeIndex( container
, offset
) {
908 index
= container
.getChild( offset
).getIndex( true );
917 return function( normalized
) {
918 var collapsed
= this.collapsed
,
920 container: this.startContainer
,
921 offset: this.startOffset
924 container: this.endContainer
,
925 offset: this.endOffset
929 normalizeTextNodes( bmStart
);
930 normalizeFCSeq( bmStart
, this.root
);
933 normalizeTextNodes( bmEnd
);
934 normalizeFCSeq( bmEnd
, this.root
);
939 start: bmStart
.container
.getAddress( normalized
),
940 end: collapsed
? null : bmEnd
.container
.getAddress( normalized
),
941 startOffset: bmStart
.offset
,
942 endOffset: bmEnd
.offset
,
943 normalized: normalized
,
944 collapsed: collapsed
,
945 is2: true // It's a createBookmark2 bookmark.
951 * Moves this range to the given bookmark. See {@link #createBookmark} and {@link #createBookmark2}.
953 * If serializable bookmark passed, then its `<span>` markers will be removed.
955 * @param {Object} bookmark
957 moveToBookmark: function( bookmark
) {
958 // Created with createBookmark2().
959 if ( bookmark
.is2
) {
960 // Get the start information.
961 var startContainer
= this.document
.getByAddress( bookmark
.start
, bookmark
.normalized
),
962 startOffset
= bookmark
.startOffset
;
964 // Get the end information.
965 var endContainer
= bookmark
.end
&& this.document
.getByAddress( bookmark
.end
, bookmark
.normalized
),
966 endOffset
= bookmark
.endOffset
;
968 // Set the start boundary.
969 this.setStart( startContainer
, startOffset
);
971 // Set the end boundary. If not available, collapse it.
973 this.setEnd( endContainer
, endOffset
);
975 this.collapse( true );
977 // Created with createBookmark().
979 var serializable
= bookmark
.serializable
,
980 startNode
= serializable
? this.document
.getById( bookmark
.startNode
) : bookmark
.startNode
,
981 endNode
= serializable
? this.document
.getById( bookmark
.endNode
) : bookmark
.endNode
;
983 // Set the range start at the bookmark start node position.
984 this.setStartBefore( startNode
);
986 // Remove it, because it may interfere in the setEndBefore call.
989 // Set the range end at the bookmark end node position, or simply
990 // collapse it if it is not available.
992 this.setEndBefore( endNode
);
995 this.collapse( true );
1001 * Returns two nodes which are on the boundaries of this range.
1004 * @returns {CKEDITOR.dom.node} return.startNode
1005 * @returns {CKEDITOR.dom.node} return.endNode
1006 * @todo precise desc/algorithm
1008 getBoundaryNodes: function() {
1009 var startNode
= this.startContainer
,
1010 endNode
= this.endContainer
,
1011 startOffset
= this.startOffset
,
1012 endOffset
= this.endOffset
,
1015 if ( startNode
.type
== CKEDITOR
.NODE_ELEMENT
) {
1016 childCount
= startNode
.getChildCount();
1017 if ( childCount
> startOffset
) {
1018 startNode
= startNode
.getChild( startOffset
);
1019 } else if ( childCount
< 1 ) {
1020 startNode
= startNode
.getPreviousSourceNode();
1022 // startOffset > childCount but childCount is not 0
1024 // Try to take the node just after the current position.
1025 startNode
= startNode
.$;
1026 while ( startNode
.lastChild
)
1027 startNode
= startNode
.lastChild
;
1028 startNode
= new CKEDITOR
.dom
.node( startNode
);
1030 // Normally we should take the next node in DFS order. But it
1031 // is also possible that we've already reached the end of
1033 startNode
= startNode
.getNextSourceNode() || startNode
;
1037 if ( endNode
.type
== CKEDITOR
.NODE_ELEMENT
) {
1038 childCount
= endNode
.getChildCount();
1039 if ( childCount
> endOffset
) {
1040 endNode
= endNode
.getChild( endOffset
).getPreviousSourceNode( true );
1041 } else if ( childCount
< 1 ) {
1042 endNode
= endNode
.getPreviousSourceNode();
1044 // endOffset > childCount but childCount is not 0.
1046 // Try to take the node just before the current position.
1047 endNode
= endNode
.$;
1048 while ( endNode
.lastChild
)
1049 endNode
= endNode
.lastChild
;
1050 endNode
= new CKEDITOR
.dom
.node( endNode
);
1054 // Sometimes the endNode will come right before startNode for collapsed
1055 // ranges. Fix it. (#3780)
1056 if ( startNode
.getPosition( endNode
) & CKEDITOR
.POSITION_FOLLOWING
)
1057 startNode
= endNode
;
1059 return { startNode: startNode
, endNode: endNode
};
1063 * Find the node which fully contains the range.
1065 * @param {Boolean} [includeSelf=false]
1066 * @param {Boolean} [ignoreTextNode=false] Whether ignore {@link CKEDITOR#NODE_TEXT} type.
1067 * @returns {CKEDITOR.dom.element}
1069 getCommonAncestor: function( includeSelf
, ignoreTextNode
) {
1070 var start
= this.startContainer
,
1071 end
= this.endContainer
,
1074 if ( start
.equals( end
) ) {
1075 if ( includeSelf
&& start
.type
== CKEDITOR
.NODE_ELEMENT
&& this.startOffset
== this.endOffset
- 1 )
1076 ancestor
= start
.getChild( this.startOffset
);
1080 ancestor
= start
.getCommonAncestor( end
);
1083 return ignoreTextNode
&& !ancestor
.is
? ancestor
.getParent() : ancestor
;
1087 * Transforms the {@link #startContainer} and {@link #endContainer} properties from text
1088 * nodes to element nodes, whenever possible. This is actually possible
1089 * if either of the boundary containers point to a text node, and its
1090 * offset is set to zero, or after the last char in the node.
1092 optimize: function() {
1093 var container
= this.startContainer
;
1094 var offset
= this.startOffset
;
1096 if ( container
.type
!= CKEDITOR
.NODE_ELEMENT
) {
1098 this.setStartBefore( container
);
1099 else if ( offset
>= container
.getLength() )
1100 this.setStartAfter( container
);
1103 container
= this.endContainer
;
1104 offset
= this.endOffset
;
1106 if ( container
.type
!= CKEDITOR
.NODE_ELEMENT
) {
1108 this.setEndBefore( container
);
1109 else if ( offset
>= container
.getLength() )
1110 this.setEndAfter( container
);
1115 * Move the range out of bookmark nodes if they'd been the container.
1117 optimizeBookmark: function() {
1118 var startNode
= this.startContainer
,
1119 endNode
= this.endContainer
;
1121 if ( startNode
.is
&& startNode
.is( 'span' ) && startNode
.data( 'cke-bookmark' ) )
1122 this.setStartAt( startNode
, CKEDITOR
.POSITION_BEFORE_START
);
1123 if ( endNode
&& endNode
.is
&& endNode
.is( 'span' ) && endNode
.data( 'cke-bookmark' ) )
1124 this.setEndAt( endNode
, CKEDITOR
.POSITION_AFTER_END
);
1128 * @param {Boolean} [ignoreStart=false]
1129 * @param {Boolean} [ignoreEnd=false]
1130 * @todo precise desc/algorithm
1132 trim: function( ignoreStart
, ignoreEnd
) {
1133 var startContainer
= this.startContainer
,
1134 startOffset
= this.startOffset
,
1135 collapsed
= this.collapsed
;
1136 if ( ( !ignoreStart
|| collapsed
) && startContainer
&& startContainer
.type
== CKEDITOR
.NODE_TEXT
) {
1137 // If the offset is zero, we just insert the new node before
1139 if ( !startOffset
) {
1140 startOffset
= startContainer
.getIndex();
1141 startContainer
= startContainer
.getParent();
1143 // If the offset is at the end, we'll insert it after the text
1145 else if ( startOffset
>= startContainer
.getLength() ) {
1146 startOffset
= startContainer
.getIndex() + 1;
1147 startContainer
= startContainer
.getParent();
1149 // In other case, we split the text node and insert the new
1150 // node at the split point.
1152 var nextText
= startContainer
.split( startOffset
);
1154 startOffset
= startContainer
.getIndex() + 1;
1155 startContainer
= startContainer
.getParent();
1157 // Check all necessity of updating the end boundary.
1158 if ( this.startContainer
.equals( this.endContainer
) )
1159 this.setEnd( nextText
, this.endOffset
- this.startOffset
);
1160 else if ( startContainer
.equals( this.endContainer
) )
1161 this.endOffset
+= 1;
1164 this.setStart( startContainer
, startOffset
);
1167 this.collapse( true );
1172 var endContainer
= this.endContainer
;
1173 var endOffset
= this.endOffset
;
1175 if ( !( ignoreEnd
|| collapsed
) && endContainer
&& endContainer
.type
== CKEDITOR
.NODE_TEXT
) {
1176 // If the offset is zero, we just insert the new node before
1179 endOffset
= endContainer
.getIndex();
1180 endContainer
= endContainer
.getParent();
1182 // If the offset is at the end, we'll insert it after the text
1184 else if ( endOffset
>= endContainer
.getLength() ) {
1185 endOffset
= endContainer
.getIndex() + 1;
1186 endContainer
= endContainer
.getParent();
1188 // In other case, we split the text node and insert the new
1189 // node at the split point.
1191 endContainer
.split( endOffset
);
1193 endOffset
= endContainer
.getIndex() + 1;
1194 endContainer
= endContainer
.getParent();
1197 this.setEnd( endContainer
, endOffset
);
1202 * Expands the range so that partial units are completely contained.
1204 * @param unit {Number} The unit type to expand with.
1205 * @param {Boolean} [excludeBrs=false] Whether include line-breaks when expanding.
1207 enlarge: function( unit
, excludeBrs
) {
1208 var leadingWhitespaceRegex
= new RegExp( /[^\s\ufeff]/ );
1211 case CKEDITOR
.ENLARGE_INLINE:
1212 var enlargeInlineOnly
= 1;
1215 case CKEDITOR
.ENLARGE_ELEMENT:
1217 if ( this.collapsed
)
1220 // Get the common ancestor.
1221 var commonAncestor
= this.getCommonAncestor();
1223 var boundary
= this.root
;
1225 // For each boundary
1226 // a. Depending on its position, find out the first node to be checked (a sibling) or,
1227 // if not available, to be enlarge.
1228 // b. Go ahead checking siblings and enlarging the boundary as much as possible until the
1229 // common ancestor is not reached. After reaching the common ancestor, just save the
1230 // enlargeable node to be used later.
1232 var startTop
, endTop
;
1234 var enlargeable
, sibling
, commonReached
;
1236 // Indicates that the node can be added only if whitespace
1237 // is available before it.
1238 var needsWhiteSpace
= false;
1242 // Process the start boundary.
1244 var container
= this.startContainer
;
1245 var offset
= this.startOffset
;
1247 if ( container
.type
== CKEDITOR
.NODE_TEXT
) {
1249 // Check if there is any non-space text before the
1250 // offset. Otherwise, container is null.
1251 container
= !CKEDITOR
.tools
.trim( container
.substring( 0, offset
) ).length
&& container
;
1253 // If we found only whitespace in the node, it
1254 // means that we'll need more whitespace to be able
1255 // to expand. For example, <i> can be expanded in
1256 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
1257 needsWhiteSpace
= !!container
;
1261 if ( !( sibling
= container
.getPrevious() ) )
1262 enlargeable
= container
.getParent();
1265 // If we have offset, get the node preceeding it as the
1266 // first sibling to be checked.
1268 sibling
= container
.getChild( offset
- 1 ) || container
.getLast();
1270 // If there is no sibling, mark the container to be
1273 enlargeable
= container
;
1276 // Ensures that enlargeable can be indeed enlarged, if not it will be nulled.
1277 enlargeable
= getValidEnlargeable( enlargeable
);
1279 while ( enlargeable
|| sibling
) {
1280 if ( enlargeable
&& !sibling
) {
1281 // If we reached the common ancestor, mark the flag
1283 if ( !commonReached
&& enlargeable
.equals( commonAncestor
) )
1284 commonReached
= true;
1286 if ( enlargeInlineOnly
? enlargeable
.isBlockBoundary() : !boundary
.contains( enlargeable
) )
1289 // If we don't need space or this element breaks
1290 // the line, then enlarge it.
1291 if ( !needsWhiteSpace
|| enlargeable
.getComputedStyle( 'display' ) != 'inline' ) {
1292 needsWhiteSpace
= false;
1294 // If the common ancestor has been reached,
1295 // we'll not enlarge it immediately, but just
1296 // mark it to be enlarged later if the end
1297 // boundary also enlarges it.
1298 if ( commonReached
)
1299 startTop
= enlargeable
;
1301 this.setStartBefore( enlargeable
);
1304 sibling
= enlargeable
.getPrevious();
1307 // Check all sibling nodes preceeding the enlargeable
1308 // node. The node wil lbe enlarged only if none of them
1311 // This flag indicates that this node has
1312 // whitespaces at the end.
1313 isWhiteSpace
= false;
1315 if ( sibling
.type
== CKEDITOR
.NODE_COMMENT
) {
1316 sibling
= sibling
.getPrevious();
1318 } else if ( sibling
.type
== CKEDITOR
.NODE_TEXT
) {
1319 siblingText
= sibling
.getText();
1321 if ( leadingWhitespaceRegex
.test( siblingText
) )
1324 isWhiteSpace
= /[\s\ufeff]$/.test( siblingText
);
1326 // #12221 (Chrome) plus #11111 (Safari).
1327 var offsetWidth0
= CKEDITOR
.env
.webkit
? 1 : 0;
1329 // If this is a visible element.
1330 // We need to check for the bookmark attribute because IE insists on
1331 // rendering the display:none nodes we use for bookmarks. (#3363)
1332 // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)
1333 if ( ( sibling
.$.offsetWidth
> offsetWidth0
|| excludeBrs
&& sibling
.is( 'br' ) ) && !sibling
.data( 'cke-bookmark' ) ) {
1334 // We'll accept it only if we need
1335 // whitespace, and this is an inline
1336 // element with whitespace only.
1337 if ( needsWhiteSpace
&& CKEDITOR
.dtd
.$removeEmpty
[ sibling
.getName() ] ) {
1338 // It must contains spaces and inline elements only.
1340 siblingText
= sibling
.getText();
1342 if ( leadingWhitespaceRegex
.test( siblingText
) ) // Spaces + Zero Width No-Break Space (U+FEFF)
1345 var allChildren
= sibling
.$.getElementsByTagName( '*' );
1346 for ( var i
= 0, child
; child
= allChildren
[ i
++ ]; ) {
1347 if ( !CKEDITOR
.dtd
.$removeEmpty
[ child
.nodeName
.toLowerCase() ] ) {
1355 isWhiteSpace
= !!siblingText
.length
;
1362 // A node with whitespaces has been found.
1363 if ( isWhiteSpace
) {
1364 // Enlarge the last enlargeable node, if we
1365 // were waiting for spaces.
1366 if ( needsWhiteSpace
) {
1367 if ( commonReached
)
1368 startTop
= enlargeable
;
1369 else if ( enlargeable
)
1370 this.setStartBefore( enlargeable
);
1372 needsWhiteSpace
= true;
1377 var next
= sibling
.getPrevious();
1379 if ( !enlargeable
&& !next
) {
1380 // Set the sibling as enlargeable, so it's
1381 // parent will be get later outside this while.
1382 enlargeable
= sibling
;
1389 // If sibling has been set to null, then we
1390 // need to stop enlarging.
1396 enlargeable
= getValidEnlargeable( enlargeable
.getParent() );
1399 // Process the end boundary. This is basically the same
1400 // code used for the start boundary, with small changes to
1401 // make it work in the oposite side (to the right). This
1402 // makes it difficult to reuse the code here. So, fixes to
1403 // the above code are likely to be replicated here.
1405 container
= this.endContainer
;
1406 offset
= this.endOffset
;
1408 // Reset the common variables.
1409 enlargeable
= sibling
= null;
1410 commonReached
= needsWhiteSpace
= false;
1412 // Function check if there are only whitespaces from the given starting point
1413 // (startContainer and startOffset) till the end on block.
1414 // Examples ("[" is the start point):
1415 // - <p>foo[ </p> - will return true,
1416 // - <p><b>foo[ </b> </p> - will return true,
1417 // - <p>foo[ bar</p> - will return false,
1418 // - <p><b>foo[ </b>bar</p> - will return false,
1419 // - <p>foo[ <b></b></p> - will return false.
1420 function onlyWhiteSpaces( startContainer
, startOffset
) {
1421 // We need to enlarge range if there is white space at the end of the block,
1422 // because it is not displayed in WYSIWYG mode and user can not select it. So
1423 // "<p>foo[bar] </p>" should be changed to "<p>foo[bar ]</p>". On the other hand
1424 // we should do nothing if we are not at the end of the block, so this should not
1425 // be changed: "<p><i>[foo] </i>bar</p>".
1426 var walkerRange
= new CKEDITOR
.dom
.range( boundary
);
1427 walkerRange
.setStart( startContainer
, startOffset
);
1428 // The guard will find the end of range so I put boundary here.
1429 walkerRange
.setEndAt( boundary
, CKEDITOR
.POSITION_BEFORE_END
);
1431 var walker
= new CKEDITOR
.dom
.walker( walkerRange
),
1434 walker
.guard = function( node
) {
1435 // Stop if you exit block.
1436 return !( node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.isBlockBoundary() );
1439 while ( ( node
= walker
.next() ) ) {
1440 if ( node
.type
!= CKEDITOR
.NODE_TEXT
) {
1441 // Stop if you enter to any node (walker.next() will return node only
1442 // it goes out, not if it is go into node).
1445 // Trim the first node to startOffset.
1446 if ( node
!= startContainer
)
1447 siblingText
= node
.getText();
1449 siblingText
= node
.substring( startOffset
);
1451 // Check if it is white space.
1452 if ( leadingWhitespaceRegex
.test( siblingText
) )
1460 if ( container
.type
== CKEDITOR
.NODE_TEXT
) {
1461 // Check if there is only white space after the offset.
1462 if ( CKEDITOR
.tools
.trim( container
.substring( offset
) ).length
) {
1463 // If we found only whitespace in the node, it
1464 // means that we'll need more whitespace to be able
1465 // to expand. For example, <i> can be expanded in
1466 // "A <i> [B]</i>", but not in "A<i> [B]</i>".
1467 needsWhiteSpace
= true;
1469 needsWhiteSpace
= !container
.getLength();
1471 if ( offset
== container
.getLength() ) {
1472 // If we are at the end of container and this is the last text node,
1473 // we should enlarge end to the parent.
1474 if ( !( sibling
= container
.getNext() ) )
1475 enlargeable
= container
.getParent();
1477 // If we are in the middle on text node and there are only whitespaces
1478 // till the end of block, we should enlarge element.
1479 if ( onlyWhiteSpaces( container
, offset
) )
1480 enlargeable
= container
.getParent();
1484 // Get the node right after the boudary to be checked
1486 sibling
= container
.getChild( offset
);
1489 enlargeable
= container
;
1492 while ( enlargeable
|| sibling
) {
1493 if ( enlargeable
&& !sibling
) {
1494 if ( !commonReached
&& enlargeable
.equals( commonAncestor
) )
1495 commonReached
= true;
1497 if ( enlargeInlineOnly
? enlargeable
.isBlockBoundary() : !boundary
.contains( enlargeable
) )
1500 if ( !needsWhiteSpace
|| enlargeable
.getComputedStyle( 'display' ) != 'inline' ) {
1501 needsWhiteSpace
= false;
1503 if ( commonReached
)
1504 endTop
= enlargeable
;
1505 else if ( enlargeable
)
1506 this.setEndAfter( enlargeable
);
1509 sibling
= enlargeable
.getNext();
1513 isWhiteSpace
= false;
1515 if ( sibling
.type
== CKEDITOR
.NODE_TEXT
) {
1516 siblingText
= sibling
.getText();
1518 // Check if there are not whitespace characters till the end of editable.
1519 // If so stop expanding.
1520 if ( !onlyWhiteSpaces( sibling
, 0 ) )
1523 isWhiteSpace
= /^[\s\ufeff]/.test( siblingText
);
1524 } else if ( sibling
.type
== CKEDITOR
.NODE_ELEMENT
) {
1525 // If this is a visible element.
1526 // We need to check for the bookmark attribute because IE insists on
1527 // rendering the display:none nodes we use for bookmarks. (#3363)
1528 // Line-breaks (br) are rendered with zero width, which we don't want to include. (#7041)
1529 if ( ( sibling
.$.offsetWidth
> 0 || excludeBrs
&& sibling
.is( 'br' ) ) && !sibling
.data( 'cke-bookmark' ) ) {
1530 // We'll accept it only if we need
1531 // whitespace, and this is an inline
1532 // element with whitespace only.
1533 if ( needsWhiteSpace
&& CKEDITOR
.dtd
.$removeEmpty
[ sibling
.getName() ] ) {
1534 // It must contains spaces and inline elements only.
1536 siblingText
= sibling
.getText();
1538 if ( leadingWhitespaceRegex
.test( siblingText
) )
1541 allChildren
= sibling
.$.getElementsByTagName( '*' );
1542 for ( i
= 0; child
= allChildren
[ i
++ ]; ) {
1543 if ( !CKEDITOR
.dtd
.$removeEmpty
[ child
.nodeName
.toLowerCase() ] ) {
1551 isWhiteSpace
= !!siblingText
.length
;
1560 if ( isWhiteSpace
) {
1561 if ( needsWhiteSpace
) {
1562 if ( commonReached
)
1563 endTop
= enlargeable
;
1565 this.setEndAfter( enlargeable
);
1570 next
= sibling
.getNext();
1572 if ( !enlargeable
&& !next
) {
1573 enlargeable
= sibling
;
1580 // If sibling has been set to null, then we
1581 // need to stop enlarging.
1587 enlargeable
= getValidEnlargeable( enlargeable
.getParent() );
1590 // If the common ancestor can be enlarged by both boundaries, then include it also.
1591 if ( startTop
&& endTop
) {
1592 commonAncestor
= startTop
.contains( endTop
) ? endTop : startTop
;
1594 this.setStartBefore( commonAncestor
);
1595 this.setEndAfter( commonAncestor
);
1599 case CKEDITOR
.ENLARGE_BLOCK_CONTENTS:
1600 case CKEDITOR
.ENLARGE_LIST_ITEM_CONTENTS:
1602 // Enlarging the start boundary.
1603 var walkerRange
= new CKEDITOR
.dom
.range( this.root
);
1605 boundary
= this.root
;
1607 walkerRange
.setStartAt( boundary
, CKEDITOR
.POSITION_AFTER_START
);
1608 walkerRange
.setEnd( this.startContainer
, this.startOffset
);
1610 var walker
= new CKEDITOR
.dom
.walker( walkerRange
),
1611 blockBoundary
, // The node on which the enlarging should stop.
1612 tailBr
, // In case BR as block boundary.
1613 notBlockBoundary
= CKEDITOR
.dom
.walker
.blockBoundary( ( unit
== CKEDITOR
.ENLARGE_LIST_ITEM_CONTENTS
) ? { br: 1 } : null ),
1614 inNonEditable
= null,
1615 // Record the encountered 'blockBoundary' for later use.
1616 boundaryGuard = function( node
) {
1617 // We should not check contents of non-editable elements. It may happen
1618 // that inline widget has display:table child which should not block range#enlarge.
1619 // When encoutered non-editable element...
1620 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.getAttribute( 'contenteditable' ) == 'false' ) {
1621 if ( inNonEditable
) {
1622 // ... in which we already were, reset it (because we're leaving it) and return.
1623 if ( inNonEditable
.equals( node
) ) {
1624 inNonEditable
= null;
1627 // ... which we're entering, remember it but check it (no return).
1629 inNonEditable
= node
;
1631 // When we are in non-editable element, do not check if current node is a block boundary.
1632 } else if ( inNonEditable
) {
1636 var retval
= notBlockBoundary( node
);
1638 blockBoundary
= node
;
1641 // Record the encounted 'tailBr' for later use.
1642 tailBrGuard = function( node
) {
1643 var retval
= boundaryGuard( node
);
1644 if ( !retval
&& node
.is
&& node
.is( 'br' ) )
1649 walker
.guard
= boundaryGuard
;
1651 enlargeable
= walker
.lastBackward();
1653 // It's the body which stop the enlarging if no block boundary found.
1654 blockBoundary
= blockBoundary
|| boundary
;
1656 // Start the range either after the end of found block (<p>...</p>[text)
1657 // or at the start of block (<p>[text...), by comparing the document position
1658 // with 'enlargeable' node.
1659 this.setStartAt( blockBoundary
, !blockBoundary
.is( 'br' ) && ( !enlargeable
&& this.checkStartOfBlock() ||
1660 enlargeable
&& blockBoundary
.contains( enlargeable
) ) ? CKEDITOR
.POSITION_AFTER_START : CKEDITOR
.POSITION_AFTER_END
);
1662 // Avoid enlarging the range further when end boundary spans right after the BR. (#7490)
1663 if ( unit
== CKEDITOR
.ENLARGE_LIST_ITEM_CONTENTS
) {
1664 var theRange
= this.clone();
1665 walker
= new CKEDITOR
.dom
.walker( theRange
);
1667 var whitespaces
= CKEDITOR
.dom
.walker
.whitespaces(),
1668 bookmark
= CKEDITOR
.dom
.walker
.bookmark();
1670 walker
.evaluator = function( node
) {
1671 return !whitespaces( node
) && !bookmark( node
);
1673 var previous
= walker
.previous();
1674 if ( previous
&& previous
.type
== CKEDITOR
.NODE_ELEMENT
&& previous
.is( 'br' ) )
1678 // Enlarging the end boundary.
1679 // Set up new range and reset all flags (blockBoundary, inNonEditable, tailBr).
1681 walkerRange
= this.clone();
1682 walkerRange
.collapse();
1683 walkerRange
.setEndAt( boundary
, CKEDITOR
.POSITION_BEFORE_END
);
1684 walker
= new CKEDITOR
.dom
.walker( walkerRange
);
1686 // tailBrGuard only used for on range end.
1687 walker
.guard
= ( unit
== CKEDITOR
.ENLARGE_LIST_ITEM_CONTENTS
) ? tailBrGuard : boundaryGuard
;
1688 blockBoundary
= inNonEditable
= tailBr
= null;
1690 // End the range right before the block boundary node.
1691 enlargeable
= walker
.lastForward();
1693 // It's the body which stop the enlarging if no block boundary found.
1694 blockBoundary
= blockBoundary
|| boundary
;
1696 // Close the range either before the found block start (text]<p>...</p>) or at the block end (...text]</p>)
1697 // by comparing the document position with 'enlargeable' node.
1698 this.setEndAt( blockBoundary
, ( !enlargeable
&& this.checkEndOfBlock() ||
1699 enlargeable
&& blockBoundary
.contains( enlargeable
) ) ? CKEDITOR
.POSITION_BEFORE_END : CKEDITOR
.POSITION_BEFORE_START
);
1700 // We must include the <br> at the end of range if there's
1701 // one and we're expanding list item contents
1703 this.setEndAfter( tailBr
);
1707 // Ensures that returned element can be enlarged by selection, null otherwise.
1708 // @param {CKEDITOR.dom.element} enlargeable
1709 // @returns {CKEDITOR.dom.element/null}
1710 function getValidEnlargeable( enlargeable
) {
1711 return enlargeable
&& enlargeable
.type
== CKEDITOR
.NODE_ELEMENT
&& enlargeable
.hasAttribute( 'contenteditable' ) ?
1717 * Descrease the range to make sure that boundaries
1718 * always anchor beside text nodes or innermost element.
1720 * @param {Number} mode The shrinking mode ({@link CKEDITOR#SHRINK_ELEMENT} or {@link CKEDITOR#SHRINK_TEXT}).
1722 * * {@link CKEDITOR#SHRINK_ELEMENT} - Shrink the range boundaries to the edge of the innermost element.
1723 * * {@link CKEDITOR#SHRINK_TEXT} - Shrink the range boudaries to anchor by the side of enclosed text
1724 * node, range remains if there's no text nodes on boundaries at all.
1726 * @param {Boolean} selectContents Whether result range anchors at the inner OR outer boundary of the node.
1728 shrink: function( mode
, selectContents
, shrinkOnBlockBoundary
) {
1729 // Unable to shrink a collapsed range.
1730 if ( !this.collapsed
) {
1731 mode
= mode
|| CKEDITOR
.SHRINK_TEXT
;
1733 var walkerRange
= this.clone();
1735 var startContainer
= this.startContainer
,
1736 endContainer
= this.endContainer
,
1737 startOffset
= this.startOffset
,
1738 endOffset
= this.endOffset
;
1740 // Whether the start/end boundary is moveable.
1744 if ( startContainer
&& startContainer
.type
== CKEDITOR
.NODE_TEXT
) {
1746 walkerRange
.setStartBefore( startContainer
);
1747 else if ( startOffset
>= startContainer
.getLength() )
1748 walkerRange
.setStartAfter( startContainer
);
1750 // Enlarge the range properly to avoid walker making
1751 // DOM changes caused by triming the text nodes later.
1752 walkerRange
.setStartBefore( startContainer
);
1757 if ( endContainer
&& endContainer
.type
== CKEDITOR
.NODE_TEXT
) {
1759 walkerRange
.setEndBefore( endContainer
);
1760 else if ( endOffset
>= endContainer
.getLength() )
1761 walkerRange
.setEndAfter( endContainer
);
1763 walkerRange
.setEndAfter( endContainer
);
1768 var walker
= new CKEDITOR
.dom
.walker( walkerRange
),
1769 isBookmark
= CKEDITOR
.dom
.walker
.bookmark();
1771 walker
.evaluator = function( node
) {
1772 return node
.type
== ( mode
== CKEDITOR
.SHRINK_ELEMENT
? CKEDITOR
.NODE_ELEMENT : CKEDITOR
.NODE_TEXT
);
1776 walker
.guard = function( node
, movingOut
) {
1777 if ( isBookmark( node
) )
1780 // Stop when we're shrink in element mode while encountering a text node.
1781 if ( mode
== CKEDITOR
.SHRINK_ELEMENT
&& node
.type
== CKEDITOR
.NODE_TEXT
)
1784 // Stop when we've already walked "through" an element.
1785 if ( movingOut
&& node
.equals( currentElement
) )
1788 if ( shrinkOnBlockBoundary
=== false && node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.isBlockBoundary() )
1791 // Stop shrinking when encountering an editable border.
1792 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.hasAttribute( 'contenteditable' ) )
1795 if ( !movingOut
&& node
.type
== CKEDITOR
.NODE_ELEMENT
)
1796 currentElement
= node
;
1802 var textStart
= walker
[ mode
== CKEDITOR
.SHRINK_ELEMENT
? 'lastForward' : 'next' ]();
1803 textStart
&& this.setStartAt( textStart
, selectContents
? CKEDITOR
.POSITION_AFTER_START : CKEDITOR
.POSITION_BEFORE_START
);
1808 var textEnd
= walker
[ mode
== CKEDITOR
.SHRINK_ELEMENT
? 'lastBackward' : 'previous' ]();
1809 textEnd
&& this.setEndAt( textEnd
, selectContents
? CKEDITOR
.POSITION_BEFORE_END : CKEDITOR
.POSITION_AFTER_END
);
1812 return !!( moveStart
|| moveEnd
);
1817 * Inserts a node at the start of the range. The range will be expanded
1818 * the contain the node.
1820 * @param {CKEDITOR.dom.node} node
1822 insertNode: function( node
) {
1823 this.optimizeBookmark();
1824 this.trim( false, true );
1826 var startContainer
= this.startContainer
;
1827 var startOffset
= this.startOffset
;
1829 var nextNode
= startContainer
.getChild( startOffset
);
1832 node
.insertBefore( nextNode
);
1834 startContainer
.append( node
);
1836 // Check if we need to update the end boundary.
1837 if ( node
.getParent() && node
.getParent().equals( this.endContainer
) )
1840 // Expand the range to embrace the new node.
1841 this.setStartBefore( node
);
1845 * Moves the range to given position according to specified node.
1847 * // HTML: <p>Foo <b>bar</b></p>
1848 * range.moveToPosition( elB, CKEDITOR.POSITION_BEFORE_START );
1849 * // Range will be moved to: <p>Foo ^<b>bar</b></p>
1851 * See also {@link #setStartAt} and {@link #setEndAt}.
1853 * @param {CKEDITOR.dom.node} node The node according to which position will be set.
1854 * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START},
1855 * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END},
1856 * {@link CKEDITOR#POSITION_AFTER_END}.
1858 moveToPosition: function( node
, position
) {
1859 this.setStartAt( node
, position
);
1860 this.collapse( true );
1864 * Moves the range to the exact position of the specified range.
1866 * @param {CKEDITOR.dom.range} range
1868 moveToRange: function( range
) {
1869 this.setStart( range
.startContainer
, range
.startOffset
);
1870 this.setEnd( range
.endContainer
, range
.endOffset
);
1874 * Select nodes content. Range will start and end inside this node.
1876 * @param {CKEDITOR.dom.node} node
1878 selectNodeContents: function( node
) {
1879 this.setStart( node
, 0 );
1880 this.setEnd( node
, node
.type
== CKEDITOR
.NODE_TEXT
? node
.getLength() : node
.getChildCount() );
1884 * Sets the start position of a range.
1886 * @param {CKEDITOR.dom.node} startNode The node to start the range.
1887 * @param {Number} startOffset An integer greater than or equal to zero
1888 * representing the offset for the start of the range from the start
1891 setStart: function( startNode
, startOffset
) {
1892 // W3C requires a check for the new position. If it is after the end
1893 // boundary, the range should be collapsed to the new start. It seams
1894 // we will not need this check for our use of this class so we can
1895 // ignore it for now.
1897 // Fixing invalid range start inside dtd empty elements.
1898 if ( startNode
.type
== CKEDITOR
.NODE_ELEMENT
&& CKEDITOR
.dtd
.$empty
[ startNode
.getName() ] )
1899 startOffset
= startNode
.getIndex(), startNode
= startNode
.getParent();
1901 this._setStartContainer( startNode
);
1902 this.startOffset
= startOffset
;
1904 if ( !this.endContainer
) {
1905 this._setEndContainer( startNode
);
1906 this.endOffset
= startOffset
;
1909 updateCollapsed( this );
1913 * Sets the end position of a Range.
1915 * @param {CKEDITOR.dom.node} endNode The node to end the range.
1916 * @param {Number} endOffset An integer greater than or equal to zero
1917 * representing the offset for the end of the range from the start
1920 setEnd: function( endNode
, endOffset
) {
1921 // W3C requires a check for the new position. If it is before the start
1922 // boundary, the range should be collapsed to the new end. It seams we
1923 // will not need this check for our use of this class so we can ignore
1926 // Fixing invalid range end inside dtd empty elements.
1927 if ( endNode
.type
== CKEDITOR
.NODE_ELEMENT
&& CKEDITOR
.dtd
.$empty
[ endNode
.getName() ] )
1928 endOffset
= endNode
.getIndex() + 1, endNode
= endNode
.getParent();
1930 this._setEndContainer( endNode
);
1931 this.endOffset
= endOffset
;
1933 if ( !this.startContainer
) {
1934 this._setStartContainer( endNode
);
1935 this.startOffset
= endOffset
;
1938 updateCollapsed( this );
1942 * Sets start of this range after the specified node.
1944 * // Range: <p>foo<b>bar</b>^</p>
1945 * range.setStartAfter( textFoo );
1946 * // The range will be changed to:
1947 * // <p>foo[<b>bar</b>]</p>
1949 * @param {CKEDITOR.dom.node} node
1951 setStartAfter: function( node
) {
1952 this.setStart( node
.getParent(), node
.getIndex() + 1 );
1956 * Sets start of this range after the specified node.
1958 * // Range: <p>foo<b>bar</b>^</p>
1959 * range.setStartBefore( elB );
1960 * // The range will be changed to:
1961 * // <p>foo[<b>bar</b>]</p>
1963 * @param {CKEDITOR.dom.node} node
1965 setStartBefore: function( node
) {
1966 this.setStart( node
.getParent(), node
.getIndex() );
1970 * Sets end of this range after the specified node.
1972 * // Range: <p>foo^<b>bar</b></p>
1973 * range.setEndAfter( elB );
1974 * // The range will be changed to:
1975 * // <p>foo[<b>bar</b>]</p>
1977 * @param {CKEDITOR.dom.node} node
1979 setEndAfter: function( node
) {
1980 this.setEnd( node
.getParent(), node
.getIndex() + 1 );
1984 * Sets end of this range before the specified node.
1986 * // Range: <p>^foo<b>bar</b></p>
1987 * range.setStartAfter( textBar );
1988 * // The range will be changed to:
1989 * // <p>[foo<b>]bar</b></p>
1991 * @param {CKEDITOR.dom.node} node
1993 setEndBefore: function( node
) {
1994 this.setEnd( node
.getParent(), node
.getIndex() );
1998 * Moves the start of this range to given position according to specified node.
2000 * // HTML: <p>Foo <b>bar</b>^</p>
2001 * range.setStartAt( elB, CKEDITOR.POSITION_AFTER_START );
2002 * // The range will be changed to:
2003 * // <p>Foo <b>[bar</b>]</p>
2005 * See also {@link #setEndAt} and {@link #moveToPosition}.
2007 * @param {CKEDITOR.dom.node} node The node according to which position will be set.
2008 * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START},
2009 * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END},
2010 * {@link CKEDITOR#POSITION_AFTER_END}.
2012 setStartAt: function( node
, position
) {
2013 switch ( position
) {
2014 case CKEDITOR
.POSITION_AFTER_START:
2015 this.setStart( node
, 0 );
2018 case CKEDITOR
.POSITION_BEFORE_END:
2019 if ( node
.type
== CKEDITOR
.NODE_TEXT
)
2020 this.setStart( node
, node
.getLength() );
2022 this.setStart( node
, node
.getChildCount() );
2025 case CKEDITOR
.POSITION_BEFORE_START:
2026 this.setStartBefore( node
);
2029 case CKEDITOR
.POSITION_AFTER_END:
2030 this.setStartAfter( node
);
2033 updateCollapsed( this );
2037 * Moves the end of this range to given position according to specified node.
2039 * // HTML: <p>^Foo <b>bar</b></p>
2040 * range.setEndAt( textBar, CKEDITOR.POSITION_BEFORE_START );
2041 * // The range will be changed to:
2042 * // <p>[Foo <b>]bar</b></p>
2044 * See also {@link #setStartAt} and {@link #moveToPosition}.
2046 * @param {CKEDITOR.dom.node} node The node according to which position will be set.
2047 * @param {Number} position One of {@link CKEDITOR#POSITION_BEFORE_START},
2048 * {@link CKEDITOR#POSITION_AFTER_START}, {@link CKEDITOR#POSITION_BEFORE_END},
2049 * {@link CKEDITOR#POSITION_AFTER_END}.
2051 setEndAt: function( node
, position
) {
2052 switch ( position
) {
2053 case CKEDITOR
.POSITION_AFTER_START:
2054 this.setEnd( node
, 0 );
2057 case CKEDITOR
.POSITION_BEFORE_END:
2058 if ( node
.type
== CKEDITOR
.NODE_TEXT
)
2059 this.setEnd( node
, node
.getLength() );
2061 this.setEnd( node
, node
.getChildCount() );
2064 case CKEDITOR
.POSITION_BEFORE_START:
2065 this.setEndBefore( node
);
2068 case CKEDITOR
.POSITION_AFTER_END:
2069 this.setEndAfter( node
);
2072 updateCollapsed( this );
2076 * Wraps inline content found around the range's start or end boundary
2077 * with a block element.
2079 * // Assuming the following range:
2080 * // <h1>foo</h1>ba^r<br />bom<p>foo</p>
2081 * // The result of executing:
2082 * range.fixBlock( true, 'p' );
2084 * // <h1>foo</h1><p>ba^r<br />bom</p><p>foo</p>
2086 * Non-collapsed range:
2088 * // Assuming the following range:
2089 * // ba[r<p>foo</p>bo]m
2090 * // The result of executing:
2091 * range.fixBlock( false, 'p' );
2093 * // ba[r<p>foo</p><p>bo]m</p>
2095 * @param {Boolean} isStart Whether the start or end boundary of a range should be checked.
2096 * @param {String} blockTag The name of a block element in which content will be wrapped.
2097 * For example: `'p'`.
2098 * @returns {CKEDITOR.dom.element} Created block wrapper.
2100 fixBlock: function( isStart
, blockTag
) {
2101 var bookmark
= this.createBookmark(),
2102 fixedBlock
= this.document
.createElement( blockTag
);
2104 this.collapse( isStart
);
2106 this.enlarge( CKEDITOR
.ENLARGE_BLOCK_CONTENTS
);
2108 this.extractContents().appendTo( fixedBlock
);
2111 this.insertNode( fixedBlock
);
2113 // Bogus <br> could already exist in the range's container before fixBlock() was called. In such case it was
2114 // extracted and appended to the fixBlock. However, we are not sure that it's at the end of
2115 // the fixedBlock, because of FF's terrible bug. When creating a bookmark in an empty editable
2116 // FF moves the bogus <br> before that bookmark (<editable><br /><bm />[]</editable>).
2117 // So even if the initial range was placed before the bogus <br>, after creating the bookmark it
2118 // is placed before the bookmark.
2119 // Fortunately, getBogus() is able to skip the bookmark so it finds the bogus <br> in this case.
2120 // We remove incorrectly placed one and add a brand new one. (#13001)
2121 var bogus
= fixedBlock
.getBogus();
2125 fixedBlock
.appendBogus();
2127 this.moveToBookmark( bookmark
);
2134 * @param {Boolean} [cloneId=false] Whether to preserve ID attributes in the result blocks.
2136 splitBlock: function( blockTag
, cloneId
) {
2137 var startPath
= new CKEDITOR
.dom
.elementPath( this.startContainer
, this.root
),
2138 endPath
= new CKEDITOR
.dom
.elementPath( this.endContainer
, this.root
);
2140 var startBlockLimit
= startPath
.blockLimit
,
2141 endBlockLimit
= endPath
.blockLimit
;
2143 var startBlock
= startPath
.block
,
2144 endBlock
= endPath
.block
;
2146 var elementPath
= null;
2147 // Do nothing if the boundaries are in different block limits.
2148 if ( !startBlockLimit
.equals( endBlockLimit
) )
2151 // Get or fix current blocks.
2152 if ( blockTag
!= 'br' ) {
2153 if ( !startBlock
) {
2154 startBlock
= this.fixBlock( true, blockTag
);
2155 endBlock
= new CKEDITOR
.dom
.elementPath( this.endContainer
, this.root
).block
;
2159 endBlock
= this.fixBlock( false, blockTag
);
2162 // Get the range position.
2163 var isStartOfBlock
= startBlock
&& this.checkStartOfBlock(),
2164 isEndOfBlock
= endBlock
&& this.checkEndOfBlock();
2166 // Delete the current contents.
2167 // TODO: Why is 2.x doing CheckIsEmpty()?
2168 this.deleteContents();
2170 if ( startBlock
&& startBlock
.equals( endBlock
) ) {
2171 if ( isEndOfBlock
) {
2172 elementPath
= new CKEDITOR
.dom
.elementPath( this.startContainer
, this.root
);
2173 this.moveToPosition( endBlock
, CKEDITOR
.POSITION_AFTER_END
);
2175 } else if ( isStartOfBlock
) {
2176 elementPath
= new CKEDITOR
.dom
.elementPath( this.startContainer
, this.root
);
2177 this.moveToPosition( startBlock
, CKEDITOR
.POSITION_BEFORE_START
);
2180 endBlock
= this.splitElement( startBlock
, cloneId
|| false );
2182 // In Gecko, the last child node must be a bogus <br>.
2183 // Note: bogus <br> added under <ul> or <ol> would cause
2184 // lists to be incorrectly rendered.
2185 if ( !startBlock
.is( 'ul', 'ol' ) )
2186 startBlock
.appendBogus();
2191 previousBlock: startBlock
,
2192 nextBlock: endBlock
,
2193 wasStartOfBlock: isStartOfBlock
,
2194 wasEndOfBlock: isEndOfBlock
,
2195 elementPath: elementPath
2200 * Branch the specified element from the collapsed range position and
2201 * place the caret between the two result branches.
2203 * **Note:** The range must be collapsed and been enclosed by this element.
2205 * @param {CKEDITOR.dom.element} element
2206 * @param {Boolean} [cloneId=false] Whether to preserve ID attributes in the result elements.
2207 * @returns {CKEDITOR.dom.element} Root element of the new branch after the split.
2209 splitElement: function( toSplit
, cloneId
) {
2210 if ( !this.collapsed
)
2213 // Extract the contents of the block from the selection point to the end
2215 this.setEndAt( toSplit
, CKEDITOR
.POSITION_BEFORE_END
);
2216 var documentFragment
= this.extractContents( false, cloneId
|| false );
2218 // Duplicate the element after it.
2219 var clone
= toSplit
.clone( false, cloneId
|| false );
2221 // Place the extracted contents into the duplicated element.
2222 documentFragment
.appendTo( clone
);
2223 clone
.insertAfter( toSplit
);
2224 this.moveToPosition( toSplit
, CKEDITOR
.POSITION_AFTER_END
);
2229 * Recursively remove any empty path blocks at the range boundary.
2232 * @param {Boolean} atEnd Removal to perform at the end boundary,
2233 * otherwise to perform at the start.
2235 removeEmptyBlocksAtEnd: ( function() {
2237 var whitespace
= CKEDITOR
.dom
.walker
.whitespaces(),
2238 bookmark
= CKEDITOR
.dom
.walker
.bookmark( false );
2240 function childEval( parent
) {
2241 return function( node
) {
2242 // Whitespace, bookmarks, empty inlines.
2243 if ( whitespace( node
) || bookmark( node
) ||
2244 node
.type
== CKEDITOR
.NODE_ELEMENT
&&
2245 node
.isEmptyInlineRemoveable() ) {
2247 } else if ( parent
.is( 'table' ) && node
.is( 'caption' ) ) {
2255 return function( atEnd
) {
2257 var bm
= this.createBookmark();
2258 var path
= this[ atEnd
? 'endPath' : 'startPath' ]();
2259 var block
= path
.block
|| path
.blockLimit
, parent
;
2261 // Remove any childless block, including list and table.
2262 while ( block
&& !block
.equals( path
.root
) &&
2263 !block
.getFirst( childEval( block
) ) ) {
2264 parent
= block
.getParent();
2265 this[ atEnd
? 'setEndAt' : 'setStartAt' ]( block
, CKEDITOR
.POSITION_AFTER_END
);
2270 this.moveToBookmark( bm
);
2276 * Gets {@link CKEDITOR.dom.elementPath} for the {@link #startContainer}.
2278 * @returns {CKEDITOR.dom.elementPath}
2280 startPath: function() {
2281 return new CKEDITOR
.dom
.elementPath( this.startContainer
, this.root
);
2285 * Gets {@link CKEDITOR.dom.elementPath} for the {@link #endContainer}.
2287 * @returns {CKEDITOR.dom.elementPath}
2289 endPath: function() {
2290 return new CKEDITOR
.dom
.elementPath( this.endContainer
, this.root
);
2294 * Check whether a range boundary is at the inner boundary of a given
2297 * @param {CKEDITOR.dom.element} element The target element to check.
2298 * @param {Number} checkType The boundary to check for both the range
2299 * and the element. It can be {@link CKEDITOR#START} or {@link CKEDITOR#END}.
2300 * @returns {Boolean} `true` if the range boundary is at the inner
2301 * boundary of the element.
2303 checkBoundaryOfElement: function( element
, checkType
) {
2304 var checkStart
= ( checkType
== CKEDITOR
.START
);
2306 // Create a copy of this range, so we can manipulate it for our checks.
2307 var walkerRange
= this.clone();
2309 // Collapse the range at the proper size.
2310 walkerRange
.collapse( checkStart
);
2312 // Expand the range to element boundary.
2313 walkerRange
[ checkStart
? 'setStartAt' : 'setEndAt' ]( element
, checkStart
? CKEDITOR
.POSITION_AFTER_START : CKEDITOR
.POSITION_BEFORE_END
);
2315 // Create the walker, which will check if we have anything useful
2317 var walker
= new CKEDITOR
.dom
.walker( walkerRange
);
2318 walker
.evaluator
= elementBoundaryEval( checkStart
);
2320 return walker
[ checkStart
? 'checkBackward' : 'checkForward' ]();
2324 * **Note:** Calls to this function may produce changes to the DOM. The range may
2325 * be updated to reflect such changes.
2327 * @returns {Boolean}
2330 checkStartOfBlock: function() {
2331 var startContainer
= this.startContainer
,
2332 startOffset
= this.startOffset
;
2334 // [IE] Special handling for range start in text with a leading NBSP,
2335 // we it to be isolated, for bogus check.
2336 if ( CKEDITOR
.env
.ie
&& startOffset
&& startContainer
.type
== CKEDITOR
.NODE_TEXT
) {
2337 var textBefore
= CKEDITOR
.tools
.ltrim( startContainer
.substring( 0, startOffset
) );
2338 if ( nbspRegExp
.test( textBefore
) )
2342 // Antecipate the trim() call here, so the walker will not make
2343 // changes to the DOM, which would not get reflected into this
2347 // We need to grab the block element holding the start boundary, so
2348 // let's use an element path for it.
2349 var path
= new CKEDITOR
.dom
.elementPath( this.startContainer
, this.root
);
2351 // Creates a range starting at the block start until the range start.
2352 var walkerRange
= this.clone();
2353 walkerRange
.collapse( true );
2354 walkerRange
.setStartAt( path
.block
|| path
.blockLimit
, CKEDITOR
.POSITION_AFTER_START
);
2356 var walker
= new CKEDITOR
.dom
.walker( walkerRange
);
2357 walker
.evaluator
= getCheckStartEndBlockEvalFunction();
2359 return walker
.checkBackward();
2363 * **Note:** Calls to this function may produce changes to the DOM. The range may
2364 * be updated to reflect such changes.
2366 * @returns {Boolean}
2369 checkEndOfBlock: function() {
2370 var endContainer
= this.endContainer
,
2371 endOffset
= this.endOffset
;
2373 // [IE] Special handling for range end in text with a following NBSP,
2374 // we it to be isolated, for bogus check.
2375 if ( CKEDITOR
.env
.ie
&& endContainer
.type
== CKEDITOR
.NODE_TEXT
) {
2376 var textAfter
= CKEDITOR
.tools
.rtrim( endContainer
.substring( endOffset
) );
2377 if ( nbspRegExp
.test( textAfter
) )
2381 // Antecipate the trim() call here, so the walker will not make
2382 // changes to the DOM, which would not get reflected into this
2386 // We need to grab the block element holding the start boundary, so
2387 // let's use an element path for it.
2388 var path
= new CKEDITOR
.dom
.elementPath( this.endContainer
, this.root
);
2390 // Creates a range starting at the block start until the range start.
2391 var walkerRange
= this.clone();
2392 walkerRange
.collapse( false );
2393 walkerRange
.setEndAt( path
.block
|| path
.blockLimit
, CKEDITOR
.POSITION_BEFORE_END
);
2395 var walker
= new CKEDITOR
.dom
.walker( walkerRange
);
2396 walker
.evaluator
= getCheckStartEndBlockEvalFunction();
2398 return walker
.checkForward();
2402 * Traverse with {@link CKEDITOR.dom.walker} to retrieve the previous element before the range start.
2404 * @param {Function} evaluator Function used as the walker's evaluator.
2405 * @param {Function} [guard] Function used as the walker's guard.
2406 * @param {CKEDITOR.dom.element} [boundary] A range ancestor element in which the traversal is limited,
2407 * default to the root editable if not defined.
2408 * @returns {CKEDITOR.dom.element/null} The returned node from the traversal.
2410 getPreviousNode: function( evaluator
, guard
, boundary
) {
2411 var walkerRange
= this.clone();
2412 walkerRange
.collapse( 1 );
2413 walkerRange
.setStartAt( boundary
|| this.root
, CKEDITOR
.POSITION_AFTER_START
);
2415 var walker
= new CKEDITOR
.dom
.walker( walkerRange
);
2416 walker
.evaluator
= evaluator
;
2417 walker
.guard
= guard
;
2418 return walker
.previous();
2422 * Traverse with {@link CKEDITOR.dom.walker} to retrieve the next element before the range start.
2424 * @param {Function} evaluator Function used as the walker's evaluator.
2425 * @param {Function} [guard] Function used as the walker's guard.
2426 * @param {CKEDITOR.dom.element} [boundary] A range ancestor element in which the traversal is limited,
2427 * default to the root editable if not defined.
2428 * @returns {CKEDITOR.dom.element/null} The returned node from the traversal.
2430 getNextNode: function( evaluator
, guard
, boundary
) {
2431 var walkerRange
= this.clone();
2432 walkerRange
.collapse();
2433 walkerRange
.setEndAt( boundary
|| this.root
, CKEDITOR
.POSITION_BEFORE_END
);
2435 var walker
= new CKEDITOR
.dom
.walker( walkerRange
);
2436 walker
.evaluator
= evaluator
;
2437 walker
.guard
= guard
;
2438 return walker
.next();
2442 * Check if elements at which the range boundaries anchor are read-only,
2443 * with respect to `contenteditable` attribute.
2445 * @returns {Boolean}
2447 checkReadOnly: ( function() {
2448 function checkNodesEditable( node
, anotherEnd
) {
2450 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
) {
2451 if ( node
.getAttribute( 'contentEditable' ) == 'false' && !node
.data( 'cke-editable' ) )
2454 // Range enclosed entirely in an editable element.
2455 else if ( node
.is( 'html' ) || node
.getAttribute( 'contentEditable' ) == 'true' && ( node
.contains( anotherEnd
) || node
.equals( anotherEnd
) ) )
2459 node
= node
.getParent();
2466 var startNode
= this.startContainer
,
2467 endNode
= this.endContainer
;
2469 // Check if elements path at both boundaries are editable.
2470 return !( checkNodesEditable( startNode
, endNode
) && checkNodesEditable( endNode
, startNode
) );
2475 * Moves the range boundaries to the first/end editing point inside an
2478 * For example, in an element tree like
2479 * `<p><b><i></i></b> Text</p>`, the start editing point is
2480 * `<p><b><i>^</i></b> Text</p>` (inside `<i>`).
2482 * @param {CKEDITOR.dom.element} el The element into which look for the
2484 * @param {Boolean} isMoveToEnd Whether move to the end editable position.
2485 * @returns {Boolean} Whether range was moved.
2487 moveToElementEditablePosition: function( el
, isMoveToEnd
) {
2489 function nextDFS( node
, childOnly
) {
2492 if ( node
.type
== CKEDITOR
.NODE_ELEMENT
&& node
.isEditable( false ) )
2493 next
= node
[ isMoveToEnd
? 'getLast' : 'getFirst' ]( notIgnoredEval
);
2495 if ( !childOnly
&& !next
)
2496 next
= node
[ isMoveToEnd
? 'getPrevious' : 'getNext' ]( notIgnoredEval
);
2501 // Handle non-editable element e.g. HR.
2502 if ( el
.type
== CKEDITOR
.NODE_ELEMENT
&& !el
.isEditable( false ) ) {
2503 this.moveToPosition( el
, isMoveToEnd
? CKEDITOR
.POSITION_AFTER_END : CKEDITOR
.POSITION_BEFORE_START
);
2510 // Stop immediately if we've found a text node.
2511 if ( el
.type
== CKEDITOR
.NODE_TEXT
) {
2512 // Put cursor before block filler.
2513 if ( isMoveToEnd
&& this.endContainer
&& this.checkEndOfBlock() && nbspRegExp
.test( el
.getText() ) )
2514 this.moveToPosition( el
, CKEDITOR
.POSITION_BEFORE_START
);
2516 this.moveToPosition( el
, isMoveToEnd
? CKEDITOR
.POSITION_AFTER_END : CKEDITOR
.POSITION_BEFORE_START
);
2521 // If an editable element is found, move inside it, but not stop the searching.
2522 if ( el
.type
== CKEDITOR
.NODE_ELEMENT
) {
2523 if ( el
.isEditable() ) {
2524 this.moveToPosition( el
, isMoveToEnd
? CKEDITOR
.POSITION_BEFORE_END : CKEDITOR
.POSITION_AFTER_START
);
2527 // Put cursor before padding block br.
2528 else if ( isMoveToEnd
&& el
.is( 'br' ) && this.endContainer
&& this.checkEndOfBlock() )
2529 this.moveToPosition( el
, CKEDITOR
.POSITION_BEFORE_START
);
2530 // Special case - non-editable block. Select entire element, because it does not make sense
2531 // to place collapsed selection next to it, because browsers can't handle that.
2532 else if ( el
.getAttribute( 'contenteditable' ) == 'false' && el
.is( CKEDITOR
.dtd
.$block
) ) {
2533 this.setStartBefore( el
);
2534 this.setEndAfter( el
);
2539 el
= nextDFS( el
, found
);
2546 * Moves the range boundaries to the closest editing point after/before an
2547 * element or the current range position (depends on whether the element was specified).
2549 * For example, if the start element has `id="start"`,
2550 * `<p><b>foo</b><span id="start">start</start></p>`, the closest previous editing point is
2551 * `<p><b>foo</b>^<span id="start">start</start></p>` (between `<b>` and `<span>`).
2553 * See also: {@link #moveToElementEditablePosition}.
2556 * @param {CKEDITOR.dom.element} [element] The starting element. If not specified, the current range
2557 * position will be used.
2558 * @param {Boolean} [isMoveForward] Whether move to the end of editable. Otherwise, look back.
2559 * @returns {Boolean} Whether the range was moved.
2561 moveToClosestEditablePosition: function( element
, isMoveForward
) {
2562 // We don't want to modify original range if there's no editable position.
2567 positions
= [ CKEDITOR
.POSITION_AFTER_END
, CKEDITOR
.POSITION_BEFORE_START
];
2570 // Set collapsed range at one of ends of element.
2571 // Can't clone this range, because this range might not be yet positioned (no containers => errors).
2572 range
= new CKEDITOR
.dom
.range( this.root
);
2573 range
.moveToPosition( element
, positions
[ isMoveForward
? 0 : 1 ] );
2575 range
= this.clone();
2578 // Start element isn't a block, so we can automatically place range
2580 if ( element
&& !element
.is( CKEDITOR
.dtd
.$block
) )
2583 // Look for first node that fulfills eval function and place range next to it.
2584 sibling
= range
[ isMoveForward
? 'getNextEditableNode' : 'getPreviousEditableNode' ]();
2587 isElement
= sibling
.type
== CKEDITOR
.NODE_ELEMENT
;
2589 // Special case - eval accepts block element only if it's a non-editable block,
2590 // which we want to select, not place collapsed selection next to it (which browsers
2592 if ( isElement
&& sibling
.is( CKEDITOR
.dtd
.$block
) && sibling
.getAttribute( 'contenteditable' ) == 'false' ) {
2593 range
.setStartAt( sibling
, CKEDITOR
.POSITION_BEFORE_START
);
2594 range
.setEndAt( sibling
, CKEDITOR
.POSITION_AFTER_END
);
2596 // Handle empty blocks which can be selection containers on old IEs.
2597 else if ( !CKEDITOR
.env
.needsBrFiller
&& isElement
&& sibling
.is( CKEDITOR
.dom
.walker
.validEmptyBlockContainers
) ) {
2598 range
.setEnd( sibling
, 0 );
2601 range
.moveToPosition( sibling
, positions
[ isMoveForward
? 1 : 0 ] );
2607 this.moveToRange( range
);
2613 * See {@link #moveToElementEditablePosition}.
2615 * @returns {Boolean} Whether range was moved.
2617 moveToElementEditStart: function( target
) {
2618 return this.moveToElementEditablePosition( target
);
2622 * See {@link #moveToElementEditablePosition}.
2624 * @returns {Boolean} Whether range was moved.
2626 moveToElementEditEnd: function( target
) {
2627 return this.moveToElementEditablePosition( target
, true );
2631 * Get the single node enclosed within the range if there's one.
2633 * @returns {CKEDITOR.dom.node}
2635 getEnclosedNode: function() {
2636 var walkerRange
= this.clone();
2638 // Optimize and analyze the range to avoid DOM destructive nature of walker. (#5780)
2639 walkerRange
.optimize();
2640 if ( walkerRange
.startContainer
.type
!= CKEDITOR
.NODE_ELEMENT
|| walkerRange
.endContainer
.type
!= CKEDITOR
.NODE_ELEMENT
)
2643 var walker
= new CKEDITOR
.dom
.walker( walkerRange
),
2644 isNotBookmarks
= CKEDITOR
.dom
.walker
.bookmark( false, true ),
2645 isNotWhitespaces
= CKEDITOR
.dom
.walker
.whitespaces( true );
2647 walker
.evaluator = function( node
) {
2648 return isNotWhitespaces( node
) && isNotBookmarks( node
);
2650 var node
= walker
.next();
2652 return node
&& node
.equals( walker
.previous() ) ? node : null;
2656 * Get the node adjacent to the range start or {@link #startContainer}.
2658 * @returns {CKEDITOR.dom.node}
2660 getTouchedStartNode: function() {
2661 var container
= this.startContainer
;
2663 if ( this.collapsed
|| container
.type
!= CKEDITOR
.NODE_ELEMENT
)
2666 return container
.getChild( this.startOffset
) || container
;
2670 * Get the node adjacent to the range end or {@link #endContainer}.
2672 * @returns {CKEDITOR.dom.node}
2674 getTouchedEndNode: function() {
2675 var container
= this.endContainer
;
2677 if ( this.collapsed
|| container
.type
!= CKEDITOR
.NODE_ELEMENT
)
2680 return container
.getChild( this.endOffset
- 1 ) || container
;
2684 * Gets next node which can be a container of a selection.
2685 * This methods mimics a behavior of right/left arrow keys in case of
2686 * collapsed selection. It does not return an exact position (with offset) though,
2687 * but just a selection's container.
2689 * Note: use this method on a collapsed range.
2692 * @returns {CKEDITOR.dom.element/CKEDITOR.dom.text}
2694 getNextEditableNode: getNextEditableNode(),
2697 * See {@link #getNextEditableNode}.
2700 * @returns {CKEDITOR.dom.element/CKEDITOR.dom.text}
2702 getPreviousEditableNode: getNextEditableNode( 1 ),
2705 * Scrolls the start of current range into view.
2707 scrollIntoView: function() {
2709 // The reference element contains a zero-width space to avoid
2710 // a premature removal. The view is to be scrolled with respect
2712 var reference
= new CKEDITOR
.dom
.element
.createFromHtml( '<span> </span>', this.document
),
2713 afterCaretNode
, startContainerText
, isStartText
;
2715 var range
= this.clone();
2717 // Work with the range to obtain a proper caret position.
2720 // Currently in a text node, so we need to split it into two
2721 // halves and put the reference between.
2722 if ( isStartText
= range
.startContainer
.type
== CKEDITOR
.NODE_TEXT
) {
2723 // Keep the original content. It will be restored.
2724 startContainerText
= range
.startContainer
.getText();
2726 // Split the startContainer at the this position.
2727 afterCaretNode
= range
.startContainer
.split( range
.startOffset
);
2729 // Insert the reference between two text nodes.
2730 reference
.insertAfter( range
.startContainer
);
2733 // If not in a text node, simply insert the reference into the range.
2735 range
.insertNode( reference
);
2738 // Scroll with respect to the reference element.
2739 reference
.scrollIntoView();
2741 // Get rid of split parts if "in a text node" case.
2742 // Revert the original text of the startContainer.
2743 if ( isStartText
) {
2744 range
.startContainer
.setText( startContainerText
);
2745 afterCaretNode
.remove();
2748 // Get rid of the reference node. It is no longer necessary.
2753 * Setter for the {@link #startContainer}.
2757 * @param {CKEDITOR.dom.element} startContainer
2759 _setStartContainer: function( startContainer
) {
2761 var isRootAscendantOrSelf
= this.root
.equals( startContainer
) || this.root
.contains( startContainer
);
2763 if ( !isRootAscendantOrSelf
) {
2764 CKEDITOR
.warn( 'range-startcontainer', { startContainer: startContainer
, root: this.root
} );
2767 this.startContainer
= startContainer
;
2771 * Setter for the {@link #endContainer}.
2775 * @param {CKEDITOR.dom.element} endContainer
2777 _setEndContainer: function( endContainer
) {
2779 var isRootAscendantOrSelf
= this.root
.equals( endContainer
) || this.root
.contains( endContainer
);
2781 if ( !isRootAscendantOrSelf
) {
2782 CKEDITOR
.warn( 'range-endcontainer', { endContainer: endContainer
, root: this.root
} );
2785 this.endContainer
= endContainer
;
2793 * Indicates a position after start of a node.
2795 * // When used according to an element:
2796 * // <element>^contents</element>
2798 * // When used according to a text node:
2799 * // "^text" (range is anchored in the text node)
2801 * It is used as a parameter of methods like: {@link CKEDITOR.dom.range#moveToPosition},
2802 * {@link CKEDITOR.dom.range#setStartAt} and {@link CKEDITOR.dom.range#setEndAt}.
2806 * @property {Number} [=1]
2808 CKEDITOR
.POSITION_AFTER_START
= 1;
2811 * Indicates a position before end of a node.
2813 * // When used according to an element:
2814 * // <element>contents^</element>
2816 * // When used according to a text node:
2817 * // "text^" (range is anchored in the text node)
2819 * It is used as a parameter of methods like: {@link CKEDITOR.dom.range#moveToPosition},
2820 * {@link CKEDITOR.dom.range#setStartAt} and {@link CKEDITOR.dom.range#setEndAt}.
2824 * @property {Number} [=2]
2826 CKEDITOR
.POSITION_BEFORE_END
= 2;
2829 * Indicates a position before start of a node.
2831 * // When used according to an element:
2832 * // ^<element>contents</element> (range is anchored in element's parent)
2834 * // When used according to a text node:
2835 * // ^"text" (range is anchored in text node's parent)
2837 * It is used as a parameter of methods like: {@link CKEDITOR.dom.range#moveToPosition},
2838 * {@link CKEDITOR.dom.range#setStartAt} and {@link CKEDITOR.dom.range#setEndAt}.
2842 * @property {Number} [=3]
2844 CKEDITOR
.POSITION_BEFORE_START
= 3;
2847 * Indicates a position after end of a node.
2849 * // When used according to an element:
2850 * // <element>contents</element>^ (range is anchored in element's parent)
2852 * // When used according to a text node:
2853 * // "text"^ (range is anchored in text node's parent)
2855 * It is used as a parameter of methods like: {@link CKEDITOR.dom.range#moveToPosition},
2856 * {@link CKEDITOR.dom.range#setStartAt} and {@link CKEDITOR.dom.range#setEndAt}.
2860 * @property {Number} [=4]
2862 CKEDITOR
.POSITION_AFTER_END
= 4;
2864 CKEDITOR
.ENLARGE_ELEMENT
= 1;
2865 CKEDITOR
.ENLARGE_BLOCK_CONTENTS
= 2;
2866 CKEDITOR
.ENLARGE_LIST_ITEM_CONTENTS
= 3;
2867 CKEDITOR
.ENLARGE_INLINE
= 4;
2869 // Check boundary types.
2872 * See {@link CKEDITOR.dom.range#checkBoundaryOfElement}.
2876 * @property {Number} [=1]
2881 * See {@link CKEDITOR.dom.range#checkBoundaryOfElement}.
2885 * @property {Number} [=2]
2889 // Shrink range types.
2892 * See {@link CKEDITOR.dom.range#shrink}.
2896 * @property {Number} [=1]
2898 CKEDITOR
.SHRINK_ELEMENT
= 1;
2901 * See {@link CKEDITOR.dom.range#shrink}.
2905 * @property {Number} [=2]
2907 CKEDITOR
.SHRINK_TEXT
= 2;