]> git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blob - sources/core/dom/elementpath.js
Add oembed
[perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git] / sources / core / dom / elementpath.js
1 /**
2 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
3 * For licensing, see LICENSE.md or http://ckeditor.com/license
4 */
5
6 'use strict';
7
8 ( function() {
9
10 var pathBlockLimitElements = {},
11 pathBlockElements = {},
12 tag;
13
14 // Elements that are considered the "Block limit" in an element path.
15 for ( tag in CKEDITOR.dtd.$blockLimit ) {
16 // Exclude from list roots.
17 if ( !( tag in CKEDITOR.dtd.$list ) )
18 pathBlockLimitElements[ tag ] = 1;
19 }
20
21 // Elements that are considered the "End level Block" in an element path.
22 for ( tag in CKEDITOR.dtd.$block ) {
23 // Exclude block limits, and empty block element, e.g. hr.
24 if ( !( tag in CKEDITOR.dtd.$blockLimit || tag in CKEDITOR.dtd.$empty ) )
25 pathBlockElements[ tag ] = 1;
26 }
27
28 // Check if an element contains any block element.
29 function checkHasBlock( element ) {
30 var childNodes = element.getChildren();
31
32 for ( var i = 0, count = childNodes.count(); i < count; i++ ) {
33 var child = childNodes.getItem( i );
34
35 if ( child.type == CKEDITOR.NODE_ELEMENT && CKEDITOR.dtd.$block[ child.getName() ] )
36 return true;
37 }
38
39 return false;
40 }
41
42 /**
43 * Retrieve the list of nodes walked from the start node up to the editable element of the editor.
44 *
45 * @class
46 * @constructor Creates an element path class instance.
47 * @param {CKEDITOR.dom.element} startNode From which the path should start.
48 * @param {CKEDITOR.dom.element} root To which element the path should stop, defaults to the `body` element.
49 */
50 CKEDITOR.dom.elementPath = function( startNode, root ) {
51 var block = null,
52 blockLimit = null,
53 elements = [],
54 e = startNode,
55 elementName;
56
57 // Backward compact.
58 root = root || startNode.getDocument().getBody();
59
60 // Assign root value if startNode is null (#424)(https://dev.ckeditor.com/ticket/17028).
61 if ( !e ) {
62 e = root;
63 }
64
65 do {
66 if ( e.type == CKEDITOR.NODE_ELEMENT ) {
67 elements.push( e );
68
69 if ( !this.lastElement ) {
70 this.lastElement = e;
71
72 // If an object or non-editable element is fully selected at the end of the element path,
73 // it must not become the block limit.
74 if ( e.is( CKEDITOR.dtd.$object ) || e.getAttribute( 'contenteditable' ) == 'false' )
75 continue;
76 }
77
78 if ( e.equals( root ) )
79 break;
80
81 if ( !blockLimit ) {
82 elementName = e.getName();
83
84 // First editable element becomes a block limit, because it cannot be split.
85 if ( e.getAttribute( 'contenteditable' ) == 'true' )
86 blockLimit = e;
87 // "Else" because element cannot be both - block and block levelimit.
88 else if ( !block && pathBlockElements[ elementName ] )
89 block = e;
90
91 if ( pathBlockLimitElements[ elementName ] ) {
92 // End level DIV is considered as the block, if no block is available. (http://dev.ckeditor.com/ticket/525)
93 // But it must NOT be the root element (checked above).
94 if ( !block && elementName == 'div' && !checkHasBlock( e ) )
95 block = e;
96 else
97 blockLimit = e;
98 }
99 }
100 }
101 }
102 while ( ( e = e.getParent() ) );
103
104 // Block limit defaults to root.
105 if ( !blockLimit )
106 blockLimit = root;
107
108 /**
109 * First non-empty block element which:
110 *
111 * * is not a {@link CKEDITOR.dtd#$blockLimit},
112 * * or is a `div` which does not contain block elements and is not a `root`.
113 *
114 * This means a first, splittable block in elements path.
115 *
116 * @readonly
117 * @property {CKEDITOR.dom.element}
118 */
119 this.block = block;
120
121 /**
122 * See the {@link CKEDITOR.dtd#$blockLimit} description.
123 *
124 * @readonly
125 * @property {CKEDITOR.dom.element}
126 */
127 this.blockLimit = blockLimit;
128
129 /**
130 * The root of the elements path - `root` argument passed to class constructor or a `body` element.
131 *
132 * @readonly
133 * @property {CKEDITOR.dom.element}
134 */
135 this.root = root;
136
137 /**
138 * An array of elements (from `startNode` to `root`) in the path.
139 *
140 * @readonly
141 * @property {CKEDITOR.dom.element[]}
142 */
143 this.elements = elements;
144
145 /**
146 * The last element of the elements path - `startNode` or its parent.
147 *
148 * @readonly
149 * @property {CKEDITOR.dom.element} lastElement
150 */
151 };
152
153 } )();
154
155 CKEDITOR.dom.elementPath.prototype = {
156 /**
157 * Compares this element path with another one.
158 *
159 * @param {CKEDITOR.dom.elementPath} otherPath The elementPath object to be
160 * compared with this one.
161 * @returns {Boolean} `true` if the paths are equal, containing the same
162 * number of elements and the same elements in the same order.
163 */
164 compare: function( otherPath ) {
165 var thisElements = this.elements;
166 var otherElements = otherPath && otherPath.elements;
167
168 if ( !otherElements || thisElements.length != otherElements.length )
169 return false;
170
171 for ( var i = 0; i < thisElements.length; i++ ) {
172 if ( !thisElements[ i ].equals( otherElements[ i ] ) )
173 return false;
174 }
175
176 return true;
177 },
178
179 /**
180 * Search the path elements that meets the specified criteria.
181 *
182 * @param {String/Array/Function/Object/CKEDITOR.dom.element} query The criteria that can be
183 * either a tag name, list (array and object) of tag names, element or an node evaluator function.
184 * @param {Boolean} [excludeRoot] Not taking path root element into consideration.
185 * @param {Boolean} [fromTop] Search start from the topmost element instead of bottom.
186 * @returns {CKEDITOR.dom.element} The first matched dom element or `null`.
187 */
188 contains: function( query, excludeRoot, fromTop ) {
189 var i = 0,
190 evaluator;
191
192 if ( typeof query == 'string' )
193 evaluator = function( node ) {
194 return node.getName() == query;
195 };
196 if ( query instanceof CKEDITOR.dom.element )
197 evaluator = function( node ) {
198 return node.equals( query );
199 };
200 else if ( CKEDITOR.tools.isArray( query ) )
201 evaluator = function( node ) {
202 return CKEDITOR.tools.indexOf( query, node.getName() ) > -1;
203 };
204 else if ( typeof query == 'function' )
205 evaluator = query;
206 else if ( typeof query == 'object' )
207 evaluator = function( node ) {
208 return node.getName() in query;
209 };
210
211 var elements = this.elements,
212 length = elements.length;
213
214 if ( excludeRoot ) {
215 if ( !fromTop ) {
216 length -= 1;
217 } else {
218 i += 1;
219 }
220 }
221
222 if ( fromTop ) {
223 elements = Array.prototype.slice.call( elements, 0 );
224 elements.reverse();
225 }
226
227 for ( ; i < length; i++ ) {
228 if ( evaluator( elements[ i ] ) )
229 return elements[ i ];
230 }
231
232 return null;
233 },
234
235 /**
236 * Check whether the elements path is the proper context for the specified
237 * tag name in the DTD.
238 *
239 * @param {String} tag The tag name.
240 * @returns {Boolean}
241 */
242 isContextFor: function( tag ) {
243 var holder;
244
245 // Check for block context.
246 if ( tag in CKEDITOR.dtd.$block ) {
247 // Indeterminate elements which are not subjected to be splitted or surrounded must be checked first.
248 var inter = this.contains( CKEDITOR.dtd.$intermediate );
249 holder = inter || ( this.root.equals( this.block ) && this.block ) || this.blockLimit;
250 return !!holder.getDtd()[ tag ];
251 }
252
253 return true;
254 },
255
256 /**
257 * Retrieve the text direction for this elements path.
258 *
259 * @returns {'ltr'/'rtl'}
260 */
261 direction: function() {
262 var directionNode = this.block || this.blockLimit || this.root;
263 return directionNode.getDirection( 1 );
264 }
265 };