diff options
author | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-02-19 23:38:52 +0100 |
---|---|---|
committer | Ismaël Bouya <ismael.bouya@normalesup.org> | 2016-02-19 23:38:52 +0100 |
commit | 3332bebe4da6dfa0fe3e4b2abddc84b1cc62f8f5 (patch) | |
tree | a4f77655fe55b79606e7d3416504686a1ab8b058 /sources/core/dom/walker.js | |
download | piedsjaloux-ckeditor-component-3332bebe4da6dfa0fe3e4b2abddc84b1cc62f8f5.tar.gz piedsjaloux-ckeditor-component-3332bebe4da6dfa0fe3e4b2abddc84b1cc62f8f5.tar.zst piedsjaloux-ckeditor-component-3332bebe4da6dfa0fe3e4b2abddc84b1cc62f8f5.zip |
Initial commit4.5.7
Diffstat (limited to 'sources/core/dom/walker.js')
-rw-r--r-- | sources/core/dom/walker.js | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/sources/core/dom/walker.js b/sources/core/dom/walker.js new file mode 100644 index 0000000..746b406 --- /dev/null +++ b/sources/core/dom/walker.js | |||
@@ -0,0 +1,652 @@ | |||
1 | /** | ||
2 | * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. | ||
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license | ||
4 | */ | ||
5 | |||
6 | ( function() { | ||
7 | // This function is to be called under a "walker" instance scope. | ||
8 | function iterate( rtl, breakOnFalse ) { | ||
9 | var range = this.range; | ||
10 | |||
11 | // Return null if we have reached the end. | ||
12 | if ( this._.end ) | ||
13 | return null; | ||
14 | |||
15 | // This is the first call. Initialize it. | ||
16 | if ( !this._.start ) { | ||
17 | this._.start = 1; | ||
18 | |||
19 | // A collapsed range must return null at first call. | ||
20 | if ( range.collapsed ) { | ||
21 | this.end(); | ||
22 | return null; | ||
23 | } | ||
24 | |||
25 | // Move outside of text node edges. | ||
26 | range.optimize(); | ||
27 | } | ||
28 | |||
29 | var node, | ||
30 | startCt = range.startContainer, | ||
31 | endCt = range.endContainer, | ||
32 | startOffset = range.startOffset, | ||
33 | endOffset = range.endOffset, | ||
34 | guard, | ||
35 | userGuard = this.guard, | ||
36 | type = this.type, | ||
37 | getSourceNodeFn = ( rtl ? 'getPreviousSourceNode' : 'getNextSourceNode' ); | ||
38 | |||
39 | // Create the LTR guard function, if necessary. | ||
40 | if ( !rtl && !this._.guardLTR ) { | ||
41 | // The node that stops walker from moving up. | ||
42 | var limitLTR = endCt.type == CKEDITOR.NODE_ELEMENT ? endCt : endCt.getParent(); | ||
43 | |||
44 | // The node that stops the walker from going to next. | ||
45 | var blockerLTR = endCt.type == CKEDITOR.NODE_ELEMENT ? endCt.getChild( endOffset ) : endCt.getNext(); | ||
46 | |||
47 | this._.guardLTR = function( node, movingOut ) { | ||
48 | return ( ( !movingOut || !limitLTR.equals( node ) ) && ( !blockerLTR || !node.equals( blockerLTR ) ) && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) ); | ||
49 | }; | ||
50 | } | ||
51 | |||
52 | // Create the RTL guard function, if necessary. | ||
53 | if ( rtl && !this._.guardRTL ) { | ||
54 | // The node that stops walker from moving up. | ||
55 | var limitRTL = startCt.type == CKEDITOR.NODE_ELEMENT ? startCt : startCt.getParent(); | ||
56 | |||
57 | // The node that stops the walker from going to next. | ||
58 | var blockerRTL = startCt.type == CKEDITOR.NODE_ELEMENT ? startOffset ? startCt.getChild( startOffset - 1 ) : null : startCt.getPrevious(); | ||
59 | |||
60 | this._.guardRTL = function( node, movingOut ) { | ||
61 | return ( ( !movingOut || !limitRTL.equals( node ) ) && ( !blockerRTL || !node.equals( blockerRTL ) ) && ( node.type != CKEDITOR.NODE_ELEMENT || !movingOut || !node.equals( range.root ) ) ); | ||
62 | }; | ||
63 | } | ||
64 | |||
65 | // Define which guard function to use. | ||
66 | var stopGuard = rtl ? this._.guardRTL : this._.guardLTR; | ||
67 | |||
68 | // Make the user defined guard function participate in the process, | ||
69 | // otherwise simply use the boundary guard. | ||
70 | if ( userGuard ) { | ||
71 | guard = function( node, movingOut ) { | ||
72 | if ( stopGuard( node, movingOut ) === false ) | ||
73 | return false; | ||
74 | |||
75 | return userGuard( node, movingOut ); | ||
76 | }; | ||
77 | } else { | ||
78 | guard = stopGuard; | ||
79 | } | ||
80 | |||
81 | if ( this.current ) | ||
82 | node = this.current[ getSourceNodeFn ]( false, type, guard ); | ||
83 | else { | ||
84 | // Get the first node to be returned. | ||
85 | if ( rtl ) { | ||
86 | node = endCt; | ||
87 | |||
88 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { | ||
89 | if ( endOffset > 0 ) | ||
90 | node = node.getChild( endOffset - 1 ); | ||
91 | else | ||
92 | node = ( guard( node, true ) === false ) ? null : node.getPreviousSourceNode( true, type, guard ); | ||
93 | } | ||
94 | } else { | ||
95 | node = startCt; | ||
96 | |||
97 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { | ||
98 | if ( !( node = node.getChild( startOffset ) ) ) | ||
99 | node = ( guard( startCt, true ) === false ) ? null : startCt.getNextSourceNode( true, type, guard ); | ||
100 | } | ||
101 | } | ||
102 | |||
103 | if ( node && guard( node ) === false ) | ||
104 | node = null; | ||
105 | } | ||
106 | |||
107 | while ( node && !this._.end ) { | ||
108 | this.current = node; | ||
109 | |||
110 | if ( !this.evaluator || this.evaluator( node ) !== false ) { | ||
111 | if ( !breakOnFalse ) | ||
112 | return node; | ||
113 | } else if ( breakOnFalse && this.evaluator ) { | ||
114 | return false; | ||
115 | } | ||
116 | |||
117 | node = node[ getSourceNodeFn ]( false, type, guard ); | ||
118 | } | ||
119 | |||
120 | this.end(); | ||
121 | return this.current = null; | ||
122 | } | ||
123 | |||
124 | function iterateToLast( rtl ) { | ||
125 | var node, | ||
126 | last = null; | ||
127 | |||
128 | while ( ( node = iterate.call( this, rtl ) ) ) | ||
129 | last = node; | ||
130 | |||
131 | return last; | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Utility class to "walk" the DOM inside range boundaries. If the | ||
136 | * range starts or ends in the middle of the text node, this node will | ||
137 | * be included as a whole. Outside changes to the range may break the walker. | ||
138 | * | ||
139 | * The walker may return nodes that are not totally included in the | ||
140 | * range boundaries. Let us take the following range representation, | ||
141 | * where the square brackets indicate the boundaries: | ||
142 | * | ||
143 | * [<p>Some <b>sample] text</b> | ||
144 | * | ||
145 | * While walking forward into the above range, the following nodes are | ||
146 | * returned: `<p>`, `"Some "`, `<b>` and `"sample"`. Going | ||
147 | * backwards instead we have: `"sample"` and `"Some "`. So note that the | ||
148 | * walker always returns nodes when "entering" them, but not when | ||
149 | * "leaving" them. The {@link #guard} function is instead called both when | ||
150 | * entering and when leaving nodes. | ||
151 | * | ||
152 | * @class | ||
153 | */ | ||
154 | CKEDITOR.dom.walker = CKEDITOR.tools.createClass( { | ||
155 | /** | ||
156 | * Creates a walker class instance. | ||
157 | * | ||
158 | * @constructor | ||
159 | * @param {CKEDITOR.dom.range} range The range within which to walk. | ||
160 | */ | ||
161 | $: function( range ) { | ||
162 | this.range = range; | ||
163 | |||
164 | /** | ||
165 | * A function executed for every matched node to check whether | ||
166 | * it is to be considered in the walk or not. If not provided, all | ||
167 | * matched nodes are considered good. | ||
168 | * | ||
169 | * If the function returns `false`, the node is ignored. | ||
170 | * | ||
171 | * @property {Function} evaluator | ||
172 | */ | ||
173 | // this.evaluator = null; | ||
174 | |||
175 | /** | ||
176 | * A function executed for every node the walk passes by to check | ||
177 | * whether the walk is to be finished. It is called both when | ||
178 | * entering and when exiting nodes, as well as for the matched nodes. | ||
179 | * | ||
180 | * If this function returns `false`, the walking ends and no more | ||
181 | * nodes are evaluated. | ||
182 | |||
183 | * @property {Function} guard | ||
184 | */ | ||
185 | // this.guard = null; | ||
186 | |||
187 | /** @private */ | ||
188 | this._ = {}; | ||
189 | }, | ||
190 | |||
191 | // statics : | ||
192 | // { | ||
193 | // /* Creates a CKEDITOR.dom.walker instance to walk inside DOM boundaries set by nodes. | ||
194 | // * @param {CKEDITOR.dom.node} startNode The node from which the walk | ||
195 | // * will start. | ||
196 | // * @param {CKEDITOR.dom.node} [endNode] The last node to be considered | ||
197 | // * in the walk. No more nodes are retrieved after touching or | ||
198 | // * passing it. If not provided, the walker stops at the | ||
199 | // * <body> closing boundary. | ||
200 | // * @returns {CKEDITOR.dom.walker} A DOM walker for the nodes between the | ||
201 | // * provided nodes. | ||
202 | // */ | ||
203 | // createOnNodes : function( startNode, endNode, startInclusive, endInclusive ) | ||
204 | // { | ||
205 | // var range = new CKEDITOR.dom.range(); | ||
206 | // if ( startNode ) | ||
207 | // range.setStartAt( startNode, startInclusive ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_END ) ; | ||
208 | // else | ||
209 | // range.setStartAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_AFTER_START ) ; | ||
210 | // | ||
211 | // if ( endNode ) | ||
212 | // range.setEndAt( endNode, endInclusive ? CKEDITOR.POSITION_AFTER_END : CKEDITOR.POSITION_BEFORE_START ) ; | ||
213 | // else | ||
214 | // range.setEndAt( startNode.getDocument().getBody(), CKEDITOR.POSITION_BEFORE_END ) ; | ||
215 | // | ||
216 | // return new CKEDITOR.dom.walker( range ); | ||
217 | // } | ||
218 | // }, | ||
219 | // | ||
220 | proto: { | ||
221 | /** | ||
222 | * Stops walking. No more nodes are retrieved if this function is called. | ||
223 | */ | ||
224 | end: function() { | ||
225 | this._.end = 1; | ||
226 | }, | ||
227 | |||
228 | /** | ||
229 | * Retrieves the next node (on the right). | ||
230 | * | ||
231 | * @returns {CKEDITOR.dom.node} The next node or `null` if no more | ||
232 | * nodes are available. | ||
233 | */ | ||
234 | next: function() { | ||
235 | return iterate.call( this ); | ||
236 | }, | ||
237 | |||
238 | /** | ||
239 | * Retrieves the previous node (on the left). | ||
240 | * | ||
241 | * @returns {CKEDITOR.dom.node} The previous node or `null` if no more | ||
242 | * nodes are available. | ||
243 | */ | ||
244 | previous: function() { | ||
245 | return iterate.call( this, 1 ); | ||
246 | }, | ||
247 | |||
248 | /** | ||
249 | * Checks all nodes on the right, executing the evaluation function. | ||
250 | * | ||
251 | * @returns {Boolean} `false` if the evaluator function returned | ||
252 | * `false` for any of the matched nodes. Otherwise `true`. | ||
253 | */ | ||
254 | checkForward: function() { | ||
255 | return iterate.call( this, 0, 1 ) !== false; | ||
256 | }, | ||
257 | |||
258 | /** | ||
259 | * Check all nodes on the left, executing the evaluation function. | ||
260 | * | ||
261 | * @returns {Boolean} `false` if the evaluator function returned | ||
262 | * `false` for any of the matched nodes. Otherwise `true`. | ||
263 | */ | ||
264 | checkBackward: function() { | ||
265 | return iterate.call( this, 1, 1 ) !== false; | ||
266 | }, | ||
267 | |||
268 | /** | ||
269 | * Executes a full walk forward (to the right), until no more nodes | ||
270 | * are available, returning the last valid node. | ||
271 | * | ||
272 | * @returns {CKEDITOR.dom.node} The last node on the right or `null` | ||
273 | * if no valid nodes are available. | ||
274 | */ | ||
275 | lastForward: function() { | ||
276 | return iterateToLast.call( this ); | ||
277 | }, | ||
278 | |||
279 | /** | ||
280 | * Executes a full walk backwards (to the left), until no more nodes | ||
281 | * are available, returning the last valid node. | ||
282 | * | ||
283 | * @returns {CKEDITOR.dom.node} The last node on the left or `null` | ||
284 | * if no valid nodes are available. | ||
285 | */ | ||
286 | lastBackward: function() { | ||
287 | return iterateToLast.call( this, 1 ); | ||
288 | }, | ||
289 | |||
290 | /** | ||
291 | * Resets the walker. | ||
292 | */ | ||
293 | reset: function() { | ||
294 | delete this.current; | ||
295 | this._ = {}; | ||
296 | } | ||
297 | |||
298 | } | ||
299 | } ); | ||
300 | |||
301 | // Anything whose display computed style is block, list-item, table, | ||
302 | // table-row-group, table-header-group, table-footer-group, table-row, | ||
303 | // table-column-group, table-column, table-cell, table-caption, or whose node | ||
304 | // name is hr, br (when enterMode is br only) is a block boundary. | ||
305 | var blockBoundaryDisplayMatch = { | ||
306 | block: 1, 'list-item': 1, table: 1, 'table-row-group': 1, | ||
307 | 'table-header-group': 1, 'table-footer-group': 1, 'table-row': 1, 'table-column-group': 1, | ||
308 | 'table-column': 1, 'table-cell': 1, 'table-caption': 1 | ||
309 | }, | ||
310 | outOfFlowPositions = { absolute: 1, fixed: 1 }; | ||
311 | |||
312 | /** | ||
313 | * Checks whether an element is displayed as a block. | ||
314 | * | ||
315 | * @member CKEDITOR.dom.element | ||
316 | * @param [customNodeNames] Custom list of nodes which will extend | ||
317 | * the default {@link CKEDITOR.dtd#$block} list. | ||
318 | * @returns {Boolean} | ||
319 | */ | ||
320 | CKEDITOR.dom.element.prototype.isBlockBoundary = function( customNodeNames ) { | ||
321 | // Whether element is in normal page flow. Floated or positioned elements are out of page flow. | ||
322 | // Don't consider floated or positioned formatting as block boundary, fall back to dtd check in that case. (#6297) | ||
323 | var inPageFlow = this.getComputedStyle( 'float' ) == 'none' && !( this.getComputedStyle( 'position' ) in outOfFlowPositions ); | ||
324 | |||
325 | if ( inPageFlow && blockBoundaryDisplayMatch[ this.getComputedStyle( 'display' ) ] ) | ||
326 | return true; | ||
327 | |||
328 | // Either in $block or in customNodeNames if defined. | ||
329 | return !!( this.is( CKEDITOR.dtd.$block ) || customNodeNames && this.is( customNodeNames ) ); | ||
330 | }; | ||
331 | |||
332 | /** | ||
333 | * Returns a function which checks whether the node is a block boundary. | ||
334 | * See {@link CKEDITOR.dom.element#isBlockBoundary}. | ||
335 | * | ||
336 | * @static | ||
337 | * @param customNodeNames | ||
338 | * @returns {Function} | ||
339 | */ | ||
340 | CKEDITOR.dom.walker.blockBoundary = function( customNodeNames ) { | ||
341 | return function( node ) { | ||
342 | return !( node.type == CKEDITOR.NODE_ELEMENT && node.isBlockBoundary( customNodeNames ) ); | ||
343 | }; | ||
344 | }; | ||
345 | |||
346 | /** | ||
347 | * @static | ||
348 | * @todo | ||
349 | */ | ||
350 | CKEDITOR.dom.walker.listItemBoundary = function() { | ||
351 | return this.blockBoundary( { br: 1 } ); | ||
352 | }; | ||
353 | |||
354 | /** | ||
355 | * Returns a function which checks whether the node is a bookmark node or the bookmark node | ||
356 | * inner content. | ||
357 | * | ||
358 | * @static | ||
359 | * @param {Boolean} [contentOnly=false] Whether only test against the text content of | ||
360 | * a bookmark node instead of the element itself (default). | ||
361 | * @param {Boolean} [isReject=false] Whether to return `false` for the bookmark | ||
362 | * node instead of `true` (default). | ||
363 | * @returns {Function} | ||
364 | */ | ||
365 | CKEDITOR.dom.walker.bookmark = function( contentOnly, isReject ) { | ||
366 | function isBookmarkNode( node ) { | ||
367 | return ( node && node.getName && node.getName() == 'span' && node.data( 'cke-bookmark' ) ); | ||
368 | } | ||
369 | |||
370 | return function( node ) { | ||
371 | var isBookmark, parent; | ||
372 | // Is bookmark inner text node? | ||
373 | isBookmark = ( node && node.type != CKEDITOR.NODE_ELEMENT && ( parent = node.getParent() ) && isBookmarkNode( parent ) ); | ||
374 | // Is bookmark node? | ||
375 | isBookmark = contentOnly ? isBookmark : isBookmark || isBookmarkNode( node ); | ||
376 | return !!( isReject ^ isBookmark ); | ||
377 | }; | ||
378 | }; | ||
379 | |||
380 | /** | ||
381 | * Returns a function which checks whether the node is a text node containing only whitespace characters. | ||
382 | * | ||
383 | * @static | ||
384 | * @param {Boolean} [isReject=false] | ||
385 | * @returns {Function} | ||
386 | */ | ||
387 | CKEDITOR.dom.walker.whitespaces = function( isReject ) { | ||
388 | return function( node ) { | ||
389 | var isWhitespace; | ||
390 | if ( node && node.type == CKEDITOR.NODE_TEXT ) { | ||
391 | // Whitespace, as well as the Filling Char Sequence text node used in Webkit. (#9384, #13816) | ||
392 | isWhitespace = !CKEDITOR.tools.trim( node.getText() ) || | ||
393 | CKEDITOR.env.webkit && node.getText() == CKEDITOR.dom.selection.FILLING_CHAR_SEQUENCE; | ||
394 | } | ||
395 | |||
396 | return !!( isReject ^ isWhitespace ); | ||
397 | }; | ||
398 | }; | ||
399 | |||
400 | /** | ||
401 | * Returns a function which checks whether the node is invisible in the WYSIWYG mode. | ||
402 | * | ||
403 | * @static | ||
404 | * @param {Boolean} [isReject=false] | ||
405 | * @returns {Function} | ||
406 | */ | ||
407 | CKEDITOR.dom.walker.invisible = function( isReject ) { | ||
408 | var whitespace = CKEDITOR.dom.walker.whitespaces(), | ||
409 | // #12221 (Chrome) plus #11111 (Safari). | ||
410 | offsetWidth0 = CKEDITOR.env.webkit ? 1 : 0; | ||
411 | |||
412 | return function( node ) { | ||
413 | var invisible; | ||
414 | |||
415 | if ( whitespace( node ) ) | ||
416 | invisible = 1; | ||
417 | else { | ||
418 | // Visibility should be checked on element. | ||
419 | if ( node.type == CKEDITOR.NODE_TEXT ) | ||
420 | node = node.getParent(); | ||
421 | |||
422 | // Nodes that take no spaces in wysiwyg: | ||
423 | // 1. White-spaces but not including NBSP. | ||
424 | // 2. Empty inline elements, e.g. <b></b>. | ||
425 | // 3. <br> elements (bogus, surrounded by text) (#12423). | ||
426 | invisible = node.$.offsetWidth <= offsetWidth0; | ||
427 | } | ||
428 | |||
429 | return !!( isReject ^ invisible ); | ||
430 | }; | ||
431 | }; | ||
432 | |||
433 | /** | ||
434 | * Returns a function which checks whether the node type is equal to the passed one. | ||
435 | * | ||
436 | * @static | ||
437 | * @param {Number} type | ||
438 | * @param {Boolean} [isReject=false] | ||
439 | * @returns {Function} | ||
440 | */ | ||
441 | CKEDITOR.dom.walker.nodeType = function( type, isReject ) { | ||
442 | return function( node ) { | ||
443 | return !!( isReject ^ ( node.type == type ) ); | ||
444 | }; | ||
445 | }; | ||
446 | |||
447 | /** | ||
448 | * Returns a function which checks whether the node is a bogus (filler) node from | ||
449 | * `contenteditable` element's point of view. | ||
450 | * | ||
451 | * @static | ||
452 | * @param {Boolean} [isReject=false] | ||
453 | * @returns {Function} | ||
454 | */ | ||
455 | CKEDITOR.dom.walker.bogus = function( isReject ) { | ||
456 | function nonEmpty( node ) { | ||
457 | return !isWhitespaces( node ) && !isBookmark( node ); | ||
458 | } | ||
459 | |||
460 | return function( node ) { | ||
461 | var isBogus = CKEDITOR.env.needsBrFiller ? node.is && node.is( 'br' ) : node.getText && tailNbspRegex.test( node.getText() ); | ||
462 | |||
463 | if ( isBogus ) { | ||
464 | var parent = node.getParent(), | ||
465 | next = node.getNext( nonEmpty ); | ||
466 | |||
467 | isBogus = parent.isBlockBoundary() && ( !next || next.type == CKEDITOR.NODE_ELEMENT && next.isBlockBoundary() ); | ||
468 | } | ||
469 | |||
470 | return !!( isReject ^ isBogus ); | ||
471 | }; | ||
472 | }; | ||
473 | |||
474 | /** | ||
475 | * Returns a function which checks whether the node is a temporary element | ||
476 | * (element with the `data-cke-temp` attribute) or its child. | ||
477 | * | ||
478 | * @since 4.3 | ||
479 | * @static | ||
480 | * @param {Boolean} [isReject=false] Whether to return `false` for the | ||
481 | * temporary element instead of `true` (default). | ||
482 | * @returns {Function} | ||
483 | */ | ||
484 | CKEDITOR.dom.walker.temp = function( isReject ) { | ||
485 | return function( node ) { | ||
486 | if ( node.type != CKEDITOR.NODE_ELEMENT ) | ||
487 | node = node.getParent(); | ||
488 | |||
489 | var isTemp = node && node.hasAttribute( 'data-cke-temp' ); | ||
490 | |||
491 | return !!( isReject ^ isTemp ); | ||
492 | }; | ||
493 | }; | ||
494 | |||
495 | var tailNbspRegex = /^[\t\r\n ]*(?: |\xa0)$/, | ||
496 | isWhitespaces = CKEDITOR.dom.walker.whitespaces(), | ||
497 | isBookmark = CKEDITOR.dom.walker.bookmark(), | ||
498 | isTemp = CKEDITOR.dom.walker.temp(), | ||
499 | toSkip = function( node ) { | ||
500 | return isBookmark( node ) || | ||
501 | isWhitespaces( node ) || | ||
502 | node.type == CKEDITOR.NODE_ELEMENT && node.is( CKEDITOR.dtd.$inline ) && !node.is( CKEDITOR.dtd.$empty ); | ||
503 | }; | ||
504 | |||
505 | /** | ||
506 | * Returns a function which checks whether the node should be ignored in terms of "editability". | ||
507 | * | ||
508 | * This includes: | ||
509 | * | ||
510 | * * whitespaces (see {@link CKEDITOR.dom.walker#whitespaces}), | ||
511 | * * bookmarks (see {@link CKEDITOR.dom.walker#bookmark}), | ||
512 | * * temporary elements (see {@link CKEDITOR.dom.walker#temp}). | ||
513 | * | ||
514 | * @since 4.3 | ||
515 | * @static | ||
516 | * @param {Boolean} [isReject=false] Whether to return `false` for the | ||
517 | * ignored element instead of `true` (default). | ||
518 | * @returns {Function} | ||
519 | */ | ||
520 | CKEDITOR.dom.walker.ignored = function( isReject ) { | ||
521 | return function( node ) { | ||
522 | var isIgnored = isWhitespaces( node ) || isBookmark( node ) || isTemp( node ); | ||
523 | |||
524 | return !!( isReject ^ isIgnored ); | ||
525 | }; | ||
526 | }; | ||
527 | |||
528 | var isIgnored = CKEDITOR.dom.walker.ignored(); | ||
529 | |||
530 | /** | ||
531 | * Returns a function which checks whether the node is empty. | ||
532 | * | ||
533 | * @since 4.5 | ||
534 | * @static | ||
535 | * @param {Boolean} [isReject=false] Whether to return `false` for the | ||
536 | * ignored element instead of `true` (default). | ||
537 | * @returns {Function} | ||
538 | */ | ||
539 | CKEDITOR.dom.walker.empty = function( isReject ) { | ||
540 | return function( node ) { | ||
541 | var i = 0, | ||
542 | l = node.getChildCount(); | ||
543 | |||
544 | for ( ; i < l; ++i ) { | ||
545 | if ( !isIgnored( node.getChild( i ) ) ) { | ||
546 | return !!isReject; | ||
547 | } | ||
548 | } | ||
549 | |||
550 | return !isReject; | ||
551 | }; | ||
552 | }; | ||
553 | |||
554 | var isEmpty = CKEDITOR.dom.walker.empty(); | ||
555 | |||
556 | function filterTextContainers( dtd ) { | ||
557 | var hash = {}, | ||
558 | name; | ||
559 | |||
560 | for ( name in dtd ) { | ||
561 | if ( CKEDITOR.dtd[ name ][ '#' ] ) | ||
562 | hash[ name ] = 1; | ||
563 | } | ||
564 | return hash; | ||
565 | } | ||
566 | |||
567 | /** | ||
568 | * A hash of element names which in browsers that {@link CKEDITOR.env#needsBrFiller do not need `<br>` fillers} | ||
569 | * can be selection containers despite being empty. | ||
570 | * | ||
571 | * @since 4.5 | ||
572 | * @static | ||
573 | * @property {Object} validEmptyBlockContainers | ||
574 | */ | ||
575 | var validEmptyBlocks = CKEDITOR.dom.walker.validEmptyBlockContainers = CKEDITOR.tools.extend( | ||
576 | filterTextContainers( CKEDITOR.dtd.$block ), | ||
577 | { caption: 1, td: 1, th: 1 } | ||
578 | ); | ||
579 | |||
580 | function isEditable( node ) { | ||
581 | // Skip temporary elements, bookmarks and whitespaces. | ||
582 | if ( isIgnored( node ) ) | ||
583 | return false; | ||
584 | |||
585 | if ( node.type == CKEDITOR.NODE_TEXT ) | ||
586 | return true; | ||
587 | |||
588 | if ( node.type == CKEDITOR.NODE_ELEMENT ) { | ||
589 | // All inline and non-editable elements are valid editable places. | ||
590 | // Note: the <hr> is currently the only element in CKEDITOR.dtd.$empty and CKEDITOR.dtd.$block, | ||
591 | // but generally speaking we need an intersection of these two sets. | ||
592 | // Note: non-editable block has to be treated differently (should be selected entirely). | ||
593 | if ( node.is( CKEDITOR.dtd.$inline ) || node.is( 'hr' ) || node.getAttribute( 'contenteditable' ) == 'false' ) | ||
594 | return true; | ||
595 | |||
596 | // Empty blocks are editable on IE. | ||
597 | if ( !CKEDITOR.env.needsBrFiller && node.is( validEmptyBlocks ) && isEmpty( node ) ) | ||
598 | return true; | ||
599 | } | ||
600 | |||
601 | // Skip all other nodes. | ||
602 | return false; | ||
603 | } | ||
604 | |||
605 | /** | ||
606 | * Returns a function which checks whether the node can be a container or a sibling | ||
607 | * of the selection end. | ||
608 | * | ||
609 | * This includes: | ||
610 | * | ||
611 | * * text nodes (but not whitespaces), | ||
612 | * * inline elements, | ||
613 | * * intersection of {@link CKEDITOR.dtd#$empty} and {@link CKEDITOR.dtd#$block} (currently | ||
614 | * it is only `<hr>`), | ||
615 | * * non-editable blocks (special case — such blocks cannot be containers nor | ||
616 | * siblings, they need to be selected entirely), | ||
617 | * * empty {@link #validEmptyBlockContainers blocks} which can contain text | ||
618 | * ({@link CKEDITOR.env#needsBrFiller old IEs only}). | ||
619 | * | ||
620 | * @since 4.3 | ||
621 | * @static | ||
622 | * @param {Boolean} [isReject=false] Whether to return `false` for the | ||
623 | * ignored element instead of `true` (default). | ||
624 | * @returns {Function} | ||
625 | */ | ||
626 | CKEDITOR.dom.walker.editable = function( isReject ) { | ||
627 | return function( node ) { | ||
628 | return !!( isReject ^ isEditable( node ) ); | ||
629 | }; | ||
630 | }; | ||
631 | |||
632 | /** | ||
633 | * Checks if there is a filler node at the end of an element, and returns it. | ||
634 | * | ||
635 | * @member CKEDITOR.dom.element | ||
636 | * @returns {CKEDITOR.dom.node/Boolean} Bogus node or `false`. | ||
637 | */ | ||
638 | CKEDITOR.dom.element.prototype.getBogus = function() { | ||
639 | // Bogus are not always at the end, e.g. <p><a>text<br /></a></p> (#7070). | ||
640 | var tail = this; | ||
641 | do { | ||
642 | tail = tail.getPreviousSourceNode(); | ||
643 | } | ||
644 | while ( toSkip( tail ) ); | ||
645 | |||
646 | if ( tail && ( CKEDITOR.env.needsBrFiller ? tail.is && tail.is( 'br' ) : tail.getText && tailNbspRegex.test( tail.getText() ) ) ) | ||
647 | return tail; | ||
648 | |||
649 | return false; | ||
650 | }; | ||
651 | |||
652 | } )(); | ||