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/tools.js | |
download | connexionswing-ckeditor-component-4.5.6.tar.gz connexionswing-ckeditor-component-4.5.6.tar.zst connexionswing-ckeditor-component-4.5.6.zip |
Initial commit4.5.6
Diffstat (limited to 'sources/core/tools.js')
-rw-r--r-- | sources/core/tools.js | 1386 |
1 files changed, 1386 insertions, 0 deletions
diff --git a/sources/core/tools.js b/sources/core/tools.js new file mode 100644 index 00000000..0cf824f9 --- /dev/null +++ b/sources/core/tools.js | |||
@@ -0,0 +1,1386 @@ | |||
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 | * @fileOverview Defines the {@link CKEDITOR.tools} object that contains | ||
8 | * utility functions. | ||
9 | */ | ||
10 | |||
11 | ( function() { | ||
12 | var functions = [], | ||
13 | cssVendorPrefix = | ||
14 | CKEDITOR.env.gecko ? '-moz-' : | ||
15 | CKEDITOR.env.webkit ? '-webkit-' : | ||
16 | CKEDITOR.env.ie ? '-ms-' : | ||
17 | '', | ||
18 | ampRegex = /&/g, | ||
19 | gtRegex = />/g, | ||
20 | ltRegex = /</g, | ||
21 | quoteRegex = /"/g, | ||
22 | tokenCharset = 'abcdefghijklmnopqrstuvwxyz0123456789', | ||
23 | TOKEN_COOKIE_NAME = 'ckCsrfToken', | ||
24 | TOKEN_LENGTH = 40, | ||
25 | |||
26 | allEscRegex = /&(lt|gt|amp|quot|nbsp|shy|#\d{1,5});/g, | ||
27 | namedEntities = { | ||
28 | lt: '<', | ||
29 | gt: '>', | ||
30 | amp: '&', | ||
31 | quot: '"', | ||
32 | nbsp: '\u00a0', | ||
33 | shy: '\u00ad' | ||
34 | }, | ||
35 | allEscDecode = function( match, code ) { | ||
36 | if ( code[ 0 ] == '#' ) { | ||
37 | return String.fromCharCode( parseInt( code.slice( 1 ), 10 ) ); | ||
38 | } else { | ||
39 | return namedEntities[ code ]; | ||
40 | } | ||
41 | }; | ||
42 | |||
43 | CKEDITOR.on( 'reset', function() { | ||
44 | functions = []; | ||
45 | } ); | ||
46 | |||
47 | /** | ||
48 | * Utility functions. | ||
49 | * | ||
50 | * @class | ||
51 | * @singleton | ||
52 | */ | ||
53 | CKEDITOR.tools = { | ||
54 | /** | ||
55 | * Compares the elements of two arrays. | ||
56 | * | ||
57 | * var a = [ 1, 'a', 3 ]; | ||
58 | * var b = [ 1, 3, 'a' ]; | ||
59 | * var c = [ 1, 'a', 3 ]; | ||
60 | * var d = [ 1, 'a', 3, 4 ]; | ||
61 | * | ||
62 | * alert( CKEDITOR.tools.arrayCompare( a, b ) ); // false | ||
63 | * alert( CKEDITOR.tools.arrayCompare( a, c ) ); // true | ||
64 | * alert( CKEDITOR.tools.arrayCompare( a, d ) ); // false | ||
65 | * | ||
66 | * @param {Array} arrayA An array to be compared. | ||
67 | * @param {Array} arrayB The other array to be compared. | ||
68 | * @returns {Boolean} `true` if the arrays have the same length and | ||
69 | * their elements match. | ||
70 | */ | ||
71 | arrayCompare: function( arrayA, arrayB ) { | ||
72 | if ( !arrayA && !arrayB ) | ||
73 | return true; | ||
74 | |||
75 | if ( !arrayA || !arrayB || arrayA.length != arrayB.length ) | ||
76 | return false; | ||
77 | |||
78 | for ( var i = 0; i < arrayA.length; i++ ) { | ||
79 | if ( arrayA[ i ] != arrayB[ i ] ) | ||
80 | return false; | ||
81 | } | ||
82 | |||
83 | return true; | ||
84 | }, | ||
85 | |||
86 | /** | ||
87 | * Finds the index of the first element in an array for which the `compareFunction` returns `true`. | ||
88 | * | ||
89 | * CKEDITOR.tools.getIndex( [ 1, 2, 4, 3, 5 ], function( el ) { | ||
90 | * return el >= 3; | ||
91 | * } ); // 2 | ||
92 | * | ||
93 | * @since 4.5 | ||
94 | * @param {Array} array Array to search in. | ||
95 | * @param {Function} compareFunction Compare function. | ||
96 | * @returns {Number} The index of the first matching element or `-1` if none matches. | ||
97 | */ | ||
98 | getIndex: function( arr, compareFunction ) { | ||
99 | for ( var i = 0; i < arr.length; ++i ) { | ||
100 | if ( compareFunction( arr[ i ] ) ) | ||
101 | return i; | ||
102 | } | ||
103 | return -1; | ||
104 | }, | ||
105 | |||
106 | /** | ||
107 | * Creates a deep copy of an object. | ||
108 | * | ||
109 | * **Note**: Recursive references are not supported. | ||
110 | * | ||
111 | * var obj = { | ||
112 | * name: 'John', | ||
113 | * cars: { | ||
114 | * Mercedes: { color: 'blue' }, | ||
115 | * Porsche: { color: 'red' } | ||
116 | * } | ||
117 | * }; | ||
118 | * var clone = CKEDITOR.tools.clone( obj ); | ||
119 | * clone.name = 'Paul'; | ||
120 | * clone.cars.Porsche.color = 'silver'; | ||
121 | * | ||
122 | * alert( obj.name ); // 'John' | ||
123 | * alert( clone.name ); // 'Paul' | ||
124 | * alert( obj.cars.Porsche.color ); // 'red' | ||
125 | * alert( clone.cars.Porsche.color ); // 'silver' | ||
126 | * | ||
127 | * @param {Object} object The object to be cloned. | ||
128 | * @returns {Object} The object clone. | ||
129 | */ | ||
130 | clone: function( obj ) { | ||
131 | var clone; | ||
132 | |||
133 | // Array. | ||
134 | if ( obj && ( obj instanceof Array ) ) { | ||
135 | clone = []; | ||
136 | |||
137 | for ( var i = 0; i < obj.length; i++ ) | ||
138 | clone[ i ] = CKEDITOR.tools.clone( obj[ i ] ); | ||
139 | |||
140 | return clone; | ||
141 | } | ||
142 | |||
143 | // "Static" types. | ||
144 | if ( obj === null || ( typeof obj != 'object' ) || ( obj instanceof String ) || ( obj instanceof Number ) || ( obj instanceof Boolean ) || ( obj instanceof Date ) || ( obj instanceof RegExp ) ) | ||
145 | return obj; | ||
146 | |||
147 | // DOM objects and window. | ||
148 | if ( obj.nodeType || obj.window === obj ) | ||
149 | return obj; | ||
150 | |||
151 | // Objects. | ||
152 | clone = new obj.constructor(); | ||
153 | |||
154 | for ( var propertyName in obj ) { | ||
155 | var property = obj[ propertyName ]; | ||
156 | clone[ propertyName ] = CKEDITOR.tools.clone( property ); | ||
157 | } | ||
158 | |||
159 | return clone; | ||
160 | }, | ||
161 | |||
162 | /** | ||
163 | * Turns the first letter of a string to upper-case. | ||
164 | * | ||
165 | * @param {String} str | ||
166 | * @param {Boolean} [keepCase] Keep the case of 2nd to last letter. | ||
167 | * @returns {String} | ||
168 | */ | ||
169 | capitalize: function( str, keepCase ) { | ||
170 | return str.charAt( 0 ).toUpperCase() + ( keepCase ? str.slice( 1 ) : str.slice( 1 ).toLowerCase() ); | ||
171 | }, | ||
172 | |||
173 | /** | ||
174 | * Copies the properties from one object to another. By default, properties | ||
175 | * already present in the target object **are not** overwritten. | ||
176 | * | ||
177 | * // Create the sample object. | ||
178 | * var myObject = { | ||
179 | * prop1: true | ||
180 | * }; | ||
181 | * | ||
182 | * // Extend the above object with two properties. | ||
183 | * CKEDITOR.tools.extend( myObject, { | ||
184 | * prop2: true, | ||
185 | * prop3: true | ||
186 | * } ); | ||
187 | * | ||
188 | * // Alert 'prop1', 'prop2' and 'prop3'. | ||
189 | * for ( var p in myObject ) | ||
190 | * alert( p ); | ||
191 | * | ||
192 | * @param {Object} target The object to be extended. | ||
193 | * @param {Object...} source The object(s) from properties will be | ||
194 | * copied. Any number of objects can be passed to this function. | ||
195 | * @param {Boolean} [overwrite] If `true` is specified, it indicates that | ||
196 | * properties already present in the target object could be | ||
197 | * overwritten by subsequent objects. | ||
198 | * @param {Object} [properties] Only properties within the specified names | ||
199 | * list will be received from the source object. | ||
200 | * @returns {Object} The extended object (target). | ||
201 | */ | ||
202 | extend: function( target ) { | ||
203 | var argsLength = arguments.length, | ||
204 | overwrite, propertiesList; | ||
205 | |||
206 | if ( typeof ( overwrite = arguments[ argsLength - 1 ] ) == 'boolean' ) | ||
207 | argsLength--; | ||
208 | else if ( typeof ( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' ) { | ||
209 | propertiesList = arguments[ argsLength - 1 ]; | ||
210 | argsLength -= 2; | ||
211 | } | ||
212 | for ( var i = 1; i < argsLength; i++ ) { | ||
213 | var source = arguments[ i ]; | ||
214 | for ( var propertyName in source ) { | ||
215 | // Only copy existed fields if in overwrite mode. | ||
216 | if ( overwrite === true || target[ propertyName ] == null ) { | ||
217 | // Only copy specified fields if list is provided. | ||
218 | if ( !propertiesList || ( propertyName in propertiesList ) ) | ||
219 | target[ propertyName ] = source[ propertyName ]; | ||
220 | |||
221 | } | ||
222 | } | ||
223 | } | ||
224 | |||
225 | return target; | ||
226 | }, | ||
227 | |||
228 | /** | ||
229 | * Creates an object which is an instance of a class whose prototype is a | ||
230 | * predefined object. All properties defined in the source object are | ||
231 | * automatically inherited by the resulting object, including future | ||
232 | * changes to it. | ||
233 | * | ||
234 | * @param {Object} source The source object to be used as the prototype for | ||
235 | * the final object. | ||
236 | * @returns {Object} The resulting copy. | ||
237 | */ | ||
238 | prototypedCopy: function( source ) { | ||
239 | var copy = function() {}; | ||
240 | copy.prototype = source; | ||
241 | return new copy(); | ||
242 | }, | ||
243 | |||
244 | /** | ||
245 | * Makes fast (shallow) copy of an object. | ||
246 | * This method is faster than {@link #clone} which does | ||
247 | * a deep copy of an object (including arrays). | ||
248 | * | ||
249 | * @since 4.1 | ||
250 | * @param {Object} source The object to be copied. | ||
251 | * @returns {Object} Copy of `source`. | ||
252 | */ | ||
253 | copy: function( source ) { | ||
254 | var obj = {}, | ||
255 | name; | ||
256 | |||
257 | for ( name in source ) | ||
258 | obj[ name ] = source[ name ]; | ||
259 | |||
260 | return obj; | ||
261 | }, | ||
262 | |||
263 | /** | ||
264 | * Checks if an object is an Array. | ||
265 | * | ||
266 | * alert( CKEDITOR.tools.isArray( [] ) ); // true | ||
267 | * alert( CKEDITOR.tools.isArray( 'Test' ) ); // false | ||
268 | * | ||
269 | * @param {Object} object The object to be checked. | ||
270 | * @returns {Boolean} `true` if the object is an Array, otherwise `false`. | ||
271 | */ | ||
272 | isArray: function( object ) { | ||
273 | return Object.prototype.toString.call( object ) == '[object Array]'; | ||
274 | }, | ||
275 | |||
276 | /** | ||
277 | * Whether the object contains no properties of its own. | ||
278 | * | ||
279 | * @param object | ||
280 | * @returns {Boolean} | ||
281 | */ | ||
282 | isEmpty: function( object ) { | ||
283 | for ( var i in object ) { | ||
284 | if ( object.hasOwnProperty( i ) ) | ||
285 | return false; | ||
286 | } | ||
287 | return true; | ||
288 | }, | ||
289 | |||
290 | /** | ||
291 | * Generates an object or a string containing vendor-specific and vendor-free CSS properties. | ||
292 | * | ||
293 | * CKEDITOR.tools.cssVendorPrefix( 'border-radius', '0', true ); | ||
294 | * // On Firefox: '-moz-border-radius:0;border-radius:0' | ||
295 | * // On Chrome: '-webkit-border-radius:0;border-radius:0' | ||
296 | * | ||
297 | * @param {String} property The CSS property name. | ||
298 | * @param {String} value The CSS value. | ||
299 | * @param {Boolean} [asString=false] If `true`, then the returned value will be a CSS string. | ||
300 | * @returns {Object/String} The object containing CSS properties or its stringified version. | ||
301 | */ | ||
302 | cssVendorPrefix: function( property, value, asString ) { | ||
303 | if ( asString ) | ||
304 | return cssVendorPrefix + property + ':' + value + ';' + property + ':' + value; | ||
305 | |||
306 | var ret = {}; | ||
307 | ret[ property ] = value; | ||
308 | ret[ cssVendorPrefix + property ] = value; | ||
309 | |||
310 | return ret; | ||
311 | }, | ||
312 | |||
313 | /** | ||
314 | * Transforms a CSS property name to its relative DOM style name. | ||
315 | * | ||
316 | * alert( CKEDITOR.tools.cssStyleToDomStyle( 'background-color' ) ); // 'backgroundColor' | ||
317 | * alert( CKEDITOR.tools.cssStyleToDomStyle( 'float' ) ); // 'cssFloat' | ||
318 | * | ||
319 | * @method | ||
320 | * @param {String} cssName The CSS property name. | ||
321 | * @returns {String} The transformed name. | ||
322 | */ | ||
323 | cssStyleToDomStyle: ( function() { | ||
324 | var test = document.createElement( 'div' ).style; | ||
325 | |||
326 | var cssFloat = ( typeof test.cssFloat != 'undefined' ) ? 'cssFloat' : ( typeof test.styleFloat != 'undefined' ) ? 'styleFloat' : 'float'; | ||
327 | |||
328 | return function( cssName ) { | ||
329 | if ( cssName == 'float' ) | ||
330 | return cssFloat; | ||
331 | else { | ||
332 | return cssName.replace( /-./g, function( match ) { | ||
333 | return match.substr( 1 ).toUpperCase(); | ||
334 | } ); | ||
335 | } | ||
336 | }; | ||
337 | } )(), | ||
338 | |||
339 | /** | ||
340 | * Builds a HTML snippet from a set of `<style>/<link>`. | ||
341 | * | ||
342 | * @param {String/Array} css Each of which are URLs (absolute) of a CSS file or | ||
343 | * a trunk of style text. | ||
344 | * @returns {String} | ||
345 | */ | ||
346 | buildStyleHtml: function( css ) { | ||
347 | css = [].concat( css ); | ||
348 | var item, | ||
349 | retval = []; | ||
350 | for ( var i = 0; i < css.length; i++ ) { | ||
351 | if ( ( item = css[ i ] ) ) { | ||
352 | // Is CSS style text ? | ||
353 | if ( /@import|[{}]/.test( item ) ) | ||
354 | retval.push( '<style>' + item + '</style>' ); | ||
355 | else | ||
356 | retval.push( '<link type="text/css" rel=stylesheet href="' + item + '">' ); | ||
357 | } | ||
358 | } | ||
359 | return retval.join( '' ); | ||
360 | }, | ||
361 | |||
362 | /** | ||
363 | * Replaces special HTML characters in a string with their relative HTML | ||
364 | * entity values. | ||
365 | * | ||
366 | * alert( CKEDITOR.tools.htmlEncode( 'A > B & C < D' ) ); // 'A > B & C < D' | ||
367 | * | ||
368 | * @param {String} text The string to be encoded. | ||
369 | * @returns {String} The encoded string. | ||
370 | */ | ||
371 | htmlEncode: function( text ) { | ||
372 | // Backwards compatibility - accept also non-string values (casting is done below). | ||
373 | // Since 4.4.8 we return empty string for null and undefined because these values make no sense. | ||
374 | if ( text === undefined || text === null ) { | ||
375 | return ''; | ||
376 | } | ||
377 | |||
378 | return String( text ).replace( ampRegex, '&' ).replace( gtRegex, '>' ).replace( ltRegex, '<' ); | ||
379 | }, | ||
380 | |||
381 | /** | ||
382 | * Decodes HTML entities that browsers tend to encode when used in text nodes. | ||
383 | * | ||
384 | * alert( CKEDITOR.tools.htmlDecode( '<a & b >' ) ); // '<a & b >' | ||
385 | * | ||
386 | * Read more about chosen entities in the [research](http://dev.ckeditor.com/ticket/13105#comment:8). | ||
387 | * | ||
388 | * @param {String} The string to be decoded. | ||
389 | * @returns {String} The decoded string. | ||
390 | */ | ||
391 | htmlDecode: function( text ) { | ||
392 | // See: | ||
393 | // * http://dev.ckeditor.com/ticket/13105#comment:8 and comment:9, | ||
394 | // * http://jsperf.com/wth-is-going-on-with-jsperf JSPerf has some serious problems, but you can observe | ||
395 | // that combined regexp tends to be quicker (except on V8). It will also not be prone to fail on '&lt;' | ||
396 | // (see http://dev.ckeditor.com/ticket/13105#DXWTF:CKEDITOR.tools.htmlEnDecodeAttr). | ||
397 | return text.replace( allEscRegex, allEscDecode ); | ||
398 | }, | ||
399 | |||
400 | /** | ||
401 | * Replaces special HTML characters in HTMLElement attribute with their relative HTML entity values. | ||
402 | * | ||
403 | * alert( CKEDITOR.tools.htmlEncodeAttr( '<a " b >' ) ); // '<a " b >' | ||
404 | * | ||
405 | * @param {String} The attribute value to be encoded. | ||
406 | * @returns {String} The encoded value. | ||
407 | */ | ||
408 | htmlEncodeAttr: function( text ) { | ||
409 | return CKEDITOR.tools.htmlEncode( text ).replace( quoteRegex, '"' ); | ||
410 | }, | ||
411 | |||
412 | /** | ||
413 | * Decodes HTML entities that browsers tend to encode when used in attributes. | ||
414 | * | ||
415 | * alert( CKEDITOR.tools.htmlDecodeAttr( '<a " b>' ) ); // '<a " b>' | ||
416 | * | ||
417 | * Since CKEditor 4.5 this method simply executes {@link #htmlDecode} which covers | ||
418 | * all necessary entities. | ||
419 | * | ||
420 | * @param {String} text The text to be decoded. | ||
421 | * @returns {String} The decoded text. | ||
422 | */ | ||
423 | htmlDecodeAttr: function( text ) { | ||
424 | return CKEDITOR.tools.htmlDecode( text ); | ||
425 | }, | ||
426 | |||
427 | /** | ||
428 | * Transforms text to valid HTML: creates paragraphs, replaces tabs with non-breaking spaces etc. | ||
429 | * | ||
430 | * @since 4.5 | ||
431 | * @param {String} text Text to transform. | ||
432 | * @param {Number} enterMode Editor {@link CKEDITOR.config#enterMode Enter mode}. | ||
433 | * @returns {String} HTML generated from the text. | ||
434 | */ | ||
435 | transformPlainTextToHtml: function( text, enterMode ) { | ||
436 | var isEnterBrMode = enterMode == CKEDITOR.ENTER_BR, | ||
437 | // CRLF -> LF | ||
438 | html = this.htmlEncode( text.replace( /\r\n/g, '\n' ) ); | ||
439 | |||
440 | // Tab ->   x 4; | ||
441 | html = html.replace( /\t/g, ' ' ); | ||
442 | |||
443 | var paragraphTag = enterMode == CKEDITOR.ENTER_P ? 'p' : 'div'; | ||
444 | |||
445 | // Two line-breaks create one paragraphing block. | ||
446 | if ( !isEnterBrMode ) { | ||
447 | var duoLF = /\n{2}/g; | ||
448 | if ( duoLF.test( html ) ) { | ||
449 | var openTag = '<' + paragraphTag + '>', endTag = '</' + paragraphTag + '>'; | ||
450 | html = openTag + html.replace( duoLF, function() { | ||
451 | return endTag + openTag; | ||
452 | } ) + endTag; | ||
453 | } | ||
454 | } | ||
455 | |||
456 | // One <br> per line-break. | ||
457 | html = html.replace( /\n/g, '<br>' ); | ||
458 | |||
459 | // Compensate padding <br> at the end of block, avoid loosing them during insertion. | ||
460 | if ( !isEnterBrMode ) { | ||
461 | html = html.replace( new RegExp( '<br>(?=</' + paragraphTag + '>)' ), function( match ) { | ||
462 | return CKEDITOR.tools.repeat( match, 2 ); | ||
463 | } ); | ||
464 | } | ||
465 | |||
466 | // Preserve spaces at the ends, so they won't be lost after insertion (merged with adjacent ones). | ||
467 | html = html.replace( /^ | $/g, ' ' ); | ||
468 | |||
469 | // Finally, preserve whitespaces that are to be lost. | ||
470 | html = html.replace( /(>|\s) /g, function( match, before ) { | ||
471 | return before + ' '; | ||
472 | } ).replace( / (?=<)/g, ' ' ); | ||
473 | |||
474 | return html; | ||
475 | }, | ||
476 | |||
477 | /** | ||
478 | * Gets a unique number for this CKEDITOR execution session. It returns | ||
479 | * consecutive numbers starting from 1. | ||
480 | * | ||
481 | * alert( CKEDITOR.tools.getNextNumber() ); // (e.g.) 1 | ||
482 | * alert( CKEDITOR.tools.getNextNumber() ); // 2 | ||
483 | * | ||
484 | * @method | ||
485 | * @returns {Number} A unique number. | ||
486 | */ | ||
487 | getNextNumber: ( function() { | ||
488 | var last = 0; | ||
489 | return function() { | ||
490 | return ++last; | ||
491 | }; | ||
492 | } )(), | ||
493 | |||
494 | /** | ||
495 | * Gets a unique ID for CKEditor interface elements. It returns a | ||
496 | * string with the "cke_" prefix and a consecutive number. | ||
497 | * | ||
498 | * alert( CKEDITOR.tools.getNextId() ); // (e.g.) 'cke_1' | ||
499 | * alert( CKEDITOR.tools.getNextId() ); // 'cke_2' | ||
500 | * | ||
501 | * @returns {String} A unique ID. | ||
502 | */ | ||
503 | getNextId: function() { | ||
504 | return 'cke_' + this.getNextNumber(); | ||
505 | }, | ||
506 | |||
507 | /** | ||
508 | * Gets a universally unique ID. It returns a random string | ||
509 | * compliant with ISO/IEC 11578:1996, without dashes, with the "e" prefix to | ||
510 | * make sure that the ID does not start with a number. | ||
511 | * | ||
512 | * @returns {String} A global unique ID. | ||
513 | */ | ||
514 | getUniqueId: function() { | ||
515 | var uuid = 'e'; // Make sure that id does not start with number. | ||
516 | for ( var i = 0; i < 8; i++ ) { | ||
517 | uuid += Math.floor( ( 1 + Math.random() ) * 0x10000 ).toString( 16 ).substring( 1 ); | ||
518 | } | ||
519 | return uuid; | ||
520 | }, | ||
521 | |||
522 | /** | ||
523 | * Creates a function override. | ||
524 | * | ||
525 | * var obj = { | ||
526 | * myFunction: function( name ) { | ||
527 | * alert( 'Name: ' + name ); | ||
528 | * } | ||
529 | * }; | ||
530 | * | ||
531 | * obj.myFunction = CKEDITOR.tools.override( obj.myFunction, function( myFunctionOriginal ) { | ||
532 | * return function( name ) { | ||
533 | * alert( 'Overriden name: ' + name ); | ||
534 | * myFunctionOriginal.call( this, name ); | ||
535 | * }; | ||
536 | * } ); | ||
537 | * | ||
538 | * @param {Function} originalFunction The function to be overridden. | ||
539 | * @param {Function} functionBuilder A function that returns the new | ||
540 | * function. The original function reference will be passed to this function. | ||
541 | * @returns {Function} The new function. | ||
542 | */ | ||
543 | override: function( originalFunction, functionBuilder ) { | ||
544 | var newFn = functionBuilder( originalFunction ); | ||
545 | newFn.prototype = originalFunction.prototype; | ||
546 | return newFn; | ||
547 | }, | ||
548 | |||
549 | /** | ||
550 | * Executes a function after a specified delay. | ||
551 | * | ||
552 | * CKEDITOR.tools.setTimeout( function() { | ||
553 | * alert( 'Executed after 2 seconds' ); | ||
554 | * }, 2000 ); | ||
555 | * | ||
556 | * @param {Function} func The function to be executed. | ||
557 | * @param {Number} [milliseconds=0] The amount of time (in milliseconds) to wait | ||
558 | * to fire the function execution. | ||
559 | * @param {Object} [scope=window] The object to store the function execution scope | ||
560 | * (the `this` object). | ||
561 | * @param {Object/Array} [args] A single object, or an array of objects, to | ||
562 | * pass as argument to the function. | ||
563 | * @param {Object} [ownerWindow=window] The window that will be used to set the | ||
564 | * timeout. | ||
565 | * @returns {Object} A value that can be used to cancel the function execution. | ||
566 | */ | ||
567 | setTimeout: function( func, milliseconds, scope, args, ownerWindow ) { | ||
568 | if ( !ownerWindow ) | ||
569 | ownerWindow = window; | ||
570 | |||
571 | if ( !scope ) | ||
572 | scope = ownerWindow; | ||
573 | |||
574 | return ownerWindow.setTimeout( function() { | ||
575 | if ( args ) | ||
576 | func.apply( scope, [].concat( args ) ); | ||
577 | else | ||
578 | func.apply( scope ); | ||
579 | }, milliseconds || 0 ); | ||
580 | }, | ||
581 | |||
582 | /** | ||
583 | * Removes spaces from the start and the end of a string. The following | ||
584 | * characters are removed: space, tab, line break, line feed. | ||
585 | * | ||
586 | * alert( CKEDITOR.tools.trim( ' example ' ); // 'example' | ||
587 | * | ||
588 | * @method | ||
589 | * @param {String} str The text from which the spaces will be removed. | ||
590 | * @returns {String} The modified string without the boundary spaces. | ||
591 | */ | ||
592 | trim: ( function() { | ||
593 | // We are not using \s because we don't want "non-breaking spaces" to be caught. | ||
594 | var trimRegex = /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g; | ||
595 | return function( str ) { | ||
596 | return str.replace( trimRegex, '' ); | ||
597 | }; | ||
598 | } )(), | ||
599 | |||
600 | /** | ||
601 | * Removes spaces from the start (left) of a string. The following | ||
602 | * characters are removed: space, tab, line break, line feed. | ||
603 | * | ||
604 | * alert( CKEDITOR.tools.ltrim( ' example ' ); // 'example ' | ||
605 | * | ||
606 | * @method | ||
607 | * @param {String} str The text from which the spaces will be removed. | ||
608 | * @returns {String} The modified string excluding the removed spaces. | ||
609 | */ | ||
610 | ltrim: ( function() { | ||
611 | // We are not using \s because we don't want "non-breaking spaces" to be caught. | ||
612 | var trimRegex = /^[ \t\n\r]+/g; | ||
613 | return function( str ) { | ||
614 | return str.replace( trimRegex, '' ); | ||
615 | }; | ||
616 | } )(), | ||
617 | |||
618 | /** | ||
619 | * Removes spaces from the end (right) of a string. The following | ||
620 | * characters are removed: space, tab, line break, line feed. | ||
621 | * | ||
622 | * alert( CKEDITOR.tools.ltrim( ' example ' ); // ' example' | ||
623 | * | ||
624 | * @method | ||
625 | * @param {String} str The text from which spaces will be removed. | ||
626 | * @returns {String} The modified string excluding the removed spaces. | ||
627 | */ | ||
628 | rtrim: ( function() { | ||
629 | // We are not using \s because we don't want "non-breaking spaces" to be caught. | ||
630 | var trimRegex = /[ \t\n\r]+$/g; | ||
631 | return function( str ) { | ||
632 | return str.replace( trimRegex, '' ); | ||
633 | }; | ||
634 | } )(), | ||
635 | |||
636 | /** | ||
637 | * Returns the index of an element in an array. | ||
638 | * | ||
639 | * var letters = [ 'a', 'b', 0, 'c', false ]; | ||
640 | * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // -1 because 0 !== '0' | ||
641 | * alert( CKEDITOR.tools.indexOf( letters, false ) ); // 4 because 0 !== false | ||
642 | * | ||
643 | * @param {Array} array The array to be searched. | ||
644 | * @param {Object/Function} value The element to be found. This can be an | ||
645 | * evaluation function which receives a single parameter call for | ||
646 | * each entry in the array, returning `true` if the entry matches. | ||
647 | * @returns {Number} The (zero-based) index of the first entry that matches | ||
648 | * the entry, or `-1` if not found. | ||
649 | */ | ||
650 | indexOf: function( array, value ) { | ||
651 | if ( typeof value == 'function' ) { | ||
652 | for ( var i = 0, len = array.length; i < len; i++ ) { | ||
653 | if ( value( array[ i ] ) ) | ||
654 | return i; | ||
655 | } | ||
656 | } else if ( array.indexOf ) | ||
657 | return array.indexOf( value ); | ||
658 | else { | ||
659 | for ( i = 0, len = array.length; i < len; i++ ) { | ||
660 | if ( array[ i ] === value ) | ||
661 | return i; | ||
662 | } | ||
663 | } | ||
664 | return -1; | ||
665 | }, | ||
666 | |||
667 | /** | ||
668 | * Returns the index of an element in an array. | ||
669 | * | ||
670 | * var obj = { prop: true }; | ||
671 | * var letters = [ 'a', 'b', 0, obj, false ]; | ||
672 | * | ||
673 | * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // null | ||
674 | * alert( CKEDITOR.tools.indexOf( letters, function( value ) { | ||
675 | * // Return true when passed value has property 'prop'. | ||
676 | * return value && 'prop' in value; | ||
677 | * } ) ); // obj | ||
678 | * | ||
679 | * @param {Array} array The array to be searched. | ||
680 | * @param {Object/Function} value The element to be found. Can be an | ||
681 | * evaluation function which receives a single parameter call for | ||
682 | * each entry in the array, returning `true` if the entry matches. | ||
683 | * @returns Object The value that was found in an array. | ||
684 | */ | ||
685 | search: function( array, value ) { | ||
686 | var index = CKEDITOR.tools.indexOf( array, value ); | ||
687 | return index >= 0 ? array[ index ] : null; | ||
688 | }, | ||
689 | |||
690 | /** | ||
691 | * Creates a function that will always execute in the context of a | ||
692 | * specified object. | ||
693 | * | ||
694 | * var obj = { text: 'My Object' }; | ||
695 | * | ||
696 | * function alertText() { | ||
697 | * alert( this.text ); | ||
698 | * } | ||
699 | * | ||
700 | * var newFunc = CKEDITOR.tools.bind( alertText, obj ); | ||
701 | * newFunc(); // Alerts 'My Object'. | ||
702 | * | ||
703 | * @param {Function} func The function to be executed. | ||
704 | * @param {Object} obj The object to which the execution context will be bound. | ||
705 | * @returns {Function} The function that can be used to execute the | ||
706 | * `func` function in the context of `obj`. | ||
707 | */ | ||
708 | bind: function( func, obj ) { | ||
709 | return function() { | ||
710 | return func.apply( obj, arguments ); | ||
711 | }; | ||
712 | }, | ||
713 | |||
714 | /** | ||
715 | * Class creation based on prototype inheritance which supports of the | ||
716 | * following features: | ||
717 | * | ||
718 | * * Static fields | ||
719 | * * Private fields | ||
720 | * * Public (prototype) fields | ||
721 | * * Chainable base class constructor | ||
722 | * | ||
723 | * @param {Object} definition The class definition object. | ||
724 | * @returns {Function} A class-like JavaScript function. | ||
725 | */ | ||
726 | createClass: function( definition ) { | ||
727 | var $ = definition.$, | ||
728 | baseClass = definition.base, | ||
729 | privates = definition.privates || definition._, | ||
730 | proto = definition.proto, | ||
731 | statics = definition.statics; | ||
732 | |||
733 | // Create the constructor, if not present in the definition. | ||
734 | !$ && ( $ = function() { | ||
735 | baseClass && this.base.apply( this, arguments ); | ||
736 | } ); | ||
737 | |||
738 | if ( privates ) { | ||
739 | var originalConstructor = $; | ||
740 | $ = function() { | ||
741 | // Create (and get) the private namespace. | ||
742 | var _ = this._ || ( this._ = {} ); | ||
743 | |||
744 | // Make some magic so "this" will refer to the main | ||
745 | // instance when coding private functions. | ||
746 | for ( var privateName in privates ) { | ||
747 | var priv = privates[ privateName ]; | ||
748 | |||
749 | _[ privateName ] = ( typeof priv == 'function' ) ? CKEDITOR.tools.bind( priv, this ) : priv; | ||
750 | } | ||
751 | |||
752 | originalConstructor.apply( this, arguments ); | ||
753 | }; | ||
754 | } | ||
755 | |||
756 | if ( baseClass ) { | ||
757 | $.prototype = this.prototypedCopy( baseClass.prototype ); | ||
758 | $.prototype.constructor = $; | ||
759 | // Super references. | ||
760 | $.base = baseClass; | ||
761 | $.baseProto = baseClass.prototype; | ||
762 | // Super constructor. | ||
763 | $.prototype.base = function() { | ||
764 | this.base = baseClass.prototype.base; | ||
765 | baseClass.apply( this, arguments ); | ||
766 | this.base = arguments.callee; | ||
767 | }; | ||
768 | } | ||
769 | |||
770 | if ( proto ) | ||
771 | this.extend( $.prototype, proto, true ); | ||
772 | |||
773 | if ( statics ) | ||
774 | this.extend( $, statics, true ); | ||
775 | |||
776 | return $; | ||
777 | }, | ||
778 | |||
779 | /** | ||
780 | * Creates a function reference that can be called later using | ||
781 | * {@link #callFunction}. This approach is especially useful to | ||
782 | * make DOM attribute function calls to JavaScript-defined functions. | ||
783 | * | ||
784 | * var ref = CKEDITOR.tools.addFunction( function() { | ||
785 | * alert( 'Hello!'); | ||
786 | * } ); | ||
787 | * CKEDITOR.tools.callFunction( ref ); // 'Hello!' | ||
788 | * | ||
789 | * @param {Function} fn The function to be executed on call. | ||
790 | * @param {Object} [scope] The object to have the context on `fn` execution. | ||
791 | * @returns {Number} A unique reference to be used in conjuction with | ||
792 | * {@link #callFunction}. | ||
793 | */ | ||
794 | addFunction: function( fn, scope ) { | ||
795 | return functions.push( function() { | ||
796 | return fn.apply( scope || this, arguments ); | ||
797 | } ) - 1; | ||
798 | }, | ||
799 | |||
800 | /** | ||
801 | * Removes the function reference created with {@link #addFunction}. | ||
802 | * | ||
803 | * @param {Number} ref The function reference created with | ||
804 | * {@link #addFunction}. | ||
805 | */ | ||
806 | removeFunction: function( ref ) { | ||
807 | functions[ ref ] = null; | ||
808 | }, | ||
809 | |||
810 | /** | ||
811 | * Executes a function based on the reference created with {@link #addFunction}. | ||
812 | * | ||
813 | * var ref = CKEDITOR.tools.addFunction( function() { | ||
814 | * alert( 'Hello!'); | ||
815 | * } ); | ||
816 | * CKEDITOR.tools.callFunction( ref ); // 'Hello!' | ||
817 | * | ||
818 | * @param {Number} ref The function reference created with {@link #addFunction}. | ||
819 | * @param {Mixed} params Any number of parameters to be passed to the executed function. | ||
820 | * @returns {Mixed} The return value of the function. | ||
821 | */ | ||
822 | callFunction: function( ref ) { | ||
823 | var fn = functions[ ref ]; | ||
824 | return fn && fn.apply( window, Array.prototype.slice.call( arguments, 1 ) ); | ||
825 | }, | ||
826 | |||
827 | /** | ||
828 | * Appends the `px` length unit to the size value if it is missing. | ||
829 | * | ||
830 | * var cssLength = CKEDITOR.tools.cssLength; | ||
831 | * cssLength( 42 ); // '42px' | ||
832 | * cssLength( '42' ); // '42px' | ||
833 | * cssLength( '42px' ); // '42px' | ||
834 | * cssLength( '42%' ); // '42%' | ||
835 | * cssLength( 'bold' ); // 'bold' | ||
836 | * cssLength( false ); // '' | ||
837 | * cssLength( NaN ); // '' | ||
838 | * | ||
839 | * @method | ||
840 | * @param {Number/String/Boolean} length | ||
841 | */ | ||
842 | cssLength: ( function() { | ||
843 | var pixelRegex = /^-?\d+\.?\d*px$/, | ||
844 | lengthTrimmed; | ||
845 | |||
846 | return function( length ) { | ||
847 | lengthTrimmed = CKEDITOR.tools.trim( length + '' ) + 'px'; | ||
848 | |||
849 | if ( pixelRegex.test( lengthTrimmed ) ) | ||
850 | return lengthTrimmed; | ||
851 | else | ||
852 | return length || ''; | ||
853 | }; | ||
854 | } )(), | ||
855 | |||
856 | /** | ||
857 | * Converts the specified CSS length value to the calculated pixel length inside this page. | ||
858 | * | ||
859 | * **Note:** Percentage-based value is left intact. | ||
860 | * | ||
861 | * @method | ||
862 | * @param {String} cssLength CSS length value. | ||
863 | */ | ||
864 | convertToPx: ( function() { | ||
865 | var calculator; | ||
866 | |||
867 | return function( cssLength ) { | ||
868 | if ( !calculator ) { | ||
869 | calculator = CKEDITOR.dom.element.createFromHtml( '<div style="position:absolute;left:-9999px;' + | ||
870 | 'top:-9999px;margin:0px;padding:0px;border:0px;"' + | ||
871 | '></div>', CKEDITOR.document ); | ||
872 | CKEDITOR.document.getBody().append( calculator ); | ||
873 | } | ||
874 | |||
875 | if ( !( /%$/ ).test( cssLength ) ) { | ||
876 | calculator.setStyle( 'width', cssLength ); | ||
877 | return calculator.$.clientWidth; | ||
878 | } | ||
879 | |||
880 | return cssLength; | ||
881 | }; | ||
882 | } )(), | ||
883 | |||
884 | /** | ||
885 | * String specified by `str` repeats `times` times. | ||
886 | * | ||
887 | * @param {String} str | ||
888 | * @param {Number} times | ||
889 | * @returns {String} | ||
890 | */ | ||
891 | repeat: function( str, times ) { | ||
892 | return new Array( times + 1 ).join( str ); | ||
893 | }, | ||
894 | |||
895 | /** | ||
896 | * Returns the first successfully executed return value of a function that | ||
897 | * does not throw any exception. | ||
898 | * | ||
899 | * @param {Function...} fn | ||
900 | * @returns {Mixed} | ||
901 | */ | ||
902 | tryThese: function() { | ||
903 | var returnValue; | ||
904 | for ( var i = 0, length = arguments.length; i < length; i++ ) { | ||
905 | var lambda = arguments[ i ]; | ||
906 | try { | ||
907 | returnValue = lambda(); | ||
908 | break; | ||
909 | } catch ( e ) {} | ||
910 | } | ||
911 | return returnValue; | ||
912 | }, | ||
913 | |||
914 | /** | ||
915 | * Generates a combined key from a series of params. | ||
916 | * | ||
917 | * var key = CKEDITOR.tools.genKey( 'key1', 'key2', 'key3' ); | ||
918 | * alert( key ); // 'key1-key2-key3'. | ||
919 | * | ||
920 | * @param {String} subKey One or more strings used as subkeys. | ||
921 | * @returns {String} | ||
922 | */ | ||
923 | genKey: function() { | ||
924 | return Array.prototype.slice.call( arguments ).join( '-' ); | ||
925 | }, | ||
926 | |||
927 | /** | ||
928 | * Creates a "deferred" function which will not run immediately, | ||
929 | * but rather runs as soon as the interpreter’s call stack is empty. | ||
930 | * Behaves much like `window.setTimeout` with a delay. | ||
931 | * | ||
932 | * **Note:** The return value of the original function will be lost. | ||
933 | * | ||
934 | * @param {Function} fn The callee function. | ||
935 | * @returns {Function} The new deferred function. | ||
936 | */ | ||
937 | defer: function( fn ) { | ||
938 | return function() { | ||
939 | var args = arguments, | ||
940 | self = this; | ||
941 | window.setTimeout( function() { | ||
942 | fn.apply( self, args ); | ||
943 | }, 0 ); | ||
944 | }; | ||
945 | }, | ||
946 | |||
947 | /** | ||
948 | * Normalizes CSS data in order to avoid differences in the style attribute. | ||
949 | * | ||
950 | * @param {String} styleText The style data to be normalized. | ||
951 | * @param {Boolean} [nativeNormalize=false] Parse the data using the browser. | ||
952 | * @returns {String} The normalized value. | ||
953 | */ | ||
954 | normalizeCssText: function( styleText, nativeNormalize ) { | ||
955 | var props = [], | ||
956 | name, | ||
957 | parsedProps = CKEDITOR.tools.parseCssText( styleText, true, nativeNormalize ); | ||
958 | |||
959 | for ( name in parsedProps ) | ||
960 | props.push( name + ':' + parsedProps[ name ] ); | ||
961 | |||
962 | props.sort(); | ||
963 | |||
964 | return props.length ? ( props.join( ';' ) + ';' ) : ''; | ||
965 | }, | ||
966 | |||
967 | /** | ||
968 | * Finds and converts `rgb(x,x,x)` color definition to hexadecimal notation. | ||
969 | * | ||
970 | * @param {String} styleText The style data (or just a string containing RGB colors) to be converted. | ||
971 | * @returns {String} The style data with RGB colors converted to hexadecimal equivalents. | ||
972 | */ | ||
973 | convertRgbToHex: function( styleText ) { | ||
974 | return styleText.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue ) { | ||
975 | var color = [ red, green, blue ]; | ||
976 | // Add padding zeros if the hex value is less than 0x10. | ||
977 | for ( var i = 0; i < 3; i++ ) | ||
978 | color[ i ] = ( '0' + parseInt( color[ i ], 10 ).toString( 16 ) ).slice( -2 ); | ||
979 | return '#' + color.join( '' ); | ||
980 | } ); | ||
981 | }, | ||
982 | |||
983 | /** | ||
984 | * Turns inline style text properties into one hash. | ||
985 | * | ||
986 | * @param {String} styleText The style data to be parsed. | ||
987 | * @param {Boolean} [normalize=false] Normalize properties and values | ||
988 | * (e.g. trim spaces, convert to lower case). | ||
989 | * @param {Boolean} [nativeNormalize=false] Parse the data using the browser. | ||
990 | * @returns {Object} The object containing parsed properties. | ||
991 | */ | ||
992 | parseCssText: function( styleText, normalize, nativeNormalize ) { | ||
993 | var retval = {}; | ||
994 | |||
995 | if ( nativeNormalize ) { | ||
996 | // Injects the style in a temporary span object, so the browser parses it, | ||
997 | // retrieving its final format. | ||
998 | var temp = new CKEDITOR.dom.element( 'span' ); | ||
999 | temp.setAttribute( 'style', styleText ); | ||
1000 | styleText = CKEDITOR.tools.convertRgbToHex( temp.getAttribute( 'style' ) || '' ); | ||
1001 | } | ||
1002 | |||
1003 | // IE will leave a single semicolon when failed to parse the style text. (#3891) | ||
1004 | if ( !styleText || styleText == ';' ) | ||
1005 | return retval; | ||
1006 | |||
1007 | styleText.replace( /"/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) { | ||
1008 | if ( normalize ) { | ||
1009 | name = name.toLowerCase(); | ||
1010 | // Normalize font-family property, ignore quotes and being case insensitive. (#7322) | ||
1011 | // http://www.w3.org/TR/css3-fonts/#font-family-the-font-family-property | ||
1012 | if ( name == 'font-family' ) | ||
1013 | value = value.toLowerCase().replace( /["']/g, '' ).replace( /\s*,\s*/g, ',' ); | ||
1014 | value = CKEDITOR.tools.trim( value ); | ||
1015 | } | ||
1016 | |||
1017 | retval[ name ] = value; | ||
1018 | } ); | ||
1019 | return retval; | ||
1020 | }, | ||
1021 | |||
1022 | /** | ||
1023 | * Serializes the `style name => value` hash to a style text. | ||
1024 | * | ||
1025 | * var styleObj = CKEDITOR.tools.parseCssText( 'color: red; border: none' ); | ||
1026 | * console.log( styleObj.color ); // -> 'red' | ||
1027 | * CKEDITOR.tools.writeCssText( styleObj ); // -> 'color:red; border:none' | ||
1028 | * CKEDITOR.tools.writeCssText( styleObj, true ); // -> 'border:none; color:red' | ||
1029 | * | ||
1030 | * @since 4.1 | ||
1031 | * @param {Object} styles The object contaning style properties. | ||
1032 | * @param {Boolean} [sort] Whether to sort CSS properties. | ||
1033 | * @returns {String} The serialized style text. | ||
1034 | */ | ||
1035 | writeCssText: function( styles, sort ) { | ||
1036 | var name, | ||
1037 | stylesArr = []; | ||
1038 | |||
1039 | for ( name in styles ) | ||
1040 | stylesArr.push( name + ':' + styles[ name ] ); | ||
1041 | |||
1042 | if ( sort ) | ||
1043 | stylesArr.sort(); | ||
1044 | |||
1045 | return stylesArr.join( '; ' ); | ||
1046 | }, | ||
1047 | |||
1048 | /** | ||
1049 | * Compares two objects. | ||
1050 | * | ||
1051 | * **Note:** This method performs shallow, non-strict comparison. | ||
1052 | * | ||
1053 | * @since 4.1 | ||
1054 | * @param {Object} left | ||
1055 | * @param {Object} right | ||
1056 | * @param {Boolean} [onlyLeft] Check only the properties that are present in the `left` object. | ||
1057 | * @returns {Boolean} Whether objects are identical. | ||
1058 | */ | ||
1059 | objectCompare: function( left, right, onlyLeft ) { | ||
1060 | var name; | ||
1061 | |||
1062 | if ( !left && !right ) | ||
1063 | return true; | ||
1064 | if ( !left || !right ) | ||
1065 | return false; | ||
1066 | |||
1067 | for ( name in left ) { | ||
1068 | if ( left[ name ] != right[ name ] ) | ||
1069 | return false; | ||
1070 | |||
1071 | } | ||
1072 | |||
1073 | if ( !onlyLeft ) { | ||
1074 | for ( name in right ) { | ||
1075 | if ( left[ name ] != right[ name ] ) | ||
1076 | return false; | ||
1077 | } | ||
1078 | } | ||
1079 | |||
1080 | return true; | ||
1081 | }, | ||
1082 | |||
1083 | /** | ||
1084 | * Returns an array of passed object's keys. | ||
1085 | * | ||
1086 | * console.log( CKEDITOR.tools.objectKeys( { foo: 1, bar: false } ); | ||
1087 | * // -> [ 'foo', 'bar' ] | ||
1088 | * | ||
1089 | * @since 4.1 | ||
1090 | * @param {Object} obj | ||
1091 | * @returns {Array} Object's keys. | ||
1092 | */ | ||
1093 | objectKeys: function( obj ) { | ||
1094 | var keys = []; | ||
1095 | for ( var i in obj ) | ||
1096 | keys.push( i ); | ||
1097 | |||
1098 | return keys; | ||
1099 | }, | ||
1100 | |||
1101 | /** | ||
1102 | * Converts an array to an object by rewriting array items | ||
1103 | * to object properties. | ||
1104 | * | ||
1105 | * var arr = [ 'foo', 'bar', 'foo' ]; | ||
1106 | * console.log( CKEDITOR.tools.convertArrayToObject( arr ) ); | ||
1107 | * // -> { foo: true, bar: true } | ||
1108 | * console.log( CKEDITOR.tools.convertArrayToObject( arr, 1 ) ); | ||
1109 | * // -> { foo: 1, bar: 1 } | ||
1110 | * | ||
1111 | * @since 4.1 | ||
1112 | * @param {Array} arr The array to be converted to an object. | ||
1113 | * @param [fillWith=true] Set each property of an object to `fillWith` value. | ||
1114 | */ | ||
1115 | convertArrayToObject: function( arr, fillWith ) { | ||
1116 | var obj = {}; | ||
1117 | |||
1118 | if ( arguments.length == 1 ) | ||
1119 | fillWith = true; | ||
1120 | |||
1121 | for ( var i = 0, l = arr.length; i < l; ++i ) | ||
1122 | obj[ arr[ i ] ] = fillWith; | ||
1123 | |||
1124 | return obj; | ||
1125 | }, | ||
1126 | |||
1127 | /** | ||
1128 | * Tries to fix the `document.domain` of the current document to match the | ||
1129 | * parent window domain, avoiding "Same Origin" policy issues. | ||
1130 | * This is an Internet Explorer only requirement. | ||
1131 | * | ||
1132 | * @since 4.1.2 | ||
1133 | * @returns {Boolean} `true` if the current domain is already good or if | ||
1134 | * it has been fixed successfully. | ||
1135 | */ | ||
1136 | fixDomain: function() { | ||
1137 | var domain; | ||
1138 | |||
1139 | while ( 1 ) { | ||
1140 | try { | ||
1141 | // Try to access the parent document. It throws | ||
1142 | // "access denied" if restricted by the "Same Origin" policy. | ||
1143 | domain = window.parent.document.domain; | ||
1144 | break; | ||
1145 | } catch ( e ) { | ||
1146 | // Calculate the value to set to document.domain. | ||
1147 | domain = domain ? | ||
1148 | |||
1149 | // If it is not the first pass, strip one part of the | ||
1150 | // name. E.g. "test.example.com" => "example.com" | ||
1151 | domain.replace( /.+?(?:\.|$)/, '' ) : | ||
1152 | |||
1153 | // In the first pass, we'll handle the | ||
1154 | // "document.domain = document.domain" case. | ||
1155 | document.domain; | ||
1156 | |||
1157 | // Stop here if there is no more domain parts available. | ||
1158 | if ( !domain ) | ||
1159 | break; | ||
1160 | |||
1161 | document.domain = domain; | ||
1162 | } | ||
1163 | } | ||
1164 | |||
1165 | return !!domain; | ||
1166 | }, | ||
1167 | |||
1168 | /** | ||
1169 | * Buffers `input` events (or any `input` calls) | ||
1170 | * and triggers `output` not more often than once per `minInterval`. | ||
1171 | * | ||
1172 | * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() { | ||
1173 | * console.log( 'foo!' ); | ||
1174 | * } ); | ||
1175 | * | ||
1176 | * buffer.input(); | ||
1177 | * // 'foo!' logged immediately. | ||
1178 | * buffer.input(); | ||
1179 | * // Nothing logged. | ||
1180 | * buffer.input(); | ||
1181 | * // Nothing logged. | ||
1182 | * // ... after 200ms a single 'foo!' will be logged. | ||
1183 | * | ||
1184 | * Can be easily used with events: | ||
1185 | * | ||
1186 | * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() { | ||
1187 | * console.log( 'foo!' ); | ||
1188 | * } ); | ||
1189 | * | ||
1190 | * editor.on( 'key', buffer.input ); | ||
1191 | * // Note: There is no need to bind buffer as a context. | ||
1192 | * | ||
1193 | * @since 4.2.1 | ||
1194 | * @param {Number} minInterval Minimum interval between `output` calls in milliseconds. | ||
1195 | * @param {Function} output Function that will be executed as `output`. | ||
1196 | * @param {Object} [scopeObj] The object used to scope the listener call (the `this` object). | ||
1197 | * @returns {Object} | ||
1198 | * @returns {Function} return.input Buffer's input method. | ||
1199 | * @returns {Function} return.reset Resets buffered events — `output` will not be executed | ||
1200 | * until next `input` is triggered. | ||
1201 | */ | ||
1202 | eventsBuffer: function( minInterval, output, scopeObj ) { | ||
1203 | var scheduled, | ||
1204 | lastOutput = 0; | ||
1205 | |||
1206 | function triggerOutput() { | ||
1207 | lastOutput = ( new Date() ).getTime(); | ||
1208 | scheduled = false; | ||
1209 | if ( scopeObj ) { | ||
1210 | output.call( scopeObj ); | ||
1211 | } else { | ||
1212 | output(); | ||
1213 | } | ||
1214 | } | ||
1215 | |||
1216 | return { | ||
1217 | input: function() { | ||
1218 | if ( scheduled ) | ||
1219 | return; | ||
1220 | |||
1221 | var diff = ( new Date() ).getTime() - lastOutput; | ||
1222 | |||
1223 | // If less than minInterval passed after last check, | ||
1224 | // schedule next for minInterval after previous one. | ||
1225 | if ( diff < minInterval ) | ||
1226 | scheduled = setTimeout( triggerOutput, minInterval - diff ); | ||
1227 | else | ||
1228 | triggerOutput(); | ||
1229 | }, | ||
1230 | |||
1231 | reset: function() { | ||
1232 | if ( scheduled ) | ||
1233 | clearTimeout( scheduled ); | ||
1234 | |||
1235 | scheduled = lastOutput = 0; | ||
1236 | } | ||
1237 | }; | ||
1238 | }, | ||
1239 | |||
1240 | /** | ||
1241 | * Enables HTML5 elements for older browsers (IE8) in the passed document. | ||
1242 | * | ||
1243 | * In IE8 this method can also be executed on a document fragment. | ||
1244 | * | ||
1245 | * **Note:** This method has to be used in the `<head>` section of the document. | ||
1246 | * | ||
1247 | * @since 4.3 | ||
1248 | * @param {Object} doc Native `Document` or `DocumentFragment` in which the elements will be enabled. | ||
1249 | * @param {Boolean} [withAppend] Whether to append created elements to the `doc`. | ||
1250 | */ | ||
1251 | enableHtml5Elements: function( doc, withAppend ) { | ||
1252 | var els = 'abbr,article,aside,audio,bdi,canvas,data,datalist,details,figcaption,figure,footer,header,hgroup,main,mark,meter,nav,output,progress,section,summary,time,video'.split( ',' ), | ||
1253 | i = els.length, | ||
1254 | el; | ||
1255 | |||
1256 | while ( i-- ) { | ||
1257 | el = doc.createElement( els[ i ] ); | ||
1258 | if ( withAppend ) | ||
1259 | doc.appendChild( el ); | ||
1260 | } | ||
1261 | }, | ||
1262 | |||
1263 | /** | ||
1264 | * Checks if any of the `arr` items match the provided regular expression. | ||
1265 | * | ||
1266 | * @param {Array} arr The array whose items will be checked. | ||
1267 | * @param {RegExp} regexp The regular expression. | ||
1268 | * @returns {Boolean} Returns `true` for the first occurrence of the search pattern. | ||
1269 | * @since 4.4 | ||
1270 | */ | ||
1271 | checkIfAnyArrayItemMatches: function( arr, regexp ) { | ||
1272 | for ( var i = 0, l = arr.length; i < l; ++i ) { | ||
1273 | if ( arr[ i ].match( regexp ) ) | ||
1274 | return true; | ||
1275 | } | ||
1276 | return false; | ||
1277 | }, | ||
1278 | |||
1279 | /** | ||
1280 | * Checks if any of the `obj` properties match the provided regular expression. | ||
1281 | * | ||
1282 | * @param obj The object whose properties will be checked. | ||
1283 | * @param {RegExp} regexp The regular expression. | ||
1284 | * @returns {Boolean} Returns `true` for the first occurrence of the search pattern. | ||
1285 | * @since 4.4 | ||
1286 | */ | ||
1287 | checkIfAnyObjectPropertyMatches: function( obj, regexp ) { | ||
1288 | for ( var i in obj ) { | ||
1289 | if ( i.match( regexp ) ) | ||
1290 | return true; | ||
1291 | } | ||
1292 | return false; | ||
1293 | }, | ||
1294 | |||
1295 | /** | ||
1296 | * The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`. | ||
1297 | * | ||
1298 | * @since 4.4 | ||
1299 | * @readonly | ||
1300 | */ | ||
1301 | transparentImageData: 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==', | ||
1302 | |||
1303 | |||
1304 | /** | ||
1305 | * Returns the value of the cookie with a given name or `null` if the cookie is not found. | ||
1306 | * | ||
1307 | * @since 4.5.6 | ||
1308 | * @param {String} name | ||
1309 | * @returns {String} | ||
1310 | */ | ||
1311 | getCookie: function( name ) { | ||
1312 | name = name.toLowerCase(); | ||
1313 | var parts = document.cookie.split( ';' ); | ||
1314 | var pair, key; | ||
1315 | |||
1316 | for ( var i = 0; i < parts.length; i++ ) { | ||
1317 | pair = parts[ i ].split( '=' ); | ||
1318 | key = decodeURIComponent( CKEDITOR.tools.trim( pair[ 0 ] ).toLowerCase() ); | ||
1319 | |||
1320 | if ( key === name ) { | ||
1321 | return decodeURIComponent( pair.length > 1 ? pair[ 1 ] : '' ); | ||
1322 | } | ||
1323 | } | ||
1324 | |||
1325 | return null; | ||
1326 | }, | ||
1327 | |||
1328 | /** | ||
1329 | * Sets the value of the cookie with a given name. | ||
1330 | * | ||
1331 | * @since 4.5.6 | ||
1332 | * @param {String} name | ||
1333 | * @param {String} value | ||
1334 | */ | ||
1335 | setCookie: function( name, value ) { | ||
1336 | document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( value ) + ';path=/'; | ||
1337 | }, | ||
1338 | |||
1339 | /** | ||
1340 | * Returns the CSRF token value. The value is a hash stored in `document.cookie` | ||
1341 | * under the `ckCsrfToken` key. The CSRF token can be used to secure the communication | ||
1342 | * between the web browser and the server, i.e. for the file upload feature in the editor. | ||
1343 | * | ||
1344 | * @since 4.5.6 | ||
1345 | * @returns {String} | ||
1346 | */ | ||
1347 | getCsrfToken: function() { | ||
1348 | var token = CKEDITOR.tools.getCookie( TOKEN_COOKIE_NAME ); | ||
1349 | |||
1350 | if ( !token || token.length != TOKEN_LENGTH ) { | ||
1351 | token = generateToken( TOKEN_LENGTH ); | ||
1352 | CKEDITOR.tools.setCookie( TOKEN_COOKIE_NAME, token ); | ||
1353 | } | ||
1354 | |||
1355 | return token; | ||
1356 | } | ||
1357 | }; | ||
1358 | |||
1359 | // Generates a CSRF token with a given length. | ||
1360 | // | ||
1361 | // @since 4.5.6 | ||
1362 | // @param {Number} length | ||
1363 | // @returns {string} | ||
1364 | function generateToken( length ) { | ||
1365 | var randValues = []; | ||
1366 | var result = ''; | ||
1367 | |||
1368 | if ( window.crypto && window.crypto.getRandomValues ) { | ||
1369 | randValues = new Uint8Array( length ); | ||
1370 | window.crypto.getRandomValues( randValues ); | ||
1371 | } else { | ||
1372 | for ( var i = 0; i < length; i++ ) { | ||
1373 | randValues.push( Math.floor( Math.random() * 256 ) ); | ||
1374 | } | ||
1375 | } | ||
1376 | |||
1377 | for ( var j = 0; j < randValues.length; j++ ) { | ||
1378 | var character = tokenCharset.charAt( randValues[ j ] % tokenCharset.length ); | ||
1379 | result += Math.random() > 0.5 ? character.toUpperCase() : character; | ||
1380 | } | ||
1381 | |||
1382 | return result; | ||
1383 | } | ||
1384 | } )(); | ||
1385 | |||
1386 | // PACKAGER_RENAME( CKEDITOR.tools ) | ||