]>
Commit | Line | Data |
---|---|---|
7adcb81e IB |
1 | /** |
2 | * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. | |
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license | |
4 | */ | |
5 | ||
6 | /** | |
7 | * @ignore | |
8 | * File overview: DOM iterator which iterates over list items, lines and paragraphs. | |
9 | */ | |
10 | ||
11 | 'use strict'; | |
12 | ||
13 | ( function() { | |
14 | /** | |
15 | * Represents the iterator class. It can be used to iterate | |
16 | * over all elements (or even text nodes in case of {@link #enlargeBr} set to `false`) | |
17 | * which establish "paragraph-like" spaces within the passed range. | |
18 | * | |
19 | * // <h1>[foo</h1><p>bar]</p> | |
20 | * var iterator = range.createIterator(); | |
21 | * iterator.getNextParagraph(); // h1 element | |
22 | * iterator.getNextParagraph(); // p element | |
23 | * | |
24 | * // <ul><li>[foo</li><li>bar]</li> | |
25 | * // With enforceRealBlocks set to false the iterator will return two list item elements. | |
26 | * // With enforceRealBlocks set to true the iterator will return two paragraphs and the DOM will be changed to: | |
27 | * // <ul><li><p>foo</p></li><li><p>bar</p></li> | |
28 | * | |
29 | * @class CKEDITOR.dom.iterator | |
30 | * @constructor Creates an iterator class instance. | |
31 | * @param {CKEDITOR.dom.range} range | |
32 | */ | |
33 | function iterator( range ) { | |
34 | if ( arguments.length < 1 ) | |
35 | return; | |
36 | ||
37 | /** | |
38 | * @readonly | |
39 | * @property {CKEDITOR.dom.range} | |
40 | */ | |
41 | this.range = range; | |
42 | ||
43 | /** | |
44 | * @property {Boolean} [forceBrBreak=false] | |
45 | */ | |
46 | this.forceBrBreak = 0; | |
47 | ||
48 | // (#3730). | |
49 | /** | |
50 | * Whether to include `<br>` elements in the enlarged range. Should be | |
51 | * set to `false` when using the iterator in the {@link CKEDITOR#ENTER_BR} mode. | |
52 | * | |
53 | * @property {Boolean} [enlargeBr=true] | |
54 | */ | |
55 | this.enlargeBr = 1; | |
56 | ||
57 | /** | |
58 | * Whether the iterator should create a transformable block | |
59 | * if the current one contains text and cannot be transformed. | |
60 | * For example new blocks will be established in elements like | |
61 | * `<li>` or `<td>`. | |
62 | * | |
63 | * @property {Boolean} [enforceRealBlocks=false] | |
64 | */ | |
65 | this.enforceRealBlocks = 0; | |
66 | ||
67 | this._ || ( this._ = {} ); | |
68 | } | |
69 | ||
70 | /** | |
71 | * Default iterator's filter. It is set only for nested iterators. | |
72 | * | |
73 | * @since 4.3 | |
74 | * @readonly | |
75 | * @property {CKEDITOR.filter} filter | |
76 | */ | |
77 | ||
78 | /** | |
79 | * Iterator's active filter. It is set by the {@link #getNextParagraph} method | |
80 | * when it enters a nested editable. | |
81 | * | |
82 | * @since 4.3 | |
83 | * @readonly | |
84 | * @property {CKEDITOR.filter} activeFilter | |
85 | */ | |
86 | ||
87 | var beginWhitespaceRegex = /^[\r\n\t ]+$/, | |
88 | // Ignore bookmark nodes.(#3783) | |
89 | bookmarkGuard = CKEDITOR.dom.walker.bookmark( false, true ), | |
90 | whitespacesGuard = CKEDITOR.dom.walker.whitespaces( true ), | |
91 | skipGuard = function( node ) { | |
92 | return bookmarkGuard( node ) && whitespacesGuard( node ); | |
93 | }, | |
94 | listItemNames = { dd: 1, dt: 1, li: 1 }; | |
95 | ||
96 | iterator.prototype = { | |
97 | /** | |
98 | * Returns the next paragraph-like element or `null` if the end of a range is reached. | |
99 | * | |
100 | * @param {String} [blockTag='p'] Name of a block element which will be established by | |
101 | * the iterator in block-less elements (see {@link #enforceRealBlocks}). | |
102 | */ | |
103 | getNextParagraph: function( blockTag ) { | |
104 | // The block element to be returned. | |
105 | var block; | |
106 | ||
107 | // The range object used to identify the paragraph contents. | |
108 | var range; | |
109 | ||
110 | // Indicats that the current element in the loop is the last one. | |
111 | var isLast; | |
112 | ||
113 | // Instructs to cleanup remaining BRs. | |
114 | var removePreviousBr, removeLastBr; | |
115 | ||
116 | blockTag = blockTag || 'p'; | |
117 | ||
118 | // We're iterating over nested editable. | |
119 | if ( this._.nestedEditable ) { | |
120 | // Get next block from nested iterator and returns it if was found. | |
121 | block = this._.nestedEditable.iterator.getNextParagraph( blockTag ); | |
122 | if ( block ) { | |
123 | // Inherit activeFilter from the nested iterator. | |
124 | this.activeFilter = this._.nestedEditable.iterator.activeFilter; | |
125 | return block; | |
126 | } | |
127 | ||
128 | // No block in nested iterator means that we reached the end of the nested editable. | |
129 | // Reset the active filter to the default filter (or undefined if this iterator didn't have it). | |
130 | this.activeFilter = this.filter; | |
131 | ||
132 | // Try to find next nested editable or get back to parent (this) iterator. | |
133 | if ( startNestedEditableIterator( this, blockTag, this._.nestedEditable.container, this._.nestedEditable.remaining ) ) { | |
134 | // Inherit activeFilter from the nested iterator. | |
135 | this.activeFilter = this._.nestedEditable.iterator.activeFilter; | |
136 | return this._.nestedEditable.iterator.getNextParagraph( blockTag ); | |
137 | } else { | |
138 | this._.nestedEditable = null; | |
139 | } | |
140 | } | |
141 | ||
142 | // Block-less range should be checked first. | |
143 | if ( !this.range.root.getDtd()[ blockTag ] ) | |
144 | return null; | |
145 | ||
146 | // This is the first iteration. Let's initialize it. | |
147 | if ( !this._.started ) | |
148 | range = startIterator.call( this ); | |
149 | ||
150 | var currentNode = this._.nextNode, | |
151 | lastNode = this._.lastNode; | |
152 | ||
153 | this._.nextNode = null; | |
154 | while ( currentNode ) { | |
155 | // closeRange indicates that a paragraph boundary has been found, | |
156 | // so the range can be closed. | |
157 | var closeRange = 0, | |
158 | parentPre = currentNode.hasAscendant( 'pre' ); | |
159 | ||
160 | // includeNode indicates that the current node is good to be part | |
161 | // of the range. By default, any non-element node is ok for it. | |
162 | var includeNode = ( currentNode.type != CKEDITOR.NODE_ELEMENT ), | |
163 | continueFromSibling = 0; | |
164 | ||
165 | // If it is an element node, let's check if it can be part of the range. | |
166 | if ( !includeNode ) { | |
167 | var nodeName = currentNode.getName(); | |
168 | ||
169 | // Non-editable block was found - return it and move to processing | |
170 | // its nested editables if they exist. | |
171 | if ( CKEDITOR.dtd.$block[ nodeName ] && currentNode.getAttribute( 'contenteditable' ) == 'false' ) { | |
172 | block = currentNode; | |
173 | ||
174 | // Setup iterator for first of nested editables. | |
175 | // If there's no editable, then algorithm will move to next element after current block. | |
176 | startNestedEditableIterator( this, blockTag, block ); | |
177 | ||
178 | // Gets us straight to the end of getParagraph() because block variable is set. | |
179 | break; | |
180 | } else if ( currentNode.isBlockBoundary( this.forceBrBreak && !parentPre && { br: 1 } ) ) { | |
181 | // <br> boundaries must be part of the range. It will | |
182 | // happen only if ForceBrBreak. | |
183 | if ( nodeName == 'br' ) | |
184 | includeNode = 1; | |
185 | else if ( !range && !currentNode.getChildCount() && nodeName != 'hr' ) { | |
186 | // If we have found an empty block, and haven't started | |
187 | // the range yet, it means we must return this block. | |
188 | block = currentNode; | |
189 | isLast = currentNode.equals( lastNode ); | |
190 | break; | |
191 | } | |
192 | ||
193 | // The range must finish right before the boundary, | |
194 | // including possibly skipped empty spaces. (#1603) | |
195 | if ( range ) { | |
196 | range.setEndAt( currentNode, CKEDITOR.POSITION_BEFORE_START ); | |
197 | ||
198 | // The found boundary must be set as the next one at this | |
199 | // point. (#1717) | |
200 | if ( nodeName != 'br' ) { | |
201 | this._.nextNode = currentNode; | |
202 | } | |
203 | } | |
204 | ||
205 | closeRange = 1; | |
206 | } else { | |
207 | // If we have child nodes, let's check them. | |
208 | if ( currentNode.getFirst() ) { | |
209 | // If we don't have a range yet, let's start it. | |
210 | if ( !range ) { | |
211 | range = this.range.clone(); | |
212 | range.setStartAt( currentNode, CKEDITOR.POSITION_BEFORE_START ); | |
213 | } | |
214 | ||
215 | currentNode = currentNode.getFirst(); | |
216 | continue; | |
217 | } | |
218 | includeNode = 1; | |
219 | } | |
220 | } else if ( currentNode.type == CKEDITOR.NODE_TEXT ) { | |
221 | // Ignore normal whitespaces (i.e. not including or | |
222 | // other unicode whitespaces) before/after a block node. | |
223 | if ( beginWhitespaceRegex.test( currentNode.getText() ) ) | |
224 | includeNode = 0; | |
225 | } | |
226 | ||
227 | // The current node is good to be part of the range and we are | |
228 | // starting a new range, initialize it first. | |
229 | if ( includeNode && !range ) { | |
230 | range = this.range.clone(); | |
231 | range.setStartAt( currentNode, CKEDITOR.POSITION_BEFORE_START ); | |
232 | } | |
233 | ||
234 | // The last node has been found. | |
235 | isLast = ( ( !closeRange || includeNode ) && currentNode.equals( lastNode ) ); | |
236 | ||
237 | // If we are in an element boundary, let's check if it is time | |
238 | // to close the range, otherwise we include the parent within it. | |
239 | if ( range && !closeRange ) { | |
240 | while ( !currentNode.getNext( skipGuard ) && !isLast ) { | |
241 | var parentNode = currentNode.getParent(); | |
242 | ||
243 | if ( parentNode.isBlockBoundary( this.forceBrBreak && !parentPre && { br: 1 } ) ) { | |
244 | closeRange = 1; | |
245 | includeNode = 0; | |
246 | isLast = isLast || ( parentNode.equals( lastNode ) ); | |
247 | // Make sure range includes bookmarks at the end of the block. (#7359) | |
248 | range.setEndAt( parentNode, CKEDITOR.POSITION_BEFORE_END ); | |
249 | break; | |
250 | } | |
251 | ||
252 | currentNode = parentNode; | |
253 | includeNode = 1; | |
254 | isLast = ( currentNode.equals( lastNode ) ); | |
255 | continueFromSibling = 1; | |
256 | } | |
257 | } | |
258 | ||
259 | // Now finally include the node. | |
260 | if ( includeNode ) | |
261 | range.setEndAt( currentNode, CKEDITOR.POSITION_AFTER_END ); | |
262 | ||
263 | currentNode = this._getNextSourceNode( currentNode, continueFromSibling, lastNode ); | |
264 | isLast = !currentNode; | |
265 | ||
266 | // We have found a block boundary. Let's close the range and move out of the | |
267 | // loop. | |
268 | if ( isLast || ( closeRange && range ) ) | |
269 | break; | |
270 | } | |
271 | ||
272 | // Now, based on the processed range, look for (or create) the block to be returned. | |
273 | if ( !block ) { | |
274 | // If no range has been found, this is the end. | |
275 | if ( !range ) { | |
276 | this._.docEndMarker && this._.docEndMarker.remove(); | |
277 | this._.nextNode = null; | |
278 | return null; | |
279 | } | |
280 | ||
281 | var startPath = new CKEDITOR.dom.elementPath( range.startContainer, range.root ); | |
282 | var startBlockLimit = startPath.blockLimit, | |
283 | checkLimits = { div: 1, th: 1, td: 1 }; | |
284 | block = startPath.block; | |
285 | ||
286 | if ( !block && startBlockLimit && !this.enforceRealBlocks && checkLimits[ startBlockLimit.getName() ] && | |
287 | range.checkStartOfBlock() && range.checkEndOfBlock() && !startBlockLimit.equals( range.root ) ) { | |
288 | block = startBlockLimit; | |
289 | } else if ( !block || ( this.enforceRealBlocks && block.is( listItemNames ) ) ) { | |
290 | // Create the fixed block. | |
291 | block = this.range.document.createElement( blockTag ); | |
292 | ||
293 | // Move the contents of the temporary range to the fixed block. | |
294 | range.extractContents().appendTo( block ); | |
295 | block.trim(); | |
296 | ||
297 | // Insert the fixed block into the DOM. | |
298 | range.insertNode( block ); | |
299 | ||
300 | removePreviousBr = removeLastBr = true; | |
301 | } else if ( block.getName() != 'li' ) { | |
302 | // If the range doesn't includes the entire contents of the | |
303 | // block, we must split it, isolating the range in a dedicated | |
304 | // block. | |
305 | if ( !range.checkStartOfBlock() || !range.checkEndOfBlock() ) { | |
306 | // The resulting block will be a clone of the current one. | |
307 | block = block.clone( false ); | |
308 | ||
309 | // Extract the range contents, moving it to the new block. | |
310 | range.extractContents().appendTo( block ); | |
311 | block.trim(); | |
312 | ||
313 | // Split the block. At this point, the range will be in the | |
314 | // right position for our intents. | |
315 | var splitInfo = range.splitBlock(); | |
316 | ||
317 | removePreviousBr = !splitInfo.wasStartOfBlock; | |
318 | removeLastBr = !splitInfo.wasEndOfBlock; | |
319 | ||
320 | // Insert the new block into the DOM. | |
321 | range.insertNode( block ); | |
322 | } | |
323 | } else if ( !isLast ) { | |
324 | // LIs are returned as is, with all their children (due to the | |
325 | // nested lists). But, the next node is the node right after | |
326 | // the current range, which could be an <li> child (nested | |
327 | // lists) or the next sibling <li>. | |
328 | ||
329 | this._.nextNode = ( block.equals( lastNode ) ? null : this._getNextSourceNode( range.getBoundaryNodes().endNode, 1, lastNode ) ); | |
330 | } | |
331 | } | |
332 | ||
333 | if ( removePreviousBr ) { | |
334 | var previousSibling = block.getPrevious(); | |
335 | if ( previousSibling && previousSibling.type == CKEDITOR.NODE_ELEMENT ) { | |
336 | if ( previousSibling.getName() == 'br' ) | |
337 | previousSibling.remove(); | |
338 | else if ( previousSibling.getLast() && previousSibling.getLast().$.nodeName.toLowerCase() == 'br' ) | |
339 | previousSibling.getLast().remove(); | |
340 | } | |
341 | } | |
342 | ||
343 | if ( removeLastBr ) { | |
344 | var lastChild = block.getLast(); | |
345 | if ( lastChild && lastChild.type == CKEDITOR.NODE_ELEMENT && lastChild.getName() == 'br' ) { | |
346 | // Remove br filler on browser which do not need it. | |
347 | if ( !CKEDITOR.env.needsBrFiller || lastChild.getPrevious( bookmarkGuard ) || lastChild.getNext( bookmarkGuard ) ) | |
348 | lastChild.remove(); | |
349 | } | |
350 | } | |
351 | ||
352 | // Get a reference for the next element. This is important because the | |
353 | // above block can be removed or changed, so we can rely on it for the | |
354 | // next interation. | |
355 | if ( !this._.nextNode ) { | |
356 | this._.nextNode = ( isLast || block.equals( lastNode ) || !lastNode ) ? null : this._getNextSourceNode( block, 1, lastNode ); | |
357 | } | |
358 | ||
359 | return block; | |
360 | }, | |
361 | ||
362 | /** | |
363 | * Gets the next element to check or `null` when the `lastNode` or the | |
364 | * {@link #range}'s {@link CKEDITOR.dom.range#root root} is reached. Bookmarks are skipped. | |
365 | * | |
366 | * @since 4.4.6 | |
367 | * @private | |
368 | * @param {CKEDITOR.dom.node} node | |
369 | * @param {Boolean} startFromSibling | |
370 | * @param {CKEDITOR.dom.node} lastNode | |
371 | * @returns {CKEDITOR.dom.node} | |
372 | */ | |
373 | _getNextSourceNode: function( node, startFromSibling, lastNode ) { | |
374 | var rootNode = this.range.root, | |
375 | next; | |
376 | ||
377 | // Here we are checking in guard function whether current element | |
378 | // reach lastNode(default behaviour) and root node to prevent against | |
379 | // getting out of editor instance root DOM object. | |
380 | // #12484 | |
381 | function guardFunction( node ) { | |
382 | return !( node.equals( lastNode ) || node.equals( rootNode ) ); | |
383 | } | |
384 | ||
385 | next = node.getNextSourceNode( startFromSibling, null, guardFunction ); | |
386 | while ( !bookmarkGuard( next ) ) { | |
387 | next = next.getNextSourceNode( startFromSibling, null, guardFunction ); | |
388 | } | |
389 | return next; | |
390 | } | |
391 | }; | |
392 | ||
393 | // @context CKEDITOR.dom.iterator | |
394 | // @returns Collapsed range which will be reused when during furter processing. | |
395 | function startIterator() { | |
396 | var range = this.range.clone(), | |
397 | // Indicate at least one of the range boundaries is inside a preformat block. | |
398 | touchPre, | |
399 | ||
400 | // (#12178) | |
401 | // Remember if following situation takes place: | |
402 | // * startAtInnerBoundary: <p>foo[</p>... | |
403 | // * endAtInnerBoundary: ...<p>]bar</p> | |
404 | // Because information about line break will be lost when shrinking range. | |
405 | // Note that we test only if path block exist, because we must properly shrink | |
406 | // range containing table and/or table cells. | |
407 | // Note: When range is collapsed there's no way it can be shrinked. | |
408 | // By checking if range is collapsed we also prevent #12308. | |
409 | startPath = range.startPath(), | |
410 | endPath = range.endPath(), | |
411 | startAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, startPath.block ), | |
412 | endAtInnerBoundary = !range.collapsed && rangeAtInnerBlockBoundary( range, endPath.block, 1 ); | |
413 | ||
414 | // Shrink the range to exclude harmful "noises" (#4087, #4450, #5435). | |
415 | range.shrink( CKEDITOR.SHRINK_ELEMENT, true ); | |
416 | ||
417 | if ( startAtInnerBoundary ) | |
418 | range.setStartAt( startPath.block, CKEDITOR.POSITION_BEFORE_END ); | |
419 | if ( endAtInnerBoundary ) | |
420 | range.setEndAt( endPath.block, CKEDITOR.POSITION_AFTER_START ); | |
421 | ||
422 | touchPre = range.endContainer.hasAscendant( 'pre', true ) || range.startContainer.hasAscendant( 'pre', true ); | |
423 | ||
424 | range.enlarge( this.forceBrBreak && !touchPre || !this.enlargeBr ? CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS : CKEDITOR.ENLARGE_BLOCK_CONTENTS ); | |
425 | ||
426 | if ( !range.collapsed ) { | |
427 | var walker = new CKEDITOR.dom.walker( range.clone() ), | |
428 | ignoreBookmarkTextEvaluator = CKEDITOR.dom.walker.bookmark( true, true ); | |
429 | // Avoid anchor inside bookmark inner text. | |
430 | walker.evaluator = ignoreBookmarkTextEvaluator; | |
431 | this._.nextNode = walker.next(); | |
432 | // TODO: It's better to have walker.reset() used here. | |
433 | walker = new CKEDITOR.dom.walker( range.clone() ); | |
434 | walker.evaluator = ignoreBookmarkTextEvaluator; | |
435 | var lastNode = walker.previous(); | |
436 | this._.lastNode = lastNode.getNextSourceNode( true, null, range.root ); | |
437 | ||
438 | // We may have an empty text node at the end of block due to [3770]. | |
439 | // If that node is the lastNode, it would cause our logic to leak to the | |
440 | // next block.(#3887) | |
441 | if ( this._.lastNode && this._.lastNode.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.trim( this._.lastNode.getText() ) && this._.lastNode.getParent().isBlockBoundary() ) { | |
442 | var testRange = this.range.clone(); | |
443 | testRange.moveToPosition( this._.lastNode, CKEDITOR.POSITION_AFTER_END ); | |
444 | if ( testRange.checkEndOfBlock() ) { | |
445 | var path = new CKEDITOR.dom.elementPath( testRange.endContainer, testRange.root ), | |
446 | lastBlock = path.block || path.blockLimit; | |
447 | this._.lastNode = lastBlock.getNextSourceNode( true ); | |
448 | } | |
449 | } | |
450 | ||
451 | // The end of document or range.root was reached, so we need a marker node inside. | |
452 | if ( !this._.lastNode || !range.root.contains( this._.lastNode ) ) { | |
453 | this._.lastNode = this._.docEndMarker = range.document.createText( '' ); | |
454 | this._.lastNode.insertAfter( lastNode ); | |
455 | } | |
456 | ||
457 | // Let's reuse this variable. | |
458 | range = null; | |
459 | } | |
460 | ||
461 | this._.started = 1; | |
462 | ||
463 | return range; | |
464 | } | |
465 | ||
466 | // Does a nested editables lookup inside editablesContainer. | |
467 | // If remainingEditables is set will lookup inside this array. | |
468 | // @param {CKEDITOR.dom.element} editablesContainer | |
469 | // @param {CKEDITOR.dom.element[]} [remainingEditables] | |
470 | function getNestedEditableIn( editablesContainer, remainingEditables ) { | |
471 | if ( remainingEditables == null ) | |
472 | remainingEditables = findNestedEditables( editablesContainer ); | |
473 | ||
474 | var editable; | |
475 | ||
476 | while ( ( editable = remainingEditables.shift() ) ) { | |
477 | if ( isIterableEditable( editable ) ) | |
478 | return { element: editable, remaining: remainingEditables }; | |
479 | } | |
480 | ||
481 | return null; | |
482 | } | |
483 | ||
484 | // Checkes whether we can iterate over this editable. | |
485 | function isIterableEditable( editable ) { | |
486 | // Reject blockless editables. | |
487 | return editable.getDtd().p; | |
488 | } | |
489 | ||
490 | // Finds nested editables within container. Does not return | |
491 | // editables nested in another editable (twice). | |
492 | function findNestedEditables( container ) { | |
493 | var editables = []; | |
494 | ||
495 | container.forEach( function( element ) { | |
496 | if ( element.getAttribute( 'contenteditable' ) == 'true' ) { | |
497 | editables.push( element ); | |
498 | return false; // Skip children. | |
499 | } | |
500 | }, CKEDITOR.NODE_ELEMENT, true ); | |
501 | ||
502 | return editables; | |
503 | } | |
504 | ||
505 | // Looks for a first nested editable after previousEditable (if passed) and creates | |
506 | // nested iterator for it. | |
507 | function startNestedEditableIterator( parentIterator, blockTag, editablesContainer, remainingEditables ) { | |
508 | var editable = getNestedEditableIn( editablesContainer, remainingEditables ); | |
509 | ||
510 | if ( !editable ) | |
511 | return 0; | |
512 | ||
513 | var filter = CKEDITOR.filter.instances[ editable.element.data( 'cke-filter' ) ]; | |
514 | ||
515 | // If current editable has a filter and this filter does not allow for block tag, | |
516 | // search for next nested editable in remaining ones. | |
517 | if ( filter && !filter.check( blockTag ) ) | |
518 | return startNestedEditableIterator( parentIterator, blockTag, editablesContainer, editable.remaining ); | |
519 | ||
520 | var range = new CKEDITOR.dom.range( editable.element ); | |
521 | range.selectNodeContents( editable.element ); | |
522 | ||
523 | var iterator = range.createIterator(); | |
524 | // This setting actually does not change anything in this case, | |
525 | // because entire range contents is selected, so there're no <br>s to be included. | |
526 | // But it seems right to copy it too. | |
527 | iterator.enlargeBr = parentIterator.enlargeBr; | |
528 | // Inherit configuration from parent iterator. | |
529 | iterator.enforceRealBlocks = parentIterator.enforceRealBlocks; | |
530 | // Set the activeFilter (which can be overriden when this iteator will start nested iterator) | |
531 | // and the default filter, which will make it possible to reset to | |
532 | // current iterator's activeFilter after leaving nested editable. | |
533 | iterator.activeFilter = iterator.filter = filter; | |
534 | ||
535 | parentIterator._.nestedEditable = { | |
536 | element: editable.element, | |
537 | container: editablesContainer, | |
538 | remaining: editable.remaining, | |
539 | iterator: iterator | |
540 | }; | |
541 | ||
542 | return 1; | |
543 | } | |
544 | ||
545 | // Checks whether range starts or ends at inner block boundary. | |
546 | // See usage comments to learn more. | |
547 | function rangeAtInnerBlockBoundary( range, block, checkEnd ) { | |
548 | if ( !block ) | |
549 | return false; | |
550 | ||
551 | var testRange = range.clone(); | |
552 | testRange.collapse( !checkEnd ); | |
553 | return testRange.checkBoundaryOfElement( block, checkEnd ? CKEDITOR.START : CKEDITOR.END ); | |
554 | } | |
555 | ||
556 | /** | |
557 | * Creates a {CKEDITOR.dom.iterator} instance for this range. | |
558 | * | |
559 | * @member CKEDITOR.dom.range | |
560 | * @returns {CKEDITOR.dom.iterator} | |
561 | */ | |
562 | CKEDITOR.dom.range.prototype.createIterator = function() { | |
563 | return new iterator( this ); | |
564 | }; | |
565 | } )(); |