]>
Commit | Line | Data |
---|---|---|
3332bebe | 1 | /** |
317f8f8f | 2 | * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved. |
3332bebe IB |
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 | ||
317f8f8f IB |
983 | /** |
984 | * Normalizes hexadecimal notation so that the color string is always 6 characters long and lowercase. | |
985 | * | |
986 | * @param {String} styleText The style data (or just a string containing hex colors) to be converted. | |
987 | * @returns {String} The style data with hex colors normalized. | |
988 | */ | |
989 | normalizeHex: function( styleText ) { | |
990 | return styleText.replace( /#(([0-9a-f]{3}){1,2})($|;|\s+)/gi, function( match, hexColor, hexColorPart, separator ) { | |
991 | var normalizedHexColor = hexColor.toLowerCase(); | |
992 | if ( normalizedHexColor.length == 3 ) { | |
993 | var parts = normalizedHexColor.split( '' ); | |
994 | normalizedHexColor = [ parts[ 0 ], parts[ 0 ], parts[ 1 ], parts[ 1 ], parts[ 2 ], parts[ 2 ] ].join( '' ); | |
995 | } | |
996 | return '#' + normalizedHexColor + separator; | |
997 | } ); | |
998 | }, | |
999 | ||
3332bebe IB |
1000 | /** |
1001 | * Turns inline style text properties into one hash. | |
1002 | * | |
1003 | * @param {String} styleText The style data to be parsed. | |
1004 | * @param {Boolean} [normalize=false] Normalize properties and values | |
1005 | * (e.g. trim spaces, convert to lower case). | |
1006 | * @param {Boolean} [nativeNormalize=false] Parse the data using the browser. | |
1007 | * @returns {Object} The object containing parsed properties. | |
1008 | */ | |
1009 | parseCssText: function( styleText, normalize, nativeNormalize ) { | |
1010 | var retval = {}; | |
1011 | ||
1012 | if ( nativeNormalize ) { | |
1013 | // Injects the style in a temporary span object, so the browser parses it, | |
1014 | // retrieving its final format. | |
1015 | var temp = new CKEDITOR.dom.element( 'span' ); | |
317f8f8f IB |
1016 | styleText = temp.setAttribute( 'style', styleText ).getAttribute( 'style' ) || ''; |
1017 | } | |
1018 | ||
1019 | // Normalize colors. | |
1020 | if ( styleText ) { | |
1021 | styleText = CKEDITOR.tools.normalizeHex( CKEDITOR.tools.convertRgbToHex( styleText ) ); | |
3332bebe IB |
1022 | } |
1023 | ||
317f8f8f | 1024 | // IE will leave a single semicolon when failed to parse the style text. (http://dev.ckeditor.com/ticket/3891) |
3332bebe IB |
1025 | if ( !styleText || styleText == ';' ) |
1026 | return retval; | |
1027 | ||
1028 | styleText.replace( /"/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) { | |
1029 | if ( normalize ) { | |
1030 | name = name.toLowerCase(); | |
317f8f8f | 1031 | // Drop extra whitespacing from font-family. |
3332bebe | 1032 | if ( name == 'font-family' ) |
317f8f8f | 1033 | value = value.replace( /\s*,\s*/g, ',' ); |
3332bebe IB |
1034 | value = CKEDITOR.tools.trim( value ); |
1035 | } | |
1036 | ||
1037 | retval[ name ] = value; | |
1038 | } ); | |
1039 | return retval; | |
1040 | }, | |
1041 | ||
1042 | /** | |
1043 | * Serializes the `style name => value` hash to a style text. | |
1044 | * | |
1045 | * var styleObj = CKEDITOR.tools.parseCssText( 'color: red; border: none' ); | |
1046 | * console.log( styleObj.color ); // -> 'red' | |
1047 | * CKEDITOR.tools.writeCssText( styleObj ); // -> 'color:red; border:none' | |
1048 | * CKEDITOR.tools.writeCssText( styleObj, true ); // -> 'border:none; color:red' | |
1049 | * | |
1050 | * @since 4.1 | |
1051 | * @param {Object} styles The object contaning style properties. | |
1052 | * @param {Boolean} [sort] Whether to sort CSS properties. | |
1053 | * @returns {String} The serialized style text. | |
1054 | */ | |
1055 | writeCssText: function( styles, sort ) { | |
1056 | var name, | |
1057 | stylesArr = []; | |
1058 | ||
1059 | for ( name in styles ) | |
1060 | stylesArr.push( name + ':' + styles[ name ] ); | |
1061 | ||
1062 | if ( sort ) | |
1063 | stylesArr.sort(); | |
1064 | ||
1065 | return stylesArr.join( '; ' ); | |
1066 | }, | |
1067 | ||
1068 | /** | |
1069 | * Compares two objects. | |
1070 | * | |
1071 | * **Note:** This method performs shallow, non-strict comparison. | |
1072 | * | |
1073 | * @since 4.1 | |
1074 | * @param {Object} left | |
1075 | * @param {Object} right | |
1076 | * @param {Boolean} [onlyLeft] Check only the properties that are present in the `left` object. | |
1077 | * @returns {Boolean} Whether objects are identical. | |
1078 | */ | |
1079 | objectCompare: function( left, right, onlyLeft ) { | |
1080 | var name; | |
1081 | ||
1082 | if ( !left && !right ) | |
1083 | return true; | |
1084 | if ( !left || !right ) | |
1085 | return false; | |
1086 | ||
1087 | for ( name in left ) { | |
1088 | if ( left[ name ] != right[ name ] ) | |
1089 | return false; | |
1090 | ||
1091 | } | |
1092 | ||
1093 | if ( !onlyLeft ) { | |
1094 | for ( name in right ) { | |
1095 | if ( left[ name ] != right[ name ] ) | |
1096 | return false; | |
1097 | } | |
1098 | } | |
1099 | ||
1100 | return true; | |
1101 | }, | |
1102 | ||
1103 | /** | |
1104 | * Returns an array of passed object's keys. | |
1105 | * | |
1106 | * console.log( CKEDITOR.tools.objectKeys( { foo: 1, bar: false } ); | |
1107 | * // -> [ 'foo', 'bar' ] | |
1108 | * | |
1109 | * @since 4.1 | |
1110 | * @param {Object} obj | |
1111 | * @returns {Array} Object's keys. | |
1112 | */ | |
1113 | objectKeys: function( obj ) { | |
1114 | var keys = []; | |
1115 | for ( var i in obj ) | |
1116 | keys.push( i ); | |
1117 | ||
1118 | return keys; | |
1119 | }, | |
1120 | ||
1121 | /** | |
1122 | * Converts an array to an object by rewriting array items | |
1123 | * to object properties. | |
1124 | * | |
1125 | * var arr = [ 'foo', 'bar', 'foo' ]; | |
1126 | * console.log( CKEDITOR.tools.convertArrayToObject( arr ) ); | |
1127 | * // -> { foo: true, bar: true } | |
1128 | * console.log( CKEDITOR.tools.convertArrayToObject( arr, 1 ) ); | |
1129 | * // -> { foo: 1, bar: 1 } | |
1130 | * | |
1131 | * @since 4.1 | |
1132 | * @param {Array} arr The array to be converted to an object. | |
1133 | * @param [fillWith=true] Set each property of an object to `fillWith` value. | |
1134 | */ | |
1135 | convertArrayToObject: function( arr, fillWith ) { | |
1136 | var obj = {}; | |
1137 | ||
1138 | if ( arguments.length == 1 ) | |
1139 | fillWith = true; | |
1140 | ||
1141 | for ( var i = 0, l = arr.length; i < l; ++i ) | |
1142 | obj[ arr[ i ] ] = fillWith; | |
1143 | ||
1144 | return obj; | |
1145 | }, | |
1146 | ||
1147 | /** | |
1148 | * Tries to fix the `document.domain` of the current document to match the | |
1149 | * parent window domain, avoiding "Same Origin" policy issues. | |
1150 | * This is an Internet Explorer only requirement. | |
1151 | * | |
1152 | * @since 4.1.2 | |
1153 | * @returns {Boolean} `true` if the current domain is already good or if | |
1154 | * it has been fixed successfully. | |
1155 | */ | |
1156 | fixDomain: function() { | |
1157 | var domain; | |
1158 | ||
1159 | while ( 1 ) { | |
1160 | try { | |
1161 | // Try to access the parent document. It throws | |
1162 | // "access denied" if restricted by the "Same Origin" policy. | |
1163 | domain = window.parent.document.domain; | |
1164 | break; | |
1165 | } catch ( e ) { | |
1166 | // Calculate the value to set to document.domain. | |
1167 | domain = domain ? | |
1168 | ||
1169 | // If it is not the first pass, strip one part of the | |
1170 | // name. E.g. "test.example.com" => "example.com" | |
1171 | domain.replace( /.+?(?:\.|$)/, '' ) : | |
1172 | ||
1173 | // In the first pass, we'll handle the | |
1174 | // "document.domain = document.domain" case. | |
1175 | document.domain; | |
1176 | ||
1177 | // Stop here if there is no more domain parts available. | |
1178 | if ( !domain ) | |
1179 | break; | |
1180 | ||
1181 | document.domain = domain; | |
1182 | } | |
1183 | } | |
1184 | ||
1185 | return !!domain; | |
1186 | }, | |
1187 | ||
1188 | /** | |
1189 | * Buffers `input` events (or any `input` calls) | |
1190 | * and triggers `output` not more often than once per `minInterval`. | |
1191 | * | |
1192 | * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() { | |
1193 | * console.log( 'foo!' ); | |
1194 | * } ); | |
1195 | * | |
1196 | * buffer.input(); | |
1197 | * // 'foo!' logged immediately. | |
1198 | * buffer.input(); | |
1199 | * // Nothing logged. | |
1200 | * buffer.input(); | |
1201 | * // Nothing logged. | |
1202 | * // ... after 200ms a single 'foo!' will be logged. | |
1203 | * | |
1204 | * Can be easily used with events: | |
1205 | * | |
1206 | * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() { | |
1207 | * console.log( 'foo!' ); | |
1208 | * } ); | |
1209 | * | |
1210 | * editor.on( 'key', buffer.input ); | |
1211 | * // Note: There is no need to bind buffer as a context. | |
1212 | * | |
1213 | * @since 4.2.1 | |
1214 | * @param {Number} minInterval Minimum interval between `output` calls in milliseconds. | |
1215 | * @param {Function} output Function that will be executed as `output`. | |
1216 | * @param {Object} [scopeObj] The object used to scope the listener call (the `this` object). | |
1217 | * @returns {Object} | |
1218 | * @returns {Function} return.input Buffer's input method. | |
1219 | * @returns {Function} return.reset Resets buffered events — `output` will not be executed | |
1220 | * until next `input` is triggered. | |
1221 | */ | |
1222 | eventsBuffer: function( minInterval, output, scopeObj ) { | |
1223 | var scheduled, | |
1224 | lastOutput = 0; | |
1225 | ||
1226 | function triggerOutput() { | |
1227 | lastOutput = ( new Date() ).getTime(); | |
1228 | scheduled = false; | |
1229 | if ( scopeObj ) { | |
1230 | output.call( scopeObj ); | |
1231 | } else { | |
1232 | output(); | |
1233 | } | |
1234 | } | |
1235 | ||
1236 | return { | |
1237 | input: function() { | |
1238 | if ( scheduled ) | |
1239 | return; | |
1240 | ||
1241 | var diff = ( new Date() ).getTime() - lastOutput; | |
1242 | ||
1243 | // If less than minInterval passed after last check, | |
1244 | // schedule next for minInterval after previous one. | |
1245 | if ( diff < minInterval ) | |
1246 | scheduled = setTimeout( triggerOutput, minInterval - diff ); | |
1247 | else | |
1248 | triggerOutput(); | |
1249 | }, | |
1250 | ||
1251 | reset: function() { | |
1252 | if ( scheduled ) | |
1253 | clearTimeout( scheduled ); | |
1254 | ||
1255 | scheduled = lastOutput = 0; | |
1256 | } | |
1257 | }; | |
1258 | }, | |
1259 | ||
1260 | /** | |
1261 | * Enables HTML5 elements for older browsers (IE8) in the passed document. | |
1262 | * | |
1263 | * In IE8 this method can also be executed on a document fragment. | |
1264 | * | |
1265 | * **Note:** This method has to be used in the `<head>` section of the document. | |
1266 | * | |
1267 | * @since 4.3 | |
1268 | * @param {Object} doc Native `Document` or `DocumentFragment` in which the elements will be enabled. | |
1269 | * @param {Boolean} [withAppend] Whether to append created elements to the `doc`. | |
1270 | */ | |
1271 | enableHtml5Elements: function( doc, withAppend ) { | |
1272 | 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( ',' ), | |
1273 | i = els.length, | |
1274 | el; | |
1275 | ||
1276 | while ( i-- ) { | |
1277 | el = doc.createElement( els[ i ] ); | |
1278 | if ( withAppend ) | |
1279 | doc.appendChild( el ); | |
1280 | } | |
1281 | }, | |
1282 | ||
1283 | /** | |
1284 | * Checks if any of the `arr` items match the provided regular expression. | |
1285 | * | |
1286 | * @param {Array} arr The array whose items will be checked. | |
1287 | * @param {RegExp} regexp The regular expression. | |
1288 | * @returns {Boolean} Returns `true` for the first occurrence of the search pattern. | |
1289 | * @since 4.4 | |
1290 | */ | |
1291 | checkIfAnyArrayItemMatches: function( arr, regexp ) { | |
1292 | for ( var i = 0, l = arr.length; i < l; ++i ) { | |
1293 | if ( arr[ i ].match( regexp ) ) | |
1294 | return true; | |
1295 | } | |
1296 | return false; | |
1297 | }, | |
1298 | ||
1299 | /** | |
1300 | * Checks if any of the `obj` properties match the provided regular expression. | |
1301 | * | |
1302 | * @param obj The object whose properties will be checked. | |
1303 | * @param {RegExp} regexp The regular expression. | |
1304 | * @returns {Boolean} Returns `true` for the first occurrence of the search pattern. | |
1305 | * @since 4.4 | |
1306 | */ | |
1307 | checkIfAnyObjectPropertyMatches: function( obj, regexp ) { | |
1308 | for ( var i in obj ) { | |
1309 | if ( i.match( regexp ) ) | |
1310 | return true; | |
1311 | } | |
1312 | return false; | |
1313 | }, | |
1314 | ||
317f8f8f IB |
1315 | /** |
1316 | * Converts a keystroke to its string representation. Returns an object with two fields: | |
1317 | * | |
1318 | * * `display` – A string that should be used for visible labels. | |
1319 | * For Mac devices it uses `⌥` for `ALT`, `⇧` for `SHIFT` and `⌘` for `COMMAND`. | |
1320 | * * `aria` – A string that should be used for ARIA descriptions. | |
1321 | * It does not use special characters such as `⌥`, `⇧` or `⌘`. | |
1322 | * | |
1323 | * var lang = editor.lang.common.keyboard; | |
1324 | * var shortcut = CKEDITOR.tools.keystrokeToString( lang, CKEDITOR.CTRL + 88 ); | |
1325 | * console.log( shortcut.display ); // 'CTRL + X', on Mac '⌘ + X'. | |
1326 | * console.log( shortcut.aria ); // 'CTRL + X', on Mac 'COMMAND + X'. | |
1327 | * | |
1328 | * @since 4.6.0 | |
1329 | * @param {Object} lang A language object with the key name translation. | |
1330 | * @param {Number} keystroke The keystroke to convert. | |
1331 | * @returns {{display: String, aria: String}} | |
1332 | */ | |
1333 | keystrokeToString: function( lang, keystroke ) { | |
1334 | var special = keystroke & 0xFF0000, | |
1335 | key = keystroke & 0x00FFFF, | |
1336 | isMac = CKEDITOR.env.mac, | |
1337 | CTRL = 17, | |
1338 | CMD = 224, | |
1339 | ALT = 18, | |
1340 | SHIFT = 16, | |
1341 | display = [], | |
1342 | aria = []; | |
1343 | ||
1344 | ||
1345 | if ( special & CKEDITOR.CTRL ) { | |
1346 | display.push( isMac ? '⌘' : lang[ CTRL ] ); | |
1347 | aria.push( isMac ? lang[ CMD ] : lang[ CTRL ] ); | |
1348 | } | |
1349 | ||
1350 | if ( special & CKEDITOR.ALT ) { | |
1351 | display.push( isMac ? '⌥' : lang[ ALT ] ); | |
1352 | aria.push( lang[ ALT ] ); | |
1353 | } | |
1354 | ||
1355 | if ( special & CKEDITOR.SHIFT ) { | |
1356 | display.push( isMac ? '⇧' : lang[ SHIFT ] ); | |
1357 | aria.push( lang[ SHIFT ] ); | |
1358 | } | |
1359 | ||
1360 | if ( key ) { | |
1361 | if ( lang[ key ] ) { | |
1362 | display.push( lang[ key ] ); | |
1363 | aria.push( lang[ key ] ); | |
1364 | } else { | |
1365 | display.push( String.fromCharCode( key ) ); | |
1366 | aria.push( String.fromCharCode( key ) ); | |
1367 | } | |
1368 | } | |
1369 | ||
1370 | return { | |
1371 | display: display.join( '+' ), | |
1372 | aria: aria.join( '+' ) | |
1373 | }; | |
1374 | }, | |
1375 | ||
3332bebe IB |
1376 | /** |
1377 | * The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`. | |
1378 | * | |
1379 | * @since 4.4 | |
1380 | * @readonly | |
1381 | */ | |
1382 | transparentImageData: 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==', | |
1383 | ||
1384 | ||
1385 | /** | |
1386 | * Returns the value of the cookie with a given name or `null` if the cookie is not found. | |
1387 | * | |
1388 | * @since 4.5.6 | |
1389 | * @param {String} name | |
1390 | * @returns {String} | |
1391 | */ | |
1392 | getCookie: function( name ) { | |
1393 | name = name.toLowerCase(); | |
1394 | var parts = document.cookie.split( ';' ); | |
1395 | var pair, key; | |
1396 | ||
1397 | for ( var i = 0; i < parts.length; i++ ) { | |
1398 | pair = parts[ i ].split( '=' ); | |
1399 | key = decodeURIComponent( CKEDITOR.tools.trim( pair[ 0 ] ).toLowerCase() ); | |
1400 | ||
1401 | if ( key === name ) { | |
1402 | return decodeURIComponent( pair.length > 1 ? pair[ 1 ] : '' ); | |
1403 | } | |
1404 | } | |
1405 | ||
1406 | return null; | |
1407 | }, | |
1408 | ||
1409 | /** | |
1410 | * Sets the value of the cookie with a given name. | |
1411 | * | |
1412 | * @since 4.5.6 | |
1413 | * @param {String} name | |
1414 | * @param {String} value | |
1415 | */ | |
1416 | setCookie: function( name, value ) { | |
1417 | document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( value ) + ';path=/'; | |
1418 | }, | |
1419 | ||
1420 | /** | |
1421 | * Returns the CSRF token value. The value is a hash stored in `document.cookie` | |
1422 | * under the `ckCsrfToken` key. The CSRF token can be used to secure the communication | |
1423 | * between the web browser and the server, i.e. for the file upload feature in the editor. | |
1424 | * | |
1425 | * @since 4.5.6 | |
1426 | * @returns {String} | |
1427 | */ | |
1428 | getCsrfToken: function() { | |
1429 | var token = CKEDITOR.tools.getCookie( TOKEN_COOKIE_NAME ); | |
1430 | ||
1431 | if ( !token || token.length != TOKEN_LENGTH ) { | |
1432 | token = generateToken( TOKEN_LENGTH ); | |
1433 | CKEDITOR.tools.setCookie( TOKEN_COOKIE_NAME, token ); | |
1434 | } | |
1435 | ||
1436 | return token; | |
317f8f8f IB |
1437 | }, |
1438 | ||
1439 | /** | |
1440 | * Returns an escaped CSS selector. `CSS.escape()` is used if defined, leading digit is escaped otherwise. | |
1441 | * | |
1442 | * @since 4.5.10 | |
1443 | * @param {String} selector A CSS selector to escape. | |
1444 | * @returns {String} An escaped selector. | |
1445 | */ | |
1446 | escapeCss: function( selector ) { | |
1447 | // Invalid input. | |
1448 | if ( !selector ) { | |
1449 | return ''; | |
1450 | } | |
1451 | ||
1452 | // CSS.escape() can be used. | |
1453 | if ( window.CSS && CSS.escape ) { | |
1454 | return CSS.escape( selector ); | |
1455 | } | |
1456 | ||
1457 | // Simple leading digit escape. | |
1458 | if ( !isNaN( parseInt( selector.charAt( 0 ), 10 ) ) ) { | |
1459 | return '\\3' + selector.charAt( 0 ) + ' ' + selector.substring( 1, selector.length ); | |
1460 | } | |
1461 | ||
1462 | return selector; | |
1463 | }, | |
1464 | ||
1465 | /** | |
1466 | * Detects which mouse button generated a given DOM event. | |
1467 | * | |
1468 | * @since 4.7.3 | |
1469 | * @param {CKEDITOR.dom.event} evt DOM event. | |
1470 | * @returns {Number|Boolean} Returns a number indicating the mouse button or `false` | |
1471 | * if the mouse button cannot be determined. | |
1472 | */ | |
1473 | getMouseButton: function( evt ) { | |
1474 | var evtData = evt.data, | |
1475 | domEvent = evtData && evtData.$; | |
1476 | ||
1477 | if ( !( evtData && domEvent ) ) { | |
1478 | // Added in case when there's no data available. That's the case in some unit test in built version which | |
1479 | // mock event but doesn't put data object. | |
1480 | return false; | |
1481 | } | |
1482 | ||
1483 | if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { | |
1484 | if ( domEvent.button === 4 ) { | |
1485 | return CKEDITOR.MOUSE_BUTTON_MIDDLE; | |
1486 | } else if ( domEvent.button === 1 ) { | |
1487 | return CKEDITOR.MOUSE_BUTTON_LEFT; | |
1488 | } else { | |
1489 | return CKEDITOR.MOUSE_BUTTON_RIGHT; | |
1490 | } | |
1491 | } | |
1492 | ||
1493 | return domEvent.button; | |
1494 | }, | |
1495 | ||
1496 | /** | |
1497 | * A set of functions for operations on styles. | |
1498 | * | |
1499 | * @property {CKEDITOR.tools.style} | |
1500 | */ | |
1501 | style: { | |
1502 | /** | |
1503 | * Methods to parse miscellaneous CSS properties. | |
1504 | * | |
1505 | * @property {CKEDITOR.tools.style.parse} | |
1506 | * @member CKEDITOR.tools.style | |
1507 | */ | |
1508 | parse: { | |
1509 | // Color list based on https://www.w3.org/TR/css-color-4/#named-colors. | |
1510 | _colors: { | |
1511 | aliceblue: '#F0F8FF', | |
1512 | antiquewhite: '#FAEBD7', | |
1513 | aqua: '#00FFFF', | |
1514 | aquamarine: '#7FFFD4', | |
1515 | azure: '#F0FFFF', | |
1516 | beige: '#F5F5DC', | |
1517 | bisque: '#FFE4C4', | |
1518 | black: '#000000', | |
1519 | blanchedalmond: '#FFEBCD', | |
1520 | blue: '#0000FF', | |
1521 | blueviolet: '#8A2BE2', | |
1522 | brown: '#A52A2A', | |
1523 | burlywood: '#DEB887', | |
1524 | cadetblue: '#5F9EA0', | |
1525 | chartreuse: '#7FFF00', | |
1526 | chocolate: '#D2691E', | |
1527 | coral: '#FF7F50', | |
1528 | cornflowerblue: '#6495ED', | |
1529 | cornsilk: '#FFF8DC', | |
1530 | crimson: '#DC143C', | |
1531 | cyan: '#00FFFF', | |
1532 | darkblue: '#00008B', | |
1533 | darkcyan: '#008B8B', | |
1534 | darkgoldenrod: '#B8860B', | |
1535 | darkgray: '#A9A9A9', | |
1536 | darkgreen: '#006400', | |
1537 | darkgrey: '#A9A9A9', | |
1538 | darkkhaki: '#BDB76B', | |
1539 | darkmagenta: '#8B008B', | |
1540 | darkolivegreen: '#556B2F', | |
1541 | darkorange: '#FF8C00', | |
1542 | darkorchid: '#9932CC', | |
1543 | darkred: '#8B0000', | |
1544 | darksalmon: '#E9967A', | |
1545 | darkseagreen: '#8FBC8F', | |
1546 | darkslateblue: '#483D8B', | |
1547 | darkslategray: '#2F4F4F', | |
1548 | darkslategrey: '#2F4F4F', | |
1549 | darkturquoise: '#00CED1', | |
1550 | darkviolet: '#9400D3', | |
1551 | deeppink: '#FF1493', | |
1552 | deepskyblue: '#00BFFF', | |
1553 | dimgray: '#696969', | |
1554 | dimgrey: '#696969', | |
1555 | dodgerblue: '#1E90FF', | |
1556 | firebrick: '#B22222', | |
1557 | floralwhite: '#FFFAF0', | |
1558 | forestgreen: '#228B22', | |
1559 | fuchsia: '#FF00FF', | |
1560 | gainsboro: '#DCDCDC', | |
1561 | ghostwhite: '#F8F8FF', | |
1562 | gold: '#FFD700', | |
1563 | goldenrod: '#DAA520', | |
1564 | gray: '#808080', | |
1565 | green: '#008000', | |
1566 | greenyellow: '#ADFF2F', | |
1567 | grey: '#808080', | |
1568 | honeydew: '#F0FFF0', | |
1569 | hotpink: '#FF69B4', | |
1570 | indianred: '#CD5C5C', | |
1571 | indigo: '#4B0082', | |
1572 | ivory: '#FFFFF0', | |
1573 | khaki: '#F0E68C', | |
1574 | lavender: '#E6E6FA', | |
1575 | lavenderblush: '#FFF0F5', | |
1576 | lawngreen: '#7CFC00', | |
1577 | lemonchiffon: '#FFFACD', | |
1578 | lightblue: '#ADD8E6', | |
1579 | lightcoral: '#F08080', | |
1580 | lightcyan: '#E0FFFF', | |
1581 | lightgoldenrodyellow: '#FAFAD2', | |
1582 | lightgray: '#D3D3D3', | |
1583 | lightgreen: '#90EE90', | |
1584 | lightgrey: '#D3D3D3', | |
1585 | lightpink: '#FFB6C1', | |
1586 | lightsalmon: '#FFA07A', | |
1587 | lightseagreen: '#20B2AA', | |
1588 | lightskyblue: '#87CEFA', | |
1589 | lightslategray: '#778899', | |
1590 | lightslategrey: '#778899', | |
1591 | lightsteelblue: '#B0C4DE', | |
1592 | lightyellow: '#FFFFE0', | |
1593 | lime: '#00FF00', | |
1594 | limegreen: '#32CD32', | |
1595 | linen: '#FAF0E6', | |
1596 | magenta: '#FF00FF', | |
1597 | maroon: '#800000', | |
1598 | mediumaquamarine: '#66CDAA', | |
1599 | mediumblue: '#0000CD', | |
1600 | mediumorchid: '#BA55D3', | |
1601 | mediumpurple: '#9370DB', | |
1602 | mediumseagreen: '#3CB371', | |
1603 | mediumslateblue: '#7B68EE', | |
1604 | mediumspringgreen: '#00FA9A', | |
1605 | mediumturquoise: '#48D1CC', | |
1606 | mediumvioletred: '#C71585', | |
1607 | midnightblue: '#191970', | |
1608 | mintcream: '#F5FFFA', | |
1609 | mistyrose: '#FFE4E1', | |
1610 | moccasin: '#FFE4B5', | |
1611 | navajowhite: '#FFDEAD', | |
1612 | navy: '#000080', | |
1613 | oldlace: '#FDF5E6', | |
1614 | olive: '#808000', | |
1615 | olivedrab: '#6B8E23', | |
1616 | orange: '#FFA500', | |
1617 | orangered: '#FF4500', | |
1618 | orchid: '#DA70D6', | |
1619 | palegoldenrod: '#EEE8AA', | |
1620 | palegreen: '#98FB98', | |
1621 | paleturquoise: '#AFEEEE', | |
1622 | palevioletred: '#DB7093', | |
1623 | papayawhip: '#FFEFD5', | |
1624 | peachpuff: '#FFDAB9', | |
1625 | peru: '#CD853F', | |
1626 | pink: '#FFC0CB', | |
1627 | plum: '#DDA0DD', | |
1628 | powderblue: '#B0E0E6', | |
1629 | purple: '#800080', | |
1630 | rebeccapurple: '#663399', | |
1631 | red: '#FF0000', | |
1632 | rosybrown: '#BC8F8F', | |
1633 | royalblue: '#4169E1', | |
1634 | saddlebrown: '#8B4513', | |
1635 | salmon: '#FA8072', | |
1636 | sandybrown: '#F4A460', | |
1637 | seagreen: '#2E8B57', | |
1638 | seashell: '#FFF5EE', | |
1639 | sienna: '#A0522D', | |
1640 | silver: '#C0C0C0', | |
1641 | skyblue: '#87CEEB', | |
1642 | slateblue: '#6A5ACD', | |
1643 | slategray: '#708090', | |
1644 | slategrey: '#708090', | |
1645 | snow: '#FFFAFA', | |
1646 | springgreen: '#00FF7F', | |
1647 | steelblue: '#4682B4', | |
1648 | tan: '#D2B48C', | |
1649 | teal: '#008080', | |
1650 | thistle: '#D8BFD8', | |
1651 | tomato: '#FF6347', | |
1652 | turquoise: '#40E0D0', | |
1653 | violet: '#EE82EE', | |
1654 | wheat: '#F5DEB3', | |
1655 | white: '#FFFFFF', | |
1656 | whitesmoke: '#F5F5F5', | |
1657 | yellow: '#FFFF00', | |
1658 | yellowgreen: '#9ACD32' | |
1659 | }, | |
1660 | ||
1661 | _borderStyle: [ | |
1662 | 'none', | |
1663 | 'hidden', | |
1664 | 'dotted', | |
1665 | 'dashed', | |
1666 | 'solid', | |
1667 | 'double', | |
1668 | 'groove', | |
1669 | 'ridge', | |
1670 | 'inset', | |
1671 | 'outset' | |
1672 | ], | |
1673 | ||
1674 | _widthRegExp: /^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/, | |
1675 | ||
1676 | _rgbaRegExp: /rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi, | |
1677 | ||
1678 | _hslaRegExp: /hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi, | |
1679 | ||
1680 | /** | |
1681 | * Parses the `value` used as a `background` property shorthand and returns information as an object. | |
1682 | * | |
1683 | * **Note:** Currently only the `color` property is extracted. Any other parts will go into the `unprocessed` property. | |
1684 | * | |
1685 | * var background = CKEDITOR.tools.style.parse.background( '#0C0 url(foo.png)' ); | |
1686 | * console.log( background ); | |
1687 | * // Logs: { color: '#0C0', unprocessed: 'url(foo.png)' } | |
1688 | * | |
1689 | * @param {String} value The value of the `background` property. | |
1690 | * @returns {Object} An object with information extracted from the background. | |
1691 | * @returns {String} return.color The **first** color value found. The color format remains the same as in input. | |
1692 | * @returns {String} return.unprocessed The remaining part of the `value` that has not been processed. | |
1693 | * @member CKEDITOR.tools.style.parse | |
1694 | */ | |
1695 | background: function( value ) { | |
1696 | var ret = {}, | |
1697 | colors = this._findColor( value ); | |
1698 | ||
1699 | if ( colors.length ) { | |
1700 | ret.color = colors[ 0 ]; | |
1701 | ||
1702 | CKEDITOR.tools.array.forEach( colors, function( colorToken ) { | |
1703 | value = value.replace( colorToken, '' ); | |
1704 | } ); | |
1705 | } | |
1706 | ||
1707 | value = CKEDITOR.tools.trim( value ); | |
1708 | ||
1709 | if ( value ) { | |
1710 | // If anything was left unprocessed include it as unprocessed part. | |
1711 | ret.unprocessed = value; | |
1712 | } | |
1713 | ||
1714 | return ret; | |
1715 | }, | |
1716 | ||
1717 | /** | |
1718 | * Parses the `margin` CSS property shorthand format. | |
1719 | * | |
1720 | * console.log( CKEDITOR.tools.parse.margin( '3px 0 2' ) ); | |
1721 | * // Logs: { top: "3px", right: "0", bottom: "2", left: "0" } | |
1722 | * | |
1723 | * @param {String} value The `margin` property value. | |
1724 | * @returns {Object} | |
1725 | * @returns {Number} return.top Top margin. | |
1726 | * @returns {Number} return.right Right margin. | |
1727 | * @returns {Number} return.bottom Bottom margin. | |
1728 | * @returns {Number} return.left Left margin. | |
1729 | * @member CKEDITOR.tools.style.parse | |
1730 | */ | |
1731 | margin: function( value ) { | |
1732 | var ret = {}; | |
1733 | ||
1734 | var widths = value.match( /(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset)/g ) || [ '0px' ]; | |
1735 | ||
1736 | switch ( widths.length ) { | |
1737 | case 1: | |
1738 | mapStyles( [ 0, 0, 0, 0 ] ); | |
1739 | break; | |
1740 | case 2: | |
1741 | mapStyles( [ 0, 1, 0, 1 ] ); | |
1742 | break; | |
1743 | case 3: | |
1744 | mapStyles( [ 0, 1, 2, 1 ] ); | |
1745 | break; | |
1746 | case 4: | |
1747 | mapStyles( [ 0, 1, 2, 3 ] ); | |
1748 | break; | |
1749 | } | |
1750 | ||
1751 | function mapStyles( map ) { | |
1752 | ret.top = widths[ map[ 0 ] ]; | |
1753 | ret.right = widths[ map[ 1 ] ]; | |
1754 | ret.bottom = widths[ map[ 2 ] ]; | |
1755 | ret.left = widths[ map[ 3 ] ]; | |
1756 | } | |
1757 | ||
1758 | return ret; | |
1759 | }, | |
1760 | ||
1761 | /** | |
1762 | * Parses the `border` CSS property shorthand format. | |
1763 | * This CSS property does not support inheritance (https://www.w3.org/TR/css3-background/#the-border-shorthands). | |
1764 | * | |
1765 | * console.log( CKEDITOR.tools.style.parse.border( '3px solid #ffeedd' ) ); | |
1766 | * // Logs: { width: "3px", style: "solid", color: "#ffeedd" } | |
1767 | * | |
1768 | * @param {String} value The `border` property value. | |
1769 | * @returns {Object} | |
1770 | * @returns {String} return.width The border-width attribute. | |
1771 | * @returns {String} return.style The border-style attribute. | |
1772 | * @returns {String} return.color The border-color attribute. | |
1773 | * @member CKEDITOR.tools.style.parse | |
1774 | */ | |
1775 | border: function( value ) { | |
1776 | var ret = {}, | |
1777 | input = value.split( /\s+/ ); | |
1778 | ||
1779 | CKEDITOR.tools.array.forEach( input, function( val ) { | |
1780 | if ( !ret.color ) { | |
1781 | var parseColor = CKEDITOR.tools.style.parse._findColor( val ); | |
1782 | if ( parseColor.length ) { | |
1783 | ret.color = parseColor[ 0 ]; | |
1784 | return; | |
1785 | } | |
1786 | } | |
1787 | ||
1788 | if ( !ret.style ) { | |
1789 | if ( CKEDITOR.tools.indexOf( CKEDITOR.tools.style.parse._borderStyle, val ) !== -1 ) { | |
1790 | ret.style = val; | |
1791 | return; | |
1792 | } | |
1793 | } | |
1794 | ||
1795 | if ( !ret.width ) { | |
1796 | if ( CKEDITOR.tools.style.parse._widthRegExp.test( val ) ) { | |
1797 | ret.width = val; | |
1798 | return; | |
1799 | } | |
1800 | } | |
1801 | ||
1802 | } ); | |
1803 | return ret; | |
1804 | }, | |
1805 | ||
1806 | /** | |
1807 | * Searches the `value` for any CSS color occurrences and returns it. | |
1808 | * | |
1809 | * @private | |
1810 | * @param {String} value | |
1811 | * @returns {String[]} An array of matched results. | |
1812 | * @member CKEDITOR.tools.style.parse | |
1813 | */ | |
1814 | _findColor: function( value ) { | |
1815 | var ret = [], | |
1816 | arrayTools = CKEDITOR.tools.array; | |
1817 | ||
1818 | ||
1819 | // Check for rgb(a). | |
1820 | ret = ret.concat( value.match( this._rgbaRegExp ) || [] ); | |
1821 | ||
1822 | // Check for hsl(a). | |
1823 | ret = ret.concat( value.match( this._hslaRegExp ) || [] ); | |
1824 | ||
1825 | ret = ret.concat( arrayTools.filter( value.split( /\s+/ ), function( colorEntry ) { | |
1826 | // Check for hex format. | |
1827 | if ( colorEntry.match( /^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi ) ) { | |
1828 | return true; | |
1829 | } | |
1830 | ||
1831 | // Check for preset names. | |
1832 | return colorEntry.toLowerCase() in CKEDITOR.tools.style.parse._colors; | |
1833 | } ) ); | |
1834 | ||
1835 | return ret; | |
1836 | } | |
1837 | } | |
1838 | }, | |
1839 | ||
1840 | /** | |
1841 | * A set of array helpers. | |
1842 | * | |
1843 | * @property {CKEDITOR.tools.array} | |
1844 | * @member CKEDITOR.tools | |
1845 | */ | |
1846 | array: { | |
1847 | /** | |
1848 | * Returns a copy of `array` filtered using the `fn` function. Any elements that the `fn` will return `false` for | |
1849 | * will get removed from the returned array. | |
1850 | * | |
1851 | * var filtered = this.array.filter( [ 0, 1, 2, 3 ], function( value ) { | |
1852 | * // Leave only values equal or greater than 2. | |
1853 | * return value >= 2; | |
1854 | * } ); | |
1855 | * console.log( filtered ); | |
1856 | * // Logs: [ 2, 3 ] | |
1857 | * | |
1858 | * @param {Array} array | |
1859 | * @param {Function} fn A function that gets called with each `array` item. Any item that `fn` | |
1860 | * returned a `false`-alike value for will be filtered out of the `array`. | |
1861 | * @param {Mixed} fn.value The currently iterated array value. | |
1862 | * @param {Number} fn.index The index of the currently iterated value in an array. | |
1863 | * @param {Array} fn.array The original array passed as the `array` variable. | |
1864 | * @param {Mixed} [thisArg=undefined] A context object for `fn`. | |
1865 | * @returns {Array} The filtered array. | |
1866 | * @member CKEDITOR.tools.array | |
1867 | */ | |
1868 | filter: function( array, fn, thisArg ) { | |
1869 | var ret = []; | |
1870 | ||
1871 | this.forEach( array, function( val, i ) { | |
1872 | if ( fn.call( thisArg, val, i, array ) ) { | |
1873 | ret.push( val ); | |
1874 | } | |
1875 | } ); | |
1876 | ||
1877 | return ret; | |
1878 | }, | |
1879 | ||
1880 | /** | |
1881 | * Iterates over every element in the `array`. | |
1882 | * | |
1883 | * @param {Array} array An array to be iterated over. | |
1884 | * @param {Function} fn The function called for every `array` element. | |
1885 | * @param {Mixed} fn.value The currently iterated array value. | |
1886 | * @param {Number} fn.index The index of the currently iterated value in an array. | |
1887 | * @param {Array} fn.array The original array passed as an `array` variable. | |
1888 | * @param {Mixed} [thisArg=undefined] The context object for `fn`. | |
1889 | * @member CKEDITOR.tools.array | |
1890 | */ | |
1891 | forEach: function( array, fn, thisArg ) { | |
1892 | var len = array.length, | |
1893 | i; | |
1894 | ||
1895 | for ( i = 0; i < len; i++ ) { | |
1896 | fn.call( thisArg, array[ i ], i, array ); | |
1897 | } | |
1898 | }, | |
1899 | ||
1900 | /** | |
1901 | * Applies a function to each element of an array and returns the array of results in the same order. | |
1902 | * Note the order of the parameters. | |
1903 | * | |
1904 | * @param {Array} array An array of elements that `fn` is applied on. | |
1905 | * @param {Function} fn A function with the signature `a -> b`. | |
1906 | * @param {Mixed} [thisArg=undefined] The context object for `fn`. | |
1907 | * @returns {Array} An array of mapped elements. | |
1908 | * @member CKEDITOR.tools.array | |
1909 | * @since 4.6.2 | |
1910 | */ | |
1911 | map: function( array, fn, thisArg ) { | |
1912 | var result = []; | |
1913 | for ( var i = 0; i < array.length; i++ ) { | |
1914 | result.push( fn.call( thisArg, array[ i ], i, array ) ); | |
1915 | } | |
1916 | return result; | |
1917 | }, | |
1918 | ||
1919 | /** | |
1920 | * Applies a function against each value in an array storing the result in an accumulator passed to the next iteration. | |
1921 | * Note the order of the parameters. | |
1922 | * | |
1923 | * @param {Array} array An array of elements that `fn` is applied on. | |
1924 | * @param {Function} fn A function with the signature `(accumulator, a, index, array) -> b`. | |
1925 | * @param {Mixed} initial Initial value of the accumulator. | |
1926 | * @param {Mixed} [thisArg=undefined] The context object for `fn`. | |
1927 | * @returns {Mixed} The final value of the accumulator. | |
1928 | * @member CKEDITOR.tools.array | |
1929 | * @since 4.6.2 | |
1930 | */ | |
1931 | reduce: function( array, fn, initial, thisArg ) { | |
1932 | var acc = initial; | |
1933 | for ( var i = 0; i < array.length; i++ ) { | |
1934 | acc = fn.call( thisArg, acc, array[ i ], i, array ); | |
1935 | } | |
1936 | return acc; | |
1937 | } | |
1938 | }, | |
1939 | ||
1940 | /** | |
1941 | * A set of object helpers. | |
1942 | * | |
1943 | * @property {CKEDITOR.tools.object} | |
1944 | * @member CKEDITOR.tools | |
1945 | */ | |
1946 | object: { | |
1947 | /** | |
1948 | * Returns the first key from `obj` which has a given `value`. | |
1949 | * | |
1950 | * @param {Object} obj An object whose `key` is looked for. | |
1951 | * @param {Mixed} value An object's `value` to be looked for. | |
1952 | * @returns {String/null} Matched `key` or `null` if not found. | |
1953 | * @member CKEDITOR.tools.object | |
1954 | */ | |
1955 | ||
1956 | findKey: function( obj, value ) { | |
1957 | if ( typeof obj !== 'object' ) { | |
1958 | return null; | |
1959 | } | |
1960 | ||
1961 | var key; | |
1962 | ||
1963 | for ( key in obj ) { | |
1964 | if ( obj[ key ] === value ) { | |
1965 | return key; | |
1966 | } | |
1967 | } | |
1968 | ||
1969 | return null; | |
1970 | } | |
3332bebe IB |
1971 | } |
1972 | }; | |
1973 | ||
1974 | // Generates a CSRF token with a given length. | |
1975 | // | |
1976 | // @since 4.5.6 | |
1977 | // @param {Number} length | |
1978 | // @returns {string} | |
1979 | function generateToken( length ) { | |
1980 | var randValues = []; | |
1981 | var result = ''; | |
1982 | ||
1983 | if ( window.crypto && window.crypto.getRandomValues ) { | |
1984 | randValues = new Uint8Array( length ); | |
1985 | window.crypto.getRandomValues( randValues ); | |
1986 | } else { | |
1987 | for ( var i = 0; i < length; i++ ) { | |
1988 | randValues.push( Math.floor( Math.random() * 256 ) ); | |
1989 | } | |
1990 | } | |
1991 | ||
1992 | for ( var j = 0; j < randValues.length; j++ ) { | |
1993 | var character = tokenCharset.charAt( randValues[ j ] % tokenCharset.length ); | |
1994 | result += Math.random() > 0.5 ? character.toUpperCase() : character; | |
1995 | } | |
1996 | ||
1997 | return result; | |
1998 | } | |
317f8f8f IB |
1999 | |
2000 | /** | |
2001 | * @member CKEDITOR.tools.array | |
2002 | * @method indexOf | |
2003 | * @inheritdoc CKEDITOR.tools#indexOf | |
2004 | */ | |
2005 | CKEDITOR.tools.array.indexOf = CKEDITOR.tools.indexOf; | |
2006 | ||
2007 | /** | |
2008 | * @member CKEDITOR.tools.array | |
2009 | * @method isArray | |
2010 | * @inheritdoc CKEDITOR.tools#isArray | |
2011 | */ | |
2012 | CKEDITOR.tools.array.isArray = CKEDITOR.tools.isArray; | |
2013 | ||
2014 | /** | |
2015 | * Left mouse button. | |
2016 | * | |
2017 | * @since 4.7.3 | |
2018 | * @readonly | |
2019 | * @property {Number} [=0] | |
2020 | * @member CKEDITOR | |
2021 | */ | |
2022 | CKEDITOR.MOUSE_BUTTON_LEFT = 0; | |
2023 | ||
2024 | /** | |
2025 | * Middle mouse button. | |
2026 | * | |
2027 | * @since 4.7.3 | |
2028 | * @readonly | |
2029 | * @property {Number} [=1] | |
2030 | * @member CKEDITOR | |
2031 | */ | |
2032 | CKEDITOR.MOUSE_BUTTON_MIDDLE = 1; | |
2033 | ||
2034 | /** | |
2035 | * Right mouse button. | |
2036 | * | |
2037 | * @since 4.7.3 | |
2038 | * @readonly | |
2039 | * @property {Number} [=2] | |
2040 | * @member CKEDITOR | |
2041 | */ | |
2042 | CKEDITOR.MOUSE_BUTTON_RIGHT = 2; | |
2043 | ||
2044 | /** | |
2045 | * The namespace containing functions to work on CSS properties. | |
2046 | * | |
2047 | * @since 4.6.1 | |
2048 | * @class CKEDITOR.tools.style | |
2049 | */ | |
2050 | ||
2051 | /** | |
2052 | * The namespace with helper functions to parse some common CSS properties. | |
2053 | * | |
2054 | * @since 4.6.1 | |
2055 | * @class CKEDITOR.tools.style.parse | |
2056 | */ | |
2057 | ||
2058 | /** | |
2059 | * The namespace with helper functions and polyfills for arrays. | |
2060 | * | |
2061 | * @since 4.6.1 | |
2062 | * @class CKEDITOR.tools.array | |
2063 | */ | |
2064 | ||
2065 | /** | |
2066 | * The namespace with helper functions and polyfills for objects. | |
2067 | * | |
2068 | * @since 4.7.1 | |
2069 | * @class CKEDITOR.tools.object | |
2070 | */ | |
3332bebe IB |
2071 | } )(); |
2072 | ||
2073 | // PACKAGER_RENAME( CKEDITOR.tools ) |