diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-01-25 17:45:33 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-01-25 18:00:33 +0100 |
commit | 7adcb81e4f83f98c468889aaa5a85558ba88c770 (patch) | |
tree | 0d6ede733777b29060b48df4afaa2c64bfbae276 /sources/core/dom/iterator.js | |
download | connexionswing-ckeditor-component-7adcb81e4f83f98c468889aaa5a85558ba88c770.tar.gz connexionswing-ckeditor-component-7adcb81e4f83f98c468889aaa5a85558ba88c770.tar.zst connexionswing-ckeditor-component-7adcb81e4f83f98c468889aaa5a85558ba88c770.zip |
Initial commit4.5.6
Diffstat (limited to 'sources/core/dom/iterator.js')
-rw-r--r-- | sources/core/dom/iterator.js | 565 |
1 files changed, 565 insertions, 0 deletions
diff --git a/sources/core/dom/iterator.js b/sources/core/dom/iterator.js new file mode 100644 index 00000000..99491218 --- /dev/null +++ b/sources/core/dom/iterator.js | |||
@@ -0,0 +1,565 @@ | |||
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 | } )(); | ||