2018-06-17 16:07:19 +00:00
|
|
|
|
/**
|
|
|
|
|
* @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
|
|
|
|
|
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* @fileOverview Defines the {@link CKEDITOR.tools} object that contains
|
2014-04-11 20:05:45 +00:00
|
|
|
|
* utility functions.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
( function() {
|
|
|
|
|
var functions = [],
|
|
|
|
|
cssVendorPrefix =
|
|
|
|
|
CKEDITOR.env.gecko ? '-moz-' :
|
|
|
|
|
CKEDITOR.env.webkit ? '-webkit-' :
|
|
|
|
|
CKEDITOR.env.ie ? '-ms-' :
|
2018-06-17 16:07:19 +00:00
|
|
|
|
'',
|
|
|
|
|
ampRegex = /&/g,
|
|
|
|
|
gtRegex = />/g,
|
|
|
|
|
ltRegex = /</g,
|
|
|
|
|
quoteRegex = /"/g,
|
|
|
|
|
tokenCharset = 'abcdefghijklmnopqrstuvwxyz0123456789',
|
|
|
|
|
TOKEN_COOKIE_NAME = 'ckCsrfToken',
|
|
|
|
|
TOKEN_LENGTH = 40,
|
|
|
|
|
|
|
|
|
|
allEscRegex = /&(lt|gt|amp|quot|nbsp|shy|#\d{1,5});/g,
|
|
|
|
|
namedEntities = {
|
|
|
|
|
lt: '<',
|
|
|
|
|
gt: '>',
|
|
|
|
|
amp: '&',
|
|
|
|
|
quot: '"',
|
|
|
|
|
nbsp: '\u00a0',
|
|
|
|
|
shy: '\u00ad'
|
|
|
|
|
},
|
|
|
|
|
allEscDecode = function( match, code ) {
|
|
|
|
|
if ( code[ 0 ] == '#' ) {
|
|
|
|
|
return String.fromCharCode( parseInt( code.slice( 1 ), 10 ) );
|
|
|
|
|
} else {
|
|
|
|
|
return namedEntities[ code ];
|
|
|
|
|
}
|
|
|
|
|
};
|
2014-04-11 20:05:45 +00:00
|
|
|
|
|
|
|
|
|
CKEDITOR.on( 'reset', function() {
|
|
|
|
|
functions = [];
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Utility functions.
|
|
|
|
|
*
|
|
|
|
|
* @class
|
|
|
|
|
* @singleton
|
|
|
|
|
*/
|
|
|
|
|
CKEDITOR.tools = {
|
|
|
|
|
/**
|
|
|
|
|
* Compares the elements of two arrays.
|
|
|
|
|
*
|
|
|
|
|
* var a = [ 1, 'a', 3 ];
|
|
|
|
|
* var b = [ 1, 3, 'a' ];
|
|
|
|
|
* var c = [ 1, 'a', 3 ];
|
|
|
|
|
* var d = [ 1, 'a', 3, 4 ];
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.arrayCompare( a, b ) ); // false
|
|
|
|
|
* alert( CKEDITOR.tools.arrayCompare( a, c ) ); // true
|
|
|
|
|
* alert( CKEDITOR.tools.arrayCompare( a, d ) ); // false
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} arrayA An array to be compared.
|
|
|
|
|
* @param {Array} arrayB The other array to be compared.
|
|
|
|
|
* @returns {Boolean} `true` if the arrays have the same length and
|
|
|
|
|
* their elements match.
|
|
|
|
|
*/
|
|
|
|
|
arrayCompare: function( arrayA, arrayB ) {
|
|
|
|
|
if ( !arrayA && !arrayB )
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
if ( !arrayA || !arrayB || arrayA.length != arrayB.length )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for ( var i = 0; i < arrayA.length; i++ ) {
|
|
|
|
|
if ( arrayA[ i ] != arrayB[ i ] )
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
2018-06-17 16:07:19 +00:00
|
|
|
|
/**
|
|
|
|
|
* Finds the index of the first element in an array for which the `compareFunction` returns `true`.
|
|
|
|
|
*
|
|
|
|
|
* CKEDITOR.tools.getIndex( [ 1, 2, 4, 3, 5 ], function( el ) {
|
|
|
|
|
* return el >= 3;
|
|
|
|
|
* } ); // 2
|
|
|
|
|
*
|
|
|
|
|
* @since 4.5
|
|
|
|
|
* @param {Array} array Array to search in.
|
|
|
|
|
* @param {Function} compareFunction Compare function.
|
|
|
|
|
* @returns {Number} The index of the first matching element or `-1` if none matches.
|
|
|
|
|
*/
|
|
|
|
|
getIndex: function( arr, compareFunction ) {
|
|
|
|
|
for ( var i = 0; i < arr.length; ++i ) {
|
|
|
|
|
if ( compareFunction( arr[ i ] ) )
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
},
|
|
|
|
|
|
2014-04-11 20:05:45 +00:00
|
|
|
|
/**
|
|
|
|
|
* Creates a deep copy of an object.
|
|
|
|
|
*
|
|
|
|
|
* **Note**: Recursive references are not supported.
|
|
|
|
|
*
|
|
|
|
|
* var obj = {
|
|
|
|
|
* name: 'John',
|
|
|
|
|
* cars: {
|
|
|
|
|
* Mercedes: { color: 'blue' },
|
|
|
|
|
* Porsche: { color: 'red' }
|
|
|
|
|
* }
|
|
|
|
|
* };
|
|
|
|
|
* var clone = CKEDITOR.tools.clone( obj );
|
|
|
|
|
* clone.name = 'Paul';
|
|
|
|
|
* clone.cars.Porsche.color = 'silver';
|
|
|
|
|
*
|
|
|
|
|
* alert( obj.name ); // 'John'
|
|
|
|
|
* alert( clone.name ); // 'Paul'
|
|
|
|
|
* alert( obj.cars.Porsche.color ); // 'red'
|
|
|
|
|
* alert( clone.cars.Porsche.color ); // 'silver'
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} object The object to be cloned.
|
|
|
|
|
* @returns {Object} The object clone.
|
|
|
|
|
*/
|
|
|
|
|
clone: function( obj ) {
|
|
|
|
|
var clone;
|
|
|
|
|
|
|
|
|
|
// Array.
|
|
|
|
|
if ( obj && ( obj instanceof Array ) ) {
|
|
|
|
|
clone = [];
|
|
|
|
|
|
|
|
|
|
for ( var i = 0; i < obj.length; i++ )
|
|
|
|
|
clone[ i ] = CKEDITOR.tools.clone( obj[ i ] );
|
|
|
|
|
|
|
|
|
|
return clone;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "Static" types.
|
2018-06-17 16:07:19 +00:00
|
|
|
|
if ( obj === null || ( typeof obj != 'object' ) || ( obj instanceof String ) || ( obj instanceof Number ) || ( obj instanceof Boolean ) || ( obj instanceof Date ) || ( obj instanceof RegExp ) )
|
|
|
|
|
return obj;
|
|
|
|
|
|
|
|
|
|
// DOM objects and window.
|
|
|
|
|
if ( obj.nodeType || obj.window === obj )
|
2014-04-11 20:05:45 +00:00
|
|
|
|
return obj;
|
|
|
|
|
|
|
|
|
|
// Objects.
|
|
|
|
|
clone = new obj.constructor();
|
|
|
|
|
|
|
|
|
|
for ( var propertyName in obj ) {
|
|
|
|
|
var property = obj[ propertyName ];
|
|
|
|
|
clone[ propertyName ] = CKEDITOR.tools.clone( property );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return clone;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Turns the first letter of a string to upper-case.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} str
|
|
|
|
|
* @param {Boolean} [keepCase] Keep the case of 2nd to last letter.
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
capitalize: function( str, keepCase ) {
|
|
|
|
|
return str.charAt( 0 ).toUpperCase() + ( keepCase ? str.slice( 1 ) : str.slice( 1 ).toLowerCase() );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Copies the properties from one object to another. By default, properties
|
|
|
|
|
* already present in the target object **are not** overwritten.
|
|
|
|
|
*
|
|
|
|
|
* // Create the sample object.
|
|
|
|
|
* var myObject = {
|
|
|
|
|
* prop1: true
|
|
|
|
|
* };
|
|
|
|
|
*
|
|
|
|
|
* // Extend the above object with two properties.
|
|
|
|
|
* CKEDITOR.tools.extend( myObject, {
|
|
|
|
|
* prop2: true,
|
|
|
|
|
* prop3: true
|
|
|
|
|
* } );
|
|
|
|
|
*
|
|
|
|
|
* // Alert 'prop1', 'prop2' and 'prop3'.
|
|
|
|
|
* for ( var p in myObject )
|
|
|
|
|
* alert( p );
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} target The object to be extended.
|
|
|
|
|
* @param {Object...} source The object(s) from properties will be
|
|
|
|
|
* copied. Any number of objects can be passed to this function.
|
|
|
|
|
* @param {Boolean} [overwrite] If `true` is specified, it indicates that
|
|
|
|
|
* properties already present in the target object could be
|
|
|
|
|
* overwritten by subsequent objects.
|
|
|
|
|
* @param {Object} [properties] Only properties within the specified names
|
|
|
|
|
* list will be received from the source object.
|
|
|
|
|
* @returns {Object} The extended object (target).
|
|
|
|
|
*/
|
|
|
|
|
extend: function( target ) {
|
|
|
|
|
var argsLength = arguments.length,
|
|
|
|
|
overwrite, propertiesList;
|
|
|
|
|
|
2018-06-17 16:07:19 +00:00
|
|
|
|
if ( typeof ( overwrite = arguments[ argsLength - 1 ] ) == 'boolean' )
|
2014-04-11 20:05:45 +00:00
|
|
|
|
argsLength--;
|
2018-06-17 16:07:19 +00:00
|
|
|
|
else if ( typeof ( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' ) {
|
2014-04-11 20:05:45 +00:00
|
|
|
|
propertiesList = arguments[ argsLength - 1 ];
|
|
|
|
|
argsLength -= 2;
|
|
|
|
|
}
|
|
|
|
|
for ( var i = 1; i < argsLength; i++ ) {
|
|
|
|
|
var source = arguments[ i ];
|
|
|
|
|
for ( var propertyName in source ) {
|
|
|
|
|
// Only copy existed fields if in overwrite mode.
|
2018-06-17 16:07:19 +00:00
|
|
|
|
if ( overwrite === true || target[ propertyName ] == null ) {
|
2014-04-11 20:05:45 +00:00
|
|
|
|
// Only copy specified fields if list is provided.
|
|
|
|
|
if ( !propertiesList || ( propertyName in propertiesList ) )
|
|
|
|
|
target[ propertyName ] = source[ propertyName ];
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return target;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates an object which is an instance of a class whose prototype is a
|
|
|
|
|
* predefined object. All properties defined in the source object are
|
|
|
|
|
* automatically inherited by the resulting object, including future
|
|
|
|
|
* changes to it.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} source The source object to be used as the prototype for
|
|
|
|
|
* the final object.
|
|
|
|
|
* @returns {Object} The resulting copy.
|
|
|
|
|
*/
|
|
|
|
|
prototypedCopy: function( source ) {
|
|
|
|
|
var copy = function() {};
|
|
|
|
|
copy.prototype = source;
|
|
|
|
|
return new copy();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Makes fast (shallow) copy of an object.
|
|
|
|
|
* This method is faster than {@link #clone} which does
|
|
|
|
|
* a deep copy of an object (including arrays).
|
|
|
|
|
*
|
|
|
|
|
* @since 4.1
|
|
|
|
|
* @param {Object} source The object to be copied.
|
|
|
|
|
* @returns {Object} Copy of `source`.
|
|
|
|
|
*/
|
|
|
|
|
copy: function( source ) {
|
|
|
|
|
var obj = {},
|
|
|
|
|
name;
|
|
|
|
|
|
|
|
|
|
for ( name in source )
|
|
|
|
|
obj[ name ] = source[ name ];
|
|
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if an object is an Array.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.isArray( [] ) ); // true
|
|
|
|
|
* alert( CKEDITOR.tools.isArray( 'Test' ) ); // false
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} object The object to be checked.
|
|
|
|
|
* @returns {Boolean} `true` if the object is an Array, otherwise `false`.
|
|
|
|
|
*/
|
|
|
|
|
isArray: function( object ) {
|
|
|
|
|
return Object.prototype.toString.call( object ) == '[object Array]';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Whether the object contains no properties of its own.
|
|
|
|
|
*
|
|
|
|
|
* @param object
|
|
|
|
|
* @returns {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
isEmpty: function( object ) {
|
|
|
|
|
for ( var i in object ) {
|
|
|
|
|
if ( object.hasOwnProperty( i ) )
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates an object or a string containing vendor-specific and vendor-free CSS properties.
|
|
|
|
|
*
|
|
|
|
|
* CKEDITOR.tools.cssVendorPrefix( 'border-radius', '0', true );
|
|
|
|
|
* // On Firefox: '-moz-border-radius:0;border-radius:0'
|
|
|
|
|
* // On Chrome: '-webkit-border-radius:0;border-radius:0'
|
|
|
|
|
*
|
|
|
|
|
* @param {String} property The CSS property name.
|
|
|
|
|
* @param {String} value The CSS value.
|
|
|
|
|
* @param {Boolean} [asString=false] If `true`, then the returned value will be a CSS string.
|
|
|
|
|
* @returns {Object/String} The object containing CSS properties or its stringified version.
|
|
|
|
|
*/
|
|
|
|
|
cssVendorPrefix: function( property, value, asString ) {
|
|
|
|
|
if ( asString )
|
|
|
|
|
return cssVendorPrefix + property + ':' + value + ';' + property + ':' + value;
|
|
|
|
|
|
|
|
|
|
var ret = {};
|
|
|
|
|
ret[ property ] = value;
|
|
|
|
|
ret[ cssVendorPrefix + property ] = value;
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Transforms a CSS property name to its relative DOM style name.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.cssStyleToDomStyle( 'background-color' ) ); // 'backgroundColor'
|
|
|
|
|
* alert( CKEDITOR.tools.cssStyleToDomStyle( 'float' ) ); // 'cssFloat'
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @param {String} cssName The CSS property name.
|
|
|
|
|
* @returns {String} The transformed name.
|
|
|
|
|
*/
|
|
|
|
|
cssStyleToDomStyle: ( function() {
|
|
|
|
|
var test = document.createElement( 'div' ).style;
|
|
|
|
|
|
|
|
|
|
var cssFloat = ( typeof test.cssFloat != 'undefined' ) ? 'cssFloat' : ( typeof test.styleFloat != 'undefined' ) ? 'styleFloat' : 'float';
|
|
|
|
|
|
|
|
|
|
return function( cssName ) {
|
|
|
|
|
if ( cssName == 'float' )
|
|
|
|
|
return cssFloat;
|
|
|
|
|
else {
|
|
|
|
|
return cssName.replace( /-./g, function( match ) {
|
|
|
|
|
return match.substr( 1 ).toUpperCase();
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Builds a HTML snippet from a set of `<style>/<link>`.
|
|
|
|
|
*
|
|
|
|
|
* @param {String/Array} css Each of which are URLs (absolute) of a CSS file or
|
|
|
|
|
* a trunk of style text.
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
buildStyleHtml: function( css ) {
|
|
|
|
|
css = [].concat( css );
|
|
|
|
|
var item,
|
|
|
|
|
retval = [];
|
|
|
|
|
for ( var i = 0; i < css.length; i++ ) {
|
|
|
|
|
if ( ( item = css[ i ] ) ) {
|
|
|
|
|
// Is CSS style text ?
|
|
|
|
|
if ( /@import|[{}]/.test( item ) )
|
|
|
|
|
retval.push( '<style>' + item + '</style>' );
|
|
|
|
|
else
|
|
|
|
|
retval.push( '<link type="text/css" rel=stylesheet href="' + item + '">' );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return retval.join( '' );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Replaces special HTML characters in a string with their relative HTML
|
|
|
|
|
* entity values.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.htmlEncode( 'A > B & C < D' ) ); // 'A > B & C < D'
|
|
|
|
|
*
|
|
|
|
|
* @param {String} text The string to be encoded.
|
|
|
|
|
* @returns {String} The encoded string.
|
|
|
|
|
*/
|
|
|
|
|
htmlEncode: function( text ) {
|
2018-06-17 16:07:19 +00:00
|
|
|
|
// Backwards compatibility - accept also non-string values (casting is done below).
|
|
|
|
|
// Since 4.4.8 we return empty string for null and undefined because these values make no sense.
|
|
|
|
|
if ( text === undefined || text === null ) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return String( text ).replace( ampRegex, '&' ).replace( gtRegex, '>' ).replace( ltRegex, '<' );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Decodes HTML entities that browsers tend to encode when used in text nodes.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.htmlDecode( '<a & b >' ) ); // '<a & b >'
|
|
|
|
|
*
|
|
|
|
|
* Read more about chosen entities in the [research](https://dev.ckeditor.com/ticket/13105#comment:8).
|
|
|
|
|
*
|
|
|
|
|
* @param {String} The string to be decoded.
|
|
|
|
|
* @returns {String} The decoded string.
|
|
|
|
|
*/
|
|
|
|
|
htmlDecode: function( text ) {
|
|
|
|
|
// See:
|
|
|
|
|
// * https://dev.ckeditor.com/ticket/13105#comment:8 and comment:9,
|
|
|
|
|
// * http://jsperf.com/wth-is-going-on-with-jsperf JSPerf has some serious problems, but you can observe
|
|
|
|
|
// that combined regexp tends to be quicker (except on V8). It will also not be prone to fail on '&lt;'
|
|
|
|
|
// (see https://dev.ckeditor.com/ticket/13105#DXWTF:CKEDITOR.tools.htmlEnDecodeAttr).
|
|
|
|
|
return text.replace( allEscRegex, allEscDecode );
|
2014-04-11 20:05:45 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Replaces special HTML characters in HTMLElement attribute with their relative HTML entity values.
|
|
|
|
|
*
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* alert( CKEDITOR.tools.htmlEncodeAttr( '<a " b >' ) ); // '<a " b >'
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*
|
|
|
|
|
* @param {String} The attribute value to be encoded.
|
|
|
|
|
* @returns {String} The encoded value.
|
|
|
|
|
*/
|
|
|
|
|
htmlEncodeAttr: function( text ) {
|
2018-06-17 16:07:19 +00:00
|
|
|
|
return CKEDITOR.tools.htmlEncode( text ).replace( quoteRegex, '"' );
|
2014-04-11 20:05:45 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* Decodes HTML entities that browsers tend to encode when used in attributes.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.htmlDecodeAttr( '<a " b>' ) ); // '<a " b>'
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* Since CKEditor 4.5 this method simply executes {@link #htmlDecode} which covers
|
|
|
|
|
* all necessary entities.
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*
|
|
|
|
|
* @param {String} text The text to be decoded.
|
|
|
|
|
* @returns {String} The decoded text.
|
|
|
|
|
*/
|
|
|
|
|
htmlDecodeAttr: function( text ) {
|
2018-06-17 16:07:19 +00:00
|
|
|
|
return CKEDITOR.tools.htmlDecode( text );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Transforms text to valid HTML: creates paragraphs, replaces tabs with non-breaking spaces etc.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.5
|
|
|
|
|
* @param {String} text Text to transform.
|
|
|
|
|
* @param {Number} enterMode Editor {@link CKEDITOR.config#enterMode Enter mode}.
|
|
|
|
|
* @returns {String} HTML generated from the text.
|
|
|
|
|
*/
|
|
|
|
|
transformPlainTextToHtml: function( text, enterMode ) {
|
|
|
|
|
var isEnterBrMode = enterMode == CKEDITOR.ENTER_BR,
|
|
|
|
|
// CRLF -> LF
|
|
|
|
|
html = this.htmlEncode( text.replace( /\r\n/g, '\n' ) );
|
|
|
|
|
|
|
|
|
|
// Tab ->   x 4;
|
|
|
|
|
html = html.replace( /\t/g, ' ' );
|
|
|
|
|
|
|
|
|
|
var paragraphTag = enterMode == CKEDITOR.ENTER_P ? 'p' : 'div';
|
|
|
|
|
|
|
|
|
|
// Two line-breaks create one paragraphing block.
|
|
|
|
|
if ( !isEnterBrMode ) {
|
|
|
|
|
var duoLF = /\n{2}/g;
|
|
|
|
|
if ( duoLF.test( html ) ) {
|
|
|
|
|
var openTag = '<' + paragraphTag + '>', endTag = '</' + paragraphTag + '>';
|
|
|
|
|
html = openTag + html.replace( duoLF, function() {
|
|
|
|
|
return endTag + openTag;
|
|
|
|
|
} ) + endTag;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// One <br> per line-break.
|
|
|
|
|
html = html.replace( /\n/g, '<br>' );
|
|
|
|
|
|
|
|
|
|
// Compensate padding <br> at the end of block, avoid loosing them during insertion.
|
|
|
|
|
if ( !isEnterBrMode ) {
|
|
|
|
|
html = html.replace( new RegExp( '<br>(?=</' + paragraphTag + '>)' ), function( match ) {
|
|
|
|
|
return CKEDITOR.tools.repeat( match, 2 );
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Preserve spaces at the ends, so they won't be lost after insertion (merged with adjacent ones).
|
|
|
|
|
html = html.replace( /^ | $/g, ' ' );
|
|
|
|
|
|
|
|
|
|
// Finally, preserve whitespaces that are to be lost.
|
|
|
|
|
html = html.replace( /(>|\s) /g, function( match, before ) {
|
|
|
|
|
return before + ' ';
|
|
|
|
|
} ).replace( / (?=<)/g, ' ' );
|
|
|
|
|
|
|
|
|
|
return html;
|
2014-04-11 20:05:45 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a unique number for this CKEDITOR execution session. It returns
|
|
|
|
|
* consecutive numbers starting from 1.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.getNextNumber() ); // (e.g.) 1
|
|
|
|
|
* alert( CKEDITOR.tools.getNextNumber() ); // 2
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @returns {Number} A unique number.
|
|
|
|
|
*/
|
|
|
|
|
getNextNumber: ( function() {
|
|
|
|
|
var last = 0;
|
|
|
|
|
return function() {
|
|
|
|
|
return ++last;
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Gets a unique ID for CKEditor interface elements. It returns a
|
|
|
|
|
* string with the "cke_" prefix and a consecutive number.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.getNextId() ); // (e.g.) 'cke_1'
|
|
|
|
|
* alert( CKEDITOR.tools.getNextId() ); // 'cke_2'
|
|
|
|
|
*
|
|
|
|
|
* @returns {String} A unique ID.
|
|
|
|
|
*/
|
|
|
|
|
getNextId: function() {
|
|
|
|
|
return 'cke_' + this.getNextNumber();
|
|
|
|
|
},
|
|
|
|
|
|
2018-06-17 16:07:19 +00:00
|
|
|
|
/**
|
|
|
|
|
* Gets a universally unique ID. It returns a random string
|
|
|
|
|
* compliant with ISO/IEC 11578:1996, without dashes, with the "e" prefix to
|
|
|
|
|
* make sure that the ID does not start with a number.
|
|
|
|
|
*
|
|
|
|
|
* @returns {String} A global unique ID.
|
|
|
|
|
*/
|
|
|
|
|
getUniqueId: function() {
|
|
|
|
|
var uuid = 'e'; // Make sure that id does not start with number.
|
|
|
|
|
for ( var i = 0; i < 8; i++ ) {
|
|
|
|
|
uuid += Math.floor( ( 1 + Math.random() ) * 0x10000 ).toString( 16 ).substring( 1 );
|
|
|
|
|
}
|
|
|
|
|
return uuid;
|
|
|
|
|
},
|
|
|
|
|
|
2014-04-11 20:05:45 +00:00
|
|
|
|
/**
|
|
|
|
|
* Creates a function override.
|
|
|
|
|
*
|
|
|
|
|
* var obj = {
|
|
|
|
|
* myFunction: function( name ) {
|
|
|
|
|
* alert( 'Name: ' + name );
|
|
|
|
|
* }
|
|
|
|
|
* };
|
|
|
|
|
*
|
|
|
|
|
* obj.myFunction = CKEDITOR.tools.override( obj.myFunction, function( myFunctionOriginal ) {
|
|
|
|
|
* return function( name ) {
|
|
|
|
|
* alert( 'Overriden name: ' + name );
|
|
|
|
|
* myFunctionOriginal.call( this, name );
|
|
|
|
|
* };
|
|
|
|
|
* } );
|
|
|
|
|
*
|
|
|
|
|
* @param {Function} originalFunction The function to be overridden.
|
|
|
|
|
* @param {Function} functionBuilder A function that returns the new
|
|
|
|
|
* function. The original function reference will be passed to this function.
|
|
|
|
|
* @returns {Function} The new function.
|
|
|
|
|
*/
|
|
|
|
|
override: function( originalFunction, functionBuilder ) {
|
|
|
|
|
var newFn = functionBuilder( originalFunction );
|
|
|
|
|
newFn.prototype = originalFunction.prototype;
|
|
|
|
|
return newFn;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* Executes a function after a specified delay.
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*
|
|
|
|
|
* CKEDITOR.tools.setTimeout( function() {
|
|
|
|
|
* alert( 'Executed after 2 seconds' );
|
|
|
|
|
* }, 2000 );
|
|
|
|
|
*
|
|
|
|
|
* @param {Function} func The function to be executed.
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* @param {Number} [milliseconds=0] The amount of time (in milliseconds) to wait
|
2014-04-11 20:05:45 +00:00
|
|
|
|
* to fire the function execution.
|
|
|
|
|
* @param {Object} [scope=window] The object to store the function execution scope
|
|
|
|
|
* (the `this` object).
|
|
|
|
|
* @param {Object/Array} [args] A single object, or an array of objects, to
|
|
|
|
|
* pass as argument to the function.
|
|
|
|
|
* @param {Object} [ownerWindow=window] The window that will be used to set the
|
|
|
|
|
* timeout.
|
|
|
|
|
* @returns {Object} A value that can be used to cancel the function execution.
|
|
|
|
|
*/
|
|
|
|
|
setTimeout: function( func, milliseconds, scope, args, ownerWindow ) {
|
|
|
|
|
if ( !ownerWindow )
|
|
|
|
|
ownerWindow = window;
|
|
|
|
|
|
|
|
|
|
if ( !scope )
|
|
|
|
|
scope = ownerWindow;
|
|
|
|
|
|
|
|
|
|
return ownerWindow.setTimeout( function() {
|
|
|
|
|
if ( args )
|
|
|
|
|
func.apply( scope, [].concat( args ) );
|
|
|
|
|
else
|
|
|
|
|
func.apply( scope );
|
|
|
|
|
}, milliseconds || 0 );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes spaces from the start and the end of a string. The following
|
|
|
|
|
* characters are removed: space, tab, line break, line feed.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.trim( ' example ' ); // 'example'
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @param {String} str The text from which the spaces will be removed.
|
|
|
|
|
* @returns {String} The modified string without the boundary spaces.
|
|
|
|
|
*/
|
|
|
|
|
trim: ( function() {
|
|
|
|
|
// We are not using \s because we don't want "non-breaking spaces" to be caught.
|
|
|
|
|
var trimRegex = /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;
|
|
|
|
|
return function( str ) {
|
|
|
|
|
return str.replace( trimRegex, '' );
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes spaces from the start (left) of a string. The following
|
|
|
|
|
* characters are removed: space, tab, line break, line feed.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.ltrim( ' example ' ); // 'example '
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @param {String} str The text from which the spaces will be removed.
|
|
|
|
|
* @returns {String} The modified string excluding the removed spaces.
|
|
|
|
|
*/
|
|
|
|
|
ltrim: ( function() {
|
|
|
|
|
// We are not using \s because we don't want "non-breaking spaces" to be caught.
|
|
|
|
|
var trimRegex = /^[ \t\n\r]+/g;
|
|
|
|
|
return function( str ) {
|
|
|
|
|
return str.replace( trimRegex, '' );
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes spaces from the end (right) of a string. The following
|
|
|
|
|
* characters are removed: space, tab, line break, line feed.
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.ltrim( ' example ' ); // ' example'
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @param {String} str The text from which spaces will be removed.
|
|
|
|
|
* @returns {String} The modified string excluding the removed spaces.
|
|
|
|
|
*/
|
|
|
|
|
rtrim: ( function() {
|
|
|
|
|
// We are not using \s because we don't want "non-breaking spaces" to be caught.
|
|
|
|
|
var trimRegex = /[ \t\n\r]+$/g;
|
|
|
|
|
return function( str ) {
|
|
|
|
|
return str.replace( trimRegex, '' );
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the index of an element in an array.
|
|
|
|
|
*
|
|
|
|
|
* var letters = [ 'a', 'b', 0, 'c', false ];
|
|
|
|
|
* alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // -1 because 0 !== '0'
|
|
|
|
|
* alert( CKEDITOR.tools.indexOf( letters, false ) ); // 4 because 0 !== false
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array The array to be searched.
|
|
|
|
|
* @param {Object/Function} value The element to be found. This can be an
|
|
|
|
|
* evaluation function which receives a single parameter call for
|
|
|
|
|
* each entry in the array, returning `true` if the entry matches.
|
|
|
|
|
* @returns {Number} The (zero-based) index of the first entry that matches
|
|
|
|
|
* the entry, or `-1` if not found.
|
|
|
|
|
*/
|
|
|
|
|
indexOf: function( array, value ) {
|
|
|
|
|
if ( typeof value == 'function' ) {
|
|
|
|
|
for ( var i = 0, len = array.length; i < len; i++ ) {
|
|
|
|
|
if ( value( array[ i ] ) )
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
} else if ( array.indexOf )
|
|
|
|
|
return array.indexOf( value );
|
|
|
|
|
else {
|
|
|
|
|
for ( i = 0, len = array.length; i < len; i++ ) {
|
|
|
|
|
if ( array[ i ] === value )
|
|
|
|
|
return i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the index of an element in an array.
|
|
|
|
|
*
|
|
|
|
|
* var obj = { prop: true };
|
|
|
|
|
* var letters = [ 'a', 'b', 0, obj, false ];
|
|
|
|
|
*
|
|
|
|
|
* alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // null
|
|
|
|
|
* alert( CKEDITOR.tools.indexOf( letters, function( value ) {
|
|
|
|
|
* // Return true when passed value has property 'prop'.
|
|
|
|
|
* return value && 'prop' in value;
|
|
|
|
|
* } ) ); // obj
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array The array to be searched.
|
|
|
|
|
* @param {Object/Function} value The element to be found. Can be an
|
|
|
|
|
* evaluation function which receives a single parameter call for
|
|
|
|
|
* each entry in the array, returning `true` if the entry matches.
|
|
|
|
|
* @returns Object The value that was found in an array.
|
|
|
|
|
*/
|
|
|
|
|
search: function( array, value ) {
|
|
|
|
|
var index = CKEDITOR.tools.indexOf( array, value );
|
|
|
|
|
return index >= 0 ? array[ index ] : null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a function that will always execute in the context of a
|
|
|
|
|
* specified object.
|
|
|
|
|
*
|
|
|
|
|
* var obj = { text: 'My Object' };
|
|
|
|
|
*
|
|
|
|
|
* function alertText() {
|
|
|
|
|
* alert( this.text );
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* var newFunc = CKEDITOR.tools.bind( alertText, obj );
|
|
|
|
|
* newFunc(); // Alerts 'My Object'.
|
|
|
|
|
*
|
|
|
|
|
* @param {Function} func The function to be executed.
|
|
|
|
|
* @param {Object} obj The object to which the execution context will be bound.
|
|
|
|
|
* @returns {Function} The function that can be used to execute the
|
|
|
|
|
* `func` function in the context of `obj`.
|
|
|
|
|
*/
|
|
|
|
|
bind: function( func, obj ) {
|
|
|
|
|
return function() {
|
|
|
|
|
return func.apply( obj, arguments );
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Class creation based on prototype inheritance which supports of the
|
|
|
|
|
* following features:
|
|
|
|
|
*
|
|
|
|
|
* * Static fields
|
|
|
|
|
* * Private fields
|
|
|
|
|
* * Public (prototype) fields
|
|
|
|
|
* * Chainable base class constructor
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} definition The class definition object.
|
|
|
|
|
* @returns {Function} A class-like JavaScript function.
|
|
|
|
|
*/
|
|
|
|
|
createClass: function( definition ) {
|
|
|
|
|
var $ = definition.$,
|
|
|
|
|
baseClass = definition.base,
|
|
|
|
|
privates = definition.privates || definition._,
|
|
|
|
|
proto = definition.proto,
|
|
|
|
|
statics = definition.statics;
|
|
|
|
|
|
|
|
|
|
// Create the constructor, if not present in the definition.
|
|
|
|
|
!$ && ( $ = function() {
|
|
|
|
|
baseClass && this.base.apply( this, arguments );
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
if ( privates ) {
|
|
|
|
|
var originalConstructor = $;
|
|
|
|
|
$ = function() {
|
|
|
|
|
// Create (and get) the private namespace.
|
|
|
|
|
var _ = this._ || ( this._ = {} );
|
|
|
|
|
|
|
|
|
|
// Make some magic so "this" will refer to the main
|
|
|
|
|
// instance when coding private functions.
|
|
|
|
|
for ( var privateName in privates ) {
|
|
|
|
|
var priv = privates[ privateName ];
|
|
|
|
|
|
|
|
|
|
_[ privateName ] = ( typeof priv == 'function' ) ? CKEDITOR.tools.bind( priv, this ) : priv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
originalConstructor.apply( this, arguments );
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( baseClass ) {
|
|
|
|
|
$.prototype = this.prototypedCopy( baseClass.prototype );
|
|
|
|
|
$.prototype.constructor = $;
|
|
|
|
|
// Super references.
|
|
|
|
|
$.base = baseClass;
|
|
|
|
|
$.baseProto = baseClass.prototype;
|
|
|
|
|
// Super constructor.
|
|
|
|
|
$.prototype.base = function() {
|
|
|
|
|
this.base = baseClass.prototype.base;
|
|
|
|
|
baseClass.apply( this, arguments );
|
|
|
|
|
this.base = arguments.callee;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( proto )
|
|
|
|
|
this.extend( $.prototype, proto, true );
|
|
|
|
|
|
|
|
|
|
if ( statics )
|
|
|
|
|
this.extend( $, statics, true );
|
|
|
|
|
|
|
|
|
|
return $;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a function reference that can be called later using
|
|
|
|
|
* {@link #callFunction}. This approach is especially useful to
|
|
|
|
|
* make DOM attribute function calls to JavaScript-defined functions.
|
|
|
|
|
*
|
|
|
|
|
* var ref = CKEDITOR.tools.addFunction( function() {
|
|
|
|
|
* alert( 'Hello!');
|
|
|
|
|
* } );
|
|
|
|
|
* CKEDITOR.tools.callFunction( ref ); // 'Hello!'
|
|
|
|
|
*
|
|
|
|
|
* @param {Function} fn The function to be executed on call.
|
|
|
|
|
* @param {Object} [scope] The object to have the context on `fn` execution.
|
|
|
|
|
* @returns {Number} A unique reference to be used in conjuction with
|
|
|
|
|
* {@link #callFunction}.
|
|
|
|
|
*/
|
|
|
|
|
addFunction: function( fn, scope ) {
|
|
|
|
|
return functions.push( function() {
|
|
|
|
|
return fn.apply( scope || this, arguments );
|
|
|
|
|
} ) - 1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Removes the function reference created with {@link #addFunction}.
|
|
|
|
|
*
|
|
|
|
|
* @param {Number} ref The function reference created with
|
|
|
|
|
* {@link #addFunction}.
|
|
|
|
|
*/
|
|
|
|
|
removeFunction: function( ref ) {
|
|
|
|
|
functions[ ref ] = null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Executes a function based on the reference created with {@link #addFunction}.
|
|
|
|
|
*
|
|
|
|
|
* var ref = CKEDITOR.tools.addFunction( function() {
|
|
|
|
|
* alert( 'Hello!');
|
|
|
|
|
* } );
|
|
|
|
|
* CKEDITOR.tools.callFunction( ref ); // 'Hello!'
|
|
|
|
|
*
|
|
|
|
|
* @param {Number} ref The function reference created with {@link #addFunction}.
|
|
|
|
|
* @param {Mixed} params Any number of parameters to be passed to the executed function.
|
|
|
|
|
* @returns {Mixed} The return value of the function.
|
|
|
|
|
*/
|
|
|
|
|
callFunction: function( ref ) {
|
|
|
|
|
var fn = functions[ ref ];
|
|
|
|
|
return fn && fn.apply( window, Array.prototype.slice.call( arguments, 1 ) );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Appends the `px` length unit to the size value if it is missing.
|
|
|
|
|
*
|
|
|
|
|
* var cssLength = CKEDITOR.tools.cssLength;
|
|
|
|
|
* cssLength( 42 ); // '42px'
|
|
|
|
|
* cssLength( '42' ); // '42px'
|
|
|
|
|
* cssLength( '42px' ); // '42px'
|
|
|
|
|
* cssLength( '42%' ); // '42%'
|
|
|
|
|
* cssLength( 'bold' ); // 'bold'
|
|
|
|
|
* cssLength( false ); // ''
|
|
|
|
|
* cssLength( NaN ); // ''
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @param {Number/String/Boolean} length
|
|
|
|
|
*/
|
|
|
|
|
cssLength: ( function() {
|
|
|
|
|
var pixelRegex = /^-?\d+\.?\d*px$/,
|
|
|
|
|
lengthTrimmed;
|
|
|
|
|
|
|
|
|
|
return function( length ) {
|
|
|
|
|
lengthTrimmed = CKEDITOR.tools.trim( length + '' ) + 'px';
|
|
|
|
|
|
|
|
|
|
if ( pixelRegex.test( lengthTrimmed ) )
|
|
|
|
|
return lengthTrimmed;
|
|
|
|
|
else
|
|
|
|
|
return length || '';
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts the specified CSS length value to the calculated pixel length inside this page.
|
|
|
|
|
*
|
|
|
|
|
* **Note:** Percentage-based value is left intact.
|
|
|
|
|
*
|
|
|
|
|
* @method
|
|
|
|
|
* @param {String} cssLength CSS length value.
|
|
|
|
|
*/
|
|
|
|
|
convertToPx: ( function() {
|
|
|
|
|
var calculator;
|
|
|
|
|
|
|
|
|
|
return function( cssLength ) {
|
|
|
|
|
if ( !calculator ) {
|
|
|
|
|
calculator = CKEDITOR.dom.element.createFromHtml( '<div style="position:absolute;left:-9999px;' +
|
|
|
|
|
'top:-9999px;margin:0px;padding:0px;border:0px;"' +
|
|
|
|
|
'></div>', CKEDITOR.document );
|
|
|
|
|
CKEDITOR.document.getBody().append( calculator );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !( /%$/ ).test( cssLength ) ) {
|
|
|
|
|
calculator.setStyle( 'width', cssLength );
|
|
|
|
|
return calculator.$.clientWidth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cssLength;
|
|
|
|
|
};
|
|
|
|
|
} )(),
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* String specified by `str` repeats `times` times.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} str
|
|
|
|
|
* @param {Number} times
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
repeat: function( str, times ) {
|
|
|
|
|
return new Array( times + 1 ).join( str );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the first successfully executed return value of a function that
|
|
|
|
|
* does not throw any exception.
|
|
|
|
|
*
|
|
|
|
|
* @param {Function...} fn
|
|
|
|
|
* @returns {Mixed}
|
|
|
|
|
*/
|
|
|
|
|
tryThese: function() {
|
|
|
|
|
var returnValue;
|
|
|
|
|
for ( var i = 0, length = arguments.length; i < length; i++ ) {
|
|
|
|
|
var lambda = arguments[ i ];
|
|
|
|
|
try {
|
|
|
|
|
returnValue = lambda();
|
|
|
|
|
break;
|
|
|
|
|
} catch ( e ) {}
|
|
|
|
|
}
|
|
|
|
|
return returnValue;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Generates a combined key from a series of params.
|
|
|
|
|
*
|
|
|
|
|
* var key = CKEDITOR.tools.genKey( 'key1', 'key2', 'key3' );
|
|
|
|
|
* alert( key ); // 'key1-key2-key3'.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} subKey One or more strings used as subkeys.
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
genKey: function() {
|
|
|
|
|
return Array.prototype.slice.call( arguments ).join( '-' );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a "deferred" function which will not run immediately,
|
|
|
|
|
* but rather runs as soon as the interpreter’s call stack is empty.
|
|
|
|
|
* Behaves much like `window.setTimeout` with a delay.
|
|
|
|
|
*
|
|
|
|
|
* **Note:** The return value of the original function will be lost.
|
|
|
|
|
*
|
|
|
|
|
* @param {Function} fn The callee function.
|
|
|
|
|
* @returns {Function} The new deferred function.
|
|
|
|
|
*/
|
|
|
|
|
defer: function( fn ) {
|
|
|
|
|
return function() {
|
|
|
|
|
var args = arguments,
|
|
|
|
|
self = this;
|
|
|
|
|
window.setTimeout( function() {
|
|
|
|
|
fn.apply( self, args );
|
|
|
|
|
}, 0 );
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Normalizes CSS data in order to avoid differences in the style attribute.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} styleText The style data to be normalized.
|
|
|
|
|
* @param {Boolean} [nativeNormalize=false] Parse the data using the browser.
|
|
|
|
|
* @returns {String} The normalized value.
|
|
|
|
|
*/
|
|
|
|
|
normalizeCssText: function( styleText, nativeNormalize ) {
|
|
|
|
|
var props = [],
|
|
|
|
|
name,
|
|
|
|
|
parsedProps = CKEDITOR.tools.parseCssText( styleText, true, nativeNormalize );
|
|
|
|
|
|
|
|
|
|
for ( name in parsedProps )
|
|
|
|
|
props.push( name + ':' + parsedProps[ name ] );
|
|
|
|
|
|
|
|
|
|
props.sort();
|
|
|
|
|
|
|
|
|
|
return props.length ? ( props.join( ';' ) + ';' ) : '';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Finds and converts `rgb(x,x,x)` color definition to hexadecimal notation.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} styleText The style data (or just a string containing RGB colors) to be converted.
|
|
|
|
|
* @returns {String} The style data with RGB colors converted to hexadecimal equivalents.
|
|
|
|
|
*/
|
|
|
|
|
convertRgbToHex: function( styleText ) {
|
|
|
|
|
return styleText.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue ) {
|
|
|
|
|
var color = [ red, green, blue ];
|
|
|
|
|
// Add padding zeros if the hex value is less than 0x10.
|
|
|
|
|
for ( var i = 0; i < 3; i++ )
|
|
|
|
|
color[ i ] = ( '0' + parseInt( color[ i ], 10 ).toString( 16 ) ).slice( -2 );
|
|
|
|
|
return '#' + color.join( '' );
|
|
|
|
|
} );
|
|
|
|
|
},
|
|
|
|
|
|
2018-06-17 16:07:19 +00:00
|
|
|
|
/**
|
|
|
|
|
* Normalizes hexadecimal notation so that the color string is always 6 characters long and lowercase.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} styleText The style data (or just a string containing hex colors) to be converted.
|
|
|
|
|
* @returns {String} The style data with hex colors normalized.
|
|
|
|
|
*/
|
|
|
|
|
normalizeHex: function( styleText ) {
|
|
|
|
|
return styleText.replace( /#(([0-9a-f]{3}){1,2})($|;|\s+)/gi, function( match, hexColor, hexColorPart, separator ) {
|
|
|
|
|
var normalizedHexColor = hexColor.toLowerCase();
|
|
|
|
|
if ( normalizedHexColor.length == 3 ) {
|
|
|
|
|
var parts = normalizedHexColor.split( '' );
|
|
|
|
|
normalizedHexColor = [ parts[ 0 ], parts[ 0 ], parts[ 1 ], parts[ 1 ], parts[ 2 ], parts[ 2 ] ].join( '' );
|
|
|
|
|
}
|
|
|
|
|
return '#' + normalizedHexColor + separator;
|
|
|
|
|
} );
|
|
|
|
|
},
|
|
|
|
|
|
2014-04-11 20:05:45 +00:00
|
|
|
|
/**
|
|
|
|
|
* Turns inline style text properties into one hash.
|
|
|
|
|
*
|
|
|
|
|
* @param {String} styleText The style data to be parsed.
|
|
|
|
|
* @param {Boolean} [normalize=false] Normalize properties and values
|
|
|
|
|
* (e.g. trim spaces, convert to lower case).
|
|
|
|
|
* @param {Boolean} [nativeNormalize=false] Parse the data using the browser.
|
|
|
|
|
* @returns {Object} The object containing parsed properties.
|
|
|
|
|
*/
|
|
|
|
|
parseCssText: function( styleText, normalize, nativeNormalize ) {
|
|
|
|
|
var retval = {};
|
|
|
|
|
|
|
|
|
|
if ( nativeNormalize ) {
|
|
|
|
|
// Injects the style in a temporary span object, so the browser parses it,
|
|
|
|
|
// retrieving its final format.
|
|
|
|
|
var temp = new CKEDITOR.dom.element( 'span' );
|
2018-06-17 16:07:19 +00:00
|
|
|
|
styleText = temp.setAttribute( 'style', styleText ).getAttribute( 'style' ) || '';
|
2014-04-11 20:05:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-17 16:07:19 +00:00
|
|
|
|
// Normalize colors.
|
|
|
|
|
if ( styleText ) {
|
|
|
|
|
styleText = CKEDITOR.tools.normalizeHex( CKEDITOR.tools.convertRgbToHex( styleText ) );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IE will leave a single semicolon when failed to parse the style text. (https://dev.ckeditor.com/ticket/3891)
|
2014-04-11 20:05:45 +00:00
|
|
|
|
if ( !styleText || styleText == ';' )
|
|
|
|
|
return retval;
|
|
|
|
|
|
|
|
|
|
styleText.replace( /"/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
|
|
|
|
|
if ( normalize ) {
|
|
|
|
|
name = name.toLowerCase();
|
2018-06-17 16:07:19 +00:00
|
|
|
|
// Drop extra whitespacing from font-family.
|
2014-04-11 20:05:45 +00:00
|
|
|
|
if ( name == 'font-family' )
|
2018-06-17 16:07:19 +00:00
|
|
|
|
value = value.replace( /\s*,\s*/g, ',' );
|
2014-04-11 20:05:45 +00:00
|
|
|
|
value = CKEDITOR.tools.trim( value );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retval[ name ] = value;
|
|
|
|
|
} );
|
|
|
|
|
return retval;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Serializes the `style name => value` hash to a style text.
|
|
|
|
|
*
|
|
|
|
|
* var styleObj = CKEDITOR.tools.parseCssText( 'color: red; border: none' );
|
|
|
|
|
* console.log( styleObj.color ); // -> 'red'
|
|
|
|
|
* CKEDITOR.tools.writeCssText( styleObj ); // -> 'color:red; border:none'
|
|
|
|
|
* CKEDITOR.tools.writeCssText( styleObj, true ); // -> 'border:none; color:red'
|
|
|
|
|
*
|
|
|
|
|
* @since 4.1
|
|
|
|
|
* @param {Object} styles The object contaning style properties.
|
|
|
|
|
* @param {Boolean} [sort] Whether to sort CSS properties.
|
|
|
|
|
* @returns {String} The serialized style text.
|
|
|
|
|
*/
|
|
|
|
|
writeCssText: function( styles, sort ) {
|
|
|
|
|
var name,
|
|
|
|
|
stylesArr = [];
|
|
|
|
|
|
|
|
|
|
for ( name in styles )
|
|
|
|
|
stylesArr.push( name + ':' + styles[ name ] );
|
|
|
|
|
|
|
|
|
|
if ( sort )
|
|
|
|
|
stylesArr.sort();
|
|
|
|
|
|
|
|
|
|
return stylesArr.join( '; ' );
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compares two objects.
|
|
|
|
|
*
|
|
|
|
|
* **Note:** This method performs shallow, non-strict comparison.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.1
|
|
|
|
|
* @param {Object} left
|
|
|
|
|
* @param {Object} right
|
|
|
|
|
* @param {Boolean} [onlyLeft] Check only the properties that are present in the `left` object.
|
|
|
|
|
* @returns {Boolean} Whether objects are identical.
|
|
|
|
|
*/
|
|
|
|
|
objectCompare: function( left, right, onlyLeft ) {
|
|
|
|
|
var name;
|
|
|
|
|
|
|
|
|
|
if ( !left && !right )
|
|
|
|
|
return true;
|
|
|
|
|
if ( !left || !right )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for ( name in left ) {
|
|
|
|
|
if ( left[ name ] != right[ name ] )
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !onlyLeft ) {
|
|
|
|
|
for ( name in right ) {
|
|
|
|
|
if ( left[ name ] != right[ name ] )
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an array of passed object's keys.
|
|
|
|
|
*
|
|
|
|
|
* console.log( CKEDITOR.tools.objectKeys( { foo: 1, bar: false } );
|
|
|
|
|
* // -> [ 'foo', 'bar' ]
|
|
|
|
|
*
|
|
|
|
|
* @since 4.1
|
|
|
|
|
* @param {Object} obj
|
|
|
|
|
* @returns {Array} Object's keys.
|
|
|
|
|
*/
|
|
|
|
|
objectKeys: function( obj ) {
|
|
|
|
|
var keys = [];
|
|
|
|
|
for ( var i in obj )
|
|
|
|
|
keys.push( i );
|
|
|
|
|
|
|
|
|
|
return keys;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts an array to an object by rewriting array items
|
|
|
|
|
* to object properties.
|
|
|
|
|
*
|
|
|
|
|
* var arr = [ 'foo', 'bar', 'foo' ];
|
|
|
|
|
* console.log( CKEDITOR.tools.convertArrayToObject( arr ) );
|
|
|
|
|
* // -> { foo: true, bar: true }
|
|
|
|
|
* console.log( CKEDITOR.tools.convertArrayToObject( arr, 1 ) );
|
|
|
|
|
* // -> { foo: 1, bar: 1 }
|
|
|
|
|
*
|
|
|
|
|
* @since 4.1
|
|
|
|
|
* @param {Array} arr The array to be converted to an object.
|
|
|
|
|
* @param [fillWith=true] Set each property of an object to `fillWith` value.
|
|
|
|
|
*/
|
|
|
|
|
convertArrayToObject: function( arr, fillWith ) {
|
|
|
|
|
var obj = {};
|
|
|
|
|
|
|
|
|
|
if ( arguments.length == 1 )
|
|
|
|
|
fillWith = true;
|
|
|
|
|
|
|
|
|
|
for ( var i = 0, l = arr.length; i < l; ++i )
|
|
|
|
|
obj[ arr[ i ] ] = fillWith;
|
|
|
|
|
|
|
|
|
|
return obj;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tries to fix the `document.domain` of the current document to match the
|
|
|
|
|
* parent window domain, avoiding "Same Origin" policy issues.
|
|
|
|
|
* This is an Internet Explorer only requirement.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.1.2
|
|
|
|
|
* @returns {Boolean} `true` if the current domain is already good or if
|
|
|
|
|
* it has been fixed successfully.
|
|
|
|
|
*/
|
|
|
|
|
fixDomain: function() {
|
|
|
|
|
var domain;
|
|
|
|
|
|
|
|
|
|
while ( 1 ) {
|
|
|
|
|
try {
|
|
|
|
|
// Try to access the parent document. It throws
|
|
|
|
|
// "access denied" if restricted by the "Same Origin" policy.
|
|
|
|
|
domain = window.parent.document.domain;
|
|
|
|
|
break;
|
|
|
|
|
} catch ( e ) {
|
|
|
|
|
// Calculate the value to set to document.domain.
|
|
|
|
|
domain = domain ?
|
|
|
|
|
|
|
|
|
|
// If it is not the first pass, strip one part of the
|
|
|
|
|
// name. E.g. "test.example.com" => "example.com"
|
|
|
|
|
domain.replace( /.+?(?:\.|$)/, '' ) :
|
|
|
|
|
|
|
|
|
|
// In the first pass, we'll handle the
|
|
|
|
|
// "document.domain = document.domain" case.
|
|
|
|
|
document.domain;
|
|
|
|
|
|
|
|
|
|
// Stop here if there is no more domain parts available.
|
|
|
|
|
if ( !domain )
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
document.domain = domain;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return !!domain;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Buffers `input` events (or any `input` calls)
|
|
|
|
|
* and triggers `output` not more often than once per `minInterval`.
|
|
|
|
|
*
|
|
|
|
|
* var buffer = CKEDITOR.tools.eventsBuffer( 200, function() {
|
|
|
|
|
* console.log( 'foo!' );
|
|
|
|
|
* } );
|
|
|
|
|
*
|
|
|
|
|
* buffer.input();
|
|
|
|
|
* // 'foo!' logged immediately.
|
|
|
|
|
* buffer.input();
|
|
|
|
|
* // Nothing logged.
|
|
|
|
|
* buffer.input();
|
|
|
|
|
* // Nothing logged.
|
|
|
|
|
* // ... after 200ms a single 'foo!' will be logged.
|
|
|
|
|
*
|
|
|
|
|
* Can be easily used with events:
|
|
|
|
|
*
|
|
|
|
|
* var buffer = CKEDITOR.tools.eventsBuffer( 200, function() {
|
|
|
|
|
* console.log( 'foo!' );
|
|
|
|
|
* } );
|
|
|
|
|
*
|
|
|
|
|
* editor.on( 'key', buffer.input );
|
|
|
|
|
* // Note: There is no need to bind buffer as a context.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.2.1
|
|
|
|
|
* @param {Number} minInterval Minimum interval between `output` calls in milliseconds.
|
|
|
|
|
* @param {Function} output Function that will be executed as `output`.
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* @param {Object} [scopeObj] The object used to scope the listener call (the `this` object).
|
2014-04-11 20:05:45 +00:00
|
|
|
|
* @returns {Object}
|
|
|
|
|
* @returns {Function} return.input Buffer's input method.
|
|
|
|
|
* @returns {Function} return.reset Resets buffered events — `output` will not be executed
|
|
|
|
|
* until next `input` is triggered.
|
|
|
|
|
*/
|
2018-06-17 16:07:19 +00:00
|
|
|
|
eventsBuffer: function( minInterval, output, scopeObj ) {
|
2014-04-11 20:05:45 +00:00
|
|
|
|
var scheduled,
|
|
|
|
|
lastOutput = 0;
|
|
|
|
|
|
|
|
|
|
function triggerOutput() {
|
|
|
|
|
lastOutput = ( new Date() ).getTime();
|
|
|
|
|
scheduled = false;
|
2018-06-17 16:07:19 +00:00
|
|
|
|
if ( scopeObj ) {
|
|
|
|
|
output.call( scopeObj );
|
|
|
|
|
} else {
|
|
|
|
|
output();
|
|
|
|
|
}
|
2014-04-11 20:05:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
input: function() {
|
|
|
|
|
if ( scheduled )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var diff = ( new Date() ).getTime() - lastOutput;
|
|
|
|
|
|
|
|
|
|
// If less than minInterval passed after last check,
|
|
|
|
|
// schedule next for minInterval after previous one.
|
|
|
|
|
if ( diff < minInterval )
|
|
|
|
|
scheduled = setTimeout( triggerOutput, minInterval - diff );
|
|
|
|
|
else
|
|
|
|
|
triggerOutput();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
reset: function() {
|
|
|
|
|
if ( scheduled )
|
|
|
|
|
clearTimeout( scheduled );
|
|
|
|
|
|
|
|
|
|
scheduled = lastOutput = 0;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* Enables HTML5 elements for older browsers (IE8) in the passed document.
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* In IE8 this method can also be executed on a document fragment.
|
2014-04-11 20:05:45 +00:00
|
|
|
|
*
|
|
|
|
|
* **Note:** This method has to be used in the `<head>` section of the document.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.3
|
2018-06-17 16:07:19 +00:00
|
|
|
|
* @param {Object} doc Native `Document` or `DocumentFragment` in which the elements will be enabled.
|
2014-04-11 20:05:45 +00:00
|
|
|
|
* @param {Boolean} [withAppend] Whether to append created elements to the `doc`.
|
|
|
|
|
*/
|
|
|
|
|
enableHtml5Elements: function( doc, withAppend ) {
|
2018-06-17 16:07:19 +00:00
|
|
|
|
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( ',' ),
|
2014-04-11 20:05:45 +00:00
|
|
|
|
i = els.length,
|
|
|
|
|
el;
|
|
|
|
|
|
|
|
|
|
while ( i-- ) {
|
|
|
|
|
el = doc.createElement( els[ i ] );
|
|
|
|
|
if ( withAppend )
|
|
|
|
|
doc.appendChild( el );
|
|
|
|
|
}
|
2018-06-17 16:07:19 +00:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if any of the `arr` items match the provided regular expression.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} arr The array whose items will be checked.
|
|
|
|
|
* @param {RegExp} regexp The regular expression.
|
|
|
|
|
* @returns {Boolean} Returns `true` for the first occurrence of the search pattern.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
checkIfAnyArrayItemMatches: function( arr, regexp ) {
|
|
|
|
|
for ( var i = 0, l = arr.length; i < l; ++i ) {
|
|
|
|
|
if ( arr[ i ].match( regexp ) )
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Checks if any of the `obj` properties match the provided regular expression.
|
|
|
|
|
*
|
|
|
|
|
* @param obj The object whose properties will be checked.
|
|
|
|
|
* @param {RegExp} regexp The regular expression.
|
|
|
|
|
* @returns {Boolean} Returns `true` for the first occurrence of the search pattern.
|
|
|
|
|
* @since 4.4
|
|
|
|
|
*/
|
|
|
|
|
checkIfAnyObjectPropertyMatches: function( obj, regexp ) {
|
|
|
|
|
for ( var i in obj ) {
|
|
|
|
|
if ( i.match( regexp ) )
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a keystroke to its string representation. Returns exactly the same
|
|
|
|
|
* members as {@link #keystrokeToArray}, but the returned object contains strings of
|
|
|
|
|
* keys joined with "+" rather than an array of keystrokes.
|
|
|
|
|
*
|
|
|
|
|
* var lang = editor.lang.common.keyboard;
|
|
|
|
|
* var shortcut = CKEDITOR.tools.keystrokeToString( lang, CKEDITOR.CTRL + 88 );
|
|
|
|
|
* console.log( shortcut.display ); // 'Ctrl + X', on Mac '⌘ + X'.
|
|
|
|
|
* console.log( shortcut.aria ); // 'Ctrl + X', on Mac 'Cmd + X'.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.6.0
|
|
|
|
|
* @param {Object} lang A language object with the key name translation.
|
|
|
|
|
* @param {Number} keystroke The keystroke to convert.
|
|
|
|
|
* @returns {{display: String, aria: String}} See {@link #keystrokeToArray}.
|
|
|
|
|
*/
|
|
|
|
|
keystrokeToString: function( lang, keystroke ) {
|
|
|
|
|
var ret = this.keystrokeToArray( lang, keystroke );
|
|
|
|
|
|
|
|
|
|
ret.display = ret.display.join( '+' );
|
|
|
|
|
ret.aria = ret.aria.join( '+' );
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a keystroke to its string representation. Returns an object with two fields:
|
|
|
|
|
*
|
|
|
|
|
* * `display` – An array of strings that should be used for visible labels.
|
|
|
|
|
* For Mac devices it uses `⌥` for <kbd>Alt</kbd>, `⇧` for <kbd>Shift</kbd> and
|
|
|
|
|
* `⌘` for <kbd>Command</kbd>.
|
|
|
|
|
* * `aria` – An array of strings that should be used for ARIA descriptions.
|
|
|
|
|
* It does not use special characters such as `⌥`, `⇧` or `⌘`.
|
|
|
|
|
*
|
|
|
|
|
* var lang = editor.lang.common.keyboard;
|
|
|
|
|
* var shortcut = CKEDITOR.tools.keystrokeToArray( lang, CKEDITOR.CTRL + 88 );
|
|
|
|
|
* console.log( shortcut.display ); // [ 'CTRL', 'X' ], on Mac [ '⌘', 'X' ].
|
|
|
|
|
* console.log( shortcut.aria ); // [ 'CTRL', 'X' ], on Mac [ 'COMMAND', 'X' ].
|
|
|
|
|
*
|
|
|
|
|
* @since 4.8.0
|
|
|
|
|
* @param {Object} lang A language object with the key name translation.
|
|
|
|
|
* @param {Number} keystroke The keystroke to convert.
|
|
|
|
|
* @returns {{display: String[], aria: String[]}}
|
|
|
|
|
*/
|
|
|
|
|
keystrokeToArray: function( lang, keystroke ) {
|
|
|
|
|
var special = keystroke & 0xFF0000,
|
|
|
|
|
key = keystroke & 0x00FFFF,
|
|
|
|
|
isMac = CKEDITOR.env.mac,
|
|
|
|
|
CTRL = 17,
|
|
|
|
|
CMD = 224,
|
|
|
|
|
ALT = 18,
|
|
|
|
|
SHIFT = 16,
|
|
|
|
|
display = [],
|
|
|
|
|
aria = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ( special & CKEDITOR.CTRL ) {
|
|
|
|
|
display.push( isMac ? '⌘' : lang[ CTRL ] );
|
|
|
|
|
aria.push( isMac ? lang[ CMD ] : lang[ CTRL ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( special & CKEDITOR.ALT ) {
|
|
|
|
|
display.push( isMac ? '⌥' : lang[ ALT ] );
|
|
|
|
|
aria.push( lang[ ALT ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( special & CKEDITOR.SHIFT ) {
|
|
|
|
|
display.push( isMac ? '⇧' : lang[ SHIFT ] );
|
|
|
|
|
aria.push( lang[ SHIFT ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( key ) {
|
|
|
|
|
if ( lang[ key ] ) {
|
|
|
|
|
display.push( lang[ key ] );
|
|
|
|
|
aria.push( lang[ key ] );
|
|
|
|
|
} else {
|
|
|
|
|
display.push( String.fromCharCode( key ) );
|
|
|
|
|
aria.push( String.fromCharCode( key ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
display: display,
|
|
|
|
|
aria: aria
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.4
|
|
|
|
|
* @readonly
|
|
|
|
|
*/
|
|
|
|
|
transparentImageData: 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the value of the cookie with a given name or `null` if the cookie is not found.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.5.6
|
|
|
|
|
* @param {String} name
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
getCookie: function( name ) {
|
|
|
|
|
name = name.toLowerCase();
|
|
|
|
|
var parts = document.cookie.split( ';' );
|
|
|
|
|
var pair, key;
|
|
|
|
|
|
|
|
|
|
for ( var i = 0; i < parts.length; i++ ) {
|
|
|
|
|
pair = parts[ i ].split( '=' );
|
|
|
|
|
key = decodeURIComponent( CKEDITOR.tools.trim( pair[ 0 ] ).toLowerCase() );
|
|
|
|
|
|
|
|
|
|
if ( key === name ) {
|
|
|
|
|
return decodeURIComponent( pair.length > 1 ? pair[ 1 ] : '' );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Sets the value of the cookie with a given name.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.5.6
|
|
|
|
|
* @param {String} name
|
|
|
|
|
* @param {String} value
|
|
|
|
|
*/
|
|
|
|
|
setCookie: function( name, value ) {
|
|
|
|
|
document.cookie = encodeURIComponent( name ) + '=' + encodeURIComponent( value ) + ';path=/';
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns the CSRF token value. The value is a hash stored in `document.cookie`
|
|
|
|
|
* under the `ckCsrfToken` key. The CSRF token can be used to secure the communication
|
|
|
|
|
* between the web browser and the server, i.e. for the file upload feature in the editor.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.5.6
|
|
|
|
|
* @returns {String}
|
|
|
|
|
*/
|
|
|
|
|
getCsrfToken: function() {
|
|
|
|
|
var token = CKEDITOR.tools.getCookie( TOKEN_COOKIE_NAME );
|
|
|
|
|
|
|
|
|
|
if ( !token || token.length != TOKEN_LENGTH ) {
|
|
|
|
|
token = generateToken( TOKEN_LENGTH );
|
|
|
|
|
CKEDITOR.tools.setCookie( TOKEN_COOKIE_NAME, token );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return token;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns an escaped CSS selector. `CSS.escape()` is used if defined, leading digit is escaped otherwise.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.5.10
|
|
|
|
|
* @param {String} selector A CSS selector to escape.
|
|
|
|
|
* @returns {String} An escaped selector.
|
|
|
|
|
*/
|
|
|
|
|
escapeCss: function( selector ) {
|
|
|
|
|
// Invalid input.
|
|
|
|
|
if ( !selector ) {
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CSS.escape() can be used.
|
|
|
|
|
if ( window.CSS && CSS.escape ) {
|
|
|
|
|
return CSS.escape( selector );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simple leading digit escape.
|
|
|
|
|
if ( !isNaN( parseInt( selector.charAt( 0 ), 10 ) ) ) {
|
|
|
|
|
return '\\3' + selector.charAt( 0 ) + ' ' + selector.substring( 1, selector.length );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return selector;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Detects which mouse button generated a given DOM event.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.7.3
|
|
|
|
|
* @param {CKEDITOR.dom.event} evt DOM event.
|
|
|
|
|
* @returns {Number|Boolean} Returns a number indicating the mouse button or `false`
|
|
|
|
|
* if the mouse button cannot be determined.
|
|
|
|
|
*/
|
|
|
|
|
getMouseButton: function( evt ) {
|
|
|
|
|
var evtData = evt.data,
|
|
|
|
|
domEvent = evtData && evtData.$;
|
|
|
|
|
|
|
|
|
|
if ( !( evtData && domEvent ) ) {
|
|
|
|
|
// Added in case when there's no data available. That's the case in some unit test in built version which
|
|
|
|
|
// mock event but doesn't put data object.
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) {
|
|
|
|
|
if ( domEvent.button === 4 ) {
|
|
|
|
|
return CKEDITOR.MOUSE_BUTTON_MIDDLE;
|
|
|
|
|
} else if ( domEvent.button === 1 ) {
|
|
|
|
|
return CKEDITOR.MOUSE_BUTTON_LEFT;
|
|
|
|
|
} else {
|
|
|
|
|
return CKEDITOR.MOUSE_BUTTON_RIGHT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return domEvent.button;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert hex string to array containing 1 byte in each cell. Bytes are represented as Integer numbers.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.8.0
|
|
|
|
|
* @param {String} hexString Contains input string which represent bytes, e.g. `"08A11D8ADA2B"`.
|
|
|
|
|
* @returns {Number[]} Bytes stored in a form of Integer numbers, e.g. `[ 8, 161, 29, 138, 218, 43 ]`.
|
|
|
|
|
*/
|
|
|
|
|
convertHexStringToBytes: function( hexString ) {
|
|
|
|
|
var bytesArray = [],
|
|
|
|
|
bytesArrayLength = hexString.length / 2,
|
|
|
|
|
i;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < bytesArrayLength; i++ ) {
|
|
|
|
|
bytesArray.push( parseInt( hexString.substr( i * 2, 2 ), 16 ) );
|
|
|
|
|
}
|
|
|
|
|
return bytesArray;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Convert bytes array into a base64 encoded string.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.8.0
|
|
|
|
|
* @param {Number[]} bytesArray An array which stores 1 byte in each cell as an Integer number.
|
|
|
|
|
* @returns {String} Base64 string which represents input bytes.
|
|
|
|
|
*/
|
|
|
|
|
convertBytesToBase64: function( bytesArray ) {
|
|
|
|
|
// Bytes are `8bit` numbers, where base64 use `6bit` to store data. That's why we process 3 Bytes into 4 characters representing base64.
|
|
|
|
|
//
|
|
|
|
|
// Algorithm:
|
|
|
|
|
// 1. Take `3 * 8bit`.
|
|
|
|
|
// 2. If there is less than 3 bytes, fill empty bits with zeros.
|
|
|
|
|
// 3. Transform `3 * 8bit` into `4 * 6bit` numbers.
|
|
|
|
|
// 4. Translate those numbers to proper characters related to base64.
|
|
|
|
|
// 5. If extra zero bytes were added fill them with `=` sign.
|
|
|
|
|
//
|
|
|
|
|
// Example:
|
|
|
|
|
// 1. Bytes Array: [ 8, 161, 29, 138, 218, 43 ] -> binary: `0000 1000 1010 0001 0001 1101 1000 1010 1101 1010 0010 1011`.
|
|
|
|
|
// 2. Binary: `0000 10|00 1010| 0001 00|01 1101| 1000 10|10 1101| 1010 00|10 1011` ← `|` (pipe) shows where base64 will cut bits during transformation.
|
|
|
|
|
// 3. Now we have 6bit numbers (written in decimal values), which are translated to indexes in `base64characters` array.
|
|
|
|
|
// Decimal: `2 10 4 29 34 45 40 43` → base64: `CKEditor`.
|
|
|
|
|
var base64characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
|
|
|
|
|
base64string = '',
|
|
|
|
|
bytesArrayLength = bytesArray.length,
|
|
|
|
|
i;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < bytesArrayLength; i += 3 ) {
|
|
|
|
|
var array3 = bytesArray.slice( i, i + 3 ),
|
|
|
|
|
array3length = array3.length,
|
|
|
|
|
array4 = [],
|
|
|
|
|
j;
|
|
|
|
|
|
|
|
|
|
if ( array3length < 3 ) {
|
|
|
|
|
for ( j = array3length; j < 3; j++ ) {
|
|
|
|
|
array3[ j ] = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 0xFC -> 11111100 || 0x03 -> 00000011 || 0x0F -> 00001111 || 0xC0 -> 11000000 || 0x3F -> 00111111
|
|
|
|
|
array4[ 0 ] = ( array3[ 0 ] & 0xFC ) >> 2;
|
|
|
|
|
array4[ 1 ] = ( ( array3[ 0 ] & 0x03 ) << 4 ) | ( array3[ 1 ] >> 4 );
|
|
|
|
|
array4[ 2 ] = ( ( array3[ 1 ] & 0x0F ) << 2 ) | ( ( array3[ 2 ] & 0xC0 ) >> 6 );
|
|
|
|
|
array4[ 3 ] = array3[ 2 ] & 0x3F;
|
|
|
|
|
|
|
|
|
|
for ( j = 0; j < 4; j++ ) {
|
|
|
|
|
// Example: if array3length == 1, then we need to add 2 equal signs at the end of base64.
|
|
|
|
|
// array3[ 0 ] is used to calculate array4[ 0 ] and array4[ 1 ], so there will be regular values,
|
|
|
|
|
// next two ones have to be replaced with `=`, because array3[ 1 ] and array3[ 2 ] wasn't present in the input string.
|
|
|
|
|
if ( j <= array3length ) {
|
|
|
|
|
base64string += base64characters.charAt( array4[ j ] );
|
|
|
|
|
} else {
|
|
|
|
|
base64string += '=';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
return base64string;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A set of functions for operations on styles.
|
|
|
|
|
*
|
|
|
|
|
* @property {CKEDITOR.tools.style}
|
|
|
|
|
*/
|
|
|
|
|
style: {
|
|
|
|
|
/**
|
|
|
|
|
* Methods to parse miscellaneous CSS properties.
|
|
|
|
|
*
|
|
|
|
|
* @property {CKEDITOR.tools.style.parse}
|
|
|
|
|
* @member CKEDITOR.tools.style
|
|
|
|
|
*/
|
|
|
|
|
parse: {
|
|
|
|
|
// Color list based on https://www.w3.org/TR/css-color-4/#named-colors.
|
|
|
|
|
_colors: {
|
|
|
|
|
aliceblue: '#F0F8FF',
|
|
|
|
|
antiquewhite: '#FAEBD7',
|
|
|
|
|
aqua: '#00FFFF',
|
|
|
|
|
aquamarine: '#7FFFD4',
|
|
|
|
|
azure: '#F0FFFF',
|
|
|
|
|
beige: '#F5F5DC',
|
|
|
|
|
bisque: '#FFE4C4',
|
|
|
|
|
black: '#000000',
|
|
|
|
|
blanchedalmond: '#FFEBCD',
|
|
|
|
|
blue: '#0000FF',
|
|
|
|
|
blueviolet: '#8A2BE2',
|
|
|
|
|
brown: '#A52A2A',
|
|
|
|
|
burlywood: '#DEB887',
|
|
|
|
|
cadetblue: '#5F9EA0',
|
|
|
|
|
chartreuse: '#7FFF00',
|
|
|
|
|
chocolate: '#D2691E',
|
|
|
|
|
coral: '#FF7F50',
|
|
|
|
|
cornflowerblue: '#6495ED',
|
|
|
|
|
cornsilk: '#FFF8DC',
|
|
|
|
|
crimson: '#DC143C',
|
|
|
|
|
cyan: '#00FFFF',
|
|
|
|
|
darkblue: '#00008B',
|
|
|
|
|
darkcyan: '#008B8B',
|
|
|
|
|
darkgoldenrod: '#B8860B',
|
|
|
|
|
darkgray: '#A9A9A9',
|
|
|
|
|
darkgreen: '#006400',
|
|
|
|
|
darkgrey: '#A9A9A9',
|
|
|
|
|
darkkhaki: '#BDB76B',
|
|
|
|
|
darkmagenta: '#8B008B',
|
|
|
|
|
darkolivegreen: '#556B2F',
|
|
|
|
|
darkorange: '#FF8C00',
|
|
|
|
|
darkorchid: '#9932CC',
|
|
|
|
|
darkred: '#8B0000',
|
|
|
|
|
darksalmon: '#E9967A',
|
|
|
|
|
darkseagreen: '#8FBC8F',
|
|
|
|
|
darkslateblue: '#483D8B',
|
|
|
|
|
darkslategray: '#2F4F4F',
|
|
|
|
|
darkslategrey: '#2F4F4F',
|
|
|
|
|
darkturquoise: '#00CED1',
|
|
|
|
|
darkviolet: '#9400D3',
|
|
|
|
|
deeppink: '#FF1493',
|
|
|
|
|
deepskyblue: '#00BFFF',
|
|
|
|
|
dimgray: '#696969',
|
|
|
|
|
dimgrey: '#696969',
|
|
|
|
|
dodgerblue: '#1E90FF',
|
|
|
|
|
firebrick: '#B22222',
|
|
|
|
|
floralwhite: '#FFFAF0',
|
|
|
|
|
forestgreen: '#228B22',
|
|
|
|
|
fuchsia: '#FF00FF',
|
|
|
|
|
gainsboro: '#DCDCDC',
|
|
|
|
|
ghostwhite: '#F8F8FF',
|
|
|
|
|
gold: '#FFD700',
|
|
|
|
|
goldenrod: '#DAA520',
|
|
|
|
|
gray: '#808080',
|
|
|
|
|
green: '#008000',
|
|
|
|
|
greenyellow: '#ADFF2F',
|
|
|
|
|
grey: '#808080',
|
|
|
|
|
honeydew: '#F0FFF0',
|
|
|
|
|
hotpink: '#FF69B4',
|
|
|
|
|
indianred: '#CD5C5C',
|
|
|
|
|
indigo: '#4B0082',
|
|
|
|
|
ivory: '#FFFFF0',
|
|
|
|
|
khaki: '#F0E68C',
|
|
|
|
|
lavender: '#E6E6FA',
|
|
|
|
|
lavenderblush: '#FFF0F5',
|
|
|
|
|
lawngreen: '#7CFC00',
|
|
|
|
|
lemonchiffon: '#FFFACD',
|
|
|
|
|
lightblue: '#ADD8E6',
|
|
|
|
|
lightcoral: '#F08080',
|
|
|
|
|
lightcyan: '#E0FFFF',
|
|
|
|
|
lightgoldenrodyellow: '#FAFAD2',
|
|
|
|
|
lightgray: '#D3D3D3',
|
|
|
|
|
lightgreen: '#90EE90',
|
|
|
|
|
lightgrey: '#D3D3D3',
|
|
|
|
|
lightpink: '#FFB6C1',
|
|
|
|
|
lightsalmon: '#FFA07A',
|
|
|
|
|
lightseagreen: '#20B2AA',
|
|
|
|
|
lightskyblue: '#87CEFA',
|
|
|
|
|
lightslategray: '#778899',
|
|
|
|
|
lightslategrey: '#778899',
|
|
|
|
|
lightsteelblue: '#B0C4DE',
|
|
|
|
|
lightyellow: '#FFFFE0',
|
|
|
|
|
lime: '#00FF00',
|
|
|
|
|
limegreen: '#32CD32',
|
|
|
|
|
linen: '#FAF0E6',
|
|
|
|
|
magenta: '#FF00FF',
|
|
|
|
|
maroon: '#800000',
|
|
|
|
|
mediumaquamarine: '#66CDAA',
|
|
|
|
|
mediumblue: '#0000CD',
|
|
|
|
|
mediumorchid: '#BA55D3',
|
|
|
|
|
mediumpurple: '#9370DB',
|
|
|
|
|
mediumseagreen: '#3CB371',
|
|
|
|
|
mediumslateblue: '#7B68EE',
|
|
|
|
|
mediumspringgreen: '#00FA9A',
|
|
|
|
|
mediumturquoise: '#48D1CC',
|
|
|
|
|
mediumvioletred: '#C71585',
|
|
|
|
|
midnightblue: '#191970',
|
|
|
|
|
mintcream: '#F5FFFA',
|
|
|
|
|
mistyrose: '#FFE4E1',
|
|
|
|
|
moccasin: '#FFE4B5',
|
|
|
|
|
navajowhite: '#FFDEAD',
|
|
|
|
|
navy: '#000080',
|
|
|
|
|
oldlace: '#FDF5E6',
|
|
|
|
|
olive: '#808000',
|
|
|
|
|
olivedrab: '#6B8E23',
|
|
|
|
|
orange: '#FFA500',
|
|
|
|
|
orangered: '#FF4500',
|
|
|
|
|
orchid: '#DA70D6',
|
|
|
|
|
palegoldenrod: '#EEE8AA',
|
|
|
|
|
palegreen: '#98FB98',
|
|
|
|
|
paleturquoise: '#AFEEEE',
|
|
|
|
|
palevioletred: '#DB7093',
|
|
|
|
|
papayawhip: '#FFEFD5',
|
|
|
|
|
peachpuff: '#FFDAB9',
|
|
|
|
|
peru: '#CD853F',
|
|
|
|
|
pink: '#FFC0CB',
|
|
|
|
|
plum: '#DDA0DD',
|
|
|
|
|
powderblue: '#B0E0E6',
|
|
|
|
|
purple: '#800080',
|
|
|
|
|
rebeccapurple: '#663399',
|
|
|
|
|
red: '#FF0000',
|
|
|
|
|
rosybrown: '#BC8F8F',
|
|
|
|
|
royalblue: '#4169E1',
|
|
|
|
|
saddlebrown: '#8B4513',
|
|
|
|
|
salmon: '#FA8072',
|
|
|
|
|
sandybrown: '#F4A460',
|
|
|
|
|
seagreen: '#2E8B57',
|
|
|
|
|
seashell: '#FFF5EE',
|
|
|
|
|
sienna: '#A0522D',
|
|
|
|
|
silver: '#C0C0C0',
|
|
|
|
|
skyblue: '#87CEEB',
|
|
|
|
|
slateblue: '#6A5ACD',
|
|
|
|
|
slategray: '#708090',
|
|
|
|
|
slategrey: '#708090',
|
|
|
|
|
snow: '#FFFAFA',
|
|
|
|
|
springgreen: '#00FF7F',
|
|
|
|
|
steelblue: '#4682B4',
|
|
|
|
|
tan: '#D2B48C',
|
|
|
|
|
teal: '#008080',
|
|
|
|
|
thistle: '#D8BFD8',
|
|
|
|
|
tomato: '#FF6347',
|
|
|
|
|
turquoise: '#40E0D0',
|
|
|
|
|
violet: '#EE82EE',
|
|
|
|
|
wheat: '#F5DEB3',
|
|
|
|
|
white: '#FFFFFF',
|
|
|
|
|
whitesmoke: '#F5F5F5',
|
|
|
|
|
yellow: '#FFFF00',
|
|
|
|
|
yellowgreen: '#9ACD32'
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_borderStyle: [
|
|
|
|
|
'none',
|
|
|
|
|
'hidden',
|
|
|
|
|
'dotted',
|
|
|
|
|
'dashed',
|
|
|
|
|
'solid',
|
|
|
|
|
'double',
|
|
|
|
|
'groove',
|
|
|
|
|
'ridge',
|
|
|
|
|
'inset',
|
|
|
|
|
'outset'
|
|
|
|
|
],
|
|
|
|
|
|
|
|
|
|
_widthRegExp: /^(thin|medium|thick|[\+-]?\d+(\.\d+)?[a-z%]+|[\+-]?0+(\.0+)?|\.\d+[a-z%]+)$/,
|
|
|
|
|
|
|
|
|
|
_rgbaRegExp: /rgba?\(\s*\d+%?\s*,\s*\d+%?\s*,\s*\d+%?\s*(?:,\s*[0-9.]+\s*)?\)/gi,
|
|
|
|
|
|
|
|
|
|
_hslaRegExp: /hsla?\(\s*[0-9.]+\s*,\s*\d+%\s*,\s*\d+%\s*(?:,\s*[0-9.]+\s*)?\)/gi,
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses the `value` used as a `background` property shorthand and returns information as an object.
|
|
|
|
|
*
|
|
|
|
|
* **Note:** Currently only the `color` property is extracted. Any other parts will go into the `unprocessed` property.
|
|
|
|
|
*
|
|
|
|
|
* var background = CKEDITOR.tools.style.parse.background( '#0C0 url(foo.png)' );
|
|
|
|
|
* console.log( background );
|
|
|
|
|
* // Logs: { color: '#0C0', unprocessed: 'url(foo.png)' }
|
|
|
|
|
*
|
|
|
|
|
* @param {String} value The value of the `background` property.
|
|
|
|
|
* @returns {Object} An object with information extracted from the background.
|
|
|
|
|
* @returns {String} return.color The **first** color value found. The color format remains the same as in input.
|
|
|
|
|
* @returns {String} return.unprocessed The remaining part of the `value` that has not been processed.
|
|
|
|
|
* @member CKEDITOR.tools.style.parse
|
|
|
|
|
*/
|
|
|
|
|
background: function( value ) {
|
|
|
|
|
var ret = {},
|
|
|
|
|
colors = this._findColor( value );
|
|
|
|
|
|
|
|
|
|
if ( colors.length ) {
|
|
|
|
|
ret.color = colors[ 0 ];
|
|
|
|
|
|
|
|
|
|
CKEDITOR.tools.array.forEach( colors, function( colorToken ) {
|
|
|
|
|
value = value.replace( colorToken, '' );
|
|
|
|
|
} );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
value = CKEDITOR.tools.trim( value );
|
|
|
|
|
|
|
|
|
|
if ( value ) {
|
|
|
|
|
// If anything was left unprocessed include it as unprocessed part.
|
|
|
|
|
ret.unprocessed = value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses the `margin` CSS property shorthand format.
|
|
|
|
|
*
|
|
|
|
|
* console.log( CKEDITOR.tools.parse.margin( '3px 0 2' ) );
|
|
|
|
|
* // Logs: { top: "3px", right: "0", bottom: "2", left: "0" }
|
|
|
|
|
*
|
|
|
|
|
* @param {String} value The `margin` property value.
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
* @returns {Number} return.top Top margin.
|
|
|
|
|
* @returns {Number} return.right Right margin.
|
|
|
|
|
* @returns {Number} return.bottom Bottom margin.
|
|
|
|
|
* @returns {Number} return.left Left margin.
|
|
|
|
|
* @member CKEDITOR.tools.style.parse
|
|
|
|
|
*/
|
|
|
|
|
margin: function( value ) {
|
|
|
|
|
var ret = {};
|
|
|
|
|
|
|
|
|
|
var widths = value.match( /(?:\-?[\.\d]+(?:%|\w*)|auto|inherit|initial|unset)/g ) || [ '0px' ];
|
|
|
|
|
|
|
|
|
|
switch ( widths.length ) {
|
|
|
|
|
case 1:
|
|
|
|
|
mapStyles( [ 0, 0, 0, 0 ] );
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
mapStyles( [ 0, 1, 0, 1 ] );
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
mapStyles( [ 0, 1, 2, 1 ] );
|
|
|
|
|
break;
|
|
|
|
|
case 4:
|
|
|
|
|
mapStyles( [ 0, 1, 2, 3 ] );
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mapStyles( map ) {
|
|
|
|
|
ret.top = widths[ map[ 0 ] ];
|
|
|
|
|
ret.right = widths[ map[ 1 ] ];
|
|
|
|
|
ret.bottom = widths[ map[ 2 ] ];
|
|
|
|
|
ret.left = widths[ map[ 3 ] ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Parses the `border` CSS property shorthand format.
|
|
|
|
|
* This CSS property does not support inheritance (https://www.w3.org/TR/css3-background/#the-border-shorthands).
|
|
|
|
|
*
|
|
|
|
|
* console.log( CKEDITOR.tools.style.parse.border( '3px solid #ffeedd' ) );
|
|
|
|
|
* // Logs: { width: "3px", style: "solid", color: "#ffeedd" }
|
|
|
|
|
*
|
|
|
|
|
* @param {String} value The `border` property value.
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
* @returns {String} return.width The border-width attribute.
|
|
|
|
|
* @returns {String} return.style The border-style attribute.
|
|
|
|
|
* @returns {String} return.color The border-color attribute.
|
|
|
|
|
* @member CKEDITOR.tools.style.parse
|
|
|
|
|
*/
|
|
|
|
|
border: function( value ) {
|
|
|
|
|
var ret = {},
|
|
|
|
|
input = value.split( /\s+/g ),
|
|
|
|
|
parseColor = CKEDITOR.tools.style.parse._findColor( value );
|
|
|
|
|
|
|
|
|
|
if ( parseColor.length ) {
|
|
|
|
|
ret.color = parseColor[ 0 ];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CKEDITOR.tools.array.forEach( input, function( val ) {
|
|
|
|
|
if ( !ret.style ) {
|
|
|
|
|
if ( CKEDITOR.tools.indexOf( CKEDITOR.tools.style.parse._borderStyle, val ) !== -1 ) {
|
|
|
|
|
ret.style = val;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ( !ret.width ) {
|
|
|
|
|
if ( CKEDITOR.tools.style.parse._widthRegExp.test( val ) ) {
|
|
|
|
|
ret.width = val;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} );
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Searches the `value` for any CSS color occurrences and returns it.
|
|
|
|
|
*
|
|
|
|
|
* @private
|
|
|
|
|
* @param {String} value
|
|
|
|
|
* @returns {String[]} An array of matched results.
|
|
|
|
|
* @member CKEDITOR.tools.style.parse
|
|
|
|
|
*/
|
|
|
|
|
_findColor: function( value ) {
|
|
|
|
|
var ret = [],
|
|
|
|
|
arrayTools = CKEDITOR.tools.array;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Check for rgb(a).
|
|
|
|
|
ret = ret.concat( value.match( this._rgbaRegExp ) || [] );
|
|
|
|
|
|
|
|
|
|
// Check for hsl(a).
|
|
|
|
|
ret = ret.concat( value.match( this._hslaRegExp ) || [] );
|
|
|
|
|
|
|
|
|
|
ret = ret.concat( arrayTools.filter( value.split( /\s+/ ), function( colorEntry ) {
|
|
|
|
|
// Check for hex format.
|
|
|
|
|
if ( colorEntry.match( /^\#[a-f0-9]{3}(?:[a-f0-9]{3})?$/gi ) ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for preset names.
|
|
|
|
|
return colorEntry.toLowerCase() in CKEDITOR.tools.style.parse._colors;
|
|
|
|
|
} ) );
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A set of array helpers.
|
|
|
|
|
*
|
|
|
|
|
* @property {CKEDITOR.tools.array}
|
|
|
|
|
* @member CKEDITOR.tools
|
|
|
|
|
*/
|
|
|
|
|
array: {
|
|
|
|
|
/**
|
|
|
|
|
* Returns a copy of `array` filtered using the `fn` function. Any elements that the `fn` will return `false` for
|
|
|
|
|
* will get removed from the returned array.
|
|
|
|
|
*
|
|
|
|
|
* var filtered = this.array.filter( [ 0, 1, 2, 3 ], function( value ) {
|
|
|
|
|
* // Leave only values equal or greater than 2.
|
|
|
|
|
* return value >= 2;
|
|
|
|
|
* } );
|
|
|
|
|
* console.log( filtered );
|
|
|
|
|
* // Logs: [ 2, 3 ]
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array
|
|
|
|
|
* @param {Function} fn A function that gets called with each `array` item. Any item that `fn`
|
|
|
|
|
* returned a `false`-alike value for will be filtered out of the `array`.
|
|
|
|
|
* @param {Mixed} fn.value The currently iterated array value.
|
|
|
|
|
* @param {Number} fn.index The index of the currently iterated value in an array.
|
|
|
|
|
* @param {Array} fn.array The original array passed as the `array` variable.
|
|
|
|
|
* @param {Mixed} [thisArg=undefined] A context object for `fn`.
|
|
|
|
|
* @returns {Array} The filtered array.
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
*/
|
|
|
|
|
filter: function( array, fn, thisArg ) {
|
|
|
|
|
var ret = [];
|
|
|
|
|
|
|
|
|
|
this.forEach( array, function( val, i ) {
|
|
|
|
|
if ( fn.call( thisArg, val, i, array ) ) {
|
|
|
|
|
ret.push( val );
|
|
|
|
|
}
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Iterates over every element in the `array`.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array An array to be iterated over.
|
|
|
|
|
* @param {Function} fn The function called for every `array` element.
|
|
|
|
|
* @param {Mixed} fn.value The currently iterated array value.
|
|
|
|
|
* @param {Number} fn.index The index of the currently iterated value in an array.
|
|
|
|
|
* @param {Array} fn.array The original array passed as an `array` variable.
|
|
|
|
|
* @param {Mixed} [thisArg=undefined] The context object for `fn`.
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
*/
|
|
|
|
|
forEach: function( array, fn, thisArg ) {
|
|
|
|
|
var len = array.length,
|
|
|
|
|
i;
|
|
|
|
|
|
|
|
|
|
for ( i = 0; i < len; i++ ) {
|
|
|
|
|
fn.call( thisArg, array[ i ], i, array );
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Applies a function to each element of an array and returns the array of results in the same order.
|
|
|
|
|
* Note the order of the parameters.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array An array of elements that `fn` is applied on.
|
|
|
|
|
* @param {Function} fn A function with the signature `a -> b`.
|
|
|
|
|
* @param {Mixed} [thisArg=undefined] The context object for `fn`.
|
|
|
|
|
* @returns {Array} An array of mapped elements.
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
* @since 4.6.2
|
|
|
|
|
*/
|
|
|
|
|
map: function( array, fn, thisArg ) {
|
|
|
|
|
var result = [];
|
|
|
|
|
for ( var i = 0; i < array.length; i++ ) {
|
|
|
|
|
result.push( fn.call( thisArg, array[ i ], i, array ) );
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Applies a function against each value in an array storing the result in an accumulator passed to the next iteration.
|
|
|
|
|
* Note the order of the parameters.
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array An array of elements that `fn` is applied on.
|
|
|
|
|
* @param {Function} fn A function with the signature `(accumulator, a, index, array) -> b`.
|
|
|
|
|
* @param {Mixed} initial Initial value of the accumulator.
|
|
|
|
|
* @param {Mixed} [thisArg=undefined] The context object for `fn`.
|
|
|
|
|
* @returns {Mixed} The final value of the accumulator.
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
* @since 4.6.2
|
|
|
|
|
*/
|
|
|
|
|
reduce: function( array, fn, initial, thisArg ) {
|
|
|
|
|
var acc = initial;
|
|
|
|
|
for ( var i = 0; i < array.length; i++ ) {
|
|
|
|
|
acc = fn.call( thisArg, acc, array[ i ], i, array );
|
|
|
|
|
}
|
|
|
|
|
return acc;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tests whether all elements in an array pass the test implemented by the provided function.
|
|
|
|
|
* Returns `true` if the provided array is empty.
|
|
|
|
|
*
|
|
|
|
|
* var every = this.array.every( [ 11, 22, 33, 44 ], function( value ) {
|
|
|
|
|
* return value > 10;
|
|
|
|
|
* } );
|
|
|
|
|
* console.log( every );
|
|
|
|
|
* // Logs: true
|
|
|
|
|
*
|
|
|
|
|
* @param {Array} array
|
|
|
|
|
* @param {Function} fn A function that gets called with each `array` item.
|
|
|
|
|
* @param {Mixed} fn.value The currently iterated array value.
|
|
|
|
|
* @param {Number} fn.index The index of the currently iterated value in an array.
|
|
|
|
|
* @param {Array} fn.array The original array passed as the `array` variable.
|
|
|
|
|
* @param {Mixed} [thisArg=undefined] A context object for `fn`.
|
|
|
|
|
* @returns {Boolean} Information whether all elements pass the test.
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
* @since 4.8.0
|
|
|
|
|
*/
|
|
|
|
|
every: function( array, fn, thisArg ) {
|
|
|
|
|
// Empty arrays always return true.
|
|
|
|
|
if ( !array.length ) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ret = this.filter( array, fn, thisArg );
|
|
|
|
|
|
|
|
|
|
return array.length === ret.length;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A set of object helpers.
|
|
|
|
|
*
|
|
|
|
|
* @property {CKEDITOR.tools.object}
|
|
|
|
|
* @member CKEDITOR.tools
|
|
|
|
|
*/
|
|
|
|
|
object: {
|
|
|
|
|
/**
|
|
|
|
|
* Returns the first key from `obj` which has a given `value`.
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} obj An object whose `key` is looked for.
|
|
|
|
|
* @param {Mixed} value An object's `value` to be looked for.
|
|
|
|
|
* @returns {String/null} Matched `key` or `null` if not found.
|
|
|
|
|
* @member CKEDITOR.tools.object
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
findKey: function( obj, value ) {
|
|
|
|
|
if ( typeof obj !== 'object' ) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var key;
|
|
|
|
|
|
|
|
|
|
for ( key in obj ) {
|
|
|
|
|
if ( obj[ key ] === value ) {
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Merges two objects and returns the new one.
|
|
|
|
|
*
|
|
|
|
|
* var obj1 = {
|
|
|
|
|
* a: 1,
|
|
|
|
|
* conflicted: 10,
|
|
|
|
|
* obj: {
|
|
|
|
|
* c: 1
|
|
|
|
|
* }
|
|
|
|
|
* },
|
|
|
|
|
* obj2 = {
|
|
|
|
|
* b: 2,
|
|
|
|
|
* conflicted: 20,
|
|
|
|
|
* obj: {
|
|
|
|
|
* d: 2
|
|
|
|
|
* }
|
|
|
|
|
* };
|
|
|
|
|
*
|
|
|
|
|
* CKEDITOR.tools.object.merge( obj1, obj2 );
|
|
|
|
|
*
|
|
|
|
|
* This code produces the following object:
|
|
|
|
|
*
|
|
|
|
|
* {
|
|
|
|
|
* a: 1,
|
|
|
|
|
* b: 2,
|
|
|
|
|
* conflicted: 20,
|
|
|
|
|
* obj: {
|
|
|
|
|
* c: 1,
|
|
|
|
|
* d: 2
|
|
|
|
|
* }
|
|
|
|
|
* }
|
|
|
|
|
*
|
|
|
|
|
* @param {Object} obj1 The source object which will be used to create a new base object.
|
|
|
|
|
* @param {Object} obj2 An object whose properties will be merged into the base one.
|
|
|
|
|
* @returns {Object} The merged object.
|
|
|
|
|
* @member CKEDITOR.tools.object
|
|
|
|
|
*/
|
|
|
|
|
merge: function( obj1, obj2 ) {
|
|
|
|
|
var tools = CKEDITOR.tools,
|
|
|
|
|
copy1 = tools.clone( obj1 ),
|
|
|
|
|
copy2 = tools.clone( obj2 );
|
|
|
|
|
|
|
|
|
|
tools.array.forEach( tools.objectKeys( copy2 ), function( key ) {
|
|
|
|
|
if ( typeof copy2[ key ] === 'object' && typeof copy1[ key ] === 'object' ) {
|
|
|
|
|
copy1[ key ] = tools.object.merge( copy1[ key ], copy2[ key ] );
|
|
|
|
|
} else {
|
|
|
|
|
copy1[ key ] = copy2[ key ];
|
|
|
|
|
}
|
|
|
|
|
} );
|
|
|
|
|
|
|
|
|
|
return copy1;
|
|
|
|
|
}
|
2014-04-11 20:05:45 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
2018-06-17 16:07:19 +00:00
|
|
|
|
|
|
|
|
|
// Generates a CSRF token with a given length.
|
|
|
|
|
//
|
|
|
|
|
// @since 4.5.6
|
|
|
|
|
// @param {Number} length
|
|
|
|
|
// @returns {string}
|
|
|
|
|
function generateToken( length ) {
|
|
|
|
|
var randValues = [];
|
|
|
|
|
var result = '';
|
|
|
|
|
|
|
|
|
|
if ( window.crypto && window.crypto.getRandomValues ) {
|
|
|
|
|
randValues = new Uint8Array( length );
|
|
|
|
|
window.crypto.getRandomValues( randValues );
|
|
|
|
|
} else {
|
|
|
|
|
for ( var i = 0; i < length; i++ ) {
|
|
|
|
|
randValues.push( Math.floor( Math.random() * 256 ) );
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for ( var j = 0; j < randValues.length; j++ ) {
|
|
|
|
|
var character = tokenCharset.charAt( randValues[ j ] % tokenCharset.length );
|
|
|
|
|
result += Math.random() > 0.5 ? character.toUpperCase() : character;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
* @method indexOf
|
|
|
|
|
* @inheritdoc CKEDITOR.tools#indexOf
|
|
|
|
|
*/
|
|
|
|
|
CKEDITOR.tools.array.indexOf = CKEDITOR.tools.indexOf;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @member CKEDITOR.tools.array
|
|
|
|
|
* @method isArray
|
|
|
|
|
* @inheritdoc CKEDITOR.tools#isArray
|
|
|
|
|
*/
|
|
|
|
|
CKEDITOR.tools.array.isArray = CKEDITOR.tools.isArray;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Left mouse button.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.7.3
|
|
|
|
|
* @readonly
|
|
|
|
|
* @property {Number} [=0]
|
|
|
|
|
* @member CKEDITOR
|
|
|
|
|
*/
|
|
|
|
|
CKEDITOR.MOUSE_BUTTON_LEFT = 0;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Middle mouse button.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.7.3
|
|
|
|
|
* @readonly
|
|
|
|
|
* @property {Number} [=1]
|
|
|
|
|
* @member CKEDITOR
|
|
|
|
|
*/
|
|
|
|
|
CKEDITOR.MOUSE_BUTTON_MIDDLE = 1;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Right mouse button.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.7.3
|
|
|
|
|
* @readonly
|
|
|
|
|
* @property {Number} [=2]
|
|
|
|
|
* @member CKEDITOR
|
|
|
|
|
*/
|
|
|
|
|
CKEDITOR.MOUSE_BUTTON_RIGHT = 2;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The namespace containing functions to work on CSS properties.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.6.1
|
|
|
|
|
* @class CKEDITOR.tools.style
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The namespace with helper functions to parse some common CSS properties.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.6.1
|
|
|
|
|
* @class CKEDITOR.tools.style.parse
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The namespace with helper functions and polyfills for arrays.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.6.1
|
|
|
|
|
* @class CKEDITOR.tools.array
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The namespace with helper functions and polyfills for objects.
|
|
|
|
|
*
|
|
|
|
|
* @since 4.7.1
|
|
|
|
|
* @class CKEDITOR.tools.object
|
|
|
|
|
*/
|
2014-04-11 20:05:45 +00:00
|
|
|
|
} )();
|
|
|
|
|
|
|
|
|
|
// PACKAGER_RENAME( CKEDITOR.tools )
|