/* Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ (function(){if(window.CKEDITOR&&window.CKEDITOR.dom)return;/** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Contains the first and essential part of the {@link CKEDITOR} * object definition. */ // #### Compressed Code // Must be updated on changes in the script as well as updated in the // ckeditor_source.js and ckeditor_basic_source.js files. // window.CKEDITOR||(window.CKEDITOR=function(){var l=Math.floor(900*Math.random())+100,b=window.CKEDITOR_BASEPATH||"";if(!b)for(var g=document.getElementsByTagName("script"),e=0;e tag. var path = window.CKEDITOR_BASEPATH || ''; if ( !path ) { var scripts = document.getElementsByTagName( 'script' ); for ( var i = 0; i < scripts.length; i++ ) { var match = scripts[ i ].src.match( /(^|.*[\\\/])ckeditor(?:_basic)?(?:_source)?.js(?:\?.*)?$/i ); if ( match ) { path = match[ 1 ]; break; } } } // In IE (only) the script.src string is the raw value entered in the // HTML source. Other browsers return the full resolved URL instead. if ( path.indexOf( ':/' ) == -1 ) { // Absolute path. if ( path.indexOf( '/' ) === 0 ) path = location.href.match( /^.*?:\/\/[^\/]*/ )[ 0 ] + path; // Relative path. else path = location.href.match( /^[^\?]*\/(?:)/ )[ 0 ] + path; } if ( !path ) throw 'The CKEditor installation path could not be automatically detected. Please set the global variable "CKEDITOR_BASEPATH" before creating editor instances.'; return path; })(), /** * Gets the full URL for CKEditor resources. By default, URLs * returned by this function contain a querystring parameter ("t") * set to the {@link CKEDITOR#timestamp} value. * * It is possible to provide a custom implementation of this * function by setting a global variable named `CKEDITOR_GETURL`. * This global variable must be set **before** the editor script * loading. If the custom implementation returns nothing (`==null`), the * default implementation is used. * * // e.g. 'http://www.example.com/ckeditor/skins/default/editor.css?t=87dm' * alert( CKEDITOR.getUrl( 'skins/default/editor.css' ) ); * * // e.g. 'http://www.example.com/skins/default/editor.css?t=87dm' * alert( CKEDITOR.getUrl( '/skins/default/editor.css' ) ); * * // e.g. 'http://www.somesite.com/skins/default/editor.css?t=87dm' * alert( CKEDITOR.getUrl( 'http://www.somesite.com/skins/default/editor.css' ) ); * * @param {String} resource The resource whose full URL we want to get. * It may be a full, absolute, or relative URL. * @returns {String} The full URL. */ getUrl: function( resource ) { // If this is not a full or absolute path. if ( resource.indexOf( ':/' ) == -1 && resource.indexOf( '/' ) !== 0 ) resource = this.basePath + resource; // Add the timestamp, except for directories. if ( this.timestamp && resource.charAt( resource.length - 1 ) != '/' && !( /[&?]t=/ ).test( resource ) ) resource += ( resource.indexOf( '?' ) >= 0 ? '&' : '?' ) + 't=' + this.timestamp; return resource; }, /** * Specify a function to execute when the DOM is fully loaded. * * If called after the DOM has been initialized, the function passed in will * be executed immediately. * * @method * @todo */ domReady: (function() { // Based on the original jQuery code. var callbacks = []; function onReady() { try { // Cleanup functions for the document ready method if ( document.addEventListener ) { document.removeEventListener( 'DOMContentLoaded', onReady, false ); executeCallbacks(); } // Make sure body exists, at least, in case IE gets a little overzealous. else if ( document.attachEvent && document.readyState === 'complete' ) { document.detachEvent( 'onreadystatechange', onReady ); executeCallbacks(); } } catch ( er ) {} } function executeCallbacks() { var i; while ( ( i = callbacks.shift() ) ) i(); } return function( fn ) { callbacks.push( fn ); // Catch cases where this is called after the // browser event has already occurred. if ( document.readyState === 'complete' ) // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( onReady, 1 ); // Run below once on demand only. if ( callbacks.length != 1 ) return; // For IE>8, Firefox, Opera and Webkit. if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( 'DOMContentLoaded', onReady, false ); // A fallback to window.onload, that will always work window.addEventListener( 'load', onReady, false ); } // If old IE event model is used else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent( 'onreadystatechange', onReady ); // A fallback to window.onload, that will always work window.attachEvent( 'onload', onReady ); // If IE and not a frame // continually check to see if the document is ready // use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ var toplevel = false; try { toplevel = !window.frameElement; } catch ( e ) {} if ( document.documentElement.doScroll && toplevel ) { function scrollCheck() { try { document.documentElement.doScroll( 'left' ); } catch ( e ) { setTimeout( scrollCheck, 1 ); return; } onReady(); } scrollCheck(); } } }; })() }; // Make it possible to override the "url" function with a custom // implementation pointing to a global named CKEDITOR_GETURL. var newGetUrl = window.CKEDITOR_GETURL; if ( newGetUrl ) { var originalGetUrl = CKEDITOR.getUrl; CKEDITOR.getUrl = function( resource ) { return newGetUrl.call( CKEDITOR, resource ) || originalGetUrl.call( CKEDITOR, resource ); }; } return CKEDITOR; })(); } /** * Function called upon loading a custom configuration file that can * modify the editor instance configuration ({@link CKEDITOR.editor#config}). * It is usually defined inside the custom configuration files that can * include developer defined settings. * * // This is supposed to be placed in the config.js file. * CKEDITOR.editorConfig = function( config ) { * // Define changes to default configuration here. For example: * config.language = 'fr'; * config.uiColor = '#AADC6E'; * }; * * @method editorConfig * @param {CKEDITOR.config} config A configuration object containing the * settings defined for a {@link CKEDITOR.editor} instance up to this * function call. Note that not all settings may still be available. See * [Configuration Loading Order](http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Setting_Configurations#Configuration_Loading_Order) * for details. */ // PACKAGER_RENAME( CKEDITOR ) /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.event} class, which serves as the * base for classes and objects that require event handling features. */ if ( !CKEDITOR.event ) { /** * Creates an event class instance. This constructor is rearely used, being * the {@link #implementOn} function used in class prototypes directly * instead. * * This is a base class for classes and objects that require event * handling features. * * Do not confuse this class with {@link CKEDITOR.dom.event} which is * instead used for DOM events. The CKEDITOR.event class implements the * internal event system used by the CKEditor to fire API related events. * * @class * @constructor Creates an event class instance. */ CKEDITOR.event = function() {}; /** * Implements the {@link CKEDITOR.event} features in an object. * * var myObject = { message: 'Example' }; * CKEDITOR.event.implementOn( myObject ); * * myObject.on( 'testEvent', function() { * alert( this.message ); * } ); * myObject.fire( 'testEvent' ); // 'Example' * * @static * @param {Object} targetObject The object into which implement the features. */ CKEDITOR.event.implementOn = function( targetObject ) { var eventProto = CKEDITOR.event.prototype; for ( var prop in eventProto ) { if ( targetObject[ prop ] == undefined ) targetObject[ prop ] = eventProto[ prop ]; } }; CKEDITOR.event.prototype = (function() { // Returns the private events object for a given object. var getPrivate = function( obj ) { var _ = ( obj.getPrivate && obj.getPrivate() ) || obj._ || ( obj._ = {} ); return _.events || ( _.events = {} ); }; var eventEntry = function( eventName ) { this.name = eventName; this.listeners = []; }; eventEntry.prototype = { // Get the listener index for a specified function. // Returns -1 if not found. getListenerIndex: function( listenerFunction ) { for ( var i = 0, listeners = this.listeners; i < listeners.length; i++ ) { if ( listeners[ i ].fn == listenerFunction ) return i; } return -1; } }; // Retrieve the event entry on the event host (create it if needed). function getEntry( name ) { // Get the event entry (create it if needed). var events = getPrivate( this ); return events[ name ] || ( events[ name ] = new eventEntry( name ) ); } return { /** * Predefine some intrinsic properties on a specific event name. * * @param {String} name The event name * @param meta * @param [meta.errorProof=false] Whether the event firing should catch error thrown from a per listener call. */ define: function( name, meta ) { var entry = getEntry.call( this, name ); CKEDITOR.tools.extend( entry, meta, true ); }, /** * Registers a listener to a specific event in the current object. * * someObject.on( 'someEvent', function() { * alert( this == someObject ); // true * } ); * * someObject.on( 'someEvent', function() { * alert( this == anotherObject ); // true * }, anotherObject ); * * someObject.on( 'someEvent', function( event ) { * alert( event.listenerData ); // 'Example' * }, null, 'Example' ); * * someObject.on( 'someEvent', function() { ... } ); // 2nd called * someObject.on( 'someEvent', function() { ... }, null, null, 100 ); // 3rd called * someObject.on( 'someEvent', function() { ... }, null, null, 1 ); // 1st called * * @param {String} eventName The event name to which listen. * @param {Function} listenerFunction The function listening to the * event. A single {@link CKEDITOR.eventInfo} object instanced * is passed to this function containing all the event data. * @param {Object} [scopeObj] The object used to scope the listener * call (the `this` object). If omitted, the current object is used. * @param {Object} [listenerData] Data to be sent as the * {@link CKEDITOR.eventInfo#listenerData} when calling the * listener. * @param {Number} [priority=10] The listener priority. Lower priority * listeners are called first. Listeners with the same priority * value are called in registration order. * @returns {Object} An object containing the `removeListener` * function, which can be used to remove the listener at any time. */ on: function( eventName, listenerFunction, scopeObj, listenerData, priority ) { // Create the function to be fired for this listener. function listenerFirer( editor, publisherData, stopFn, cancelFn ) { var ev = { name: eventName, sender: this, editor: editor, data: publisherData, listenerData: listenerData, stop: stopFn, cancel: cancelFn, removeListener: removeListener }; var ret = listenerFunction.call( scopeObj, ev ); return ret === false ? false : ev.data; } function removeListener() { me.removeListener( eventName, listenerFunction ); } var event = getEntry.call( this, eventName ); if ( event.getListenerIndex( listenerFunction ) < 0 ) { // Get the listeners. var listeners = event.listeners; // Fill the scope. if ( !scopeObj ) scopeObj = this; // Default the priority, if needed. if ( isNaN( priority ) ) priority = 10; var me = this; listenerFirer.fn = listenerFunction; listenerFirer.priority = priority; // Search for the right position for this new listener, based on its // priority. for ( var i = listeners.length - 1; i >= 0; i-- ) { // Find the item which should be before the new one. if ( listeners[ i ].priority <= priority ) { // Insert the listener in the array. listeners.splice( i + 1, 0, listenerFirer ); return { removeListener: removeListener }; } } // If no position has been found (or zero length), put it in // the front of list. listeners.unshift( listenerFirer ); } return { removeListener: removeListener }; }, /** * Similiar with {@link #on} but the listener will be called only once upon the next event firing. * * @see CKEDITOR.event#on */ once: function() { var fn = arguments[ 1 ]; arguments[ 1 ] = function( evt ) { evt.removeListener(); return fn.apply( this, arguments ); }; return this.on.apply( this, arguments ); }, /** * @static * @property {Boolean} useCapture * @todo */ /** * Register event handler under the capturing stage on supported target. */ capture: function() { CKEDITOR.event.useCapture = 1; var retval = this.on.apply( this, arguments ); CKEDITOR.event.useCapture = 0; return retval; }, /** * Fires an specific event in the object. All registered listeners are * called at this point. * * someObject.on( 'someEvent', function() { ... } ); * someObject.on( 'someEvent', function() { ... } ); * someObject.fire( 'someEvent' ); // Both listeners are called. * * someObject.on( 'someEvent', function( event ) { * alert( event.data ); // 'Example' * } ); * someObject.fire( 'someEvent', 'Example' ); * * @method * @param {String} eventName The event name to fire. * @param {Object} [data] Data to be sent as the * {@link CKEDITOR.eventInfo#data} when calling the listeners. * @param {CKEDITOR.editor} [editor] The editor instance to send as the * {@link CKEDITOR.eventInfo#editor} when calling the listener. * @returns {Boolean/Object} A boolean indicating that the event is to be * canceled, or data returned by one of the listeners. */ fire: (function() { // Create the function that marks the event as stopped. var stopped = 0; var stopEvent = function() { stopped = 1; }; // Create the function that marks the event as canceled. var canceled = 0; var cancelEvent = function() { canceled = 1; }; return function( eventName, data, editor ) { // Get the event entry. var event = getPrivate( this )[ eventName ]; // Save the previous stopped and cancelled states. We may // be nesting fire() calls. var previousStopped = stopped, previousCancelled = canceled; // Reset the stopped and canceled flags. stopped = canceled = 0; if ( event ) { var listeners = event.listeners; if ( listeners.length ) { // As some listeners may remove themselves from the // event, the original array length is dinamic. So, // let's make a copy of all listeners, so we are // sure we'll call all of them. listeners = listeners.slice( 0 ); var retData; // Loop through all listeners. for ( var i = 0; i < listeners.length; i++ ) { // Call the listener, passing the event data. if ( event.errorProof ) { try { retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent ); } catch ( er ) {} } else retData = listeners[ i ].call( this, editor, data, stopEvent, cancelEvent ); if ( retData === false ) canceled = 1; else if ( typeof retData != 'undefined' ) data = retData; // No further calls is stopped or canceled. if ( stopped || canceled ) break; } } } var ret = canceled ? false : ( typeof data == 'undefined' ? true : data ); // Restore the previous stopped and canceled states. stopped = previousStopped; canceled = previousCancelled; return ret; }; })(), /** * Fires an specific event in the object, releasing all listeners * registered to that event. The same listeners are not called again on * successive calls of it or of {@link #fire}. * * someObject.on( 'someEvent', function() { ... } ); * someObject.fire( 'someEvent' ); // Above listener called. * someObject.fireOnce( 'someEvent' ); // Above listener called. * someObject.fire( 'someEvent' ); // No listeners called. * * @param {String} eventName The event name to fire. * @param {Object} [data] Data to be sent as the * {@link CKEDITOR.eventInfo#data} when calling the listeners. * @param {CKEDITOR.editor} [editor] The editor instance to send as the * {@link CKEDITOR.eventInfo#editor} when calling the listener. * @returns {Boolean/Object} A booloan indicating that the event is to be * canceled, or data returned by one of the listeners. */ fireOnce: function( eventName, data, editor ) { var ret = this.fire( eventName, data, editor ); delete getPrivate( this )[ eventName ]; return ret; }, /** * Unregisters a listener function from being called at the specified * event. No errors are thrown if the listener has not been registered previously. * * var myListener = function() { ... }; * someObject.on( 'someEvent', myListener ); * someObject.fire( 'someEvent' ); // myListener called. * someObject.removeListener( 'someEvent', myListener ); * someObject.fire( 'someEvent' ); // myListener not called. * * @param {String} eventName The event name. * @param {Function} listenerFunction The listener function to unregister. */ removeListener: function( eventName, listenerFunction ) { // Get the event entry. var event = getPrivate( this )[ eventName ]; if ( event ) { var index = event.getListenerIndex( listenerFunction ); if ( index >= 0 ) event.listeners.splice( index, 1 ); } }, /** * Remove all existing listeners on this object, for cleanup purpose. */ removeAllListeners: function() { var events = getPrivate( this ); for ( var i in events ) delete events[ i ]; }, /** * Checks if there is any listener registered to a given event. * * var myListener = function() { ... }; * someObject.on( 'someEvent', myListener ); * alert( someObject.hasListeners( 'someEvent' ) ); // true * alert( someObject.hasListeners( 'noEvent' ) ); // false * * @param {String} eventName The event name. * @returns {Boolean} */ hasListeners: function( eventName ) { var event = getPrivate( this )[ eventName ]; return ( event && event.listeners.length > 0 ); } }; })(); } /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ if ( !CKEDITOR.editor ) { // Documented at editor.js. CKEDITOR.editor = function() { // Push this editor to the pending list. It'll be processed later once // the full editor code is loaded. CKEDITOR._.pending.push( [ this, arguments ] ); // Call the CKEDITOR.event constructor to initialize this instance. CKEDITOR.event.call( this ); }; // Both fire and fireOnce will always pass this editor instance as the // "editor" param in CKEDITOR.event.fire. So, we override it to do that // automaticaly. CKEDITOR.editor.prototype.fire = function( eventName, data ) { if ( eventName in { instanceReady:1,loaded:1 } ) this[ eventName ] = true; return CKEDITOR.event.prototype.fire.call( this, eventName, data, this ); }; CKEDITOR.editor.prototype.fireOnce = function( eventName, data ) { if ( eventName in { instanceReady:1,loaded:1 } ) this[ eventName ] = true; return CKEDITOR.event.prototype.fireOnce.call( this, eventName, data, this ); }; // "Inherit" (copy actually) from CKEDITOR.event. CKEDITOR.event.implementOn( CKEDITOR.editor.prototype ); } /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.env} object, which constains * environment and browser information. */ if ( !CKEDITOR.env ) { /** * Environment and browser information. * * @class CKEDITOR.env * @singleton */ CKEDITOR.env = (function() { var agent = navigator.userAgent.toLowerCase(); var opera = window.opera; var env = { /** * Indicates that CKEditor is running on Internet Explorer. * * if ( CKEDITOR.env.ie ) * alert( 'I\'m on IE!' ); * * @property {Boolean} */ ie: eval( '/*@cc_on!@*/false' ), // Use eval to preserve conditional comment when compiling with Google Closure Compiler (#93). /** * Indicates that CKEditor is running on Opera. * * if ( CKEDITOR.env.opera ) * alert( 'I\'m on Opera!' ); * * @property {Boolean} */ opera: ( !!opera && opera.version ), /** * Indicates that CKEditor is running on a WebKit based browser, like Safari. * * if ( CKEDITOR.env.webkit ) * alert( 'I\'m on WebKit!' ); * * @property {Boolean} */ webkit: ( agent.indexOf( ' applewebkit/' ) > -1 ), /** * Indicates that CKEditor is running on Adobe AIR. * * if ( CKEDITOR.env.air ) * alert( 'I\'m on AIR!' ); * * @property {Boolean} */ air: ( agent.indexOf( ' adobeair/' ) > -1 ), /** * Indicates that CKEditor is running on Macintosh. * * if ( CKEDITOR.env.mac ) * alert( 'I love apples!'' ); * * @property {Boolean} */ mac: ( agent.indexOf( 'macintosh' ) > -1 ), /** * Indicates that CKEditor is running on a quirks mode environemnt. * * if ( CKEDITOR.env.quirks ) * alert( 'Nooooo!' ); * * @property {Boolean} */ quirks: ( document.compatMode == 'BackCompat' ), /** * Indicates that CKEditor is running on a mobile like environemnt. * * if ( CKEDITOR.env.mobile ) * alert( 'I\'m running with CKEditor today!' ); * * @property {Boolean} */ mobile: ( agent.indexOf( 'mobile' ) > -1 ), /** * Indicates that CKEditor is running on Apple iPhone/iPad/iPod devices. * * if ( CKEDITOR.env.iOS ) * alert( 'I like little apples!' ); * * @property {Boolean} */ iOS: /(ipad|iphone|ipod)/.test( agent ), /** * Indicates that the browser has a custom domain enabled. This has * been set with `document.domain`. * * if ( CKEDITOR.env.isCustomDomain() ) * alert( 'I\'m in a custom domain!' ); * * @returns {Boolean} `true` if a custom domain is enabled. */ isCustomDomain: function() { if ( !this.ie ) return false; var domain = document.domain, hostname = window.location.hostname; return domain != hostname && domain != ( '[' + hostname + ']' ); // IPv6 IP support (#5434) }, /** * Indicates that page is running under an encrypted connection. * * if ( CKEDITOR.env.secure ) * alert( 'I\'m in SSL!' ); * * @returns {Boolean} `true` if the page has an encrypted connection. */ secure: location.protocol == 'https:' }; /** * Indicates that CKEditor is running on a Gecko based browser, like * Firefox. * * if ( CKEDITOR.env.gecko ) * alert( 'I\'m riding a gecko!' ); * * @property {Boolean} */ env.gecko = ( navigator.product == 'Gecko' && !env.webkit && !env.opera ); /** * Indicates that CKEditor is running on Chrome. * * if ( CKEDITOR.env.chrome ) * alert( 'I\'m riding Chrome!' ); * * @property {Boolean} chrome */ /** * Indicates that CKEditor is running on Safari (including mobile version). * * if ( CKEDITOR.env.safari ) * alert( 'I\'m riding Safari!' ); * * @property {Boolean} safari */ if ( env.webkit ) { if ( agent.indexOf( 'chrome' ) > -1 ) env.chrome = true; else env.safari = true; } var version = 0; // Internet Explorer 6.0+ if ( env.ie ) { // We use env.version for feature detection, so set it properly. if ( env.quirks || !document.documentMode ) version = parseFloat( agent.match( /msie (\d+)/ )[ 1 ] ); else version = document.documentMode; // Deprecated features available just for backwards compatibility. env.ie9Compat = version == 9; env.ie8Compat = version == 8; env.ie7Compat = version == 7; env.ie6Compat = version < 7 || env.quirks; /** * Indicates that CKEditor is running on an IE6-like environment, which * includes IE6 itself and IE7 and IE8 quirks mode. * * @deprecated * @property {Boolean} ie6Compat */ /** * Indicates that CKEditor is running on an IE7-like environment, which * includes IE7 itself and IE8's IE7 document mode. * * @deprecated * @property {Boolean} ie7Compat */ /** * Indicates that CKEditor is running on Internet Explorer 8 on * standards mode. * * @deprecated * @property {Boolean} ie8Compat */ /** * Indicates that CKEditor is running on Internet Explorer 9's standards mode. * * @deprecated * @property {Boolean} ie9Compat */ } // Gecko. if ( env.gecko ) { var geckoRelease = agent.match( /rv:([\d\.]+)/ ); if ( geckoRelease ) { geckoRelease = geckoRelease[ 1 ].split( '.' ); version = geckoRelease[ 0 ] * 10000 + ( geckoRelease[ 1 ] || 0 ) * 100 + ( geckoRelease[ 2 ] || 0 ) * 1; } } // Opera 9.50+ if ( env.opera ) version = parseFloat( opera.version() ); // Adobe AIR 1.0+ // Checked before Safari because AIR have the WebKit rich text editor // features from Safari 3.0.4, but the version reported is 420. if ( env.air ) version = parseFloat( agent.match( / adobeair\/(\d+)/ )[ 1 ] ); // WebKit 522+ (Safari 3+) if ( env.webkit ) version = parseFloat( agent.match( / applewebkit\/(\d+)/ )[ 1 ] ); /** * Contains the browser version. * * For gecko based browsers (like Firefox) it contains the revision * number with first three parts concatenated with a padding zero * (e.g. for revision 1.9.0.2 we have 10900). * * For webkit based browser (like Safari and Chrome) it contains the * WebKit build version (e.g. 522). * * For IE browsers, it matches the "document mode". * * if ( CKEDITOR.env.ie && CKEDITOR.env.version <= 6 ) * alert( 'Ouch!' ); * * @property {Number} */ env.version = version; /** * Indicates that CKEditor is running on a compatible browser. * * if ( CKEDITOR.env.isCompatible ) * alert( 'Your browser is pretty cool!' ); * * @property {Boolean} */ env.isCompatible = // White list of mobile devices that supports. env.iOS && version >= 534 || !env.mobile && ( ( env.ie && version > 6 ) || ( env.gecko && version >= 10801 ) || ( env.opera && version >= 9.5 ) || ( env.air && version >= 1 ) || ( env.webkit && version >= 522 ) || false ); /** * The CSS class to be appended on the main UI containers, making it * easy to apply browser specific styles to it. * * myDiv.className = CKEDITOR.env.cssClass; * * @property {String} */ env.cssClass = 'cke_browser_' + ( env.ie ? 'ie' : env.gecko ? 'gecko' : env.opera ? 'opera' : env.webkit ? 'webkit' : 'unknown' ); if ( env.quirks ) env.cssClass += ' cke_browser_quirks'; if ( env.ie ) { env.cssClass += ' cke_browser_ie' + ( env.quirks || env.version < 7 ? '6' : env.version ); if ( env.quirks ) env.cssClass += ' cke_browser_iequirks'; } if ( env.gecko ) { if ( version < 10900 ) env.cssClass += ' cke_browser_gecko18'; else if ( version <= 11000 ) env.cssClass += ' cke_browser_gecko19'; } if ( env.air ) env.cssClass += ' cke_browser_air'; return env; })(); } // PACKAGER_RENAME( CKEDITOR.env ) // PACKAGER_RENAME( CKEDITOR.env.ie ) /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Contains the second part of the {@link CKEDITOR} object * definition, which defines the basic editor features to be available in * the root ckeditor_basic.js file. */ if ( CKEDITOR.status == 'unloaded' ) { (function() { CKEDITOR.event.implementOn( CKEDITOR ); /** * Forces the full CKEditor core code, in the case only the basic code has been * loaded (`ckeditor_basic.js`). This method self-destroys (becomes undefined) in * the first call or as soon as the full code is available. * * // Check if the full core code has been loaded and load it. * if ( CKEDITOR.loadFullCore ) * CKEDITOR.loadFullCore(); * * @member CKEDITOR */ CKEDITOR.loadFullCore = function() { // If the basic code is not ready, just mark it to be loaded. if ( CKEDITOR.status != 'basic_ready' ) { CKEDITOR.loadFullCore._load = 1; return; } // Destroy this function. delete CKEDITOR.loadFullCore; // Append the script to the head. var script = document.createElement( 'script' ); script.type = 'text/javascript'; script.src = CKEDITOR.basePath + 'ckeditor.js'; document.getElementsByTagName( 'head' )[ 0 ].appendChild( script ); }; /** * The time to wait (in seconds) to load the full editor code after the * page load, if the "ckeditor_basic" file is used. If set to zero, the * editor is loaded on demand, as soon as an instance is created. * * This value must be set on the page before the page load completion. * * // Loads the full source after five seconds. * CKEDITOR.loadFullCoreTimeout = 5; * * @property * @member CKEDITOR */ CKEDITOR.loadFullCoreTimeout = 0; // Documented at ckeditor.js. CKEDITOR.add = function( editor ) { // For now, just put the editor in the pending list. It will be // processed as soon as the full code gets loaded. var pending = this._.pending || ( this._.pending = [] ); pending.push( editor ); }; (function() { var onload = function() { var loadFullCore = CKEDITOR.loadFullCore, loadFullCoreTimeout = CKEDITOR.loadFullCoreTimeout; if ( !loadFullCore ) return; CKEDITOR.status = 'basic_ready'; if ( loadFullCore && loadFullCore._load ) loadFullCore(); else if ( loadFullCoreTimeout ) { setTimeout( function() { if ( CKEDITOR.loadFullCore ) CKEDITOR.loadFullCore(); }, loadFullCoreTimeout * 1000 ); } }; CKEDITOR.domReady( onload ); })(); CKEDITOR.status = 'basic_loaded'; })(); } /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom} object, which contains DOM * manipulation objects and function. */ CKEDITOR.dom = {}; // PACKAGER_RENAME( CKEDITOR.dom ) /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.tools} object, which contains * utility functions. */ (function() { var functions = [], cssVendorPrefix = CKEDITOR.env.gecko ? '-moz-' : CKEDITOR.env.webkit ? '-webkit-' : CKEDITOR.env.opera ? '-o-' : CKEDITOR.env.ie ? '-ms-' : ''; CKEDITOR.on( 'reset', function() { functions = []; }); /** * Utility functions. * * @class * @singleton */ CKEDITOR.tools = { /** * Compare 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" is the arrays have the same lenght 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; }, /** * Creates a deep copy of an object. * * **Note**: there is no support for recursive references. * * 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 ] = this.clone( obj[ i ] ); return clone; } // "Static" types. if ( obj === null || ( typeof( obj ) != 'object' ) || ( obj instanceof String ) || ( obj instanceof Number ) || ( obj instanceof Boolean ) || ( obj instanceof Date ) || ( obj instanceof RegExp ) ) { return obj; } // Objects. clone = new obj.constructor(); for ( var propertyName in obj ) { var property = obj[ propertyName ]; clone[ propertyName ] = this.clone( property ); } return clone; }, /** * Turn the first letter of string to upper-case. * * @param {String} str * @returns {String} */ capitalize: function( str ) { return str.charAt( 0 ).toUpperCase() + str.substring( 1 ).toLowerCase(); }, /** * Copy 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 which copy * properties. 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; if ( typeof( overwrite = arguments[ argsLength - 1 ] ) == 'boolean' ) argsLength--; else if ( typeof( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' ) { 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. if ( overwrite === true || target[ propertyName ] == undefined ) { // 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 which 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(); }, /** * 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 && object instanceof Array ); }, /** * Whether the object contains no properties of it's own. * * @param object * @returns {Boolean} */ isEmpty: function( object ) { for ( var i in object ) { if ( object.hasOwnProperty( i ) ) return false; } return true; }, /** * Generate object or 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 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(); }); } }; })(), /** * Build the HTML snippet of a set of `' ); else retval.push( '' ); } } return retval.join( '' ); }, /** * Replace 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 encode string. */ htmlEncode: function( text ) { return String( text ).replace( /&/g, '&' ).replace( />/g, '>' ).replace( /' ); * alert( CKEDITOR.tools.htmlEncodeAttr( element.getAttribute( 'title' ) ); // '>a " b <' * * @param {String} The attribute's value to be encoded. * @returns {String} The encode value. */ htmlEncodeAttr: function( text ) { return text.replace( /"/g, '"' ).replace( //g, '>' ); }, /** * Gets a unique number for this CKEDITOR execution session. It returns * progressive numbers starting at 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's interface elements. It returns a * string with the "cke_" prefix and a progressive 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(); }, /** * 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; }, /** * Executes a function after specified delay. * * CKEDITOR.tools.setTimeout( function() { * alert( 'Executed after 2 seconds' ); * }, 2000 ); * * @param {Function} func The function to be executed. * @param {Number} [milliseconds=0] The amount of time (millisecods) to wait * to fire the function execution. * @param {Object} [scope=window] The object to hold the function execution scope * (the `this` object). * @param {Object/Array} [args] A single object, or an array of objects, to * pass as arguments 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 ); }, /** * Remove 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 remove the spaces. * @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, '' ); }; })(), /** * Remove 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 remove the spaces. * @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, '' ); }; })(), /** * Remove 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 remove the spaces. * @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. 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 bind the execution context. * @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, with 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 specially 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 ) ); }, /** * Append the `px` length unit to the size if it's 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 || ''; }; })(), /** * Convert 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( '
', 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 ); }, /** * Return the first successfully executed function's return value that * doesn't 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; }, /** * Generate 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 string used as sub keys. * @returns {String} */ genKey: function() { return Array.prototype.slice.call( arguments ).join( '-' ); }, /** * Create A "deferred" function which will not run immediately, * 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 loose. * * @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 ); }; }, /** * Try 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( ';' ) + ';' ) : ''; }, /** * Find and convert `rgb(x,x,x)` colors 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( '' ); }); }, /** * Turn 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 {String} 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' ); temp.setAttribute( 'style', styleText ); styleText = CKEDITOR.tools.convertRgbToHex( temp.getAttribute( 'style' ) || '' ); } // IE will leave a single semicolon when failed to parse the style text. (#3891) if ( !styleText || styleText == ';' ) return retval; styleText.replace( /"/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) { if ( normalize ) { name = name.toLowerCase(); // Normalize font-family property, ignore quotes and being case insensitive. (#7322) // http://www.w3.org/TR/css3-fonts/#font-family-the-font-family-property if ( name == 'font-family' ) value = value.toLowerCase().replace( /["']/g, '' ).replace( /\s*,\s*/g, ',' ); value = CKEDITOR.tools.trim( value ); } retval[ name ] = value; }); return retval; } }; })(); // PACKAGER_RENAME( CKEDITOR.tools ) /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dtd} object, which holds the DTD * mapping for XHTML 1.0 Transitional. This file was automatically * generated from the file: xhtml1-transitional.dtd. */ /** * Holds and object representation of the HTML DTD to be used by the * editor in its internal operations. * * Each element in the DTD is represented by a property in this object. Each * property contains the list of elements that can be contained by the element. * Text is represented by the `#` property. * * Several special grouping properties are also available. Their names start * with the `$` character. * * // Check if
can be contained in a

element. * alert( !!CKEDITOR.dtd[ 'p' ][ 'div' ] ); // false * * // Check if

can be contained in a

element. * alert( !!CKEDITOR.dtd[ 'div' ][ 'p' ] ); // true * * // Check if

is a block element. * alert( !!CKEDITOR.dtd.$block[ 'p' ] ); // true * * @class CKEDITOR.dtd * @singleton */ CKEDITOR.dtd = (function() { 'use strict'; var X = CKEDITOR.tools.extend, // Subtraction rest of sets, from the first set. Y = function( source, removed ) { var substracted = CKEDITOR.tools.clone( source ); for ( var i = 1; i < arguments.length; i++ ) { removed = arguments[ i ]; for( var name in removed ) delete substracted[ name ]; } return substracted; }; // Phrasing elements. // P = { a:1,em:1,strong:1,small:1,abbr:1,dfn:1,i:1,b:1,s:1,u:1,code:1,'var':1,samp:1,kbd:1,sup:1,sub:1,q:1,cite:1,span:1,bdo:1,bdi:1,br:1,wbr:1,ins:1,del:1,img:1,embed:1,object:1,iframe:1,map:1,area:1,script:1,noscript:1,ruby:1,video:1,audio:1,input:1,textarea:1,select:1,button:1,label:1,output:1,keygen:1,progress:1,command:1,canvas:1,time:1,meter:1,detalist:1 }, // Flow elements. // F = { a:1,p:1,hr:1,pre:1,ul:1,ol:1,dl:1,div:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hgroup:1,address:1,blockquote:1,ins:1,del:1,object:1,map:1,noscript:1,section:1,nav:1,article:1,aside:1,header:1,footer:1,video:1,audio:1,figure:1,table:1,form:1,fieldset:1,menu:1,canvas:1,details:1 }, // Text can be everywhere. // X( P, T ); // Flow elements set consists of phrasing elements set. // X( F, P ); var P = {}, F = {}, // Intersection of flow elements set and phrasing elements set. PF = { a:1,abbr:1,area:1,audio:1,b:1,bdi:1,bdo:1,br:1,button:1,canvas:1,cite:1,code:1,command:1,datalist:1,del:1,dfn:1,em:1,embed:1,i:1,iframe:1,img:1,input:1,ins:1,kbd:1,keygen:1,label:1,map:1,mark:1,meter:1,noscript:1,object:1,output:1,progress:1,q:1,ruby:1,s:1,samp:1,script:1,select:1,small:1,span:1,strong:1,sub:1,sup:1,textarea:1,time:1,u:1,'var':1,video:1,wbr:1 }, // F - PF (Flow Only). FO = { address:1,article:1,aside:1,blockquote:1,details:1,div:1,dl:1,fieldset:1,figure:1,footer:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,header:1,hgroup:1,hr:1,menu:1,nav:1,ol:1,p:1,pre:1,section:1,table:1,ul:1 }, // Metadata elements. M = { command:1,link:1,meta:1,noscript:1,script:1,style:1 }, // Empty. E = {}, // Text. T = { '#':1 }, // Deprecated phrasing elements. DP = { acronym:1,applet:1,basefont:1,big:1,font:1,isindex:1,strike:1,style:1,tt:1 }, // TODO remove "style". // Deprecated flow only elements. DFO = { center:1,dir:1,noframes:1 }; // Phrasing elements := PF + T + DP X( P, PF, T, DP ); // Flow elements := FO + P + DFO X( F, FO, P, DFO ); var dtd = { a: Y( P, { a:1,button:1 } ), // Treat as normal inline element (not a transparent one). abbr: P, address: F, area: E, article: X( { style:1 }, F ), aside: X( { style:1 }, F ), audio: X( { source:1,track:1 }, F ), b: P, base: E, bdi: P, bdo: P, blockquote: F, body: F, br: E, button: Y( P, { a:1,button:1 } ), canvas: P, // Treat as normal inline element (not a transparent one). caption: F, cite: P, code: P, col: E, colgroup: { col:1 }, command: E, datalist: X( { option:1 }, P ), dd: F, del: P, // Treat as normal inline element (not a transparent one). details: X( { summary:1 }, F ), dfn: P, div: X( { style:1 }, F ), dl: { dt:1,dd:1 }, dt: F, em: P, embed: E, fieldset: X( { legend:1 }, F ), figcaption: F, figure: X( { figcaption:1 }, F ), footer: F, form: F, h1: P, h2: P, h3: P, h4: P, h5: P, h6: P, head: X( { title:1,base:1 }, M ), header: F, hgroup: { h1:1,h2:1,h3:1,h4:1,h5:1,h6:1 }, hr: E, html: X( { head:1,body:1 }, F, M ), // Head and body are optional... i: P, iframe: T, img: E, input: E, ins: P, // Treat as normal inline element (not a transparent one). kbd: P, keygen: E, label: P, legend: P, li: F, link: E, map: F, mark: P, // Treat as normal inline element (not a transparent one). menu: X( { li:1 }, F ), meta: E, meter: Y( P, { meter:1 } ), nav: F, noscript: X( { link:1,meta:1,style:1 }, P ), // Treat as normal inline element (not a transparent one). object: X( { param:1 }, P ), // Treat as normal inline element (not a transparent one). ol: { li:1 }, optgroup: { option:1 }, option: T, output: P, p: P, param: E, pre: P, progress: Y( P, { progress:1 } ), q: P, rp: P, rt: P, ruby: X( { rp:1,rt:1 }, P ), s: P, samp: P, script: T, section: X( { style:1 }, F ), select: { optgroup:1,option:1 }, small: P, source: E, span: P, strong: P, style: T, sub: P, summary: P, sup: P, table: { caption:1,colgroup:1,thead:1,tfoot:1,tbody:1,tr:1 }, tbody: { tr:1 }, td: F, textarea: T, tfoot: { tr:1 }, th: F, thead: { tr:1 }, time: Y( P, { time:1 } ), title: T, tr: { th:1,td:1 }, track: E, u: P, ul: { li:1 }, 'var': P, video: X( { source:1,track:1 }, F ), wbr: E, // Deprecated tags. acronym: P, applet: X( { param:1 }, F ), basefont: E, big: P, center: F, dialog: E, dir: { li:1 }, font: P, isindex: E, noframes: F, strike: P, tt: P }; X( dtd, { /** * List of block elements, like `

` or `

`. */ $block: X( { audio:1,dd:1,dt:1,li:1,video:1 }, FO, DFO ), /** * List of elements that contains other blocks, in which block-level operations should be limited, * this property is not intended to be checked directly, use {@link CKEDITOR.dom.elementPath#blockLimit} instead. * * Some examples of editor behaviors that are impacted by block limits: * * * Enter key never split a block-limit element; * * Style application is constraint by the block limit of the current selection. * * Pasted html will be inserted into the block limit of the current selection. * * **Note:** As an exception `
  • ` is not considered as a block limit, as it's generally used as a text block. */ $blockLimit: { article:1,aside:1,audio:1,body:1,caption:1,details:1,dir:1,div:1,dl:1,fieldset:1,figure:1,footer:1,form:1,header:1,hgroup:1,menu:1,nav:1,ol:1,section:1,table:1,td:1,th:1,tr:1,ul:1,video:1 }, /** * List of elements that contain character data. */ $cdata: { script:1,style:1 }, /** * List of elements that are accepted as inline editing hosts. */ $editable: { address:1,article:1,aside:1,blockquote:1,body:1,details:1,div:1,fieldset:1,footer:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,header:1,hgroup:1,nav:1,p:1,pre:1,section:1 }, /** * List of empty (self-closing) elements, like `
    ` or ``. */ $empty: { area:1,base:1,basefont:1,br:1,col:1,command:1,dialog:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,link:1,meta:1,param:1,source:1,track:1,wbr:1 }, /** * List of inline (`` like) elements. */ $inline: P, /** * List of list root elements. */ $list: { dl:1,ol:1,ul:1 }, /** * List of list item elements, like `
  • ` or `
    `. */ $listItem: { dd:1,dt:1,li:1 }, /** * List of elements which may live outside body. */ $nonBodyContent: X( { body:1,head:1,html:1 }, dtd.head ), /** * Elements that accept text nodes, but are not possible to edit into the browser. */ $nonEditable: { applet:1,audio:1,button:1,embed:1,iframe:1,map:1,object:1,option:1,param:1,script:1,textarea:1,video:1 }, /** * Elements that are considered objects, therefore selected as a whole in the editor. */ $object: { applet:1,audio:1,button:1,hr:1,iframe:1,img:1,input:1,object:1,select:1,table:1,textarea:1,video:1 }, /** * List of elements that can be ignored if empty, like `` or ``. */ $removeEmpty: { abbr:1,acronym:1,b:1,bdi:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,mark:1,meter:1,output:1,q:1,ruby:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,time:1,tt:1,u:1,'var':1 }, /** * List of elements that have tabindex set to zero by default. */ $tabIndex: { a:1,area:1,button:1,input:1,object:1,select:1,textarea:1 }, /** * List of elements used inside the `` element, like `` or `' ]; for ( i = 0; i < childHtmlList.length; i++ ) { var className = 'cke_dialog_ui_hbox_child', styles = []; if ( i === 0 ) className = 'cke_dialog_ui_hbox_first'; if ( i == childHtmlList.length - 1 ) className = 'cke_dialog_ui_hbox_last'; html.push( '' ); } html.push( '' ); return html.join( '' ); }; var attribs = { role: 'presentation' }; elementDefinition && elementDefinition.align && ( attribs.align = elementDefinition.align ); CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type: 'hbox' }, htmlList, 'table', styles, attribs, innerHTML ); }, /** * Vertical layout box for dialog UI elements. * * @class CKEDITOR.ui.dialog.vbox * @extends CKEDITOR.ui.dialog.hbox * @constructor Creates a vbox class instance. * @param {CKEDITOR.dialog} dialog Parent dialog object. * @param {Array} childObjList * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container. * @param {Array} childHtmlList * Array of HTML code that correspond to the HTML output of all the * objects in childObjList. * @param {Array} htmlList Array of HTML code that this element will output to. * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition * The element definition. Accepted fields: * * * `width` (Optional) The width of the layout. * * `heights` (Optional) The heights of individual cells. * * `align` (Optional) The alignment of the layout. * * `padding` (Optional) The padding width inside child cells. * * `expand` (Optional) Whether the layout should expand * vertically to fill its container. */ vbox: function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) { if ( arguments.length < 3 ) return; this._ || ( this._ = {} ); var children = this._.children = childObjList, width = elementDefinition && elementDefinition.width || null, heights = elementDefinition && elementDefinition.heights || null; /** @ignore */ var innerHTML = function() { var html = [ '
    `. */ $tableContent: { caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1 }, /** * List of "transparent" elements. See [W3C's definition of "transparent" element](http://dev.w3.org/html5/markup/terminology.html#transparent). */ $transparent: { a:1,audio:1,canvas:1,del:1,ins:1,map:1,noscript:1,object:1,video:1 }, /** * List of elements that are not to exist standalone that must live under it's parent element. */ $intermediate: { caption:1,colgroup:1,dd:1,dt:1,figcaption:1,legend:1,li:1,optgroup:1,option:1,rp:1,rt:1,summary:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1 } } ); return dtd; })(); // PACKAGER_RENAME( CKEDITOR.dtd ) /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom.event} class, which * represents the a native DOM event object. */ /** * Represents a native DOM event object. * * @class * @constructor Creates an event class instance. * @param {Object} domEvent A native DOM event object. */ CKEDITOR.dom.event = function( domEvent ) { /** * The native DOM event object represented by this class instance. * * @readonly */ this.$ = domEvent; }; CKEDITOR.dom.event.prototype = { /** * Gets the key code associated to the event. * * alert( event.getKey() ); // '65' is 'a' has been pressed * * @returns {Number} The key code. */ getKey: function() { return this.$.keyCode || this.$.which; }, /** * Gets a number represeting the combination of the keys pressed during the * event. It is the sum with the current key code and the {@link CKEDITOR#CTRL}, * {@link CKEDITOR#SHIFT} and {@link CKEDITOR#ALT} constants. * * alert( event.getKeystroke() == 65 ); // 'a' key * alert( event.getKeystroke() == CKEDITOR.CTRL + 65 ); // CTRL + 'a' key * alert( event.getKeystroke() == CKEDITOR.CTRL + CKEDITOR.SHIFT + 65 ); // CTRL + SHIFT + 'a' key * * @returns {Number} The number representing the keys combination. */ getKeystroke: function() { var keystroke = this.getKey(); if ( this.$.ctrlKey || this.$.metaKey ) keystroke += CKEDITOR.CTRL; if ( this.$.shiftKey ) keystroke += CKEDITOR.SHIFT; if ( this.$.altKey ) keystroke += CKEDITOR.ALT; return keystroke; }, /** * Prevents the original behavior of the event to happen. It can optionally * stop propagating the event in the event chain. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.on( 'click', function( ev ) { * // The DOM event object is passed by the 'data' property. * var domEvent = ev.data; * // Prevent the click to chave any effect in the element. * domEvent.preventDefault(); * } ); * * @param {Boolean} [stopPropagation=false] Stop propagating this event in the * event chain. */ preventDefault: function( stopPropagation ) { var $ = this.$; if ( $.preventDefault ) $.preventDefault(); else $.returnValue = false; if ( stopPropagation ) this.stopPropagation(); }, /** * Stops this event propagation in the event chain. */ stopPropagation: function() { var $ = this.$; if ( $.stopPropagation ) $.stopPropagation(); else $.cancelBubble = true; }, /** * Returns the DOM node where the event was targeted to. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.on( 'click', function( ev ) { * // The DOM event object is passed by the 'data' property. * var domEvent = ev.data; * // Add a CSS class to the event target. * domEvent.getTarget().addClass( 'clicked' ); * } ); * * @returns {CKEDITOR.dom.node} The target DOM node. */ getTarget: function() { var rawNode = this.$.target || this.$.srcElement; return rawNode ? new CKEDITOR.dom.node( rawNode ) : null; }, /** * Returns an integer value that indicates the current processing phase of an event. * For browsers that doesn't support event phase, {@link CKEDITOR#EVENT_PHASE_AT_TARGET} is always returned. * * @returns {Number} One of {@link CKEDITOR#EVENT_PHASE_CAPTURING}, * {@link CKEDITOR#EVENT_PHASE_AT_TARGET}, or {@link CKEDITOR#EVENT_PHASE_BUBBLING}. */ getPhase: function() { return this.$.eventPhase || 2; }, /** * Retrieves the coordinates of the mouse pointer relative to the top-left * corner of the document, in mouse related event. * @returns {Object} The object contains the position. * @example * element.on( 'mousemouse', function( ev ) * { * var pageOffset = ev.data.getPageOffset(); * alert( pageOffset.x ); // page offset X * alert( pageOffset.y ); // page offset Y * }); */ getPageOffset : function() { var doc = this.getTarget().getDocument().$; var pageX = this.$.pageX || this.$.clientX + ( doc.documentElement.scrollLeft || doc.body.scrollLeft ); var pageY = this.$.pageY || this.$.clientY + ( doc.documentElement.scrollTop || doc.body.scrollTop ); return { x : pageX, y : pageY }; } }; // For the followind constants, we need to go over the Unicode boundaries // (0x10FFFF) to avoid collision. /** * CTRL key (0x110000). * * @readonly * @property {Number} [=0x110000] * @member CKEDITOR */ CKEDITOR.CTRL = 0x110000; /** * SHIFT key (0x220000). * * @readonly * @property {Number} [=0x220000] * @member CKEDITOR */ CKEDITOR.SHIFT = 0x220000; /** * ALT key (0x440000). * * @readonly * @property {Number} [=0x440000] * @member CKEDITOR */ CKEDITOR.ALT = 0x440000; /** * Capturing phase. * * @readonly * @property {Number} [=1] * @member CKEDITOR */ CKEDITOR.EVENT_PHASE_CAPTURING = 1; /** * Event at target. * * @readonly * @property {Number} [=2] * @member CKEDITOR */ CKEDITOR.EVENT_PHASE_AT_TARGET = 2; /** * Bubbling phase. * * @readonly * @property {Number} [=3] * @member CKEDITOR */ CKEDITOR.EVENT_PHASE_BUBBLING = 3; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.editor} class, which is the base * for other classes representing DOM objects. */ /** * Represents a DOM object. This class is not intended to be used directly. It * serves as the base class for other classes representing specific DOM * objects. * * @class * @mixins CKEDITOR.event * @constructor Creates a domObject class instance. * @param {Object} nativeDomObject A native DOM object. */ CKEDITOR.dom.domObject = function( nativeDomObject ) { if ( nativeDomObject ) { /** * The native DOM object represented by this class instance. * * var element = new CKEDITOR.dom.element( 'span' ); * alert( element.$.nodeType ); // '1' * * @readonly * @property {Object} */ this.$ = nativeDomObject; } }; CKEDITOR.dom.domObject.prototype = (function() { // Do not define other local variables here. We want to keep the native // listener closures as clean as possible. var getNativeListener = function( domObject, eventName ) { return function( domEvent ) { // In FF, when reloading the page with the editor focused, it may // throw an error because the CKEDITOR global is not anymore // available. So, we check it here first. (#2923) if ( typeof CKEDITOR != 'undefined' ) domObject.fire( eventName, new CKEDITOR.dom.event( domEvent ) ); }; }; return { /** * Get the private `_` object which is bound to the native * DOM object using {@link #getCustomData}. * * var elementA = new CKEDITOR.dom.element( nativeElement ); * elementA.getPrivate().value = 1; * ... * var elementB = new CKEDITOR.dom.element( nativeElement ); * elementB.getPrivate().value; // 1 * * @returns {Object} The private object. */ getPrivate: function() { var priv; // Get the main private object from the custom data. Create it if not defined. if ( !( priv = this.getCustomData( '_' ) ) ) this.setCustomData( '_', ( priv = {} ) ); return priv; }, // Docs inherited from event. on: function( eventName ) { // We customize the "on" function here. The basic idea is that we'll have // only one listener for a native event, which will then call all listeners // set to the event. // Get the listeners holder object. var nativeListeners = this.getCustomData( '_cke_nativeListeners' ); if ( !nativeListeners ) { nativeListeners = {}; this.setCustomData( '_cke_nativeListeners', nativeListeners ); } // Check if we have a listener for that event. if ( !nativeListeners[ eventName ] ) { var listener = nativeListeners[ eventName ] = getNativeListener( this, eventName ); if ( this.$.addEventListener ) this.$.addEventListener( eventName, listener, !!CKEDITOR.event.useCapture ); else if ( this.$.attachEvent ) this.$.attachEvent( 'on' + eventName, listener ); } // Call the original implementation. return CKEDITOR.event.prototype.on.apply( this, arguments ); }, // Docs inherited from event. removeListener: function( eventName ) { // Call the original implementation. CKEDITOR.event.prototype.removeListener.apply( this, arguments ); // If we don't have listeners for this event, clean the DOM up. if ( !this.hasListeners( eventName ) ) { var nativeListeners = this.getCustomData( '_cke_nativeListeners' ); var listener = nativeListeners && nativeListeners[ eventName ]; if ( listener ) { if ( this.$.removeEventListener ) this.$.removeEventListener( eventName, listener, false ); else if ( this.$.detachEvent ) this.$.detachEvent( 'on' + eventName, listener ); delete nativeListeners[ eventName ]; } } }, /** * Removes any listener set on this object. * * To avoid memory leaks we must assure that there are no * references left after the object is no longer needed. */ removeAllListeners: function() { var nativeListeners = this.getCustomData( '_cke_nativeListeners' ); for ( var eventName in nativeListeners ) { var listener = nativeListeners[ eventName ]; if ( this.$.detachEvent ) this.$.detachEvent( 'on' + eventName, listener ); else if ( this.$.removeEventListener ) this.$.removeEventListener( eventName, listener, false ); delete nativeListeners[ eventName ]; } } }; })(); (function( domObjectProto ) { var customData = {}; CKEDITOR.on( 'reset', function() { customData = {}; }); /** * Determines whether the specified object is equal to the current object. * * var doc = new CKEDITOR.dom.document( document ); * alert( doc.equals( CKEDITOR.document ) ); // true * alert( doc == CKEDITOR.document ); // false * * @param {Object} object The object to compare with the current object. * @returns {Boolean} `true` if the object is equal. */ domObjectProto.equals = function( object ) { // Try/Catch to avoid IE permission error when object is from different document. try { return ( object && object.$ === this.$ ); } catch ( er ) { return false; } }; /** * Sets a data slot value for this object. These values are shared by all * instances pointing to that same DOM object. * * **Note:** The created data slot is only guarantied to be available on this unique dom node, * thus any wish to continue access it from other element clones (either created by * clone node or from `innerHtml`) will fail, for such usage, please use * {@link CKEDITOR.dom.element#setAttribute} instead. * * var element = new CKEDITOR.dom.element( 'span' ); * element.setCustomData( 'hasCustomData', true ); * * @param {String} key A key used to identify the data slot. * @param {Object} value The value to set to the data slot. * @returns {CKEDITOR.dom.domObject} This DOM object instance. * @chainable */ domObjectProto.setCustomData = function( key, value ) { var expandoNumber = this.getUniqueId(), dataSlot = customData[ expandoNumber ] || ( customData[ expandoNumber ] = {} ); dataSlot[ key ] = value; return this; }; /** * Gets the value set to a data slot in this object. * * var element = new CKEDITOR.dom.element( 'span' ); * alert( element.getCustomData( 'hasCustomData' ) ); // e.g. 'true' * alert( element.getCustomData( 'nonExistingKey' ) ); // null * * @param {String} key The key used to identify the data slot. * @returns {Object} This value set to the data slot. */ domObjectProto.getCustomData = function( key ) { var expandoNumber = this.$[ 'data-cke-expando' ], dataSlot = expandoNumber && customData[ expandoNumber ]; return ( dataSlot && key in dataSlot ) ? dataSlot[ key ] : null; }; /** * Removes the value in data slot under given `key`. * * @param {String} key * @returns {Object} Removed value or `null` if not found. */ domObjectProto.removeCustomData = function( key ) { var expandoNumber = this.$[ 'data-cke-expando' ], dataSlot = expandoNumber && customData[ expandoNumber ], retval, hadKey; if ( dataSlot ) { retval = dataSlot[ key ]; hadKey = key in dataSlot; delete dataSlot[ key ]; } return hadKey ? retval : null; }; /** * Removes any data stored on this object. * To avoid memory leaks we must assure that there are no * references left after the object is no longer needed. */ domObjectProto.clearCustomData = function() { // Clear all event listeners this.removeAllListeners(); var expandoNumber = this.$[ 'data-cke-expando' ]; expandoNumber && delete customData[ expandoNumber ]; }; /** * Gets an ID that can be used to identiquely identify this DOM object in * the running session. * * @returns {Number} A unique ID. */ domObjectProto.getUniqueId = function() { return this.$[ 'data-cke-expando' ] || ( this.$[ 'data-cke-expando' ] = CKEDITOR.tools.getNextNumber() ); }; // Implement CKEDITOR.event. CKEDITOR.event.implementOn( domObjectProto ); })( CKEDITOR.dom.domObject.prototype ); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom.node} class which is the base * class for classes that represent DOM nodes. */ /** * Base class for classes representing DOM nodes. This constructor may return * an instance of a class that inherits from this class, like * {@link CKEDITOR.dom.element} or {@link CKEDITOR.dom.text}. * * @class * @extends CKEDITOR.dom.domObject * @constructor Creates a node class instance. * @param {Object} domNode A native DOM node. * @see CKEDITOR.dom.element * @see CKEDITOR.dom.text */ CKEDITOR.dom.node = function( domNode ) { if ( domNode ) { var type = domNode.nodeType == CKEDITOR.NODE_DOCUMENT ? 'document' : domNode.nodeType == CKEDITOR.NODE_ELEMENT ? 'element' : domNode.nodeType == CKEDITOR.NODE_TEXT ? 'text' : domNode.nodeType == CKEDITOR.NODE_COMMENT ? 'comment' : domNode.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ? 'documentFragment' : 'domObject'; // Call the base constructor otherwise. return new CKEDITOR.dom[ type ]( domNode ); } return this; }; CKEDITOR.dom.node.prototype = new CKEDITOR.dom.domObject(); /** * Element node type. * * @readonly * @property {Number} [=1] * @member CKEDITOR */ CKEDITOR.NODE_ELEMENT = 1; /** * Document node type. * * @readonly * @property {Number} [=9] * @member CKEDITOR */ CKEDITOR.NODE_DOCUMENT = 9; /** * Text node type. * * @readonly * @property {Number} [=3] * @member CKEDITOR */ CKEDITOR.NODE_TEXT = 3; /** * Comment node type. * * @readonly * @property {Number} [=8] * @member CKEDITOR */ CKEDITOR.NODE_COMMENT = 8; /** * Document fragment node type. * * @readonly * @property {Number} [=11] * @member CKEDITOR */ CKEDITOR.NODE_DOCUMENT_FRAGMENT = 11; CKEDITOR.POSITION_IDENTICAL = 0; CKEDITOR.POSITION_DISCONNECTED = 1; CKEDITOR.POSITION_FOLLOWING = 2; CKEDITOR.POSITION_PRECEDING = 4; CKEDITOR.POSITION_IS_CONTAINED = 8; CKEDITOR.POSITION_CONTAINS = 16; CKEDITOR.tools.extend( CKEDITOR.dom.node.prototype, { /** * Makes this node a child of another element. * * var p = new CKEDITOR.dom.element( 'p' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.appendTo( p ); * * // Result: '

    '. * * @param {CKEDITOR.dom.element} element The target element to which this node will be appended. * @returns {CKEDITOR.dom.element} The target element. */ appendTo: function( element, toStart ) { element.append( this, toStart ); return element; }, /** * Clone this node. * * **Note**: Values set by {#setCustomData} won't be available in the clone. * * @param {Boolean} [includeChildren=false] If `true` then all node's * children will be cloned recursively. * @param {Boolean} [cloneId=false] Whether ID attributes should be cloned too. * @returns {CKEDITOR.dom.node} Clone of this node. */ clone: function( includeChildren, cloneId ) { var $clone = this.$.cloneNode( includeChildren ); var removeIds = function( node ) { // Reset data-cke-expando only when has been cloned (IE and only for some types of objects). if ( node['data-cke-expando'] ) node['data-cke-expando'] = false; if ( node.nodeType != CKEDITOR.NODE_ELEMENT ) return; if ( !cloneId ) node.removeAttribute( 'id', false ); if ( includeChildren ) { var childs = node.childNodes; for ( var i = 0; i < childs.length; i++ ) removeIds( childs[ i ] ); } }; // The "id" attribute should never be cloned to avoid duplication. removeIds( $clone ); return new CKEDITOR.dom.node( $clone ); }, /** * Check if node is preceded by any sibling. * * @returns {Boolean} */ hasPrevious: function() { return !!this.$.previousSibling; }, /** * Check if node is succeeded by any sibling. * * @returns {Boolean} */ hasNext: function() { return !!this.$.nextSibling; }, /** * Inserts this element after a node. * * var em = new CKEDITOR.dom.element( 'em' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.insertAfter( em ); * * // Result: '' * * @param {CKEDITOR.dom.node} node The node that will precede this element. * @returns {CKEDITOR.dom.node} The node preceding this one after insertion. */ insertAfter: function( node ) { node.$.parentNode.insertBefore( this.$, node.$.nextSibling ); return node; }, /** * Inserts this element before a node. * * var em = new CKEDITOR.dom.element( 'em' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.insertBefore( em ); * * // result: '' * * @param {CKEDITOR.dom.node} node The node that will succeed this element. * @returns {CKEDITOR.dom.node} The node being inserted. */ insertBefore: function( node ) { node.$.parentNode.insertBefore( this.$, node.$ ); return node; }, /** * Inserts node before this node. * * var em = new CKEDITOR.dom.element( 'em' ); * var strong = new CKEDITOR.dom.element( 'strong' ); * strong.insertBeforeMe( em ); * * // result: '' * * @param {CKEDITOR.dom.node} node The node that will preceed this element. * @returns {CKEDITOR.dom.node} The node being inserted. */ insertBeforeMe: function( node ) { this.$.parentNode.insertBefore( node.$, this.$ ); return node; }, /** * Retrieves a uniquely identifiable tree address for this node. * The tree address returned is an array of integers, with each integer * indicating a child index of a DOM node, starting from * `document.documentElement`. * * For example, assuming `` is the second child * of `` (`` being the first), * and we would like to address the third child under the * fourth child of ``, the tree address returned would be: * `[1, 3, 2]`. * * The tree address cannot be used for finding back the DOM tree node once * the DOM tree structure has been modified. * * @param {Boolean} [normalized=false] See {@link #getIndex}. * @returns {Array} The address. */ getAddress: function( normalized ) { var address = []; var $documentElement = this.getDocument().$.documentElement; var node = this.$; while ( node && node != $documentElement ) { var parentNode = node.parentNode; if ( parentNode ) { // Get the node index. For performance, call getIndex // directly, instead of creating a new node object. address.unshift( this.getIndex.call({ $: node }, normalized ) ); } node = parentNode; } return address; }, /** * Gets the document containing this element. * * var element = CKEDITOR.document.getById( 'example' ); * alert( element.getDocument().equals( CKEDITOR.document ) ); // true * * @returns {CKEDITOR.dom.document} The document. */ getDocument: function() { return new CKEDITOR.dom.document( this.$.ownerDocument || this.$.parentNode.ownerDocument ); }, /** * Get index of a node in an array of its parent.childNodes. * * Let's assume having childNodes array: * * [ emptyText, element1, text, text, element2 ] * element1.getIndex(); // 1 * element1.getIndex( true ); // 0 * element2.getIndex(); // 4 * element2.getIndex( true ); // 2 * * @param {Boolean} normalized When `true` empty text nodes and one followed * by another one text node are not counted in. * @returns {Number} Index of a node. */ getIndex: function( normalized ) { // Attention: getAddress depends on this.$ // getIndex is called on a plain object: { $ : node } var current = this.$, index = -1, isNormalizing; if ( !this.$.parentNode ) return index; do { // Bypass blank node and adjacent text nodes. if ( normalized && current != this.$ && current.nodeType == CKEDITOR.NODE_TEXT && ( isNormalizing || !current.nodeValue ) ) { continue; } index++; isNormalizing = current.nodeType == CKEDITOR.NODE_TEXT; } while ( ( current = current.previousSibling ) ) return index; }, /** * @todo */ getNextSourceNode: function( startFromSibling, nodeType, guard ) { // If "guard" is a node, transform it in a function. if ( guard && !guard.call ) { var guardNode = guard; guard = function( node ) { return !node.equals( guardNode ); }; } var node = ( !startFromSibling && this.getFirst && this.getFirst() ), parent; // Guarding when we're skipping the current element( no children or 'startFromSibling' ). // send the 'moving out' signal even we don't actually dive into. if ( !node ) { if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) return null; node = this.getNext(); } while ( !node && ( parent = ( parent || this ).getParent() ) ) { // The guard check sends the "true" paramenter to indicate that // we are moving "out" of the element. if ( guard && guard( parent, true ) === false ) return null; node = parent.getNext(); } if ( !node ) return null; if ( guard && guard( node ) === false ) return null; if ( nodeType && nodeType != node.type ) return node.getNextSourceNode( false, nodeType, guard ); return node; }, /** * @todo */ getPreviousSourceNode: function( startFromSibling, nodeType, guard ) { if ( guard && !guard.call ) { var guardNode = guard; guard = function( node ) { return !node.equals( guardNode ); }; } var node = ( !startFromSibling && this.getLast && this.getLast() ), parent; // Guarding when we're skipping the current element( no children or 'startFromSibling' ). // send the 'moving out' signal even we don't actually dive into. if ( !node ) { if ( this.type == CKEDITOR.NODE_ELEMENT && guard && guard( this, true ) === false ) return null; node = this.getPrevious(); } while ( !node && ( parent = ( parent || this ).getParent() ) ) { // The guard check sends the "true" paramenter to indicate that // we are moving "out" of the element. if ( guard && guard( parent, true ) === false ) return null; node = parent.getPrevious(); } if ( !node ) return null; if ( guard && guard( node ) === false ) return null; if ( nodeType && node.type != nodeType ) return node.getPreviousSourceNode( false, nodeType, guard ); return node; }, /** * Gets the node that preceed this element in its parent's child list. * * var element = CKEDITOR.dom.element.createFromHtml( '
    prevExample
    ' ); * var first = element.getLast().getPrev(); * alert( first.getName() ); // 'i' * * @param {Function} [evaluator] Filtering the result node. * @returns {CKEDITOR.dom.node} The previous node or null if not available. */ getPrevious: function( evaluator ) { var previous = this.$, retval; do { previous = previous.previousSibling; // Avoid returning the doc type node. // http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-412266927 retval = previous && previous.nodeType != 10 && new CKEDITOR.dom.node( previous ); } while ( retval && evaluator && !evaluator( retval ) ) return retval; }, /** * Gets the node that follows this element in its parent's child list. * * var element = CKEDITOR.dom.element.createFromHtml( '
    Examplenext
    ' ); * var last = element.getFirst().getNext(); * alert( last.getName() ); // 'i' * * @param {Function} [evaluator] Filtering the result node. * @returns {CKEDITOR.dom.node} The next node or null if not available. */ getNext: function( evaluator ) { var next = this.$, retval; do { next = next.nextSibling; retval = next && new CKEDITOR.dom.node( next ); } while ( retval && evaluator && !evaluator( retval ) ) return retval; }, /** * Gets the parent element for this node. * * var node = editor.document.getBody().getFirst(); * var parent = node.getParent(); * alert( node.getName() ); // 'body' * * @param {Boolean} [allowFragmentParent=false] Consider also parent node that is of * fragment type {@link CKEDITOR#NODE_DOCUMENT_FRAGMENT}. * @returns {CKEDITOR.dom.element} The parent element. */ getParent: function( allowFragmentParent ) { var parent = this.$.parentNode; return ( parent && ( parent.nodeType == CKEDITOR.NODE_ELEMENT || allowFragmentParent && parent.nodeType == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) ) ? new CKEDITOR.dom.node( parent ) : null; }, /** * @todo */ getParents: function( closerFirst ) { var node = this; var parents = []; do { parents[ closerFirst ? 'push' : 'unshift' ]( node ); } while ( ( node = node.getParent() ) ) return parents; }, /** * @todo */ getCommonAncestor: function( node ) { if ( node.equals( this ) ) return this; if ( node.contains && node.contains( this ) ) return node; var start = this.contains ? this : this.getParent(); do { if ( start.contains( node ) ) return start; } while ( ( start = start.getParent() ) ); return null; }, /** * @todo */ getPosition: function( otherNode ) { var $ = this.$; var $other = otherNode.$; if ( $.compareDocumentPosition ) return $.compareDocumentPosition( $other ); // IE and Safari have no support for compareDocumentPosition. if ( $ == $other ) return CKEDITOR.POSITION_IDENTICAL; // Only element nodes support contains and sourceIndex. if ( this.type == CKEDITOR.NODE_ELEMENT && otherNode.type == CKEDITOR.NODE_ELEMENT ) { if ( $.contains ) { if ( $.contains( $other ) ) return CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING; if ( $other.contains( $ ) ) return CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; } if ( 'sourceIndex' in $ ) { return ( $.sourceIndex < 0 || $other.sourceIndex < 0 ) ? CKEDITOR.POSITION_DISCONNECTED : ( $.sourceIndex < $other.sourceIndex ) ? CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING; } } // For nodes that don't support compareDocumentPosition, contains // or sourceIndex, their "address" is compared. var addressOfThis = this.getAddress(), addressOfOther = otherNode.getAddress(), minLevel = Math.min( addressOfThis.length, addressOfOther.length ); // Determinate preceed/follow relationship. for ( var i = 0; i <= minLevel - 1; i++ ) { if ( addressOfThis[ i ] != addressOfOther[ i ] ) { if ( i < minLevel ) { return addressOfThis[ i ] < addressOfOther[ i ] ? CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_FOLLOWING; } break; } } // Determinate contains/contained relationship. return ( addressOfThis.length < addressOfOther.length ) ? CKEDITOR.POSITION_CONTAINS + CKEDITOR.POSITION_PRECEDING : CKEDITOR.POSITION_IS_CONTAINED + CKEDITOR.POSITION_FOLLOWING; }, /** * Gets the closest ancestor node of this node, specified by its name. * * // Suppose we have the following HTML structure: * //

    Some text

    * // If node == * ascendant = node.getAscendant( 'div' ); // ascendant ==
    * ascendant = node.getAscendant( 'b' ); // ascendant == null * ascendant = node.getAscendant( 'b', true ); // ascendant == * ascendant = node.getAscendant( { div:1,p:1 } ); // Searches for the first 'div' or 'p': ascendant ==
    * * @since 3.6.1 * @param {String} reference The name of the ancestor node to search or * an object with the node names to search for. * @param {Boolean} [includeSelf] Whether to include the current * node in the search. * @returns {CKEDITOR.dom.node} The located ancestor node or null if not found. */ getAscendant: function( reference, includeSelf ) { var $ = this.$, name; if ( !includeSelf ) $ = $.parentNode; while ( $ ) { if ( $.nodeName && ( name = $.nodeName.toLowerCase(), ( typeof reference == 'string' ? name == reference : name in reference ) ) ) return new CKEDITOR.dom.node( $ ); $ = $.parentNode; } return null; }, /** * @todo */ hasAscendant: function( name, includeSelf ) { var $ = this.$; if ( !includeSelf ) $ = $.parentNode; while ( $ ) { if ( $.nodeName && $.nodeName.toLowerCase() == name ) return true; $ = $.parentNode; } return false; }, /** * @todo */ move: function( target, toStart ) { target.append( this.remove(), toStart ); }, /** * Removes this node from the document DOM. * * var element = CKEDITOR.document.getById( 'MyElement' ); * element.remove(); * * @param {Boolean} [preserveChildren=false] Indicates that the children * elements must remain in the document, removing only the outer tags. */ remove: function( preserveChildren ) { var $ = this.$; var parent = $.parentNode; if ( parent ) { if ( preserveChildren ) { // Move all children before the node. for ( var child; ( child = $.firstChild ); ) { parent.insertBefore( $.removeChild( child ), $ ); } } parent.removeChild( $ ); } return this; }, /** * @todo */ replace: function( nodeToReplace ) { this.insertBefore( nodeToReplace ); nodeToReplace.remove(); }, /** * @todo */ trim: function() { this.ltrim(); this.rtrim(); }, /** * @todo */ ltrim: function() { var child; while ( this.getFirst && ( child = this.getFirst() ) ) { if ( child.type == CKEDITOR.NODE_TEXT ) { var trimmed = CKEDITOR.tools.ltrim( child.getText() ), originalLength = child.getLength(); if ( !trimmed ) { child.remove(); continue; } else if ( trimmed.length < originalLength ) { child.split( originalLength - trimmed.length ); // IE BUG: child.remove() may raise JavaScript errors here. (#81) this.$.removeChild( this.$.firstChild ); } } break; } }, /** * @todo */ rtrim: function() { var child; while ( this.getLast && ( child = this.getLast() ) ) { if ( child.type == CKEDITOR.NODE_TEXT ) { var trimmed = CKEDITOR.tools.rtrim( child.getText() ), originalLength = child.getLength(); if ( !trimmed ) { child.remove(); continue; } else if ( trimmed.length < originalLength ) { child.split( trimmed.length ); // IE BUG: child.getNext().remove() may raise JavaScript errors here. // (#81) this.$.lastChild.parentNode.removeChild( this.$.lastChild ); } } break; } if ( !CKEDITOR.env.ie && !CKEDITOR.env.opera ) { child = this.$.lastChild; if ( child && child.type == 1 && child.nodeName.toLowerCase() == 'br' ) { // Use "eChildNode.parentNode" instead of "node" to avoid IE bug (#324). child.parentNode.removeChild( child ); } } }, /** * Checks if this node is read-only (should not be changed). * * **Note:** When `attributeCheck` is not used, this method only work for elements * that are already presented in the document, otherwise the result * is not guaranteed, it's mainly for performance consideration. * * // For the following HTML: * //
    Some text
    * * // If "ele" is the above
    * element.isReadOnly(); // true * * @since 3.5 * @returns {Boolean} */ isReadOnly: function() { var element = this; if ( this.type != CKEDITOR.NODE_ELEMENT ) element = this.getParent(); if ( element && typeof element.$.isContentEditable != 'undefined' ) return !( element.$.isContentEditable || element.data( 'cke-editable' ) ); else { // Degrade for old browsers which don't support "isContentEditable", e.g. FF3 while ( element ) { if ( element.data( 'cke-editable' ) ) break; if ( element.getAttribute( 'contentEditable' ) == 'false' ) return true; else if ( element.getAttribute( 'contentEditable' ) == 'true' ) break; element = element.getParent(); } // Reached the root of DOM tree, no editable found. return !element; } } }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom.document} class, which * represents a DOM document. */ /** * Represents a DOM window. * * var document = new CKEDITOR.dom.window( window ); * * @class * @extends CKEDITOR.dom.domObject * @constructor Creates a window class instance. * @param {Object} domWindow A native DOM window. */ CKEDITOR.dom.window = function( domWindow ) { CKEDITOR.dom.domObject.call( this, domWindow ); }; CKEDITOR.dom.window.prototype = new CKEDITOR.dom.domObject(); CKEDITOR.tools.extend( CKEDITOR.dom.window.prototype, { /** * Moves the selection focus to this window. * * var win = new CKEDITOR.dom.window( window ); * win.focus(); */ focus: function() { this.$.focus(); }, /** * Gets the width and height of this window's viewable area. * * var win = new CKEDITOR.dom.window( window ); * var size = win.getViewPaneSize(); * alert( size.width ); * alert( size.height ); * * @returns {Object} An object with the `width` and `height` * properties containing the size. */ getViewPaneSize: function() { var doc = this.$.document, stdMode = doc.compatMode == 'CSS1Compat'; return { width: ( stdMode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0, height: ( stdMode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0 }; }, /** * Gets the current position of the window's scroll. * * var win = new CKEDITOR.dom.window( window ); * var pos = win.getScrollPosition(); * alert( pos.x ); * alert( pos.y ); * * @returns {Object} An object with the `x` and `y` properties * containing the scroll position. */ getScrollPosition: function() { var $ = this.$; if ( 'pageXOffset' in $ ) { return { x: $.pageXOffset || 0, y: $.pageYOffset || 0 }; } else { var doc = $.document; return { x: doc.documentElement.scrollLeft || doc.body.scrollLeft || 0, y: doc.documentElement.scrollTop || doc.body.scrollTop || 0 }; } }, /** * Gets the frame element containing this window context. * * @returns {CKEDITOR.dom.element} The frame element or `null` if not in a frame context. */ getFrame: function() { var iframe = this.$.frameElement; return iframe ? new CKEDITOR.dom.element.get( iframe ) : null; } }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom.document} class, which * represents a DOM document. */ /** * Represents a DOM document. * * var document = new CKEDITOR.dom.document( document ); * * @class * @extends CKEDITOR.dom.domObject * @constructor Creates a document class instance. * @param {Object} domDocument A native DOM document. */ CKEDITOR.dom.document = function( domDocument ) { CKEDITOR.dom.domObject.call( this, domDocument ); }; // PACKAGER_RENAME( CKEDITOR.dom.document ) CKEDITOR.dom.document.prototype = new CKEDITOR.dom.domObject(); CKEDITOR.tools.extend( CKEDITOR.dom.document.prototype, { /** * The node type. This is a constant value set to {@link CKEDITOR#NODE_DOCUMENT}. * * @readonly * @property {Number} [=CKEDITOR.NODE_DOCUMENT] */ type: CKEDITOR.NODE_DOCUMENT, /** * Appends a CSS file to the document. * * CKEDITOR.document.appendStyleSheet( '/mystyles.css' ); * * @param {String} cssFileUrl The CSS file URL. */ appendStyleSheet: function( cssFileUrl ) { if ( this.$.createStyleSheet ) this.$.createStyleSheet( cssFileUrl ); else { var link = new CKEDITOR.dom.element( 'link' ); link.setAttributes({ rel: 'stylesheet', type: 'text/css', href: cssFileUrl }); this.getHead().append( link ); } }, /** * Creates a CSS style sheet and inserts it into the document. * * @param cssStyleText {String} CSS style text. * @returns {Object} The created DOM native style sheet object. */ appendStyleText: function( cssStyleText ) { if ( this.$.createStyleSheet ) { var styleSheet = this.$.createStyleSheet( "" ); styleSheet.cssText = cssStyleText; } else { var style = new CKEDITOR.dom.element( 'style', this ); style.append( new CKEDITOR.dom.text( cssStyleText, this ) ); this.getHead().append( style ); } return styleSheet || style.$.sheet; }, /** * Creates {@link CKEDITOR.dom.element} instance in this document. * * @returns {CKEDITOR.dom.element} * @todo */ createElement: function( name, attribsAndStyles ) { var element = new CKEDITOR.dom.element( name, this ); if ( attribsAndStyles ) { if ( attribsAndStyles.attributes ) element.setAttributes( attribsAndStyles.attributes ); if ( attribsAndStyles.styles ) element.setStyles( attribsAndStyles.styles ); } return element; }, /** * Creates {@link CKEDITOR.dom.text} instance in this document. * * @param {String} text Value of the text node. * @returns {CKEDITOR.dom.element} */ createText: function( text ) { return new CKEDITOR.dom.text( text, this ); }, /** * Moves the selection focus to this document's window. */ focus: function() { this.getWindow().focus(); }, /** * Returns the element that is currently designated as the active element in the document. * * **Note:** Only one element can be active at a time in a document. * An active element does not necessarily have focus, * but an element with focus is always the active element in a document. * * @returns {CKEDITOR.dom.element} */ getActive: function() { return new CKEDITOR.dom.element( this.$.activeElement ); }, /** * Gets an element based on its id. * * var element = CKEDITOR.document.getById( 'myElement' ); * alert( element.getId() ); // 'myElement' * * @param {String} elementId The element id. * @returns {CKEDITOR.dom.element} The element instance, or null if not found. */ getById: function( elementId ) { var $ = this.$.getElementById( elementId ); return $ ? new CKEDITOR.dom.element( $ ) : null; }, /** * Gets a node based on its address. See {@link CKEDITOR.dom.node#getAddress}. * * @param {Array} address * @param {Boolean} [normalized=false] */ getByAddress: function( address, normalized ) { var $ = this.$.documentElement; for ( var i = 0; $ && i < address.length; i++ ) { var target = address[ i ]; if ( !normalized ) { $ = $.childNodes[ target ]; continue; } var currentIndex = -1; for ( var j = 0; j < $.childNodes.length; j++ ) { var candidate = $.childNodes[ j ]; if ( normalized === true && candidate.nodeType == 3 && candidate.previousSibling && candidate.previousSibling.nodeType == 3 ) { continue; } currentIndex++; if ( currentIndex == target ) { $ = candidate; break; } } } return $ ? new CKEDITOR.dom.node( $ ) : null; }, /** * Gets elements list based on given tag name. * * @param {String} tagName The element tag name. * @returns {CKEDITOR.dom.nodeList} The nodes list. */ getElementsByTag: function( tagName, namespace ) { if ( !( CKEDITOR.env.ie && !( document.documentMode > 8 ) ) && namespace ) tagName = namespace + ':' + tagName; return new CKEDITOR.dom.nodeList( this.$.getElementsByTagName( tagName ) ); }, /** * Gets the `` element for this document. * * var element = CKEDITOR.document.getHead(); * alert( element.getName() ); // 'head' * * @returns {CKEDITOR.dom.element} The `` element. */ getHead: function() { var head = this.$.getElementsByTagName( 'head' )[ 0 ]; if ( !head ) head = this.getDocumentElement().append( new CKEDITOR.dom.element( 'head' ), true ); else head = new CKEDITOR.dom.element( head ); return head; }, /** * Gets the `` element for this document. * * var element = CKEDITOR.document.getBody(); * alert( element.getName() ); // 'body' * * @returns {CKEDITOR.dom.element} The `` element. */ getBody: function() { return new CKEDITOR.dom.element( this.$.body ); }, /** * Gets the DOM document element for this document. * * @returns {CKEDITOR.dom.element} The DOM document element. */ getDocumentElement: function() { return new CKEDITOR.dom.element( this.$.documentElement ); }, /** * Gets the window object that holds this document. * * @returns {CKEDITOR.dom.window} The window object. */ getWindow: function() { var win = new CKEDITOR.dom.window( this.$.parentWindow || this.$.defaultView ); return ( this.getWindow = function() { return win; })(); }, /** * Defines the document contents through document.write. Note that the * previous document contents will be lost (cleaned). * * document.write( * '' + * 'Sample Doc' + * 'Document contents created by code' + * '' * ); * * @since 3.5 * @param {String} html The HTML defining the document contents. */ write: function( html ) { // Don't leave any history log in IE. (#5657) this.$.open( 'text/html', 'replace' ); // Support for custom document.domain in IE. CKEDITOR.env.isCustomDomain() && ( this.$.domain = document.domain ); this.$.write( html ); this.$.close(); } }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * Represents a list of {@link CKEDITOR.dom.node} objects. * It's a wrapper for native nodes list. * * var nodeList = CKEDITOR.document.getBody().getChildren(); * alert( nodeList.count() ); // number [0;N] * * @class * @constructor Creates a document class instance. * @param {Object} nativeList */ CKEDITOR.dom.nodeList = function( nativeList ) { this.$ = nativeList; }; CKEDITOR.dom.nodeList.prototype = { /** * Get count of nodes in this list. * * @returns {Number} */ count: function() { return this.$.length; }, /** * Get node from the list. * * @returns {CKEDITOR.dom.node} */ getItem: function( index ) { if ( index < 0 || index >= this.$.length ) return null; var $node = this.$[ index ]; return $node ? new CKEDITOR.dom.node( $node ) : null; } }; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Defines the {@link CKEDITOR.dom.element} class, which * represents a DOM element. */ /** * Represents a DOM element. * * // Create a new element. * var element = new CKEDITOR.dom.element( 'span' ); * * // Create an element based on a native DOM element. * var element = new CKEDITOR.dom.element( document.getElementById( 'myId' ) ); * * @class * @extends CKEDITOR.dom.node * @constructor Creates an element class instance. * @param {Object/String} element A native DOM element or the element name for * new elements. * @param {CKEDITOR.dom.document} [ownerDocument] The document that will contain * the element in case of element creation. */ CKEDITOR.dom.element = function( element, ownerDocument ) { if ( typeof element == 'string' ) element = ( ownerDocument ? ownerDocument.$ : document ).createElement( element ); // Call the base constructor (we must not call CKEDITOR.dom.node). CKEDITOR.dom.domObject.call( this, element ); }; // PACKAGER_RENAME( CKEDITOR.dom.element ) /** * The the {@link CKEDITOR.dom.element} representing and element. If the * element is a native DOM element, it will be transformed into a valid * CKEDITOR.dom.element object. * * var element = new CKEDITOR.dom.element( 'span' ); * alert( element == CKEDITOR.dom.element.get( element ) ); // true * * var element = document.getElementById( 'myElement' ); * alert( CKEDITOR.dom.element.get( element ).getName() ); // (e.g.) 'p' * * @static * @param {String/Object} element Element's id or name or native DOM element. * @returns {CKEDITOR.dom.element} The transformed element. */ CKEDITOR.dom.element.get = function( element ) { var el = typeof element == 'string' ? document.getElementById( element ) || document.getElementsByName( element )[ 0 ] : element; return el && ( el.$ ? el : new CKEDITOR.dom.element( el ) ); }; CKEDITOR.dom.element.prototype = new CKEDITOR.dom.node(); /** * Creates an instance of the {@link CKEDITOR.dom.element} class based on the * HTML representation of an element. * * var element = CKEDITOR.dom.element.createFromHtml( 'My element' ); * alert( element.getName() ); // 'strong' * * @static * @param {String} html The element HTML. It should define only one element in * the "root" level. The "root" element can have child nodes, but not siblings. * @returns {CKEDITOR.dom.element} The element instance. */ CKEDITOR.dom.element.createFromHtml = function( html, ownerDocument ) { var temp = new CKEDITOR.dom.element( 'div', ownerDocument ); temp.setHtml( html ); // When returning the node, remove it from its parent to detach it. return temp.getFirst().remove(); }; /** * @static * @todo */ CKEDITOR.dom.element.setMarker = function( database, element, name, value ) { var id = element.getCustomData( 'list_marker_id' ) || ( element.setCustomData( 'list_marker_id', CKEDITOR.tools.getNextNumber() ).getCustomData( 'list_marker_id' ) ), markerNames = element.getCustomData( 'list_marker_names' ) || ( element.setCustomData( 'list_marker_names', {} ).getCustomData( 'list_marker_names' ) ); database[ id ] = element; markerNames[ name ] = 1; return element.setCustomData( name, value ); }; /** * @static * @todo */ CKEDITOR.dom.element.clearAllMarkers = function( database ) { for ( var i in database ) CKEDITOR.dom.element.clearMarkers( database, database[ i ], 1 ); }; /** * @static * @todo */ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatabase ) { var names = element.getCustomData( 'list_marker_names' ), id = element.getCustomData( 'list_marker_id' ); for ( var i in names ) element.removeCustomData( i ); element.removeCustomData( 'list_marker_names' ); if ( removeFromDatabase ) { element.removeCustomData( 'list_marker_id' ); delete database[ id ]; } }; ( function() { CKEDITOR.tools.extend( CKEDITOR.dom.element.prototype, { /** * The node type. This is a constant value set to {@link CKEDITOR#NODE_ELEMENT}. * * @readonly * @property {Number} [=CKEDITOR.NODE_ELEMENT] */ type: CKEDITOR.NODE_ELEMENT, /** * Adds a CSS class to the element. It appends the class to the * already existing names. * * var element = new CKEDITOR.dom.element( 'div' ); * element.addClass( 'classA' ); //
    * element.addClass( 'classB' ); //
    * element.addClass( 'classA' ); //
    * * @param {String} className The name of the class to be added. */ addClass: function( className ) { var c = this.$.className; if ( c ) { var regex = new RegExp( '(?:^|\\s)' + className + '(?:\\s|$)', '' ); if ( !regex.test( c ) ) c += ' ' + className; } this.$.className = c || className; }, /** * Removes a CSS class name from the elements classes. Other classes * remain untouched. * * var element = new CKEDITOR.dom.element( 'div' ); * element.addClass( 'classA' ); //
    * element.addClass( 'classB' ); //
    * element.removeClass( 'classA' ); //
    * element.removeClass( 'classB' ); //
    * * @chainable * @param {String} className The name of the class to remove. */ removeClass: function( className ) { var c = this.getAttribute( 'class' ); if ( c ) { var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', 'i' ); if ( regex.test( c ) ) { c = c.replace( regex, '' ).replace( /^\s+/, '' ); if ( c ) this.setAttribute( 'class', c ); else this.removeAttribute( 'class' ); } } return this; }, /** * Checks if element has class name. * * @param {String} className * @returns {Boolean} */ hasClass: function( className ) { var regex = new RegExp( '(?:^|\\s+)' + className + '(?=\\s|$)', '' ); return regex.test( this.getAttribute( 'class' ) ); }, /** * Append a node as a child of this element. * * var p = new CKEDITOR.dom.element( 'p' ); * * var strong = new CKEDITOR.dom.element( 'strong' ); * p.append( strong ); * * var em = p.append( 'em' ); * * // Result: '

    ' * * @param {CKEDITOR.dom.node/String} node The node or element name to be appended. * @param {Boolean} [toStart=false] Indicates that the element is to be appended at the start. * @returns {CKEDITOR.dom.node} The appended node. */ append: function( node, toStart ) { if ( typeof node == 'string' ) node = this.getDocument().createElement( node ); if ( toStart ) this.$.insertBefore( node.$, this.$.firstChild ); else this.$.appendChild( node.$ ); return node; }, /** * Append HTML as a child(ren) of this element. * * @param {String} html */ appendHtml: function( html ) { if ( !this.$.childNodes.length ) this.setHtml( html ); else { var temp = new CKEDITOR.dom.element( 'div', this.getDocument() ); temp.setHtml( html ); temp.moveChildren( this ); } }, /** * Append text to this element. * * var p = new CKEDITOR.dom.element( 'p' ); * p.appendText( 'This is' ); * p.appendText( ' some text' ); * * // Result: '

    This is some text

    ' * * @param {String} text The text to be appended. * @returns {CKEDITOR.dom.node} The appended node. */ appendText: function( text ) { if ( this.$.text != undefined ) this.$.text += text; else this.append( new CKEDITOR.dom.text( text ) ); }, /** * @todo */ appendBogus: function() { var lastChild = this.getLast(); // Ignore empty/spaces text. while ( lastChild && lastChild.type == CKEDITOR.NODE_TEXT && !CKEDITOR.tools.rtrim( lastChild.getText() ) ) lastChild = lastChild.getPrevious(); if ( !lastChild || !lastChild.is || !lastChild.is( 'br' ) ) { var bogus = CKEDITOR.env.opera ? this.getDocument().createText( '' ) : this.getDocument().createElement( 'br' ); CKEDITOR.env.gecko && bogus.setAttribute( 'type', '_moz' ); this.append( bogus ); } }, /** * Breaks one of the ancestor element in the element position, moving * this element between the broken parts. * * // Before breaking: * // This is some sample test text * // If "element" is and "parent" is : * // This is some sample test text * element.breakParent( parent ); * * // Before breaking: * // This is some sample test text * // If "element" is and "parent" is : * // This is some sample test text * element.breakParent( parent ); * * @param {CKEDITOR.dom.element} parent The anscestor element to get broken. */ breakParent: function( parent ) { var range = new CKEDITOR.dom.range( this.getDocument() ); // We'll be extracting part of this element, so let's use our // range to get the correct piece. range.setStartAfter( this ); range.setEndAfter( parent ); // Extract it. var docFrag = range.extractContents(); // Move the element outside the broken element. range.insertNode( this.remove() ); // Re-insert the extracted piece after the element. docFrag.insertAfterNode( this ); }, /** * Checks if this element contains given node. * * @method * @param {CKEDITOR.dom.node} node * @returns {Boolean} */ contains: CKEDITOR.env.ie || CKEDITOR.env.webkit ? function( node ) { var $ = this.$; return node.type != CKEDITOR.NODE_ELEMENT ? $.contains( node.getParent().$ ) : $ != node.$ && $.contains( node.$ ); } : function( node ) { return !!( this.$.compareDocumentPosition( node.$ ) & 16 ); }, /** * Moves the selection focus to this element. * * var element = CKEDITOR.document.getById( 'myTextarea' ); * element.focus(); * * @method * @param {Boolean} defer Whether to asynchronously defer the * execution by 100 ms. */ focus: (function() { function exec() { // IE throws error if the element is not visible. try { this.$.focus(); } catch ( e ) {} } return function( defer ) { if ( defer ) CKEDITOR.tools.setTimeout( exec, 100, this ); else exec.call( this ); }; })(), /** * Gets the inner HTML of this element. * * var element = CKEDITOR.dom.element.createFromHtml( '
    Example
    ' ); * alert( element.getHtml() ); // 'Example' * * @returns {String} The inner HTML of this element. */ getHtml: function() { var retval = this.$.innerHTML; // Strip tags in IE. (#3341). return CKEDITOR.env.ie ? retval.replace( /<\?[^>]*>/g, '' ) : retval; }, /** * Gets the outer (inner plus tags) HTML of this element. * * var element = CKEDITOR.dom.element.createFromHtml( '
    Example
    ' ); * alert( element.getOuterHtml() ); // '
    Example
    ' * * @returns {String} The outer HTML of this element. */ getOuterHtml: function() { if ( this.$.outerHTML ) { // IE includes the tag in the outerHTML of // namespaced element. So, we must strip it here. (#3341) return this.$.outerHTML.replace( /<\?[^>]*>/, '' ); } var tmpDiv = this.$.ownerDocument.createElement( 'div' ); tmpDiv.appendChild( this.$.cloneNode( true ) ); return tmpDiv.innerHTML; }, /** * Retrieve the bounding rectangle of the current element, in pixels, * relative to the upper-left corner of the browser's client area. * * @returns {Object} The dimensions of the DOM element including * `left`, `top`, `right`, `bottom`, `width` and `height`. */ getClientRect: function() { // http://help.dottoro.com/ljvmcrrn.php var rect = CKEDITOR.tools.extend( {}, this.$.getBoundingClientRect() ); !rect.width && ( rect.width = rect.right - rect.left ); !rect.height && ( rect.height = rect.bottom - rect.top ); return rect; }, /** * Sets the inner HTML of this element. * * var p = new CKEDITOR.dom.element( 'p' ); * p.setHtml( 'Inner HTML' ); * * // Result: '

    Inner HTML

    ' * * @method * @param {String} html The HTML to be set for this element. * @returns {String} The inserted HTML. */ setHtml: (function() { var standard = function( html ) { return ( this.$.innerHTML = html ); }; if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { // old IEs throws error on HTML manipulation (through the "innerHTML" property) // on the element which resides in an DTD invalid position, e.g.
    // fortunately it can be worked around with DOM manipulation. return function( html ) { try { return standard.call( this, html ); } catch ( e ) { this.$.innerHTML = ''; var temp = new CKEDITOR.dom.element( 'body', this.getDocument() ); temp.$.innerHTML = html; var children = temp.getChildren(); while( children.count() ) this.append( children.getItem( 0 ) ); return html; } }; } else return standard; })(), /** * Sets the element contents as plain text. * * var element = new CKEDITOR.dom.element( 'div' ); * element.setText( 'A > B & C < D' ); * alert( element.innerHTML ); // 'A > B & C < D' * * @param {String} text The text to be set. * @returns {String} The inserted text. */ setText: function( text ) { CKEDITOR.dom.element.prototype.setText = ( this.$.innerText != undefined ) ? function( text ) { return this.$.innerText = text; } : function( text ) { return this.$.textContent = text; }; return this.setText( text ); }, /** * Gets the value of an element attribute. * * var element = CKEDITOR.dom.element.createFromHtml( '' ); * alert( element.getAttribute( 'type' ) ); // 'text' * * @method * @param {String} name The attribute name. * @returns {String} The attribute value or null if not defined. */ getAttribute: (function() { var standard = function( name ) { return this.$.getAttribute( name, 2 ); }; if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ) { return function( name ) { switch ( name ) { case 'class': name = 'className'; break; case 'http-equiv': name = 'httpEquiv'; break; case 'name': return this.$.name; case 'tabindex': var tabIndex = standard.call( this, name ); // IE returns tabIndex=0 by default for all // elements. For those elements, // getAtrribute( 'tabindex', 2 ) returns 32768 // instead. So, we must make this check to give a // uniform result among all browsers. if ( tabIndex !== 0 && this.$.tabIndex === 0 ) tabIndex = null; return tabIndex; break; case 'checked': { var attr = this.$.attributes.getNamedItem( name ), attrValue = attr.specified ? attr.nodeValue // For value given by parser. : this.$.checked; // For value created via DOM interface. return attrValue ? 'checked' : null; } case 'hspace': case 'value': return this.$[ name ]; case 'style': // IE does not return inline styles via getAttribute(). See #2947. return this.$.style.cssText; case 'contenteditable': case 'contentEditable': return this.$.attributes.getNamedItem( 'contentEditable' ).specified ? this.$.getAttribute( 'contentEditable' ) : null; } return standard.call( this, name ); }; } else return standard; })(), /** * Gets the nodes list containing all children of this element. * * @returns {CKEDITOR.dom.nodeList} */ getChildren: function() { return new CKEDITOR.dom.nodeList( this.$.childNodes ); }, /** * Gets the current computed value of one of the element CSS style * properties. * * var element = new CKEDITOR.dom.element( 'span' ); * alert( element.getComputedStyle( 'display' ) ); // 'inline' * * @method * @param {String} propertyName The style property name. * @returns {String} The property value. */ getComputedStyle: CKEDITOR.env.ie ? function( propertyName ) { return this.$.currentStyle[ CKEDITOR.tools.cssStyleToDomStyle( propertyName ) ]; } : function( propertyName ) { var style = this.getWindow().$.getComputedStyle( this.$, null ); // Firefox may return null if we call the above on a hidden iframe. (#9117) return style ? style.getPropertyValue( propertyName ) : ''; }, /** * Gets the DTD entries for this element. * * @returns {Object} An object containing the list of elements accepted * by this element. */ getDtd: function() { var dtd = CKEDITOR.dtd[ this.getName() ]; this.getDtd = function() { return dtd; }; return dtd; }, /** * Gets all this element's descendants having given tag name. * * @method * @param {String} tagName */ getElementsByTag: CKEDITOR.dom.document.prototype.getElementsByTag, /** * Gets the computed tabindex for this element. * * var element = CKEDITOR.document.getById( 'myDiv' ); * alert( element.getTabIndex() ); // (e.g.) '-1' * * @method * @returns {Number} The tabindex value. */ getTabIndex: CKEDITOR.env.ie ? function() { var tabIndex = this.$.tabIndex; // IE returns tabIndex=0 by default for all elements. In // those cases we must check that the element really has // the tabindex attribute set to zero, or it is one of // those element that should have zero by default. if ( tabIndex === 0 && !CKEDITOR.dtd.$tabIndex[ this.getName() ] && parseInt( this.getAttribute( 'tabindex' ), 10 ) !== 0 ) tabIndex = -1; return tabIndex; } : CKEDITOR.env.webkit ? function() { var tabIndex = this.$.tabIndex; // Safari returns "undefined" for elements that should not // have tabindex (like a div). So, we must try to get it // from the attribute. // https://bugs.webkit.org/show_bug.cgi?id=20596 if ( tabIndex == undefined ) { tabIndex = parseInt( this.getAttribute( 'tabindex' ), 10 ); // If the element don't have the tabindex attribute, // then we should return -1. if ( isNaN( tabIndex ) ) tabIndex = -1; } return tabIndex; } : function() { return this.$.tabIndex; }, /** * Gets the text value of this element. * * Only in IE (which uses innerText), `
    ` will cause linebreaks, * and sucessive whitespaces (including line breaks) will be reduced to * a single space. This behavior is ok for us, for now. It may change * in the future. * * var element = CKEDITOR.dom.element.createFromHtml( '
    Sample text.
    ' ); * alert( element.getText() ); // 'Sample text.' * * @returns {String} The text value. */ getText: function() { return this.$.textContent || this.$.innerText || ''; }, /** * Gets the window object that contains this element. * * @returns {CKEDITOR.dom.window} The window object. */ getWindow: function() { return this.getDocument().getWindow(); }, /** * Gets the value of the `id` attribute of this element. * * var element = CKEDITOR.dom.element.createFromHtml( '

    ' ); * alert( element.getId() ); // 'myId' * * @returns {String} The element id, or null if not available. */ getId: function() { return this.$.id || null; }, /** * Gets the value of the `name` attribute of this element. * * var element = CKEDITOR.dom.element.createFromHtml( '' ); * alert( element.getNameAtt() ); // 'myName' * * @returns {String} The element name, or null if not available. */ getNameAtt: function() { return this.$.name || null; }, /** * Gets the element name (tag name). The returned name is guaranteed to * be always full lowercased. * * var element = new CKEDITOR.dom.element( 'span' ); * alert( element.getName() ); // 'span' * * @returns {String} The element name. */ getName: function() { // Cache the lowercased name inside a closure. var nodeName = this.$.nodeName.toLowerCase(); if ( CKEDITOR.env.ie && !( document.documentMode > 8 ) ) { var scopeName = this.$.scopeName; if ( scopeName != 'HTML' ) nodeName = scopeName.toLowerCase() + ':' + nodeName; } return ( this.getName = function() { return nodeName; })(); }, /** * Gets the value set to this element. This value is usually available * for form field elements. * * @returns {String} The element value. */ getValue: function() { return this.$.value; }, /** * Gets the first child node of this element. * * var element = CKEDITOR.dom.element.createFromHtml( '
    Example
    ' ); * var first = element.getFirst(); * alert( first.getName() ); // 'b' * * @param {Function} evaluator Filtering the result node. * @returns {CKEDITOR.dom.node} The first child node or null if not available. */ getFirst: function( evaluator ) { var first = this.$.firstChild, retval = first && new CKEDITOR.dom.node( first ); if ( retval && evaluator && !evaluator( retval ) ) retval = retval.getNext( evaluator ); return retval; }, /** * See {@link #getFirst}. * * @param {Function} evaluator Filtering the result node. * @retunrs {CKEDITOR.dom.node} */ getLast: function( evaluator ) { var last = this.$.lastChild, retval = last && new CKEDITOR.dom.node( last ); if ( retval && evaluator && !evaluator( retval ) ) retval = retval.getPrevious( evaluator ); return retval; }, /** * Gets CSS style value. * * @param {String} name The CSS property name. * @returns {String} Style value. */ getStyle: function( name ) { return this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ]; }, /** * Checks if the element name matches the specified criteria. * * var element = new CKEDITOR.element( 'span' ); * alert( element.is( 'span' ) ); // true * alert( element.is( 'p', 'span' ) ); // true * alert( element.is( 'p' ) ); // false * alert( element.is( 'p', 'div' ) ); // false * alert( element.is( { p:1,span:1 } ) ); // true * * @param {String.../Object} name One or more names to be checked, or a {@link CKEDITOR.dtd} object. * @returns {Boolean} `true` if the element name matches any of the names. */ is: function() { var name = this.getName(); // Check against the specified DTD liternal. if ( typeof arguments[ 0 ] == 'object' ) return !!arguments[ 0 ][ name ]; // Check for tag names for ( var i = 0; i < arguments.length; i++ ) { if ( arguments[ i ] == name ) return true; } return false; }, /** * Decide whether one element is able to receive cursor. * * @param {Boolean} [textCursor=true] Only consider element that could receive text child. */ isEditable: function( textCursor ) { var name = this.getName(); if ( this.isReadOnly() || this.getComputedStyle( 'display' ) == 'none' || this.getComputedStyle( 'visibility' ) == 'hidden' || CKEDITOR.dtd.$nonEditable[ name ] || CKEDITOR.dtd.$empty[ name ] || ( this.is( 'a' ) && ( this.data( 'cke-saved-name' ) || this.hasAttribute( 'name' ) ) && !this.getChildCount() ) ) { return false; } if ( textCursor !== false ) { // Get the element DTD (defaults to span for unknown elements). var dtd = CKEDITOR.dtd[ name ] || CKEDITOR.dtd.span; // In the DTD # == text node. return !!( dtd && dtd[ '#' ] ); } return true; }, /** * Compare this element's inner html, tag name, attributes, etc. with other one. * * See [W3C's DOM Level 3 spec - node#isEqualNode](http://www.w3.org/TR/DOM-Level-3-Core/core.html#Node3-isEqualNode) * for more details. * * @param {CKEDITOR.dom.element} otherElement Element to compare. * @returns {Boolean} */ isIdentical: function( otherElement ) { // do shallow clones, but with IDs var thisEl = this.clone( 0, 1 ), otherEl = otherElement.clone( 0, 1 ); // Remove distractions. thisEl.removeAttributes( [ '_moz_dirty', 'data-cke-expando', 'data-cke-saved-href', 'data-cke-saved-name' ] ); otherEl.removeAttributes( [ '_moz_dirty', 'data-cke-expando', 'data-cke-saved-href', 'data-cke-saved-name' ] ); // Native comparison available. if ( thisEl.$.isEqualNode ) { // Styles order matters. thisEl.$.style.cssText = CKEDITOR.tools.normalizeCssText( thisEl.$.style.cssText ); otherEl.$.style.cssText = CKEDITOR.tools.normalizeCssText( otherEl.$.style.cssText ); return thisEl.$.isEqualNode( otherEl.$ ); } else { thisEl = thisEl.getOuterHtml(); otherEl = otherEl.getOuterHtml(); // Fix tiny difference between link href in older IEs. if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && this.is( 'a' ) ) { var parent = this.getParent(); if ( parent.type == CKEDITOR.NODE_ELEMENT ) { var el = parent.clone(); el.setHtml( thisEl ), thisEl = el.getHtml(); el.setHtml( otherEl ), otherEl = el.getHtml(); } } return thisEl == otherEl; } }, /** * Checks if this element is visible. May not work if the element is * child of an element with visibility set to `hidden`, but works well * on the great majority of cases. * * @returns {Boolean} True if the element is visible. */ isVisible: function() { var isVisible = ( this.$.offsetHeight || this.$.offsetWidth ) && this.getComputedStyle( 'visibility' ) != 'hidden', elementWindow, elementWindowFrame; // Webkit and Opera report non-zero offsetHeight despite that // element is inside an invisible iframe. (#4542) if ( isVisible && ( CKEDITOR.env.webkit || CKEDITOR.env.opera ) ) { elementWindow = this.getWindow(); if ( !elementWindow.equals( CKEDITOR.document.getWindow() ) && ( elementWindowFrame = elementWindow.$.frameElement ) ) { isVisible = new CKEDITOR.dom.element( elementWindowFrame ).isVisible(); } } return !!isVisible; }, /** * Whether it's an empty inline elements which has no visual impact when removed. * * @returns {Boolean} */ isEmptyInlineRemoveable: function() { if ( !CKEDITOR.dtd.$removeEmpty[ this.getName() ] ) return false; var children = this.getChildren(); for ( var i = 0, count = children.count(); i < count; i++ ) { var child = children.getItem( i ); if ( child.type == CKEDITOR.NODE_ELEMENT && child.data( 'cke-bookmark' ) ) continue; if ( child.type == CKEDITOR.NODE_ELEMENT && !child.isEmptyInlineRemoveable() || child.type == CKEDITOR.NODE_TEXT && CKEDITOR.tools.trim( child.getText() ) ) { return false; } } return true; }, /** * Checks if the element has any defined attributes. * * var element = CKEDITOR.dom.element.createFromHtml( '
    Example
    ' ); * alert( element.hasAttributes() ); // true * * var element = CKEDITOR.dom.element.createFromHtml( '
    Example
    ' ); * alert( element.hasAttributes() ); // false * * @method * @returns {Boolean} True if the element has attributes. */ hasAttributes: CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ? function() { var attributes = this.$.attributes; for ( var i = 0; i < attributes.length; i++ ) { var attribute = attributes[ i ]; switch ( attribute.nodeName ) { case 'class': // IE has a strange bug. If calling removeAttribute('className'), // the attributes collection will still contain the "class" // attribute, which will be marked as "specified", even if the // outerHTML of the element is not displaying the class attribute. // Note : I was not able to reproduce it outside the editor, // but I've faced it while working on the TC of #1391. if ( this.getAttribute( 'class' ) ) return true; // Attributes to be ignored. case 'data-cke-expando': continue; /*jsl:fallthru*/ default: if ( attribute.specified ) return true; } } return false; } : function() { var attrs = this.$.attributes, attrsNum = attrs.length; // The _moz_dirty attribute might get into the element after pasting (#5455) var execludeAttrs = { 'data-cke-expando':1,_moz_dirty:1 }; return attrsNum > 0 && ( attrsNum > 2 || !execludeAttrs[ attrs[ 0 ].nodeName ] || ( attrsNum == 2 && !execludeAttrs[ attrs[ 1 ].nodeName ] ) ); }, /** * Checks if the specified attribute is defined for this element. * * @method * @param {String} name The attribute name. * @returns {Boolean} `true` if the specified attribute is defined. */ hasAttribute: (function() { function standard( name ) { var $attr = this.$.attributes.getNamedItem( name ); return !!( $attr && $attr.specified ); } return ( CKEDITOR.env.ie && CKEDITOR.env.version < 8 ) ? function( name ) { // On IE < 8 the name attribute cannot be retrieved // right after the element creation and setting the // name with setAttribute. if ( name == 'name' ) return !!this.$.name; return standard.call( this, name ); } : standard; })(), /** * Hides this element (sets `display: none`). * * var element = CKEDITOR.document.getById( 'myElement' ); * element.hide(); */ hide: function() { this.setStyle( 'display', 'none' ); }, /** * Moves this element's children to the target element. * * @param {CKEDITOR.dom.element} target * @param {Boolean} [toStart=false] Insert moved children at the * beginning of the target element. */ moveChildren: function( target, toStart ) { var $ = this.$; target = target.$; if ( $ == target ) return; var child; if ( toStart ) { while ( ( child = $.lastChild ) ) target.insertBefore( $.removeChild( child ), target.firstChild ); } else { while ( ( child = $.firstChild ) ) target.appendChild( $.removeChild( child ) ); } }, /** * Merges sibling elements that are identical to this one. * * Identical child elements are also merged. For example: * * => * * @method * @param {Boolean} [inlineOnly=true] Allow only inline elements to be merged. */ mergeSiblings: (function() { function mergeElements( element, sibling, isNext ) { if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT ) { // Jumping over bookmark nodes and empty inline elements, e.g. , // queuing them to be moved later. (#5567) var pendingNodes = []; while ( sibling.data( 'cke-bookmark' ) || sibling.isEmptyInlineRemoveable() ) { pendingNodes.push( sibling ); sibling = isNext ? sibling.getNext() : sibling.getPrevious(); if ( !sibling || sibling.type != CKEDITOR.NODE_ELEMENT ) return; } if ( element.isIdentical( sibling ) ) { // Save the last child to be checked too, to merge things like // => var innerSibling = isNext ? element.getLast() : element.getFirst(); // Move pending nodes first into the target element. while ( pendingNodes.length ) pendingNodes.shift().move( element, !isNext ); sibling.moveChildren( element, !isNext ); sibling.remove(); // Now check the last inner child (see two comments above). if ( innerSibling && innerSibling.type == CKEDITOR.NODE_ELEMENT ) innerSibling.mergeSiblings(); } } } return function( inlineOnly ) { if ( !( inlineOnly === false || CKEDITOR.dtd.$removeEmpty[ this.getName() ] || this.is( 'a' ) ) ) // Merge empty links and anchors also. (#5567) { return; } mergeElements( this, this.getNext(), true ); mergeElements( this, this.getPrevious() ); }; })(), /** * Shows this element (displays it). * * var element = CKEDITOR.document.getById( 'myElement' ); * element.show(); */ show: function() { this.setStyles({ display: '', visibility: '' }); }, /** * Sets the value of an element attribute. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.setAttribute( 'class', 'myClass' ); * element.setAttribute( 'title', 'This is an example' ); * * @method * @param {String} name The name of the attribute. * @param {String} value The value to be set to the attribute. * @returns {CKEDITOR.dom.element} This element instance. */ setAttribute: (function() { var standard = function( name, value ) { this.$.setAttribute( name, value ); return this; }; if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ) { return function( name, value ) { if ( name == 'class' ) this.$.className = value; else if ( name == 'style' ) this.$.style.cssText = value; else if ( name == 'tabindex' ) // Case sensitive. this.$.tabIndex = value; else if ( name == 'checked' ) this.$.checked = value; else if ( name == 'contenteditable' ) standard.call( this, 'contentEditable', value ); else standard.apply( this, arguments ); return this; }; } else if ( CKEDITOR.env.ie8Compat && CKEDITOR.env.secure ) { return function( name, value ) { // IE8 throws error when setting src attribute to non-ssl value. (#7847) if ( name == 'src' && value.match( /^http:\/\// ) ) try { standard.apply( this, arguments ); } catch ( e ) {} else standard.apply( this, arguments ); return this; }; } else return standard; })(), /** * Sets the value of several element attributes. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.setAttributes( { * 'class': 'myClass', * title: 'This is an example' * } ); * * @chainable * @param {Object} attributesPairs An object containing the names and * values of the attributes. * @returns {CKEDITOR.dom.element} This element instance. */ setAttributes: function( attributesPairs ) { for ( var name in attributesPairs ) this.setAttribute( name, attributesPairs[ name ] ); return this; }, /** * Sets the element value. This function is usually used with form * field element. * * @chainable * @param {String} value The element value. * @returns {CKEDITOR.dom.element} This element instance. */ setValue: function( value ) { this.$.value = value; return this; }, /** * Removes an attribute from the element. * * var element = CKEDITOR.dom.element.createFromHtml( '
    ' ); * element.removeAttribute( 'class' ); * * @method * @param {String} name The attribute name. */ removeAttribute: (function() { var standard = function( name ) { this.$.removeAttribute( name ); }; if ( CKEDITOR.env.ie && ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) ) { return function( name ) { if ( name == 'class' ) name = 'className'; else if ( name == 'tabindex' ) name = 'tabIndex'; else if ( name == 'contenteditable' ) name = 'contentEditable'; standard.call( this, name ); }; } else return standard; })(), /** * Removes all element's attributes or just given ones. * * @param {Array} [attributes] The array with attributes names. */ removeAttributes: function( attributes ) { if ( CKEDITOR.tools.isArray( attributes ) ) { for ( var i = 0; i < attributes.length; i++ ) this.removeAttribute( attributes[ i ] ); } else { for ( var attr in attributes ) attributes.hasOwnProperty( attr ) && this.removeAttribute( attr ); } }, /** * Removes a style from the element. * * var element = CKEDITOR.dom.element.createFromHtml( '
    ' ); * element.removeStyle( 'display' ); * * @method * @param {String} name The style name. */ removeStyle: function( name ) { // Removes the specified property from the current style object. var $ = this.$.style; // "removeProperty" need to be specific on the following styles. if ( !$.removeProperty && ( name == 'border' || name == 'margin' || name == 'padding' ) ) { var names = expandedRules( name ); for ( var i = 0 ; i < names.length ; i++ ) this.removeStyle( names[ i ] ); return; } $.removeProperty ? $.removeProperty( name ) : $.removeAttribute( CKEDITOR.tools.cssStyleToDomStyle( name ) ); // Eventually remove empty style attribute. if ( !this.$.style.cssText ) this.removeAttribute( 'style' ); }, /** * Sets the value of an element style. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.setStyle( 'background-color', '#ff0000' ); * element.setStyle( 'margin-top', '10px' ); * element.setStyle( 'float', 'right' ); * * @param {String} name The name of the style. The CSS naming notation * must be used (e.g. `background-color`). * @param {String} value The value to be set to the style. * @returns {CKEDITOR.dom.element} This element instance. */ setStyle: function( name, value ) { this.$.style[ CKEDITOR.tools.cssStyleToDomStyle( name ) ] = value; return this; }, /** * Sets the value of several element styles. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.setStyles( { * position: 'absolute', * float: 'right' * } ); * * @param {Object} stylesPairs An object containing the names and * values of the styles. * @returns {CKEDITOR.dom.element} This element instance. */ setStyles: function( stylesPairs ) { for ( var name in stylesPairs ) this.setStyle( name, stylesPairs[ name ] ); return this; }, /** * Sets the opacity of an element. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.setOpacity( 0.75 ); * * @param {Number} opacity A number within the range `[0.0, 1.0]`. */ setOpacity: function( opacity ) { if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { opacity = Math.round( opacity * 100 ); this.setStyle( 'filter', opacity >= 100 ? '' : 'progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')' ); } else this.setStyle( 'opacity', opacity ); }, /** * Makes the element and its children unselectable. * * var element = CKEDITOR.document.getById( 'myElement' ); * element.unselectable(); * * @method */ unselectable: function() { // CSS unselectable. this.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'none' ) ); // For IE/Opera which doesn't support for the above CSS style, // the unselectable="on" attribute only specifies the selection // process cannot start in the element itself, and it doesn't inherit. if ( CKEDITOR.env.ie || CKEDITOR.env.opera ) { this.setAttribute( 'unselectable', 'on' ); var element, elements = this.getElementsByTag( "*" ); for ( var i = 0, count = elements.count() ; i < count ; i++ ) { element = elements.getItem( i ); element.setAttribute( 'unselectable', 'on' ); } } }, /** * Gets closest positioned (`position != static`) ancestor. * * @returns {CKEDITOR.dom.element} Positioned ancestor or `null`. */ getPositionedAncestor: function() { var current = this; while ( current.getName() != 'html' ) { if ( current.getComputedStyle( 'position' ) != 'static' ) return current; current = current.getParent(); } return null; }, /** * Gets this element's position in document. * * @param {CKEDITOR.dom.document} [refDocument] * @returns {Object} Element's position. * @returns {Number} return.x * @returns {Number} return.y * @todo refDocument */ getDocumentPosition: function( refDocument ) { var x = 0, y = 0, doc = this.getDocument(), body = doc.getBody(), quirks = doc.$.compatMode == 'BackCompat'; if ( document.documentElement[ "getBoundingClientRect" ] ) { var box = this.$.getBoundingClientRect(), $doc = doc.$, $docElem = $doc.documentElement; var clientTop = $docElem.clientTop || body.$.clientTop || 0, clientLeft = $docElem.clientLeft || body.$.clientLeft || 0, needAdjustScrollAndBorders = true; // #3804: getBoundingClientRect() works differently on IE and non-IE // browsers, regarding scroll positions. // // On IE, the top position of the element is always 0, no matter // how much you scrolled down. // // On other browsers, the top position of the element is negative // scrollTop. if ( CKEDITOR.env.ie ) { var inDocElem = doc.getDocumentElement().contains( this ), inBody = doc.getBody().contains( this ); needAdjustScrollAndBorders = ( quirks && inBody ) || ( !quirks && inDocElem ); } if ( needAdjustScrollAndBorders ) { x = box.left + ( !quirks && $docElem.scrollLeft || body.$.scrollLeft ); x -= clientLeft; y = box.top + ( !quirks && $docElem.scrollTop || body.$.scrollTop ); y -= clientTop; } } else { var current = this, previous = null, offsetParent; while ( current && !( current.getName() == 'body' || current.getName() == 'html' ) ) { x += current.$.offsetLeft - current.$.scrollLeft; y += current.$.offsetTop - current.$.scrollTop; // Opera includes clientTop|Left into offsetTop|Left. if ( !current.equals( this ) ) { x += ( current.$.clientLeft || 0 ); y += ( current.$.clientTop || 0 ); } var scrollElement = previous; while ( scrollElement && !scrollElement.equals( current ) ) { x -= scrollElement.$.scrollLeft; y -= scrollElement.$.scrollTop; scrollElement = scrollElement.getParent(); } previous = current; current = ( offsetParent = current.$.offsetParent ) ? new CKEDITOR.dom.element( offsetParent ) : null; } } if ( refDocument ) { var currentWindow = this.getWindow(), refWindow = refDocument.getWindow(); if ( !currentWindow.equals( refWindow ) && currentWindow.$.frameElement ) { var iframePosition = ( new CKEDITOR.dom.element( currentWindow.$.frameElement ) ).getDocumentPosition( refDocument ); x += iframePosition.x; y += iframePosition.y; } } if ( !document.documentElement[ "getBoundingClientRect" ] ) { // In Firefox, we'll endup one pixel before the element positions, // so we must add it here. if ( CKEDITOR.env.gecko && !quirks ) { x += this.$.clientLeft ? 1 : 0; y += this.$.clientTop ? 1 : 0; } } return { x: x, y: y }; }, /** * Make any page element visible inside the browser viewport. * * @param {Boolean} [alignToTop=false] */ scrollIntoView: function( alignToTop ) { var parent = this.getParent(); if ( !parent ) return; // Scroll the element into parent container from the inner out. do { // Check ancestors that overflows. var overflowed = parent.$.clientWidth && parent.$.clientWidth < parent.$.scrollWidth || parent.$.clientHeight && parent.$.clientHeight < parent.$.scrollHeight; // Skip body element, which will report wrong clientHeight when containing // floated content. (#9523) if ( overflowed && !parent.is( 'body' ) ) this.scrollIntoParent( parent, alignToTop, 1 ); // Walk across the frame. if ( parent.is( 'html' ) ) { var win = parent.getWindow(); // Avoid security error. try { var iframe = win.$.frameElement; iframe && ( parent = new CKEDITOR.dom.element( iframe ) ); } catch ( er ) {} } } while ( ( parent = parent.getParent() ) ); }, /** * Make any page element visible inside one of the ancestors by scrolling the parent. * * @param {CKEDITOR.dom.element/CKEDITOR.dom.window} parent The container to scroll into. * @param {Boolean} [alignToTop] Align the element's top side with the container's * when `true` is specified; align the bottom with viewport bottom when * `false` is specified. Otherwise scroll on either side with the minimum * amount to show the element. * @param {Boolean} [hscroll] Whether horizontal overflow should be considered. */ scrollIntoParent: function( parent, alignToTop, hscroll ) { !parent && ( parent = this.getWindow() ); var doc = parent.getDocument(); var isQuirks = doc.$.compatMode == 'BackCompat'; // On window is scrolled while quirks scrolls . if ( parent instanceof CKEDITOR.dom.window ) parent = isQuirks ? doc.getBody() : doc.getDocumentElement(); // Scroll the parent by the specified amount. function scrollBy( x, y ) { // Webkit doesn't support "scrollTop/scrollLeft" // on documentElement/body element. if ( /body|html/.test( parent.getName() ) ) parent.getWindow().$.scrollBy( x, y ); else { parent.$[ 'scrollLeft' ] += x; parent.$[ 'scrollTop' ] += y; } } // Figure out the element position relative to the specified window. function screenPos( element, refWin ) { var pos = { x: 0, y: 0 }; if ( !( element.is( isQuirks ? 'body' : 'html' ) ) ) { var box = element.$.getBoundingClientRect(); pos.x = box.left, pos.y = box.top; } var win = element.getWindow(); if ( !win.equals( refWin ) ) { var outerPos = screenPos( CKEDITOR.dom.element.get( win.$.frameElement ), refWin ); pos.x += outerPos.x, pos.y += outerPos.y; } return pos; } // calculated margin size. function margin( element, side ) { return parseInt( element.getComputedStyle( 'margin-' + side ) || 0, 10 ) || 0; } var win = parent.getWindow(); var thisPos = screenPos( this, win ), parentPos = screenPos( parent, win ), eh = this.$.offsetHeight, ew = this.$.offsetWidth, ch = parent.$.clientHeight, cw = parent.$.clientWidth, lt, br; // Left-top margins. lt = { x: thisPos.x - margin( this, 'left' ) - parentPos.x || 0, y: thisPos.y - margin( this, 'top' ) - parentPos.y || 0 }; // Bottom-right margins. br = { x: thisPos.x + ew + margin( this, 'right' ) - ( ( parentPos.x ) + cw ) || 0, y: thisPos.y + eh + margin( this, 'bottom' ) - ( ( parentPos.y ) + ch ) || 0 }; // 1. Do the specified alignment as much as possible; // 2. Otherwise be smart to scroll only the minimum amount; // 3. Never cut at the top; // 4. DO NOT scroll when already visible. if ( lt.y < 0 || br.y > 0 ) { scrollBy( 0, alignToTop === true ? lt.y : alignToTop === false ? br.y : lt.y < 0 ? lt.y : br.y ); } if ( hscroll && ( lt.x < 0 || br.x > 0 ) ) scrollBy( lt.x < 0 ? lt.x : br.x, 0 ); }, /** * Switch the `class` attribute to reflect one of the triple states of an * element in one of {@link CKEDITOR#TRISTATE_ON}, {@link CKEDITOR#TRISTATE_OFF} * or {@link CKEDITOR#TRISTATE_DISABLED}. * * link.setState( CKEDITOR.TRISTATE_ON ); * // ... * link.setState( CKEDITOR.TRISTATE_OFF ); * // ... * link.setState( CKEDITOR.TRISTATE_DISABLED ); * // ... * * span.setState( CKEDITOR.TRISTATE_ON, 'cke_button' ); * // ... * * @param {Number} state Indicate the element state. One of {@link CKEDITOR#TRISTATE_ON}, * {@link CKEDITOR#TRISTATE_OFF}, {@link CKEDITOR#TRISTATE_DISABLED}. * @param [base='cke'] The prefix apply to each of the state class name. * @param [useAria=true] Whether toggle the ARIA state attributes besides of class name change. */ setState: function( state, base, useAria ) { base = base || 'cke'; switch ( state ) { case CKEDITOR.TRISTATE_ON: this.addClass( base + '_on' ); this.removeClass( base + '_off' ); this.removeClass( base + '_disabled' ); useAria && this.setAttribute( 'aria-pressed', true ); useAria && this.removeAttribute( 'aria-disabled' ); break; case CKEDITOR.TRISTATE_DISABLED: this.addClass( base + '_disabled' ); this.removeClass( base + '_off' ); this.removeClass( base + '_on' ); useAria && this.setAttribute( 'aria-disabled', true ); useAria && this.removeAttribute( 'aria-pressed' ); break; default: this.addClass( base + '_off' ); this.removeClass( base + '_on' ); this.removeClass( base + '_disabled' ); useAria && this.removeAttribute( 'aria-pressed' ); useAria && this.removeAttribute( 'aria-disabled' ); break; } }, /** * Returns the inner document of this `' ); return html.join( '' ); }; // IE BUG: Parent container does not resize to contain the iframe automatically. dialog.on( 'load', function() { var iframe = CKEDITOR.document.getById( _.frameId ), contentDiv = iframe.getParent(); contentDiv.addClass( 'cke_dialog_ui_input_file' ); }); CKEDITOR.ui.dialog.labeledElement.call( this, dialog, elementDefinition, htmlList, innerHTML ); }, /** * A button for submitting the file in a file upload input. * * @class CKEDITOR.ui.dialog.fileButton * @extends CKEDITOR.ui.dialog.button * @constructor Creates a fileButton class instance. * @param {CKEDITOR.dialog} dialog Parent dialog object. * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition * The element definition. Accepted fields: * * * `for` (Required) The file input's page and element Id * to associate to, in a 2-item array format: `[ 'page_id', 'element_id' ]`. * * `validate` (Optional) The validation function. * * @param {Array} htmlList List of HTML code to output to. */ fileButton: function( dialog, elementDefinition, htmlList ) { if ( arguments.length < 3 ) return; var _ = initPrivateObject.call( this, elementDefinition ), me = this; if ( elementDefinition.validate ) this.validate = elementDefinition.validate; var myDefinition = CKEDITOR.tools.extend( {}, elementDefinition ); var onClick = myDefinition.onClick; myDefinition.className = ( myDefinition.className ? myDefinition.className + ' ' : '' ) + 'cke_dialog_ui_button'; myDefinition.onClick = function( evt ) { var target = elementDefinition[ 'for' ]; // [ pageId, elementId ] if ( !onClick || onClick.call( this, evt ) !== false ) { dialog.getContentElement( target[ 0 ], target[ 1 ] ).submit(); this.disable(); } }; dialog.on( 'load', function() { dialog.getContentElement( elementDefinition[ 'for' ][ 0 ], elementDefinition[ 'for' ][ 1 ] )._.buttons.push( me ); }); CKEDITOR.ui.dialog.button.call( this, dialog, myDefinition, htmlList ); }, html: (function() { var myHtmlRe = /^\s*<[\w:]+\s+([^>]*)?>/, theirHtmlRe = /^(\s*<[\w:]+(?:\s+[^>]*)?)((?:.|\r|\n)+)$/, emptyTagRe = /\/$/; /** * A dialog element made from raw HTML code. * * @class CKEDITOR.ui.dialog.html * @extends CKEDITOR.ui.dialog.uiElement * @constructor Creates a html class instance. * @param {CKEDITOR.dialog} dialog Parent dialog object. * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element definition. * Accepted fields: * * * `html` (Required) HTML code of this element. * * @param {Array} htmlList List of HTML code to be added to the dialog's content area. */ return function( dialog, elementDefinition, htmlList ) { if ( arguments.length < 3 ) return; var myHtmlList = [], myHtml, theirHtml = elementDefinition.html, myMatch, theirMatch; // If the HTML input doesn't contain any tags at the beginning, add a tag around it. if ( theirHtml.charAt( 0 ) != '<' ) theirHtml = '' + theirHtml + ''; // Look for focus function in definition. var focus = elementDefinition.focus; if ( focus ) { var oldFocus = this.focus; this.focus = function() { ( typeof focus == 'function' ? focus : oldFocus ).call( this ); this.fire( 'focus' ); }; if ( elementDefinition.isFocusable ) { var oldIsFocusable = this.isFocusable; this.isFocusable = oldIsFocusable; } this.keyboardFocusable = true; } CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, myHtmlList, 'span', null, null, '' ); // Append the attributes created by the uiElement call to the real HTML. myHtml = myHtmlList.join( '' ); myMatch = myHtml.match( myHtmlRe ); theirMatch = theirHtml.match( theirHtmlRe ) || [ '', '', '' ]; if ( emptyTagRe.test( theirMatch[ 1 ] ) ) { theirMatch[ 1 ] = theirMatch[ 1 ].slice( 0, -1 ); theirMatch[ 2 ] = '/' + theirMatch[ 2 ]; } htmlList.push( [ theirMatch[ 1 ], ' ', myMatch[ 1 ] || '', theirMatch[ 2 ] ].join( '' ) ); }; })(), /** * Form fieldset for grouping dialog UI elements. * * @class CKEDITOR.ui.dialog.fieldset * @extends CKEDITOR.ui.dialog.uiElement * @constructor Creates a fieldset class instance. * @param {CKEDITOR.dialog} dialog Parent dialog object. * @param {Array} childObjList * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container. * @param {Array} childHtmlList Array of HTML code that correspond to the HTML output of all the * objects in childObjList. * @param {Array} htmlList Array of HTML code that this element will output to. * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition * The element definition. Accepted fields: * * * `label` (Optional) The legend of the this fieldset. * * `children` (Required) An array of dialog field definitions which will be grouped inside this fieldset. * */ fieldset: function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) { var legendLabel = elementDefinition.label; /** @ignore */ var innerHTML = function() { var html = []; legendLabel && html.push( '' + legendLabel + '' ); for ( var i = 0; i < childHtmlList.length; i++ ) html.push( childHtmlList[ i ] ); return html.join( '' ); }; this._ = { children: childObjList }; CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition, htmlList, 'fieldset', null, null, innerHTML ); } }, true ); CKEDITOR.ui.dialog.html.prototype = new CKEDITOR.ui.dialog.uiElement; /** @class CKEDITOR.ui.dialog.labeledElement */ CKEDITOR.ui.dialog.labeledElement.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, { /** * Sets the label text of the element. * * @param {String} label The new label text. * @returns {CKEDITOR.ui.dialog.labeledElement} The current labeled element. */ setLabel: function( label ) { var node = CKEDITOR.document.getById( this._.labelId ); if ( node.getChildCount() < 1 ) ( new CKEDITOR.dom.text( label, CKEDITOR.document ) ).appendTo( node ); else node.getChild( 0 ).$.nodeValue = label; return this; }, /** * Retrieves the current label text of the elment. * * @returns {String} The current label text. */ getLabel: function() { var node = CKEDITOR.document.getById( this._.labelId ); if ( !node || node.getChildCount() < 1 ) return ''; else return node.getChild( 0 ).getText(); }, /** * Defines the onChange event for UI element definitions. * @property {Object} */ eventProcessors: commonEventProcessors }, true ); /** @class CKEDITOR.ui.dialog.button */ CKEDITOR.ui.dialog.button.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, { /** * Simulates a click to the button. * * @returns {Object} Return value of the `click` event. */ click: function() { if ( !this._.disabled ) return this.fire( 'click', { dialog: this._.dialog } ); return false; }, /** * Enables the button. */ enable: function() { this._.disabled = false; var element = this.getElement(); element && element.removeClass( 'cke_disabled' ); }, /** * Disables the button. */ disable: function() { this._.disabled = true; this.getElement().addClass( 'cke_disabled' ); }, /** * @todo */ isVisible: function() { return this.getElement().getFirst().isVisible(); }, /** * @todo */ isEnabled: function() { return !this._.disabled; }, /** * Defines the onChange event and onClick for button element definitions. * * @property {Object} */ eventProcessors: CKEDITOR.tools.extend( {}, CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, { onClick: function( dialog, func ) { this.on( 'click', function() { func.apply( this, arguments ); }); } }, true ), /** * Handler for the element's access key up event. Simulates a click to * the button. */ accessKeyUp: function() { this.click(); }, /** * Handler for the element's access key down event. Simulates a mouse * down to the button. */ accessKeyDown: function() { this.focus(); }, keyboardFocusable: true }, true ); /** @class CKEDITOR.ui.dialog.textInput */ CKEDITOR.ui.dialog.textInput.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement, { /** * Gets the text input DOM element under this UI object. * * @returns {CKEDITOR.dom.element} The DOM element of the text input. */ getInputElement: function() { return CKEDITOR.document.getById( this._.inputId ); }, /** * Puts focus into the text input. */ focus: function() { var me = this.selectParentTab(); // GECKO BUG: setTimeout() is needed to workaround invisible selections. setTimeout( function() { var element = me.getInputElement(); element && element.$.focus(); }, 0 ); }, /** * Selects all the text in the text input. */ select: function() { var me = this.selectParentTab(); // GECKO BUG: setTimeout() is needed to workaround invisible selections. setTimeout( function() { var e = me.getInputElement(); if ( e ) { e.$.focus(); e.$.select(); } }, 0 ); }, /** * Handler for the text input's access key up event. Makes a `select()` * call to the text input. */ accessKeyUp: function() { this.select(); }, /** * Sets the value of this text input object. * * uiElement.setValue( 'Blamo' ); * * @param {Object} value The new value. * @returns {CKEDITOR.ui.dialog.textInput} The current UI element. */ setValue: function( value ) { !value && ( value = '' ); return CKEDITOR.ui.dialog.uiElement.prototype.setValue.apply( this, arguments ); }, keyboardFocusable: true }, commonPrototype, true ); CKEDITOR.ui.dialog.textarea.prototype = new CKEDITOR.ui.dialog.textInput(); /** @class CKEDITOR.ui.dialog.select */ CKEDITOR.ui.dialog.select.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.labeledElement, { /** * Gets the DOM element of the select box. * * @returns {CKEDITOR.dom.element} The `` element of this file input. * * @returns {CKEDITOR.dom.element} The file input element. */ getInputElement: function() { var frameDocument = CKEDITOR.document.getById( this._.frameId ).getFrameDocument(); return frameDocument.$.forms.length > 0 ? new CKEDITOR.dom.element( frameDocument.$.forms[ 0 ].elements[ 0 ] ) : this.getElement(); }, /** * Uploads the file in the file input. * * @returns {CKEDITOR.ui.dialog.file} This object. */ submit: function() { this.getInputElement().getParent().$.submit(); return this; }, /** * Get the action assigned to the form. * * @returns {String} The value of the action. */ getAction: function() { return this.getInputElement().getParent().$.action; }, /** * The events must be applied on the inner input element, and * that must be done when the iframe & form has been loaded. */ registerEvents: function( definition ) { var regex = /^on([A-Z]\w+)/, match; var registerDomEvent = function( uiElement, dialog, eventName, func ) { uiElement.on( 'formLoaded', function() { uiElement.getInputElement().on( eventName, func, uiElement ); }); }; for ( var i in definition ) { if ( !( match = i.match( regex ) ) ) continue; if ( this.eventProcessors[ i ] ) this.eventProcessors[ i ].call( this, this._.dialog, definition[ i ] ); else registerDomEvent( this, this._.dialog, match[ 1 ].toLowerCase(), definition[ i ] ); } return this; }, /** * Redraws the file input and resets the file path in the file input. * The redraw logic is necessary because non-IE browsers tend to clear * the `' ); iframe.appendTo( body.getParent() ); } // Make the Title and Close Button unselectable. title.unselectable(); close.unselectable(); return { element: element, parts: { dialog: element.getChild( 0 ), title: title, close: close, tabs: body.getChild( 2 ), contents: body.getChild( [ 3, 0, 0, 0 ] ), footer: body.getChild( [ 3, 0, 1, 0 ] ) } }; } /** * This is the base class for runtime dialog objects. An instance of this * class represents a single named dialog for a single editor instance. * * var dialogObj = new CKEDITOR.dialog( editor, 'smiley' ); * * @class * @constructor Creates a dialog class instance. * @param {Object} editor The editor which created the dialog. * @param {String} dialogName The dialog's registered name. */ CKEDITOR.dialog = function( editor, dialogName ) { // Load the dialog definition. var definition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ], defaultDefinition = CKEDITOR.tools.clone( defaultDialogDefinition ), buttonsOrder = editor.config.dialog_buttonsOrder || 'OS', dir = editor.lang.dir, tabsToRemove = {}, i, processed, stopPropagation; if ( ( buttonsOrder == 'OS' && CKEDITOR.env.mac ) || // The buttons in MacOS Apps are in reverse order (#4750) ( buttonsOrder == 'rtl' && dir == 'ltr' ) || ( buttonsOrder == 'ltr' && dir == 'rtl' ) ) defaultDefinition.buttons.reverse(); // Completes the definition with the default values. definition = CKEDITOR.tools.extend( definition( editor ), defaultDefinition ); // Clone a functionally independent copy for this dialog. definition = CKEDITOR.tools.clone( definition ); // Create a complex definition object, extending it with the API // functions. definition = new definitionObject( this, definition ); var doc = CKEDITOR.document; var themeBuilt = buildDialog( editor ); // Initialize some basic parameters. this._ = { editor: editor, element: themeBuilt.element, name: dialogName, contentSize: { width: 0, height: 0 }, size: { width: 0, height: 0 }, contents: {}, buttons: {}, accessKeyMap: {}, // Initialize the tab and page map. tabs: {}, tabIdList: [], currentTabId: null, currentTabIndex: null, pageCount: 0, lastTab: null, tabBarMode: false, // Initialize the tab order array for input widgets. focusList: [], currentFocusIndex: 0, hasFocus: false }; this.parts = themeBuilt.parts; CKEDITOR.tools.setTimeout( function() { editor.fire( 'ariaWidget', this.parts.contents ); }, 0, this ); // Set the startup styles for the dialog, avoiding it enlarging the // page size on the dialog creation. var startStyles = { position: CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', top: 0, visibility: 'hidden' }; startStyles[ dir == 'rtl' ? 'right' : 'left' ] = 0; this.parts.dialog.setStyles( startStyles ); // Call the CKEDITOR.event constructor to initialize this instance. CKEDITOR.event.call( this ); // Fire the "dialogDefinition" event, making it possible to customize // the dialog definition. this.definition = definition = CKEDITOR.fire( 'dialogDefinition', { name: dialogName, definition: definition }, editor ).definition; // Cache tabs that should be removed. if ( !( 'removeDialogTabs' in editor._ ) && editor.config.removeDialogTabs ) { var removeContents = editor.config.removeDialogTabs.split( ';' ); for ( i = 0; i < removeContents.length; i++ ) { var parts = removeContents[ i ].split( ':' ); if ( parts.length == 2 ) { var removeDialogName = parts[ 0 ]; if ( !tabsToRemove[ removeDialogName ] ) tabsToRemove[ removeDialogName ] = []; tabsToRemove[ removeDialogName ].push( parts[ 1 ] ); } } editor._.removeDialogTabs = tabsToRemove; } // Remove tabs of this dialog. if ( editor._.removeDialogTabs && ( tabsToRemove = editor._.removeDialogTabs[ dialogName ] ) ) { for ( i = 0; i < tabsToRemove.length; i++ ) definition.removeContents( tabsToRemove[ i ] ); } // Initialize load, show, hide, ok and cancel events. if ( definition.onLoad ) this.on( 'load', definition.onLoad ); if ( definition.onShow ) this.on( 'show', definition.onShow ); if ( definition.onHide ) this.on( 'hide', definition.onHide ); if ( definition.onOk ) { this.on( 'ok', function( evt ) { // Dialog confirm might probably introduce content changes (#5415). editor.fire( 'saveSnapshot' ); setTimeout( function() { editor.fire( 'saveSnapshot' ); }, 0 ); if ( definition.onOk.call( this, evt ) === false ) evt.data.hide = false; }); } if ( definition.onCancel ) { this.on( 'cancel', function( evt ) { if ( definition.onCancel.call( this, evt ) === false ) evt.data.hide = false; }); } var me = this; // Iterates over all items inside all content in the dialog, calling a // function for each of them. var iterContents = function( func ) { var contents = me._.contents, stop = false; for ( var i in contents ) { for ( var j in contents[ i ] ) { stop = func.call( this, contents[ i ][ j ] ); if ( stop ) return; } } }; this.on( 'ok', function( evt ) { iterContents( function( item ) { if ( item.validate ) { var retval = item.validate( this ), invalid = typeof( retval ) == 'string' || retval === false; if ( invalid ) { evt.data.hide = false; evt.stop(); } handleFieldValidated.call( item, !invalid, typeof retval == 'string' ? retval : undefined ); return invalid; } }); }, this, null, 0 ); this.on( 'cancel', function( evt ) { iterContents( function( item ) { if ( item.isChanged() ) { if ( !confirm( editor.lang.common.confirmCancel ) ) evt.data.hide = false; return true; } }); }, this, null, 0 ); this.parts.close.on( 'click', function( evt ) { if ( this.fire( 'cancel', { hide: true } ).hide !== false ) this.hide(); evt.data.preventDefault(); }, this ); // Sort focus list according to tab order definitions. function setupFocus() { var focusList = me._.focusList; focusList.sort( function( a, b ) { // Mimics browser tab order logics; if ( a.tabIndex != b.tabIndex ) return b.tabIndex - a.tabIndex; // Sort is not stable in some browsers, // fall-back the comparator to 'focusIndex'; else return a.focusIndex - b.focusIndex; }); var size = focusList.length; for ( var i = 0; i < size; i++ ) focusList[ i ].focusIndex = i; } function changeFocus( offset ) { var focusList = me._.focusList; offset = offset || 0; if ( focusList.length < 1 ) return; var current = me._.currentFocusIndex; // Trigger the 'blur' event of any input element before anything, // since certain UI updates may depend on it. try { focusList[ current ].getInputElement().$.blur(); } catch ( e ) {} var startIndex = ( current + offset + focusList.length ) % focusList.length, currentIndex = startIndex; while ( offset && !focusList[ currentIndex ].isFocusable() ) { currentIndex = ( currentIndex + offset + focusList.length ) % focusList.length; if ( currentIndex == startIndex ) break; } focusList[ currentIndex ].focus(); // Select whole field content. if ( focusList[ currentIndex ].type == 'text' ) focusList[ currentIndex ].select(); } this.changeFocus = changeFocus; function keydownHandler( evt ) { // If I'm not the top dialog, ignore. if ( me != CKEDITOR.dialog._.currentTop ) return; var keystroke = evt.data.getKeystroke(), rtl = editor.lang.dir == 'rtl', button; processed = stopPropagation = 0; if ( keystroke == 9 || keystroke == CKEDITOR.SHIFT + 9 ) { var shiftPressed = ( keystroke == CKEDITOR.SHIFT + 9 ); // Handling Tab and Shift-Tab. if ( me._.tabBarMode ) { // Change tabs. var nextId = shiftPressed ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ); me.selectPage( nextId ); me._.tabs[ nextId ][ 0 ].focus(); } else { // Change the focus of inputs. changeFocus( shiftPressed ? -1 : 1 ); } processed = 1; } else if ( keystroke == CKEDITOR.ALT + 121 && !me._.tabBarMode && me.getPageCount() > 1 ) { // Alt-F10 puts focus into the current tab item in the tab bar. me._.tabBarMode = true; me._.tabs[ me._.currentTabId ][ 0 ].focus(); processed = 1; } else if ( ( keystroke == 37 || keystroke == 39 ) && me._.tabBarMode ) { // Arrow keys - used for changing tabs. nextId = ( keystroke == ( rtl ? 39 : 37 ) ? getPreviousVisibleTab.call( me ) : getNextVisibleTab.call( me ) ); me.selectPage( nextId ); me._.tabs[ nextId ][ 0 ].focus(); processed = 1; } else if ( ( keystroke == 13 || keystroke == 32 ) && me._.tabBarMode ) { this.selectPage( this._.currentTabId ); this._.tabBarMode = false; this._.currentFocusIndex = -1; changeFocus( 1 ); processed = 1; } // If user presses enter key in a text box, it implies clicking OK for the dialog. else if ( keystroke == 13 /*ENTER*/ ) { // Don't do that for a target that handles ENTER. var target = evt.data.getTarget(); if ( !target.is( 'a', 'button', 'select', 'textarea' ) && ( !target.is( 'input' ) || target.$.type != 'button' ) ) { button = this.getButton( 'ok' ); button && CKEDITOR.tools.setTimeout( button.click, 0, button ); processed = 1; } stopPropagation = 1; // Always block the propagation (#4269) } else if ( keystroke == 27 /*ESC*/ ) { button = this.getButton( 'cancel' ); // If there's a Cancel button, click it, else just fire the cancel event and hide the dialog. if ( button ) CKEDITOR.tools.setTimeout( button.click, 0, button ); else { if ( this.fire( 'cancel', { hide: true } ).hide !== false ) this.hide(); } stopPropagation = 1; // Always block the propagation (#4269) } else return; keypressHandler( evt ); } function keypressHandler( evt ) { if ( processed ) evt.data.preventDefault( 1 ); else if ( stopPropagation ) evt.data.stopPropagation(); } var dialogElement = this._.element; editor.focusManager.add( dialogElement, 1 ); // Add the dialog keyboard handlers. this.on( 'show', function() { dialogElement.on( 'keydown', keydownHandler, this ); // Some browsers instead, don't cancel key events in the keydown, but in the // keypress. So we must do a longer trip in those cases. (#4531,#8985) if ( CKEDITOR.env.opera || CKEDITOR.env.gecko ) dialogElement.on( 'keypress', keypressHandler, this ); }); this.on( 'hide', function() { dialogElement.removeListener( 'keydown', keydownHandler ); if ( CKEDITOR.env.opera || CKEDITOR.env.gecko ) dialogElement.removeListener( 'keypress', keypressHandler ); // Reset fields state when closing dialog. iterContents( function( item ) { resetField.apply( item ); }); }); this.on( 'iframeAdded', function( evt ) { var doc = new CKEDITOR.dom.document( evt.data.iframe.$.contentWindow.document ); doc.on( 'keydown', keydownHandler, this, null, 0 ); }); // Auto-focus logic in dialog. this.on( 'show', function() { // Setup tabIndex on showing the dialog instead of on loading // to allow dynamic tab order happen in dialog definition. setupFocus(); if ( editor.config.dialog_startupFocusTab && me._.pageCount > 1 ) { me._.tabBarMode = true; me._.tabs[ me._.currentTabId ][ 0 ].focus(); } else if ( !this._.hasFocus ) { this._.currentFocusIndex = -1; // Decide where to put the initial focus. if ( definition.onFocus ) { var initialFocus = definition.onFocus.call( this ); // Focus the field that the user specified. initialFocus && initialFocus.focus(); } // Focus the first field in layout order. else changeFocus( 1 ); } }, this, null, 0xffffffff ); // IE6 BUG: Text fields and text areas are only half-rendered the first time the dialog appears in IE6 (#2661). // This is still needed after [2708] and [2709] because text fields in hidden TR tags are still broken. if ( CKEDITOR.env.ie6Compat ) { this.on( 'load', function( evt ) { var outer = this.getElement(), inner = outer.getFirst(); inner.remove(); inner.appendTo( outer ); }, this ); } initDragAndDrop( this ); initResizeHandles( this ); // Insert the title. ( new CKEDITOR.dom.text( definition.title, CKEDITOR.document ) ).appendTo( this.parts.title ); // Insert the tabs and contents. for ( i = 0; i < definition.contents.length; i++ ) { var page = definition.contents[ i ]; page && this.addPage( page ); } this.parts[ 'tabs' ].on( 'click', function( evt ) { var target = evt.data.getTarget(); // If we aren't inside a tab, bail out. if ( target.hasClass( 'cke_dialog_tab' ) ) { // Get the ID of the tab, without the 'cke_' prefix and the unique number suffix. var id = target.$.id; this.selectPage( id.substring( 4, id.lastIndexOf( '_' ) ) ); if ( this._.tabBarMode ) { this._.tabBarMode = false; this._.currentFocusIndex = -1; changeFocus( 1 ); } evt.data.preventDefault(); } }, this ); // Insert buttons. var buttonsHtml = [], buttons = CKEDITOR.dialog._.uiElementBuilders.hbox.build( this, { type: 'hbox', className: 'cke_dialog_footer_buttons', widths: [], children: definition.buttons }, buttonsHtml ).getChild(); this.parts.footer.setHtml( buttonsHtml.join( '' ) ); for ( i = 0; i < buttons.length; i++ ) this._.buttons[ buttons[ i ].id ] = buttons[ i ]; }; // Focusable interface. Use it via dialog.addFocusable. function Focusable( dialog, element, index ) { this.element = element; this.focusIndex = index; // TODO: support tabIndex for focusables. this.tabIndex = 0; this.isFocusable = function() { return !element.getAttribute( 'disabled' ) && element.isVisible(); }; this.focus = function() { dialog._.currentFocusIndex = this.focusIndex; this.element.focus(); }; // Bind events element.on( 'keydown', function( e ) { if ( e.data.getKeystroke() in { 32:1,13:1 } ) this.fire( 'click' ); }); element.on( 'focus', function() { this.fire( 'mouseover' ); }); element.on( 'blur', function() { this.fire( 'mouseout' ); }); } // Re-layout the dialog on window resize. function resizeWithWindow( dialog ) { var win = CKEDITOR.document.getWindow(); function resizeHandler() { dialog.layout(); } win.on( 'resize', resizeHandler ); dialog.on( 'hide', function() { win.removeListener( 'resize', resizeHandler ); } ); } CKEDITOR.dialog.prototype = { destroy: function() { this.hide(); this._.element.remove(); }, /** * Resizes the dialog. * * dialogObj.resize( 800, 640 ); * * @method * @param {Number} width The width of the dialog in pixels. * @param {Number} height The height of the dialog in pixels. */ resize: (function() { return function( width, height ) { if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) return; CKEDITOR.dialog.fire( 'resize', { dialog: this, width: width, height: height }, this._.editor ); this.fire( 'resize', { width: width, height: height }, this._.editor ); var contents = this.parts.contents; contents.setStyles({ width: width + 'px', height: height + 'px' }); // Update dialog position when dimension get changed in RTL. if ( this._.editor.lang.dir == 'rtl' && this._.position ) this._.position.x = CKEDITOR.document.getWindow().getViewPaneSize().width - this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 ); this._.contentSize = { width: width, height: height }; }; })(), /** * Gets the current size of the dialog in pixels. * * var width = dialogObj.getSize().width; * * @returns {Object} * @returns {Number} return.width * @returns {Number} return.height */ getSize: function() { var element = this._.element.getFirst(); return { width: element.$.offsetWidth || 0, height: element.$.offsetHeight || 0 }; }, /** * Moves the dialog to an `(x, y)` coordinate relative to the window. * * dialogObj.move( 10, 40 ); * * @method * @param {Number} x The target x-coordinate. * @param {Number} y The target y-coordinate. * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up. */ move: function( x, y, save ) { // The dialog may be fixed positioned or absolute positioned. Ask the // browser what is the current situation first. var element = this._.element.getFirst(), rtl = this._.editor.lang.dir == 'rtl'; var isFixed = element.getComputedStyle( 'position' ) == 'fixed'; // (#8888) In some cases of a very small viewport, dialog is incorrectly // positioned in IE7. It also happens that it remains sticky and user cannot // scroll down/up to reveal dialog's content below/above the viewport; this is // cumbersome. // The only way to fix this is to move mouse out of the browser and // go back to see that dialog position is automagically fixed. No events, // no style change - pure magic. This is a IE7 rendering issue, which can be // fixed with dummy style redraw on each move. if ( CKEDITOR.env.ie ) element.setStyle( 'zoom', '100%' ); if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) return; // Save the current position. this._.position = { x: x, y: y }; // If not fixed positioned, add scroll position to the coordinates. if ( !isFixed ) { var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); x += scrollPosition.x; y += scrollPosition.y; } // Translate coordinate for RTL. if ( rtl ) { var dialogSize = this.getSize(), viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(); x = viewPaneSize.width - dialogSize.width - x; } var styles = { 'top': ( y > 0 ? y : 0 ) + 'px' }; styles[ rtl ? 'right' : 'left' ] = ( x > 0 ? x : 0 ) + 'px'; element.setStyles( styles ); save && ( this._.moved = 1 ); }, /** * Gets the dialog's position in the window. * * var dialogX = dialogObj.getPosition().x; * * @returns {Object} * @returns {Number} return.x * @returns {Number} return.y */ getPosition: function() { return CKEDITOR.tools.extend( {}, this._.position ); }, /** * Shows the dialog box. * * dialogObj.show(); */ show: function() { // Insert the dialog's element to the root document. var element = this._.element; var definition = this.definition; if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) element.appendTo( CKEDITOR.document.getBody() ); else element.setStyle( 'display', 'block' ); // FIREFOX BUG: Fix vanishing caret for Firefox 2 or Gecko 1.8. if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) { var dialogElement = this.parts.dialog; dialogElement.setStyle( 'position', 'absolute' ); setTimeout( function() { dialogElement.setStyle( 'position', 'fixed' ); }, 0 ); } // First, set the dialog to an appropriate size. this.resize( this._.contentSize && this._.contentSize.width || definition.width || definition.minWidth, this._.contentSize && this._.contentSize.height || definition.height || definition.minHeight ); // Reset all inputs back to their default value. this.reset(); // Select the first tab by default. this.selectPage( this.definition.contents[ 0 ].id ); // Set z-index. if ( CKEDITOR.dialog._.currentZIndex === null ) CKEDITOR.dialog._.currentZIndex = this._.editor.config.baseFloatZIndex; this._.element.getFirst().setStyle( 'z-index', CKEDITOR.dialog._.currentZIndex += 10 ); // Maintain the dialog ordering and dialog cover. if ( CKEDITOR.dialog._.currentTop === null ) { CKEDITOR.dialog._.currentTop = this; this._.parentDialog = null; showCover( this._.editor ); } else { this._.parentDialog = CKEDITOR.dialog._.currentTop; var parentElement = this._.parentDialog.getElement().getFirst(); parentElement.$.style.zIndex -= Math.floor( this._.editor.config.baseFloatZIndex / 2 ); CKEDITOR.dialog._.currentTop = this; } element.on( 'keydown', accessKeyDownHandler ); element.on( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); // Reset the hasFocus state. this._.hasFocus = false; CKEDITOR.tools.setTimeout( function() { this.layout(); resizeWithWindow( this ); this.parts.dialog.setStyle( 'visibility', '' ); // Execute onLoad for the first show. this.fireOnce( 'load', {} ); CKEDITOR.ui.fire( 'ready', this ); this.fire( 'show', {} ); this._.editor.fire( 'dialogShow', this ); if ( !this._.parentDialog ) this._.editor.focusManager.lock(); // Save the initial values of the dialog. this.foreach( function( contentObj ) { contentObj.setInitValue && contentObj.setInitValue(); }); }, 100, this ); }, /** * Rearrange the dialog to its previous position or the middle of the window. * * @since 3.5 */ layout: function() { var el = this.parts.dialog; var dialogSize = this.getSize(); var win = CKEDITOR.document.getWindow(), viewSize = win.getViewPaneSize(); var posX = ( viewSize.width - dialogSize.width ) / 2, posY = ( viewSize.height - dialogSize.height ) / 2; // Switch to absolute position when viewport is smaller than dialog size. if ( !CKEDITOR.env.ie6Compat ) { if ( dialogSize.height + ( posY > 0 ? posY : 0 ) > viewSize.height || dialogSize.width + ( posX > 0 ? posX : 0 ) > viewSize.width ) el.setStyle( 'position', 'absolute' ); else el.setStyle( 'position', 'fixed' ); } this.move( this._.moved ? this._.position.x : posX, this._.moved ? this._.position.y : posY ); }, /** * Executes a function for each UI element. * * @param {Function} fn Function to execute for each UI element. * @returns {CKEDITOR.dialog} The current dialog object. */ foreach: function( fn ) { for ( var i in this._.contents ) { for ( var j in this._.contents[ i ] ) fn.call( this, this._.contents[ i ][ j ] ); } return this; }, /** * Resets all input values in the dialog. * * dialogObj.reset(); * * @method * @chainable */ reset: (function() { var fn = function( widget ) { if ( widget.reset ) widget.reset( 1 ); }; return function() { this.foreach( fn ); return this; }; })(), /** * Calls the {@link CKEDITOR.dialog.definition.uiElement#setup} method of each * of the UI elements, with the arguments passed through it. * It is usually being called when the dialog is opened, to put the initial value inside the field. * * dialogObj.setupContent(); * * var timestamp = ( new Date() ).valueOf(); * dialogObj.setupContent( timestamp ); */ setupContent: function() { var args = arguments; this.foreach( function( widget ) { if ( widget.setup ) widget.setup.apply( widget, args ); }); }, /** * Calls the {@link CKEDITOR.dialog.definition.uiElement#commit} method of each * of the UI elements, with the arguments passed through it. * It is usually being called when the user confirms the dialog, to process the values. * * dialogObj.commitContent(); * * var timestamp = ( new Date() ).valueOf(); * dialogObj.commitContent( timestamp ); */ commitContent: function() { var args = arguments; this.foreach( function( widget ) { // Make sure IE triggers "change" event on last focused input before closing the dialog. (#7915) if ( CKEDITOR.env.ie && this._.currentFocusIndex == widget.focusIndex ) widget.getInputElement().$.blur(); if ( widget.commit ) widget.commit.apply( widget, args ); }); }, /** * Hides the dialog box. * * dialogObj.hide(); */ hide: function() { if ( !this.parts.dialog.isVisible() ) return; this.fire( 'hide', {} ); this._.editor.fire( 'dialogHide', this ); // Reset the tab page. this.selectPage( this._.tabIdList[ 0 ] ); var element = this._.element; element.setStyle( 'display', 'none' ); this.parts.dialog.setStyle( 'visibility', 'hidden' ); // Unregister all access keys associated with this dialog. unregisterAccessKey( this ); // Close any child(top) dialogs first. while ( CKEDITOR.dialog._.currentTop != this ) CKEDITOR.dialog._.currentTop.hide(); // Maintain dialog ordering and remove cover if needed. if ( !this._.parentDialog ) hideCover( this._.editor ); else { var parentElement = this._.parentDialog.getElement().getFirst(); parentElement.setStyle( 'z-index', parseInt( parentElement.$.style.zIndex, 10 ) + Math.floor( this._.editor.config.baseFloatZIndex / 2 ) ); } CKEDITOR.dialog._.currentTop = this._.parentDialog; // Deduct or clear the z-index. if ( !this._.parentDialog ) { CKEDITOR.dialog._.currentZIndex = null; // Remove access key handlers. element.removeListener( 'keydown', accessKeyDownHandler ); element.removeListener( CKEDITOR.env.opera ? 'keypress' : 'keyup', accessKeyUpHandler ); var editor = this._.editor; editor.focus(); // Give a while before unlock, waiting for focus to return to the editable. (#172) setTimeout( function() { editor.focusManager.unlock(); }, 0 ); } else CKEDITOR.dialog._.currentZIndex -= 10; delete this._.parentDialog; // Reset the initial values of the dialog. this.foreach( function( contentObj ) { contentObj.resetInitValue && contentObj.resetInitValue(); }); }, /** * Adds a tabbed page into the dialog. * * @param {Object} contents Content definition. */ addPage: function( contents ) { var pageHtml = [], titleHtml = contents.label ? ' title="' + CKEDITOR.tools.htmlEncode( contents.label ) + '"' : '', elements = contents.elements, vbox = CKEDITOR.dialog._.uiElementBuilders.vbox.build( this, { type: 'vbox', className: 'cke_dialog_page_contents', children: contents.elements, expand: !!contents.expand, padding: contents.padding, style: contents.style || 'width: 100%;' }, pageHtml ); // Create the HTML for the tab and the content block. var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); page.setAttribute( 'role', 'tabpanel' ); var env = CKEDITOR.env; var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(), tab = CKEDITOR.dom.element.createFromHtml( [ ' 0 ? ' cke_last' : 'cke_first' ), titleHtml, ( !!contents.hidden ? ' style="display:none"' : '' ), ' id="', tabId, '"', env.gecko && env.version >= 10900 && !env.hc ? '' : ' href="javascript:void(0)"', ' tabIndex="-1"', ' hidefocus="true"', ' role="tab">', contents.label, '' ].join( '' ) ); page.setAttribute( 'aria-labelledby', tabId ); // Take records for the tabs and elements created. this._.tabs[ contents.id ] = [ tab, page ]; this._.tabIdList.push( contents.id ); !contents.hidden && this._.pageCount++; this._.lastTab = tab; this.updateStyle(); var contentMap = this._.contents[ contents.id ] = {}, cursor, children = vbox.getChild(); while ( ( cursor = children.shift() ) ) { contentMap[ cursor.id ] = cursor; if ( typeof( cursor.getChild ) == 'function' ) children.push.apply( children, cursor.getChild() ); } // Attach the DOM nodes. page.setAttribute( 'name', contents.id ); page.appendTo( this.parts.contents ); tab.unselectable(); this.parts.tabs.append( tab ); // Add access key handlers if access key is defined. if ( contents.accessKey ) { registerAccessKey( this, this, 'CTRL+' + contents.accessKey, tabAccessKeyDown, tabAccessKeyUp ); this._.accessKeyMap[ 'CTRL+' + contents.accessKey ] = contents.id; } }, /** * Activates a tab page in the dialog by its id. * * dialogObj.selectPage( 'tab_1' ); * * @param {String} id The id of the dialog tab to be activated. */ selectPage: function( id ) { if ( this._.currentTabId == id ) return; // Returning true means that the event has been canceled if ( this.fire( 'selectPage', { page: id, currentPage: this._.currentTabId } ) === true ) return; // Hide the non-selected tabs and pages. for ( var i in this._.tabs ) { var tab = this._.tabs[ i ][ 0 ], page = this._.tabs[ i ][ 1 ]; if ( i != id ) { tab.removeClass( 'cke_dialog_tab_selected' ); page.hide(); } page.setAttribute( 'aria-hidden', i != id ); } var selected = this._.tabs[ id ]; selected[ 0 ].addClass( 'cke_dialog_tab_selected' ); // [IE] an invisible input[type='text'] will enlarge it's width // if it's value is long when it shows, so we clear it's value // before it shows and then recover it (#5649) if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat ) { clearOrRecoverTextInputValue( selected[ 1 ] ); selected[ 1 ].show(); setTimeout( function() { clearOrRecoverTextInputValue( selected[ 1 ], 1 ); }, 0 ); } else selected[ 1 ].show(); this._.currentTabId = id; this._.currentTabIndex = CKEDITOR.tools.indexOf( this._.tabIdList, id ); }, /** * Dialog state-specific style updates. */ updateStyle: function() { // If only a single page shown, a different style is used in the central pane. this.parts.dialog[ ( this._.pageCount === 1 ? 'add' : 'remove' ) + 'Class' ]( 'cke_single_page' ); }, /** * Hides a page's tab away from the dialog. * * dialog.hidePage( 'tab_3' ); * * @param {String} id The page's Id. */ hidePage: function( id ) { var tab = this._.tabs[ id ] && this._.tabs[ id ][ 0 ]; if ( !tab || this._.pageCount == 1 || !tab.isVisible() ) return; // Switch to other tab first when we're hiding the active tab. else if ( id == this._.currentTabId ) this.selectPage( getPreviousVisibleTab.call( this ) ); tab.hide(); this._.pageCount--; this.updateStyle(); }, /** * Unhides a page's tab. * * dialog.showPage( 'tab_2' ); * * @param {String} id The page's Id. */ showPage: function( id ) { var tab = this._.tabs[ id ] && this._.tabs[ id ][ 0 ]; if ( !tab ) return; tab.show(); this._.pageCount++; this.updateStyle(); }, /** * Gets the root DOM element of the dialog. * * var dialogElement = dialogObj.getElement().getFirst(); * dialogElement.setStyle( 'padding', '5px' ); * * @returns {CKEDITOR.dom.element} The `` element containing this dialog. */ getElement: function() { return this._.element; }, /** * Gets the name of the dialog. * * var dialogName = dialogObj.getName(); * * @returns {String} The name of this dialog. */ getName: function() { return this._.name; }, /** * Gets a dialog UI element object from a dialog page. * * dialogObj.getContentElement( 'tabId', 'elementId' ).setValue( 'Example' ); * * @param {String} pageId id of dialog page. * @param {String} elementId id of UI element. * @returns {CKEDITOR.ui.dialog.uiElement} The dialog UI element. */ getContentElement: function( pageId, elementId ) { var page = this._.contents[ pageId ]; return page && page[ elementId ]; }, /** * Gets the value of a dialog UI element. * * alert( dialogObj.getValueOf( 'tabId', 'elementId' ) ); * * @param {String} pageId id of dialog page. * @param {String} elementId id of UI element. * @returns {Object} The value of the UI element. */ getValueOf: function( pageId, elementId ) { return this.getContentElement( pageId, elementId ).getValue(); }, /** * Sets the value of a dialog UI element. * * dialogObj.setValueOf( 'tabId', 'elementId', 'Example' ); * * @param {String} pageId id of the dialog page. * @param {String} elementId id of the UI element. * @param {Object} value The new value of the UI element. */ setValueOf: function( pageId, elementId, value ) { return this.getContentElement( pageId, elementId ).setValue( value ); }, /** * Gets the UI element of a button in the dialog's button row. * * @returns {CKEDITOR.ui.dialog.button} The button object. * * @param {String} id The id of the button. */ getButton: function( id ) { return this._.buttons[ id ]; }, /** * Simulates a click to a dialog button in the dialog's button row. * * @returns The return value of the dialog's `click` event. * * @param {String} id The id of the button. */ click: function( id ) { return this._.buttons[ id ].click(); }, /** * Disables a dialog button. * * @param {String} id The id of the button. */ disableButton: function( id ) { return this._.buttons[ id ].disable(); }, /** * Enables a dialog button. * * @param {String} id The id of the button. */ enableButton: function( id ) { return this._.buttons[ id ].enable(); }, /** * Gets the number of pages in the dialog. * * @returns {Number} Page count. */ getPageCount: function() { return this._.pageCount; }, /** * Gets the editor instance which opened this dialog. * * @returns {CKEDITOR.editor} Parent editor instances. */ getParentEditor: function() { return this._.editor; }, /** * Gets the element that was selected when opening the dialog, if any. * * @returns {CKEDITOR.dom.element} The element that was selected, or `null`. */ getSelectedElement: function() { return this.getParentEditor().getSelection().getSelectedElement(); }, /** * Adds element to dialog's focusable list. * * @param {CKEDITOR.dom.element} element * @param {Number} [index] */ addFocusable: function( element, index ) { if ( typeof index == 'undefined' ) { index = this._.focusList.length; this._.focusList.push( new Focusable( this, element, index ) ); } else { this._.focusList.splice( index, 0, new Focusable( this, element, index ) ); for ( var i = index + 1; i < this._.focusList.length; i++ ) this._.focusList[ i ].focusIndex++; } } }; CKEDITOR.tools.extend( CKEDITOR.dialog, { /** * Registers a dialog. * * // Full sample plugin, which does not only register a dialog window but also adds an item to the context menu. * // To open the dialog window, choose "Open dialog" in the context menu. * CKEDITOR.plugins.add( 'myplugin', { * init: function( editor ) { * editor.addCommand( 'mydialog',new CKEDITOR.dialogCommand( 'mydialog' ) ); * * if ( editor.contextMenu ) { * editor.addMenuGroup( 'mygroup', 10 ); * editor.addMenuItem( 'My Dialog', { * label: 'Open dialog', * command: 'mydialog', * group: 'mygroup' * } ); * editor.contextMenu.addListener( function( element ) { * return { 'My Dialog': CKEDITOR.TRISTATE_OFF }; * } ); * } * * CKEDITOR.dialog.add( 'mydialog', function( api ) { * // CKEDITOR.dialog.definition * var dialogDefinition = { * title: 'Sample dialog', * minWidth: 390, * minHeight: 130, * contents: [ * { * id: 'tab1', * label: 'Label', * title: 'Title', * expand: true, * padding: 0, * elements: [ * { * type: 'html', * html: '

    This is some sample HTML content.

    ' * }, * { * type: 'textarea', * id: 'textareaId', * rows: 4, * cols: 40 * } * ] * } * ], * buttons: [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ], * onOk: function() { * // "this" is now a CKEDITOR.dialog object. * // Accessing dialog elements: * var textareaObj = this.getContentElement( 'tab1', 'textareaId' ); * alert( "You have entered: " + textareaObj.getValue() ); * } * }; * * return dialogDefinition; * } ); * } * } ); * * CKEDITOR.replace( 'editor1', { extraPlugins: 'myplugin' } ); * * @static * @param {String} name The dialog's name. * @param {Function/String} dialogDefinition * A function returning the dialog's definition, or the URL to the `.js` file holding the function. * The function should accept an argument `editor` which is the current editor instance, and * return an object conforming to {@link CKEDITOR.dialog.definition}. * @see CKEDITOR.dialog.definition */ add: function( name, dialogDefinition ) { // Avoid path registration from multiple instances override definition. if ( !this._.dialogDefinitions[ name ] || typeof dialogDefinition == 'function' ) this._.dialogDefinitions[ name ] = dialogDefinition; }, /** * @static * @todo */ exists: function( name ) { return !!this._.dialogDefinitions[ name ]; }, /** * @static * @todo */ getCurrent: function() { return CKEDITOR.dialog._.currentTop; }, /** * The default OK button for dialogs. Fires the `ok` event and closes the dialog if the event succeeds. * * @static * @method */ okButton: (function() { var retval = function( editor, override ) { override = override || {}; return CKEDITOR.tools.extend({ id: 'ok', type: 'button', label: editor.lang.common.ok, 'class': 'cke_dialog_ui_button_ok', onClick: function( evt ) { var dialog = evt.data.dialog; if ( dialog.fire( 'ok', { hide: true } ).hide !== false ) dialog.hide(); } }, override, true ); }; retval.type = 'button'; retval.override = function( override ) { return CKEDITOR.tools.extend( function( editor ) { return retval( editor, override ); }, { type: 'button' }, true ); }; return retval; })(), /** * The default cancel button for dialogs. Fires the `cancel` event and * closes the dialog if no UI element value changed. * * @static * @method */ cancelButton: (function() { var retval = function( editor, override ) { override = override || {}; return CKEDITOR.tools.extend({ id: 'cancel', type: 'button', label: editor.lang.common.cancel, 'class': 'cke_dialog_ui_button_cancel', onClick: function( evt ) { var dialog = evt.data.dialog; if ( dialog.fire( 'cancel', { hide: true } ).hide !== false ) dialog.hide(); } }, override, true ); }; retval.type = 'button'; retval.override = function( override ) { return CKEDITOR.tools.extend( function( editor ) { return retval( editor, override ); }, { type: 'button' }, true ); }; return retval; })(), /** * Registers a dialog UI element. * * @static * @param {String} typeName The name of the UI element. * @param {Function} builder The function to build the UI element. */ addUIElement: function( typeName, builder ) { this._.uiElementBuilders[ typeName ] = builder; } }); CKEDITOR.dialog._ = { uiElementBuilders: {}, dialogDefinitions: {}, currentTop: null, currentZIndex: null }; // "Inherit" (copy actually) from CKEDITOR.event. CKEDITOR.event.implementOn( CKEDITOR.dialog ); CKEDITOR.event.implementOn( CKEDITOR.dialog.prototype ); var defaultDialogDefinition = { resizable: CKEDITOR.DIALOG_RESIZE_BOTH, minWidth: 600, minHeight: 400, buttons: [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ] }; // Tool function used to return an item from an array based on its id // property. var getById = function( array, id, recurse ) { for ( var i = 0, item; ( item = array[ i ] ); i++ ) { if ( item.id == id ) return item; if ( recurse && item[ recurse ] ) { var retval = getById( item[ recurse ], id, recurse ); if ( retval ) return retval; } } return null; }; // Tool function used to add an item into an array. var addById = function( array, newItem, nextSiblingId, recurse, nullIfNotFound ) { if ( nextSiblingId ) { for ( var i = 0, item; ( item = array[ i ] ); i++ ) { if ( item.id == nextSiblingId ) { array.splice( i, 0, newItem ); return newItem; } if ( recurse && item[ recurse ] ) { var retval = addById( item[ recurse ], newItem, nextSiblingId, recurse, true ); if ( retval ) return retval; } } if ( nullIfNotFound ) return null; } array.push( newItem ); return newItem; }; // Tool function used to remove an item from an array based on its id. var removeById = function( array, id, recurse ) { for ( var i = 0, item; ( item = array[ i ] ); i++ ) { if ( item.id == id ) return array.splice( i, 1 ); if ( recurse && item[ recurse ] ) { var retval = removeById( item[ recurse ], id, recurse ); if ( retval ) return retval; } } return null; }; /** * This class is not really part of the API. It is the `definition` property value * passed to `dialogDefinition` event handlers. * * CKEDITOR.on( 'dialogDefinition', function( evt ) { * var definition = evt.data.definition; * var content = definition.getContents( 'page1' ); * // ... * } ); * * @private * @class CKEDITOR.dialog.definitionObject * @extends CKEDITOR.dialog.definition * @constructor Creates a definitionObject class instance. */ var definitionObject = function( dialog, dialogDefinition ) { // TODO : Check if needed. this.dialog = dialog; // Transform the contents entries in contentObjects. var contents = dialogDefinition.contents; for ( var i = 0, content; ( content = contents[ i ] ); i++ ) contents[ i ] = content && new contentObject( dialog, content ); CKEDITOR.tools.extend( this, dialogDefinition ); }; definitionObject.prototype = { /** * Gets a content definition. * * @param {String} id The id of the content definition. * @returns {CKEDITOR.dialog.definition.content} The content definition matching id. */ getContents: function( id ) { return getById( this.contents, id ); }, /** * Gets a button definition. * * @param {String} id The id of the button definition. * @returns {CKEDITOR.dialog.definition.button} The button definition matching id. */ getButton: function( id ) { return getById( this.buttons, id ); }, /** * Adds a content definition object under this dialog definition. * * @param {CKEDITOR.dialog.definition.content} contentDefinition The * content definition. * @param {String} [nextSiblingId] The id of an existing content * definition which the new content definition will be inserted * before. Omit if the new content definition is to be inserted as * the last item. * @returns {CKEDITOR.dialog.definition.content} The inserted content definition. */ addContents: function( contentDefinition, nextSiblingId ) { return addById( this.contents, contentDefinition, nextSiblingId ); }, /** * Adds a button definition object under this dialog definition. * * @param {CKEDITOR.dialog.definition.button} buttonDefinition The * button definition. * @param {String} [nextSiblingId] The id of an existing button * definition which the new button definition will be inserted * before. Omit if the new button definition is to be inserted as * the last item. * @returns {CKEDITOR.dialog.definition.button} The inserted button definition. */ addButton: function( buttonDefinition, nextSiblingId ) { return addById( this.buttons, buttonDefinition, nextSiblingId ); }, /** * Removes a content definition from this dialog definition. * * @param {String} id The id of the content definition to be removed. * @returns {CKEDITOR.dialog.definition.content} The removed content definition. */ removeContents: function( id ) { removeById( this.contents, id ); }, /** * Removes a button definition from the dialog definition. * * @param {String} id The id of the button definition to be removed. * @returns {CKEDITOR.dialog.definition.button} The removed button definition. */ removeButton: function( id ) { removeById( this.buttons, id ); } }; /** * This class is not really part of the API. It is the template of the * objects representing content pages inside the * CKEDITOR.dialog.definitionObject. * * CKEDITOR.on( 'dialogDefinition', function( evt ) { * var definition = evt.data.definition; * var content = definition.getContents( 'page1' ); * content.remove( 'textInput1' ); * // ... * } ); * * @private * @class CKEDITOR.dialog.definition.contentObject * @constructor Creates a contentObject class instance. */ function contentObject( dialog, contentDefinition ) { this._ = { dialog: dialog }; CKEDITOR.tools.extend( this, contentDefinition ); } contentObject.prototype = { /** * Gets a UI element definition under the content definition. * * @param {String} id The id of the UI element definition. * @returns {CKEDITOR.dialog.definition.uiElement} */ get: function( id ) { return getById( this.elements, id, 'children' ); }, /** * Adds a UI element definition to the content definition. * * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition The * UI elemnet definition to be added. * @param {String} nextSiblingId The id of an existing UI element * definition which the new UI element definition will be inserted * before. Omit if the new button definition is to be inserted as * the last item. * @returns {CKEDITOR.dialog.definition.uiElement} The element definition inserted. */ add: function( elementDefinition, nextSiblingId ) { return addById( this.elements, elementDefinition, nextSiblingId, 'children' ); }, /** * Removes a UI element definition from the content definition. * * @param {String} id The id of the UI element definition to be removed. * @returns {CKEDITOR.dialog.definition.uiElement} The element definition removed. */ remove: function( id ) { removeById( this.elements, id, 'children' ); } }; function initDragAndDrop( dialog ) { var lastCoords = null, abstractDialogCoords = null, element = dialog.getElement().getFirst(), editor = dialog.getParentEditor(), magnetDistance = editor.config.dialog_magnetDistance, margins = CKEDITOR.skin.margins || [ 0, 0, 0, 0 ]; if ( typeof magnetDistance == 'undefined' ) magnetDistance = 20; function mouseMoveHandler( evt ) { var dialogSize = dialog.getSize(), viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), x = evt.data.$.screenX, y = evt.data.$.screenY, dx = x - lastCoords.x, dy = y - lastCoords.y, realX, realY; lastCoords = { x: x, y: y }; abstractDialogCoords.x += dx; abstractDialogCoords.y += dy; if ( abstractDialogCoords.x + margins[ 3 ] < magnetDistance ) realX = -margins[ 3 ]; else if ( abstractDialogCoords.x - margins[ 1 ] > viewPaneSize.width - dialogSize.width - magnetDistance ) realX = viewPaneSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[ 1 ] ); else realX = abstractDialogCoords.x; if ( abstractDialogCoords.y + margins[ 0 ] < magnetDistance ) realY = -margins[ 0 ]; else if ( abstractDialogCoords.y - margins[ 2 ] > viewPaneSize.height - dialogSize.height - magnetDistance ) realY = viewPaneSize.height - dialogSize.height + margins[ 2 ]; else realY = abstractDialogCoords.y; dialog.move( realX, realY, 1 ); evt.data.preventDefault(); } function mouseUpHandler( evt ) { CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); if ( CKEDITOR.env.ie6Compat ) { var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); coverDoc.removeListener( 'mousemove', mouseMoveHandler ); coverDoc.removeListener( 'mouseup', mouseUpHandler ); } } dialog.parts.title.on( 'mousedown', function( evt ) { lastCoords = { x: evt.data.$.screenX, y: evt.data.$.screenY }; CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); CKEDITOR.document.on( 'mouseup', mouseUpHandler ); abstractDialogCoords = dialog.getPosition(); if ( CKEDITOR.env.ie6Compat ) { var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); coverDoc.on( 'mousemove', mouseMoveHandler ); coverDoc.on( 'mouseup', mouseUpHandler ); } evt.data.preventDefault(); }, dialog ); } function initResizeHandles( dialog ) { var def = dialog.definition, resizable = def.resizable; if ( resizable == CKEDITOR.DIALOG_RESIZE_NONE ) return; var editor = dialog.getParentEditor(); var wrapperWidth, wrapperHeight, viewSize, origin, startSize, dialogCover; var mouseDownFn = CKEDITOR.tools.addFunction( function( $event ) { startSize = dialog.getSize(); var content = dialog.parts.contents, iframeDialog = content.$.getElementsByTagName( 'iframe' ).length; // Shim to help capturing "mousemove" over iframe. if ( iframeDialog ) { dialogCover = CKEDITOR.dom.element.createFromHtml( '
    ' ); content.append( dialogCover ); } // Calculate the offset between content and chrome size. wrapperHeight = startSize.height - dialog.parts.contents.getSize( 'height', !( CKEDITOR.env.gecko || CKEDITOR.env.opera || CKEDITOR.env.ie && CKEDITOR.env.quirks ) ); wrapperWidth = startSize.width - dialog.parts.contents.getSize( 'width', 1 ); origin = { x: $event.screenX, y: $event.screenY }; viewSize = CKEDITOR.document.getWindow().getViewPaneSize(); CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); CKEDITOR.document.on( 'mouseup', mouseUpHandler ); if ( CKEDITOR.env.ie6Compat ) { var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); coverDoc.on( 'mousemove', mouseMoveHandler ); coverDoc.on( 'mouseup', mouseUpHandler ); } $event.preventDefault && $event.preventDefault(); }); // Prepend the grip to the dialog. dialog.on( 'load', function() { var direction = ''; if ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH ) direction = ' cke_resizer_horizontal'; else if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT ) direction = ' cke_resizer_vertical'; var resizer = CKEDITOR.dom.element.createFromHtml( '' + // BLACK LOWER RIGHT TRIANGLE (ltr) // BLACK LOWER LEFT TRIANGLE (rtl) ( editor.lang.dir == 'ltr' ? '\u25E2' : '\u25E3' ) + '
    ' ); dialog.parts.footer.append( resizer, 1 ); }); editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); }); function mouseMoveHandler( evt ) { var rtl = editor.lang.dir == 'rtl', dx = ( evt.data.$.screenX - origin.x ) * ( rtl ? -1 : 1 ), dy = evt.data.$.screenY - origin.y, width = startSize.width, height = startSize.height, internalWidth = width + dx * ( dialog._.moved ? 1 : 2 ), internalHeight = height + dy * ( dialog._.moved ? 1 : 2 ), element = dialog._.element.getFirst(), right = rtl && element.getComputedStyle( 'right' ), position = dialog.getPosition(); if ( position.y + internalHeight > viewSize.height ) internalHeight = viewSize.height - position.y; if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width ) internalWidth = viewSize.width - ( rtl ? right : position.x ); // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL. if ( ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) ) width = Math.max( def.minWidth || 0, internalWidth - wrapperWidth ); if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) height = Math.max( def.minHeight || 0, internalHeight - wrapperHeight ); dialog.resize( width, height ); if ( !dialog._.moved ) dialog.layout(); evt.data.preventDefault(); } function mouseUpHandler() { CKEDITOR.document.removeListener( 'mouseup', mouseUpHandler ); CKEDITOR.document.removeListener( 'mousemove', mouseMoveHandler ); if ( dialogCover ) { dialogCover.remove(); dialogCover = null; } if ( CKEDITOR.env.ie6Compat ) { var coverDoc = currentCover.getChild( 0 ).getFrameDocument(); coverDoc.removeListener( 'mouseup', mouseUpHandler ); coverDoc.removeListener( 'mousemove', mouseMoveHandler ); } } } var resizeCover; // Caching resuable covers and allowing only one cover // on screen. var covers = {}, currentCover; function cancelEvent( ev ) { ev.data.preventDefault( 1 ); } function showCover( editor ) { var win = CKEDITOR.document.getWindow(); var config = editor.config, backgroundColorStyle = config.dialog_backgroundCoverColor || 'white', backgroundCoverOpacity = config.dialog_backgroundCoverOpacity, baseFloatZIndex = config.baseFloatZIndex, coverKey = CKEDITOR.tools.genKey( backgroundColorStyle, backgroundCoverOpacity, baseFloatZIndex ), coverElement = covers[ coverKey ]; if ( !coverElement ) { var html = [ '
    ' ]; if ( CKEDITOR.env.ie6Compat ) { // Support for custom document.domain in IE. var isCustomDomain = CKEDITOR.env.isCustomDomain(), iframeHtml = ''; html.push( '' + '' ); } html.push( '
    ' ); coverElement = CKEDITOR.dom.element.createFromHtml( html.join( '' ) ); coverElement.setOpacity( backgroundCoverOpacity != undefined ? backgroundCoverOpacity : 0.5 ); coverElement.on( 'keydown', cancelEvent ); coverElement.on( 'keypress', cancelEvent ); coverElement.on( 'keyup', cancelEvent ); coverElement.appendTo( CKEDITOR.document.getBody() ); covers[ coverKey ] = coverElement; } else coverElement.show(); // Makes the dialog cover a focus holder as well. editor.focusManager.add( coverElement ); currentCover = coverElement; var resizeFunc = function() { var size = win.getViewPaneSize(); coverElement.setStyles({ width: size.width + 'px', height: size.height + 'px' }); }; var scrollFunc = function() { var pos = win.getScrollPosition(), cursor = CKEDITOR.dialog._.currentTop; coverElement.setStyles({ left: pos.x + 'px', top: pos.y + 'px' }); if ( cursor ) { do { var dialogPos = cursor.getPosition(); cursor.move( dialogPos.x, dialogPos.y ); } while ( ( cursor = cursor._.parentDialog ) ); } }; resizeCover = resizeFunc; win.on( 'resize', resizeFunc ); resizeFunc(); // Using Safari/Mac, focus must be kept where it is (#7027) if ( !( CKEDITOR.env.mac && CKEDITOR.env.webkit ) ) coverElement.focus(); if ( CKEDITOR.env.ie6Compat ) { // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. // So we need to invent a really funny way to make it work. var myScrollHandler = function() { scrollFunc(); arguments.callee.prevScrollHandler.apply( this, arguments ); }; win.$.setTimeout( function() { myScrollHandler.prevScrollHandler = window.onscroll || function() {}; window.onscroll = myScrollHandler; }, 0 ); scrollFunc(); } } function hideCover( editor ) { if ( !currentCover ) return; editor.focusManager.remove( currentCover ); var win = CKEDITOR.document.getWindow(); currentCover.hide(); win.removeListener( 'resize', resizeCover ); if ( CKEDITOR.env.ie6Compat ) { win.$.setTimeout( function() { var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; window.onscroll = prevScrollHandler || null; }, 0 ); } resizeCover = null; } function removeCovers() { for ( var coverId in covers ) covers[ coverId ].remove(); covers = {}; } var accessKeyProcessors = {}; var accessKeyDownHandler = function( evt ) { var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, alt = evt.data.$.altKey, shift = evt.data.$.shiftKey, key = String.fromCharCode( evt.data.$.keyCode ), keyProcessor = accessKeyProcessors[ ( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '' ) + ( shift ? 'SHIFT+' : '' ) + key ]; if ( !keyProcessor || !keyProcessor.length ) return; keyProcessor = keyProcessor[ keyProcessor.length - 1 ]; keyProcessor.keydown && keyProcessor.keydown.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); evt.data.preventDefault(); }; var accessKeyUpHandler = function( evt ) { var ctrl = evt.data.$.ctrlKey || evt.data.$.metaKey, alt = evt.data.$.altKey, shift = evt.data.$.shiftKey, key = String.fromCharCode( evt.data.$.keyCode ), keyProcessor = accessKeyProcessors[ ( ctrl ? 'CTRL+' : '' ) + ( alt ? 'ALT+' : '' ) + ( shift ? 'SHIFT+' : '' ) + key ]; if ( !keyProcessor || !keyProcessor.length ) return; keyProcessor = keyProcessor[ keyProcessor.length - 1 ]; if ( keyProcessor.keyup ) { keyProcessor.keyup.call( keyProcessor.uiElement, keyProcessor.dialog, keyProcessor.key ); evt.data.preventDefault(); } }; var registerAccessKey = function( uiElement, dialog, key, downFunc, upFunc ) { var procList = accessKeyProcessors[ key ] || ( accessKeyProcessors[ key ] = [] ); procList.push({ uiElement: uiElement, dialog: dialog, key: key, keyup: upFunc || uiElement.accessKeyUp, keydown: downFunc || uiElement.accessKeyDown }); }; var unregisterAccessKey = function( obj ) { for ( var i in accessKeyProcessors ) { var list = accessKeyProcessors[ i ]; for ( var j = list.length - 1; j >= 0; j-- ) { if ( list[ j ].dialog == obj || list[ j ].uiElement == obj ) list.splice( j, 1 ); } if ( list.length === 0 ) delete accessKeyProcessors[ i ]; } }; var tabAccessKeyUp = function( dialog, key ) { if ( dialog._.accessKeyMap[ key ] ) dialog.selectPage( dialog._.accessKeyMap[ key ] ); }; var tabAccessKeyDown = function( dialog, key ) {}; (function() { CKEDITOR.ui.dialog = { /** * The base class of all dialog UI elements. * * @class CKEDITOR.ui.dialog.uiElement * @constructor Creates a uiElement class instance. * @param {CKEDITOR.dialog} dialog Parent dialog object. * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition Element * definition. * * Accepted fields: * * * `id` (Required) The id of the UI element. See {@link CKEDITOR.dialog#getContentElement}. * * `type` (Required) The type of the UI element. The * value to this field specifies which UI element class will be used to * generate the final widget. * * `title` (Optional) The popup tooltip for the UI * element. * * `hidden` (Optional) A flag that tells if the element * should be initially visible. * * `className` (Optional) Additional CSS class names * to add to the UI element. Separated by space. * * `style` (Optional) Additional CSS inline styles * to add to the UI element. A semicolon (;) is required after the last * style declaration. * * `accessKey` (Optional) The alphanumeric access key * for this element. Access keys are automatically prefixed by CTRL. * * `on*` (Optional) Any UI element definition field that * starts with `on` followed immediately by a capital letter and * probably more letters is an event handler. Event handlers may be further * divided into registered event handlers and DOM event handlers. Please * refer to {@link CKEDITOR.ui.dialog.uiElement#registerEvents} and * {@link CKEDITOR.ui.dialog.uiElement#eventProcessors} for more information. * * @param {Array} htmlList * List of HTML code to be added to the dialog's content area. * @param {Function/String} [nodeNameArg='div'] * A function returning a string, or a simple string for the node name for * the root DOM node. * @param {Function/Object} [stylesArg={}] * A function returning an object, or a simple object for CSS styles applied * to the DOM node. * @param {Function/Object} [attributesArg={}] * A fucntion returning an object, or a simple object for attributes applied * to the DOM node. * @param {Function/String} [contentsArg=''] * A function returning a string, or a simple string for the HTML code inside * the root DOM node. Default is empty string. */ uiElement: function( dialog, elementDefinition, htmlList, nodeNameArg, stylesArg, attributesArg, contentsArg ) { if ( arguments.length < 4 ) return; var nodeName = ( nodeNameArg.call ? nodeNameArg( elementDefinition ) : nodeNameArg ) || 'div', html = [ '<', nodeName, ' ' ], styles = ( stylesArg && stylesArg.call ? stylesArg( elementDefinition ) : stylesArg ) || {}, attributes = ( attributesArg && attributesArg.call ? attributesArg( elementDefinition ) : attributesArg ) || {}, innerHTML = ( contentsArg && contentsArg.call ? contentsArg.call( this, dialog, elementDefinition ) : contentsArg ) || '', domId = this.domId = attributes.id || CKEDITOR.tools.getNextId() + '_uiElement', id = this.id = elementDefinition.id, i; // Set the id, a unique id is required for getElement() to work. attributes.id = domId; // Set the type and definition CSS class names. var classes = {}; if ( elementDefinition.type ) classes[ 'cke_dialog_ui_' + elementDefinition.type ] = 1; if ( elementDefinition.className ) classes[ elementDefinition.className ] = 1; if ( elementDefinition.disabled ) classes[ 'cke_disabled' ] = 1; var attributeClasses = ( attributes[ 'class' ] && attributes[ 'class' ].split ) ? attributes[ 'class' ].split( ' ' ) : []; for ( i = 0; i < attributeClasses.length; i++ ) { if ( attributeClasses[ i ] ) classes[ attributeClasses[ i ] ] = 1; } var finalClasses = []; for ( i in classes ) finalClasses.push( i ); attributes[ 'class' ] = finalClasses.join( ' ' ); // Set the popup tooltop. if ( elementDefinition.title ) attributes.title = elementDefinition.title; // Write the inline CSS styles. var styleStr = ( elementDefinition.style || '' ).split( ';' ); // Element alignment support. if ( elementDefinition.align ) { var align = elementDefinition.align; styles[ 'margin-left' ] = align == 'left' ? 0 : 'auto'; styles[ 'margin-right' ] = align == 'right' ? 0 : 'auto'; } for ( i in styles ) styleStr.push( i + ':' + styles[ i ] ); if ( elementDefinition.hidden ) styleStr.push( 'display:none' ); for ( i = styleStr.length - 1; i >= 0; i-- ) { if ( styleStr[ i ] === '' ) styleStr.splice( i, 1 ); } if ( styleStr.length > 0 ) attributes.style = ( attributes.style ? ( attributes.style + '; ' ) : '' ) + styleStr.join( '; ' ); // Write the attributes. for ( i in attributes ) html.push( i + '="' + CKEDITOR.tools.htmlEncode( attributes[ i ] ) + '" ' ); // Write the content HTML. html.push( '>', innerHTML, '' ); // Add contents to the parent HTML array. htmlList.push( html.join( '' ) ); ( this._ || ( this._ = {} ) ).dialog = dialog; // Override isChanged if it is defined in element definition. if ( typeof( elementDefinition.isChanged ) == 'boolean' ) this.isChanged = function() { return elementDefinition.isChanged; }; if ( typeof( elementDefinition.isChanged ) == 'function' ) this.isChanged = elementDefinition.isChanged; // Overload 'get(set)Value' on definition. if ( typeof( elementDefinition.setValue ) == 'function' ) { this.setValue = CKEDITOR.tools.override( this.setValue, function( org ) { return function( val ) { org.call( this, elementDefinition.setValue.call( this, val ) ); }; }); } if ( typeof( elementDefinition.getValue ) == 'function' ) { this.getValue = CKEDITOR.tools.override( this.getValue, function( org ) { return function() { return elementDefinition.getValue.call( this, org.call( this ) ); }; }); } // Add events. CKEDITOR.event.implementOn( this ); this.registerEvents( elementDefinition ); if ( this.accessKeyUp && this.accessKeyDown && elementDefinition.accessKey ) registerAccessKey( this, dialog, 'CTRL+' + elementDefinition.accessKey ); var me = this; dialog.on( 'load', function() { var input = me.getInputElement(); if ( input ) { var focusClass = me.type in { 'checkbox':1,'ratio':1 } && CKEDITOR.env.ie && CKEDITOR.env.version < 8 ? 'cke_dialog_ui_focused' : ''; input.on( 'focus', function() { dialog._.tabBarMode = false; dialog._.hasFocus = true; me.fire( 'focus' ); focusClass && this.addClass( focusClass ); }); input.on( 'blur', function() { me.fire( 'blur' ); focusClass && this.removeClass( focusClass ); }); } }); // Register the object as a tab focus if it can be included. if ( this.keyboardFocusable ) { this.tabIndex = elementDefinition.tabIndex || 0; this.focusIndex = dialog._.focusList.push( this ) - 1; this.on( 'focus', function() { dialog._.currentFocusIndex = me.focusIndex; }); } // Completes this object with everything we have in the // definition. CKEDITOR.tools.extend( this, elementDefinition ); }, /** * Horizontal layout box for dialog UI elements, auto-expends to available width of container. * * @class CKEDITOR.ui.dialog.hbox * @extends CKEDITOR.ui.dialog.uiElement * @constructor Creates a hbox class instance. * @param {CKEDITOR.dialog} dialog Parent dialog object. * @param {Array} childObjList * Array of {@link CKEDITOR.ui.dialog.uiElement} objects inside this container. * @param {Array} childHtmlList * Array of HTML code that correspond to the HTML output of all the * objects in childObjList. * @param {Array} htmlList * Array of HTML code that this element will output to. * @param {CKEDITOR.dialog.definition.uiElement} elementDefinition * The element definition. Accepted fields: * * * `widths` (Optional) The widths of child cells. * * `height` (Optional) The height of the layout. * * `padding` (Optional) The padding width inside child cells. * * `align` (Optional) The alignment of the whole layout. */ hbox: function( dialog, childObjList, childHtmlList, htmlList, elementDefinition ) { if ( arguments.length < 4 ) return; this._ || ( this._ = {} ); var children = this._.children = childObjList, widths = elementDefinition && elementDefinition.widths || null, height = elementDefinition && elementDefinition.height || null, styles = {}, i; /** @ignore */ var innerHTML = function() { var html = [ '
    ' ); for ( var i = 0; i < childHtmlList.length; i++ ) { var styles = []; html.push( '' ); } html.push( '
    0 ) html.push( 'style="', styles.join( '; ' ), '" ' ); html.push( ' class="cke_dialog_ui_vbox_child">', childHtmlList[ i ], '
    ' ); return html.join( '' ); }; CKEDITOR.ui.dialog.uiElement.call( this, dialog, elementDefinition || { type: 'vbox' }, htmlList, 'div', null, { role: 'presentation' }, innerHTML ); } }; })(); /** @class CKEDITOR.ui.dialog.uiElement */ CKEDITOR.ui.dialog.uiElement.prototype = { /** * Gets the root DOM element of this dialog UI object. * * uiElement.getElement().hide(); * * @returns {CKEDITOR.dom.element} Root DOM element of UI object. */ getElement: function() { return CKEDITOR.document.getById( this.domId ); }, /** * Gets the DOM element that the user inputs values. * * This function is used by {@link #setValue}, {@link #getValue} and {@link #focus}. It should * be overrided in child classes where the input element isn't the root * element. * * var rawValue = textInput.getInputElement().$.value; * * @returns {CKEDITOR.dom.element} The element where the user input values. */ getInputElement: function() { return this.getElement(); }, /** * Gets the parent dialog object containing this UI element. * * var dialog = uiElement.getDialog(); * * @returns {CKEDITOR.dialog} Parent dialog object. */ getDialog: function() { return this._.dialog; }, /** * Sets the value of this dialog UI object. * * uiElement.setValue( 'Dingo' ); * * @chainable * @param {Object} value The new value. * @param {Boolean} noChangeEvent Internal commit, to supress `change` event on this element. */ setValue: function( value, noChangeEvent ) { this.getInputElement().setValue( value ); !noChangeEvent && this.fire( 'change', { value: value } ); return this; }, /** * Gets the current value of this dialog UI object. * * var myValue = uiElement.getValue(); * * @returns {Object} The current value. */ getValue: function() { return this.getInputElement().getValue(); }, /** * Tells whether the UI object's value has changed. * * if ( uiElement.isChanged() ) * confirm( 'Value changed! Continue?' ); * * @returns {Boolean} `true` if changed, `false` if not changed. */ isChanged: function() { // Override in input classes. return false; }, /** * Selects the parent tab of this element. Usually called by focus() or overridden focus() methods. * * focus : function() { * this.selectParentTab(); * // do something else. * } * * @chainable */ selectParentTab: function() { var element = this.getInputElement(), cursor = element, tabId; while ( ( cursor = cursor.getParent() ) && cursor.$.className.search( 'cke_dialog_page_contents' ) == -1 ) { /*jsl:pass*/ } // Some widgets don't have parent tabs (e.g. OK and Cancel buttons). if ( !cursor ) return this; tabId = cursor.getAttribute( 'name' ); // Avoid duplicate select. if ( this._.dialog._.currentTabId != tabId ) this._.dialog.selectPage( tabId ); return this; }, /** * Puts the focus to the UI object. Switches tabs if the UI object isn't in the active tab page. * * uiElement.focus(); * * @chainable */ focus: function() { this.selectParentTab().getInputElement().focus(); return this; }, /** * Registers the `on*` event handlers defined in the element definition. * * The default behavior of this function is: * * 1. If the on* event is defined in the class's eventProcesors list, * then the registration is delegated to the corresponding function * in the eventProcessors list. * 2. If the on* event is not defined in the eventProcessors list, then * register the event handler under the corresponding DOM event of * the UI element's input DOM element (as defined by the return value * of {@link #getInputElement}). * * This function is only called at UI element instantiation, but can * be overridded in child classes if they require more flexibility. * * @chainable * @param {CKEDITOR.dialog.definition.uiElement} definition The UI element * definition. */ registerEvents: function( definition ) { var regex = /^on([A-Z]\w+)/, match; var registerDomEvent = function( uiElement, dialog, eventName, func ) { dialog.on( 'load', function() { uiElement.getInputElement().on( eventName, func, uiElement ); }); }; for ( var i in definition ) { if ( !( match = i.match( regex ) ) ) continue; if ( this.eventProcessors[ i ] ) this.eventProcessors[ i ].call( this, this._.dialog, definition[ i ] ); else registerDomEvent( this, this._.dialog, match[ 1 ].toLowerCase(), definition[ i ] ); } return this; }, /** * The event processor list used by * {@link CKEDITOR.ui.dialog.uiElement#getInputElement} at UI element * instantiation. The default list defines three `on*` events: * * 1. `onLoad` - Called when the element's parent dialog opens for the * first time. * 2. `onShow` - Called whenever the element's parent dialog opens. * 3. `onHide` - Called whenever the element's parent dialog closes. * * // This connects the 'click' event in CKEDITOR.ui.dialog.button to onClick * // handlers in the UI element's definitions. * CKEDITOR.ui.dialog.button.eventProcessors = CKEDITOR.tools.extend( {}, * CKEDITOR.ui.dialog.uiElement.prototype.eventProcessors, * { onClick : function( dialog, func ) { this.on( 'click', func ); } }, * true * ); * * @property {Object} */ eventProcessors: { onLoad: function( dialog, func ) { dialog.on( 'load', func, this ); }, onShow: function( dialog, func ) { dialog.on( 'show', func, this ); }, onHide: function( dialog, func ) { dialog.on( 'hide', func, this ); } }, /** * The default handler for a UI element's access key down event, which * tries to put focus to the UI element. * * Can be overridded in child classes for more sophisticaed behavior. * * @param {CKEDITOR.dialog} dialog The parent dialog object. * @param {String} key The key combination pressed. Since access keys * are defined to always include the `CTRL` key, its value should always * include a `'CTRL+'` prefix. */ accessKeyDown: function( dialog, key ) { this.focus(); }, /** * The default handler for a UI element's access key up event, which * does nothing. * * Can be overridded in child classes for more sophisticated behavior. * * @param {CKEDITOR.dialog} dialog The parent dialog object. * @param {String} key The key combination pressed. Since access keys * are defined to always include the `CTRL` key, its value should always * include a `'CTRL+'` prefix. */ accessKeyUp: function( dialog, key ) {}, /** * Disables a UI element. */ disable: function() { var element = this.getElement(), input = this.getInputElement(); input.setAttribute( 'disabled', 'true' ); element.addClass( 'cke_disabled' ); }, /** * Enables a UI element. */ enable: function() { var element = this.getElement(), input = this.getInputElement(); input.removeAttribute( 'disabled' ); element.removeClass( 'cke_disabled' ); }, /** * Determines whether an UI element is enabled or not. * * @returns {Boolean} Whether the UI element is enabled. */ isEnabled: function() { return !this.getElement().hasClass( 'cke_disabled' ); }, /** * Determines whether an UI element is visible or not. * * @returns {Boolean} Whether the UI element is visible. */ isVisible: function() { return this.getInputElement().isVisible(); }, /** * Determines whether an UI element is focus-able or not. * Focus-able is defined as being both visible and enabled. * * @returns {Boolean} Whether the UI element can be focused. */ isFocusable: function() { if ( !this.isEnabled() || !this.isVisible() ) return false; return true; } }; /** @class CKEDITOR.ui.dialog.hbox */ CKEDITOR.ui.dialog.hbox.prototype = CKEDITOR.tools.extend( new CKEDITOR.ui.dialog.uiElement, { /** * Gets a child UI element inside this container. * * var checkbox = hbox.getChild( [0,1] ); * checkbox.setValue( true ); * * @param {Array/Number} indices An array or a single number to indicate the child's * position in the container's descendant tree. Omit to get all the children in an array. * @returns {Array/CKEDITOR.ui.dialog.uiElement} Array of all UI elements in the container * if no argument given, or the specified UI element if indices is given. */ getChild: function( indices ) { // If no arguments, return a clone of the children array. if ( arguments.length < 1 ) return this._.children.concat(); // If indices isn't array, make it one. if ( !indices.splice ) indices = [ indices ]; // Retrieve the child element according to tree position. if ( indices.length < 2 ) return this._.children[ indices[ 0 ] ]; else return ( this._.children[ indices[ 0 ] ] && this._.children[ indices[ 0 ] ].getChild ) ? this._.children[ indices[ 0 ] ].getChild( indices.slice( 1, indices.length ) ) : null; } }, true ); CKEDITOR.ui.dialog.vbox.prototype = new CKEDITOR.ui.dialog.hbox(); (function() { var commonBuilder = { build: function( dialog, elementDefinition, output ) { var children = elementDefinition.children, child, childHtmlList = [], childObjList = []; for ( var i = 0; ( i < children.length && ( child = children[ i ] ) ); i++ ) { var childHtml = []; childHtmlList.push( childHtml ); childObjList.push( CKEDITOR.dialog._.uiElementBuilders[ child.type ].build( dialog, child, childHtml ) ); } return new CKEDITOR.ui.dialog[ elementDefinition.type ]( dialog, childObjList, childHtmlList, output, elementDefinition ); } }; CKEDITOR.dialog.addUIElement( 'hbox', commonBuilder ); CKEDITOR.dialog.addUIElement( 'vbox', commonBuilder ); })(); /** * Generic dialog command. It opens a specific dialog when executed. * * // Register the "link" command, which opens the "link" dialog. * editor.addCommand( 'link', new CKEDITOR.dialogCommand( 'link' ) ); * * @class * @constructor Creates a dialogCommand class instance. * @extends CKEDITOR.commandDefinition * @param {String} dialogName The name of the dialog to open when executing * this command. */ CKEDITOR.dialogCommand = function( dialogName, ext ) { this.dialogName = dialogName; CKEDITOR.tools.extend( this, ext, true ); }; CKEDITOR.dialogCommand.prototype = { exec: function( editor ) { // Special treatment for Opera. (#8031) CKEDITOR.env.opera ? CKEDITOR.tools.setTimeout( function() { editor.openDialog( this.dialogName ); }, 0, this ) : editor.openDialog( this.dialogName ); }, // Dialog commands just open a dialog ui, thus require no undo logic, // undo support should dedicate to specific dialog implementation. canUndo: false, editorFocus: CKEDITOR.env.ie || CKEDITOR.env.webkit }; (function() { var notEmptyRegex = /^([a]|[^a])+$/, integerRegex = /^\d*$/, numberRegex = /^\d*(?:\.\d+)?$/, htmlLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|\%)?)?$/, cssLengthRegex = /^(((\d*(\.\d+))|(\d*))(px|em|ex|in|cm|mm|pt|pc|\%)?)?$/i, inlineStyleRegex = /^(\s*[\w-]+\s*:\s*[^:;]+(?:;|$))*$/; CKEDITOR.VALIDATE_OR = 1; CKEDITOR.VALIDATE_AND = 2; CKEDITOR.dialog.validate = { functions: function() { var args = arguments; return function() { /** * It's important for validate functions to be able to accept the value * as argument in addition to this.getValue(), so that it is possible to * combine validate functions together to make more sophisticated * validators. */ var value = this && this.getValue ? this.getValue() : args[ 0 ]; var msg = undefined, relation = CKEDITOR.VALIDATE_AND, functions = [], i; for ( i = 0; i < args.length; i++ ) { if ( typeof( args[ i ] ) == 'function' ) functions.push( args[ i ] ); else break; } if ( i < args.length && typeof( args[ i ] ) == 'string' ) { msg = args[ i ]; i++; } if ( i < args.length && typeof( args[ i ] ) == 'number' ) relation = args[ i ]; var passed = ( relation == CKEDITOR.VALIDATE_AND ? true : false ); for ( i = 0; i < functions.length; i++ ) { if ( relation == CKEDITOR.VALIDATE_AND ) passed = passed && functions[ i ]( value ); else passed = passed || functions[ i ]( value ); } return !passed ? msg : true; }; }, regex: function( regex, msg ) { /* * Can be greatly shortened by deriving from functions validator if code size * turns out to be more important than performance. */ return function() { var value = this && this.getValue ? this.getValue() : arguments[ 0 ]; return !regex.test( value ) ? msg : true; }; }, notEmpty: function( msg ) { return this.regex( notEmptyRegex, msg ); }, integer: function( msg ) { return this.regex( integerRegex, msg ); }, 'number': function( msg ) { return this.regex( numberRegex, msg ); }, 'cssLength': function( msg ) { return this.functions( function( val ) { return cssLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); }, 'htmlLength': function( msg ) { return this.functions( function( val ) { return htmlLengthRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); }, 'inlineStyle': function( msg ) { return this.functions( function( val ) { return inlineStyleRegex.test( CKEDITOR.tools.trim( val ) ); }, msg ); }, equals: function( value, msg ) { return this.functions( function( val ) { return val == value; }, msg ); }, notEqual: function( value, msg ) { return this.functions( function( val ) { return val != value; }, msg ); } }; CKEDITOR.on( 'instanceDestroyed', function( evt ) { // Remove dialog cover on last instance destroy. if ( CKEDITOR.tools.isEmpty( CKEDITOR.instances ) ) { var currentTopDialog; while ( ( currentTopDialog = CKEDITOR.dialog._.currentTop ) ) currentTopDialog.hide(); removeCovers(); } var dialogs = evt.editor._.storedDialogs; for ( var name in dialogs ) dialogs[ name ].destroy(); }); })(); // Extend the CKEDITOR.editor class with dialog specific functions. CKEDITOR.tools.extend( CKEDITOR.editor.prototype, { /** * Loads and opens a registered dialog. * * CKEDITOR.instances.editor1.openDialog( 'smiley' ); * * @member CKEDITOR.editor * @param {String} dialogName The registered name of the dialog. * @param {Function} callback The function to be invoked after dialog instance created. * @returns {CKEDITOR.dialog} The dialog object corresponding to the dialog displayed. * `null` if the dialog name is not registered. * @see CKEDITOR.dialog#add */ openDialog: function( dialogName, callback ) { var dialog = null, dialogDefinitions = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; if ( CKEDITOR.dialog._.currentTop === null ) showCover( this ); // If the dialogDefinition is already loaded, open it immediately. if ( typeof dialogDefinitions == 'function' ) { var storedDialogs = this._.storedDialogs || ( this._.storedDialogs = {} ); dialog = storedDialogs[ dialogName ] || ( storedDialogs[ dialogName ] = new CKEDITOR.dialog( this, dialogName ) ); callback && callback.call( dialog, dialog ); dialog.show(); } else if ( dialogDefinitions == 'failed' ) { hideCover( this ); throw new Error( '[CKEDITOR.dialog.openDialog] Dialog "' + dialogName + '" failed when loading definition.' ); } else if ( typeof dialogDefinitions == 'string' ) { CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( dialogDefinitions ), function() { var dialogDefinition = CKEDITOR.dialog._.dialogDefinitions[ dialogName ]; // In case of plugin error, mark it as loading failed. if ( typeof dialogDefinition != 'function' ) CKEDITOR.dialog._.dialogDefinitions[ dialogName ] = 'failed'; this.openDialog( dialogName, callback ); }, this, 0, 1 ); } CKEDITOR.skin.loadPart( 'dialog' ); return dialog; } }); })(); CKEDITOR.plugins.add( 'dialog', { requires: 'dialogui', init: function( editor ) { editor.on( 'contentDom', function() { var editable = editor.editable(); // Open dialog on double-clicks. editable.attachListener( editable, 'dblclick', function( evt ) { if ( editor.readOnly ) return false; var data = { element: evt.data.getTarget() }; editor.fire( 'doubleclick', data ); data.dialog && editor.openDialog( data.dialog ); return 1; }); }); } }); // Dialog related configurations. /** * The color of the dialog background cover. It should be a valid CSS color string. * * config.dialog_backgroundCoverColor = 'rgb(255, 254, 253)'; * * @cfg {String} [dialog_backgroundCoverColor='white'] * @member CKEDITOR.config */ /** * The opacity of the dialog background cover. It should be a number within the * range `[0.0, 1.0]`. * * config.dialog_backgroundCoverOpacity = 0.7; * * @cfg {Number} [dialog_backgroundCoverOpacity=0.5] * @member CKEDITOR.config */ /** * If the dialog has more than one tab, put focus into the first tab as soon as dialog is opened. * * config.dialog_startupFocusTab = true; * * @cfg {Boolean} [dialog_startupFocusTab=false] * @member CKEDITOR.config */ /** * The distance of magnetic borders used in moving and resizing dialogs, * measured in pixels. * * config.dialog_magnetDistance = 30; * * @cfg {Number} [dialog_magnetDistance=20] * @member CKEDITOR.config */ /** * The guideline to follow when generating the dialog buttons. There are 3 possible options: * * * `'OS'` - the buttons will be displayed in the default order of the user's OS; * * `'ltr'` - for Left-To-Right order; * * `'rtl'` - for Right-To-Left order. * * Example: * * config.dialog_buttonsOrder = 'rtl'; * * @since 3.5 * @cfg {String} [dialog_buttonsOrder='OS'] * @member CKEDITOR.config */ /** * The dialog contents to removed. It's a string composed by dialog name and tab name with a colon between them. * * Separate each pair with semicolon (see example). * * **Note:** All names are case-sensitive. * * **Note:** Be cautious when specifying dialog tabs that are mandatory, * like `'info'`, dialog functionality might be broken because of this! * * config.removeDialogTabs = 'flash:advanced;image:Link'; * * @since 3.5 * @cfg {String} [removeDialogTabs=''] * @member CKEDITOR.config */ /** * Fired when a dialog definition is about to be used to create a dialog into * an editor instance. This event makes it possible to customize the definition * before creating it. * * Note that this event is called only the first time a specific dialog is * opened. Successive openings will use the cached dialog, and this event will * not get fired. * * @event dialogDefinition * @member CKEDITOR * @param {CKEDITOR.dialog.definition} data The dialog defination that * is being loaded. * @param {CKEDITOR.editor} editor The editor instance that will use the dialog. */ /** * Fired when a tab is going to be selected in a dialog. * * @event selectPage * @member CKEDITOR.dialog * @param data * @param {String} data.page The id of the page that it's gonna be selected. * @param {String} data.currentPage The id of the current page. */ /** * Fired when the user tries to dismiss a dialog. * * @event cancel * @member CKEDITOR.dialog * @param data * @param {Boolean} data.hide Whether the event should proceed or not. */ /** * Fired when the user tries to confirm a dialog. * * @event ok * @member CKEDITOR.dialog * @param data * @param {Boolean} data.hide Whether the event should proceed or not. */ /** * Fired when a dialog is shown. * * @event show * @member CKEDITOR.dialog */ /** * Fired when a dialog is shown. * * @event dialogShow * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. */ /** * Fired when a dialog is hidden. * * @event hide * @member CKEDITOR.dialog */ /** * Fired when a dialog is hidden. * * @event dialogHide * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. */ /** * Fired when a dialog is being resized. The event is fired on * both the {@link CKEDITOR.dialog} object and the dialog instance * since 3.5.3, previously it's available only in the global object. * * @static * @event resize * @member CKEDITOR.dialog * @param data * @param {CKEDITOR.dialog} data.dialog The dialog being resized (if * it's fired on the dialog itself, this parameter isn't sent). * @param {String} data.skin The skin name. * @param {Number} data.width The new width. * @param {Number} data.height The new height. */ /** * Fired when a dialog is being resized. The event is fired on * both the {@link CKEDITOR.dialog} object and the dialog instance * since 3.5.3, previously it's available only in the global object. * * @since 3.5 * @event resize * @member CKEDITOR.dialog * @param data * @param {Number} data.width The new width. * @param {Number} data.height The new height. *//** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'about', { requires: 'dialog', init: function( editor ) { var command = editor.addCommand( 'about', new CKEDITOR.dialogCommand( 'about' ) ); command.modes = { wysiwyg:1,source:1 }; command.canUndo = false; command.readOnly = 1; editor.ui.addButton && editor.ui.addButton( 'About', { label: editor.lang.about.title, command: 'about', toolbar: 'about' }); CKEDITOR.dialog.add( 'about', this.path + 'dialogs/about.js' ); } }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Plugin definition for the a11yhelp, which provides a dialog * with accessibility related help. */ (function() { var pluginName = 'a11yhelp', commandName = 'a11yHelp'; CKEDITOR.plugins.add( pluginName, { requires: 'dialog', // List of available localizations. availableLangs: { en:1,ar:1,bg:1,ca:1,et:1,cs:1,cy:1,da:1,de:1,el:1,eo:1,es:1,fa:1,fi:1,fr:1,gu:1,he:1,hi:1,hr:1,hu:1,it:1,ja:1,ku:1,lt:1,lv:1,mk:1,mn:1,nb:1,nl:1,no:1,pl:1,pt:1,'pt-br':1,ro:1,ru:1,sk:1,sl:1,sv:1,tr:1,ug:1,uk:1,vi:1,'zh-cn':1 }, init: function( editor ) { var plugin = this; editor.addCommand( commandName, { exec: function() { var langCode = editor.langCode; langCode = plugin.availableLangs[ langCode ] ? langCode : plugin.availableLangs[ langCode.replace( /-.*/, '' ) ] ? langCode.replace( /-.*/, '' ) : 'en'; CKEDITOR.scriptLoader.load( CKEDITOR.getUrl( plugin.path + 'dialogs/lang/' + langCode + '.js' ), function() { editor.lang.a11yhelp = plugin.langEntries[ langCode ]; editor.openDialog( commandName ); }); }, modes: { wysiwyg:1,source:1 }, readOnly: 1, canUndo: false }); editor.setKeystroke( CKEDITOR.ALT + 48 /*0*/, 'a11yHelp' ); CKEDITOR.dialog.add( commandName, this.path + 'dialogs/a11yhelp.js' ); } }); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { function setupAdvParams( element ) { var attrName = this.att; var value = element && element.hasAttribute( attrName ) && element.getAttribute( attrName ) || ''; if ( value !== undefined ) this.setValue( value ); } function commitAdvParams() { // Dialogs may use different parameters in the commit list, so, by // definition, we take the first CKEDITOR.dom.element available. var element; for ( var i = 0; i < arguments.length; i++ ) { if ( arguments[ i ] instanceof CKEDITOR.dom.element ) { element = arguments[ i ]; break; } } if ( element ) { var attrName = this.att, value = this.getValue(); if ( value ) element.setAttribute( attrName, value ); else element.removeAttribute( attrName, value ); } } CKEDITOR.plugins.add( 'dialogadvtab', { requires : 'dialog', // @param tabConfig // id, dir, classes, styles createAdvancedTab: function( editor, tabConfig ) { if ( !tabConfig ) tabConfig = { id:1,dir:1,classes:1,styles:1 }; var lang = editor.lang.common; var result = { id: 'advanced', label: lang.advancedTab, title: lang.advancedTab, elements: [ { type: 'vbox', padding: 1, children: [] } ] }; var contents = []; if ( tabConfig.id || tabConfig.dir ) { if ( tabConfig.id ) { contents.push({ id: 'advId', att: 'id', type: 'text', label: lang.id, setup: setupAdvParams, commit: commitAdvParams }); } if ( tabConfig.dir ) { contents.push({ id: 'advLangDir', att: 'dir', type: 'select', label: lang.langDir, 'default': '', style: 'width:100%', items: [ [ lang.notSet, '' ], [ lang.langDirLTR, 'ltr' ], [ lang.langDirRTL, 'rtl' ] ], setup: setupAdvParams, commit: commitAdvParams }); } result.elements[ 0 ].children.push({ type: 'hbox', widths: [ '50%', '50%' ], children: [].concat( contents ) }); } if ( tabConfig.styles || tabConfig.classes ) { contents = []; if ( tabConfig.styles ) { contents.push({ id: 'advStyles', att: 'style', type: 'text', label: lang.styles, 'default': '', validate: CKEDITOR.dialog.validate.inlineStyle( lang.invalidInlineStyle ), onChange: function() {}, getStyle: function( name, defaultValue ) { var match = this.getValue().match( new RegExp( '(?:^|;)\\s*' + name + '\\s*:\\s*([^;]*)', 'i' ) ); return match ? match[ 1 ] : defaultValue; }, updateStyle: function( name, value ) { var styles = this.getValue(); var tmp = editor.document.createElement( 'span' ); tmp.setAttribute( 'style', styles ); tmp.setStyle( name, value ); styles = CKEDITOR.tools.normalizeCssText( tmp.getAttribute( 'style' ) ); this.setValue( styles, 1 ); }, setup: setupAdvParams, commit: commitAdvParams }); } if ( tabConfig.classes ) { contents.push({ type: 'hbox', widths: [ '45%', '55%' ], children: [ { id: 'advCSSClasses', att: 'class', type: 'text', label: lang.cssClasses, 'default': '', setup: setupAdvParams, commit: commitAdvParams } ] }); } result.elements[ 0 ].children.push({ type: 'hbox', widths: [ '50%', '50%' ], children: [].concat( contents ) }); } return result; } }); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'basicstyles', { init: function( editor ) { var order = 0; // All buttons use the same code to register. So, to avoid // duplications, let's use this tool function. var addButtonCommand = function( buttonName, buttonLabel, commandName, styleDefiniton ) { // Disable the command if no definition is configured. if ( !styleDefiniton ) return; var style = new CKEDITOR.style( styleDefiniton ); // Listen to contextual style activation. editor.attachStyleStateChange( style, function( state ) { !editor.readOnly && editor.getCommand( commandName ).setState( state ); }); // Create the command that can be used to apply the style. editor.addCommand( commandName, new CKEDITOR.styleCommand( style ) ); // Register the button, if the button plugin is loaded. if ( editor.ui.addButton ) { editor.ui.addButton( buttonName, { label: buttonLabel, command: commandName, toolbar: 'basicstyles,' + ( order += 10 ) }); } }; var config = editor.config, lang = editor.lang.basicstyles; addButtonCommand( 'Bold', lang.bold, 'bold', config.coreStyles_bold ); addButtonCommand( 'Italic', lang.italic, 'italic', config.coreStyles_italic ); addButtonCommand( 'Underline', lang.underline, 'underline', config.coreStyles_underline ); addButtonCommand( 'Strike', lang.strike, 'strike', config.coreStyles_strike ); addButtonCommand( 'Subscript', lang.subscript, 'subscript', config.coreStyles_subscript ); addButtonCommand( 'Superscript', lang.superscript, 'superscript', config.coreStyles_superscript ); editor.setKeystroke( [ [ CKEDITOR.CTRL + 66 /*B*/, 'bold' ], [ CKEDITOR.CTRL + 73 /*I*/, 'italic' ], [ CKEDITOR.CTRL + 85 /*U*/, 'underline' ] ] ); } }); // Basic Inline Styles. /** * The style definition that applies the **bold** style to the text. * * config.coreStyles_bold = { element: 'b', overrides: 'strong' }; * * config.coreStyles_bold = { * element: 'span', * attributes: { 'class': 'Bold' } * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.coreStyles_bold = { element: 'strong', overrides: 'b' }; /** * The style definition that applies the *italics* style to the text. * * config.coreStyles_italic = { element: 'i', overrides: 'em' }; * * CKEDITOR.config.coreStyles_italic = { * element: 'span', * attributes: { 'class': 'Italic' } * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.coreStyles_italic = { element: 'em', overrides: 'i' }; /** * The style definition that applies the underline style to the text. * * CKEDITOR.config.coreStyles_underline = { * element: 'span', * attributes: { 'class': 'Underline' } * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.coreStyles_underline = { element: 'u' }; /** * The style definition that applies the strike-through style to the text. * * CKEDITOR.config.coreStyles_strike = { * element: 'span', * attributes: { 'class': 'StrikeThrough' }, * overrides: 'strike' * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.coreStyles_strike = { element: 'strike' }; /** * The style definition that applies the subscript style to the text. * * CKEDITOR.config.coreStyles_subscript = { * element: 'span', * attributes: { 'class': 'Subscript' }, * overrides: 'sub' * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.coreStyles_subscript = { element: 'sub' }; /** * The style definition that applies the superscript style to the text. * * CKEDITOR.config.coreStyles_superscript = { * element: 'span', * attributes: { 'class': 'Superscript' }, * overrides: 'sup' * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.coreStyles_superscript = { element: 'sup' }; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { var guardElements = { table:1,ul:1,ol:1,blockquote:1,div:1 }, directSelectionGuardElements = {}, // All guard elements which can have a direction applied on them. allGuardElements = {}; CKEDITOR.tools.extend( directSelectionGuardElements, guardElements, { tr:1,p:1,div:1,li:1 } ); CKEDITOR.tools.extend( allGuardElements, directSelectionGuardElements, { td:1 } ); function setToolbarStates( editor, path ) { var useComputedState = editor.config.useComputedState, selectedElement; useComputedState = useComputedState === undefined || useComputedState; // We can use computedState provided by the browser or traverse parents manually. if ( !useComputedState ) selectedElement = getElementForDirection( path.lastElement, editor.editable() ); selectedElement = selectedElement || path.block || path.blockLimit; // If we're having BODY here, user probably done CTRL+A, let's try to get the enclosed node, if any. if ( selectedElement.equals( editor.editable() ) ) { var enclosedNode = editor.getSelection().getRanges()[ 0 ].getEnclosedNode(); enclosedNode && enclosedNode.type == CKEDITOR.NODE_ELEMENT && ( selectedElement = enclosedNode ); } if ( !selectedElement ) return; var selectionDir = useComputedState ? selectedElement.getComputedStyle( 'direction' ) : selectedElement.getStyle( 'direction' ) || selectedElement.getAttribute( 'dir' ); editor.getCommand( 'bidirtl' ).setState( selectionDir == 'rtl' ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); editor.getCommand( 'bidiltr' ).setState( selectionDir == 'ltr' ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); } function handleMixedDirContent( editor, path ) { var directionNode = path.block || path.blockLimit || editor.editable(); var pathDir = directionNode.getDirection( 1 ); if ( pathDir != ( editor._.selDir || editor.lang.dir ) ) { editor._.selDir = pathDir; editor.fire( 'contentDirChanged', pathDir ); } } // Returns element with possibility of applying the direction. // @param node function getElementForDirection( node, root ) { while ( node && !( node.getName() in allGuardElements || node.equals( root ) ) ) { var parent = node.getParent(); if ( !parent ) break; node = parent; } return node; } function switchDir( element, dir, editor, database ) { if ( element.isReadOnly() || element.equals( editor.editable() ) ) return; // Mark this element as processed by switchDir. CKEDITOR.dom.element.setMarker( database, element, 'bidi_processed', 1 ); // Check whether one of the ancestors has already been styled. var parent = element, editable = editor.editable(); while ( ( parent = parent.getParent() ) && !parent.equals( editable ) ) { if ( parent.getCustomData( 'bidi_processed' ) ) { // Ancestor style must dominate. element.removeStyle( 'direction' ); element.removeAttribute( 'dir' ); return; } } var useComputedState = ( 'useComputedState' in editor.config ) ? editor.config.useComputedState : 1; var elementDir = useComputedState ? element.getComputedStyle( 'direction' ) : element.getStyle( 'direction' ) || element.hasAttribute( 'dir' ); // Stop if direction is same as present. if ( elementDir == dir ) return; // Clear direction on this element. element.removeStyle( 'direction' ); // Do the second check when computed state is ON, to check // if we need to apply explicit direction on this element. if ( useComputedState ) { element.removeAttribute( 'dir' ); if ( dir != element.getComputedStyle( 'direction' ) ) element.setAttribute( 'dir', dir ); } else // Set new direction for this element. element.setAttribute( 'dir', dir ); editor.forceNextSelectionCheck(); return; } function getFullySelected( range, elements, enterMode ) { var ancestor = range.getCommonAncestor( false, true ); range = range.clone(); range.enlarge( enterMode == CKEDITOR.ENTER_BR ? CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS : CKEDITOR.ENLARGE_BLOCK_CONTENTS ); if ( range.checkBoundaryOfElement( ancestor, CKEDITOR.START ) && range.checkBoundaryOfElement( ancestor, CKEDITOR.END ) ) { var parent; while ( ancestor && ancestor.type == CKEDITOR.NODE_ELEMENT && ( parent = ancestor.getParent() ) && parent.getChildCount() == 1 && !( ancestor.getName() in elements ) ) ancestor = parent; return ancestor.type == CKEDITOR.NODE_ELEMENT && ( ancestor.getName() in elements ) && ancestor; } } function bidiCommand( dir ) { return { // It applies to a "block-like" context. context: 'p', refresh: function( editor, path ) { setToolbarStates( editor, path ); handleMixedDirContent( editor, path ); }, exec: function( editor ) { var selection = editor.getSelection(), enterMode = editor.config.enterMode, ranges = selection.getRanges(); if ( ranges && ranges.length ) { var database = {}; // Creates bookmarks for selection, as we may split some blocks. var bookmarks = selection.createBookmarks(); var rangeIterator = ranges.createIterator(), range, i = 0; while ( ( range = rangeIterator.getNextRange( 1 ) ) ) { // Apply do directly selected elements from guardElements. var selectedElement = range.getEnclosedNode(); // If this is not our element of interest, apply to fully selected elements from guardElements. if ( !selectedElement || selectedElement && !( selectedElement.type == CKEDITOR.NODE_ELEMENT && selectedElement.getName() in directSelectionGuardElements ) ) selectedElement = getFullySelected( range, guardElements, enterMode ); selectedElement && switchDir( selectedElement, dir, editor, database ); var iterator, block; // Walker searching for guardElements. var walker = new CKEDITOR.dom.walker( range ); var start = bookmarks[ i ].startNode, end = bookmarks[ i++ ].endNode; walker.evaluator = function( node ) { return !!( node.type == CKEDITOR.NODE_ELEMENT && node.getName() in guardElements && !( node.getName() == ( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) && node.getParent().type == CKEDITOR.NODE_ELEMENT && node.getParent().getName() == 'blockquote' ) // Element must be fully included in the range as well. (#6485). && node.getPosition( start ) & CKEDITOR.POSITION_FOLLOWING && ( ( node.getPosition( end ) & CKEDITOR.POSITION_PRECEDING + CKEDITOR.POSITION_CONTAINS ) == CKEDITOR.POSITION_PRECEDING ) ); }; while ( ( block = walker.next() ) ) switchDir( block, dir, editor, database ); iterator = range.createIterator(); iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR; while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) ) switchDir( block, dir, editor, database ); } CKEDITOR.dom.element.clearAllMarkers( database ); editor.forceNextSelectionCheck(); // Restore selection position. selection.selectBookmarks( bookmarks ); editor.focus(); } } }; } CKEDITOR.plugins.add( 'bidi', { init: function( editor ) { if ( editor.blockless ) return; // All buttons use the same code to register. So, to avoid // duplications, let's use this tool function. function addButtonCommand( buttonName, buttonLabel, commandName, commandDef, order ) { editor.addCommand( commandName, new CKEDITOR.command( editor, commandDef ) ); if ( editor.ui.addButton ) { editor.ui.addButton( buttonName, { label: buttonLabel, command: commandName, toolbar: 'bidi,' + order }); } } var lang = editor.lang.bidi; if ( editor.ui.addToolbarGroup ) editor.ui.addToolbarGroup( 'bidi', 'align', 'paragraph' ); addButtonCommand( 'BidiLtr', lang.ltr, 'bidiltr', bidiCommand( 'ltr' ), 10 ); addButtonCommand( 'BidiRtl', lang.rtl, 'bidirtl', bidiCommand( 'rtl' ), 20 ); editor.on( 'contentDom', function() { editor.document.on( 'dirChanged', function( evt ) { editor.fire( 'dirChanged', { node: evt.data, dir: evt.data.getDirection( 1 ) }); }); }); // Indicate that the current selection is in different direction than the UI. editor.on( 'contentDirChanged', function( evt ) { var func = ( editor.lang.dir != evt.data ? 'add' : 'remove' ) + 'Class'; var toolbar = editor.ui.space( editor.config.toolbarLocation ); if ( toolbar ) toolbar[ func ]( 'cke_mixed_dir_content' ); }); } }); // If the element direction changed, we need to switch the margins of // the element and all its children, so it will get really reflected // like a mirror. (#5910) function isOffline( el ) { var html = el.getDocument().getBody().getParent(); while ( el ) { if ( el.equals( html ) ) return false; el = el.getParent(); } return true; } function dirChangeNotifier( org ) { var isAttribute = org == elementProto.setAttribute, isRemoveAttribute = org == elementProto.removeAttribute, dirStyleRegexp = /\bdirection\s*:\s*(.*?)\s*(:?$|;)/; return function( name, val ) { if ( !this.isReadOnly() ) { var orgDir; if ( ( name == ( isAttribute || isRemoveAttribute ? 'dir' : 'direction' ) || name == 'style' && ( isRemoveAttribute || dirStyleRegexp.test( val ) ) ) && !isOffline( this ) ) { orgDir = this.getDirection( 1 ); var retval = org.apply( this, arguments ); if ( orgDir != this.getDirection( 1 ) ) { this.getDocument().fire( 'dirChanged', this ); return retval; } } } return org.apply( this, arguments ); }; } var elementProto = CKEDITOR.dom.element.prototype, methods = [ 'setStyle', 'removeStyle', 'setAttribute', 'removeAttribute' ]; for ( var i = 0; i < methods.length; i++ ) elementProto[ methods[ i ] ] = CKEDITOR.tools.override( elementProto[ methods[ i ] ], dirChangeNotifier ); })(); /** * Fired when the language direction of an element is changed. * * @event dirChanged * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. * @param data * @param {CKEDITOR.dom.node} data.node The element that is being changed. * @param {String} data.dir The new direction. */ /** * Fired when the language direction in the specific cursor position is changed * * @event contentDirChanged * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. * @param {String} data The direction in the current position. */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { function noBlockLeft( bqBlock ) { for ( var i = 0, length = bqBlock.getChildCount(), child; i < length && ( child = bqBlock.getChild( i ) ); i++ ) { if ( child.type == CKEDITOR.NODE_ELEMENT && child.isBlockBoundary() ) return false; } return true; } var commandObject = { exec: function( editor ) { var state = editor.getCommand( 'blockquote' ).state, selection = editor.getSelection(), range = selection && selection.getRanges( true )[ 0 ]; if ( !range ) return; var bookmarks = selection.createBookmarks(); // Kludge for #1592: if the bookmark nodes are in the beginning of // blockquote, then move them to the nearest block element in the // blockquote. if ( CKEDITOR.env.ie ) { var bookmarkStart = bookmarks[ 0 ].startNode, bookmarkEnd = bookmarks[ 0 ].endNode, cursor; if ( bookmarkStart && bookmarkStart.getParent().getName() == 'blockquote' ) { cursor = bookmarkStart; while ( ( cursor = cursor.getNext() ) ) { if ( cursor.type == CKEDITOR.NODE_ELEMENT && cursor.isBlockBoundary() ) { bookmarkStart.move( cursor, true ); break; } } } if ( bookmarkEnd && bookmarkEnd.getParent().getName() == 'blockquote' ) { cursor = bookmarkEnd; while ( ( cursor = cursor.getPrevious() ) ) { if ( cursor.type == CKEDITOR.NODE_ELEMENT && cursor.isBlockBoundary() ) { bookmarkEnd.move( cursor ); break; } } } } var iterator = range.createIterator(), block; iterator.enlargeBr = editor.config.enterMode != CKEDITOR.ENTER_BR; if ( state == CKEDITOR.TRISTATE_OFF ) { var paragraphs = []; while ( ( block = iterator.getNextParagraph() ) ) paragraphs.push( block ); // If no paragraphs, create one from the current selection position. if ( paragraphs.length < 1 ) { var para = editor.document.createElement( editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ), firstBookmark = bookmarks.shift(); range.insertNode( para ); para.append( new CKEDITOR.dom.text( '\ufeff', editor.document ) ); range.moveToBookmark( firstBookmark ); range.selectNodeContents( para ); range.collapse( true ); firstBookmark = range.createBookmark(); paragraphs.push( para ); bookmarks.unshift( firstBookmark ); } // Make sure all paragraphs have the same parent. var commonParent = paragraphs[ 0 ].getParent(), tmp = []; for ( var i = 0; i < paragraphs.length; i++ ) { block = paragraphs[ i ]; commonParent = commonParent.getCommonAncestor( block.getParent() ); } // The common parent must not be the following tags: table, tbody, tr, ol, ul. var denyTags = { table:1,tbody:1,tr:1,ol:1,ul:1 }; while ( denyTags[ commonParent.getName() ] ) commonParent = commonParent.getParent(); // Reconstruct the block list to be processed such that all resulting blocks // satisfy parentNode.equals( commonParent ). var lastBlock = null; while ( paragraphs.length > 0 ) { block = paragraphs.shift(); while ( !block.getParent().equals( commonParent ) ) block = block.getParent(); if ( !block.equals( lastBlock ) ) tmp.push( block ); lastBlock = block; } // If any of the selected blocks is a blockquote, remove it to prevent // nested blockquotes. while ( tmp.length > 0 ) { block = tmp.shift(); if ( block.getName() == 'blockquote' ) { var docFrag = new CKEDITOR.dom.documentFragment( editor.document ); while ( block.getFirst() ) { docFrag.append( block.getFirst().remove() ); paragraphs.push( docFrag.getLast() ); } docFrag.replace( block ); } else paragraphs.push( block ); } // Now we have all the blocks to be included in a new blockquote node. var bqBlock = editor.document.createElement( 'blockquote' ); bqBlock.insertBefore( paragraphs[ 0 ] ); while ( paragraphs.length > 0 ) { block = paragraphs.shift(); bqBlock.append( block ); } } else if ( state == CKEDITOR.TRISTATE_ON ) { var moveOutNodes = [], database = {}; while ( ( block = iterator.getNextParagraph() ) ) { var bqParent = null, bqChild = null; while ( block.getParent() ) { if ( block.getParent().getName() == 'blockquote' ) { bqParent = block.getParent(); bqChild = block; break; } block = block.getParent(); } // Remember the blocks that were recorded down in the moveOutNodes array // to prevent duplicates. if ( bqParent && bqChild && !bqChild.getCustomData( 'blockquote_moveout' ) ) { moveOutNodes.push( bqChild ); CKEDITOR.dom.element.setMarker( database, bqChild, 'blockquote_moveout', true ); } } CKEDITOR.dom.element.clearAllMarkers( database ); var movedNodes = [], processedBlockquoteBlocks = []; database = {}; while ( moveOutNodes.length > 0 ) { var node = moveOutNodes.shift(); bqBlock = node.getParent(); // If the node is located at the beginning or the end, just take it out // without splitting. Otherwise, split the blockquote node and move the // paragraph in between the two blockquote nodes. if ( !node.getPrevious() ) node.remove().insertBefore( bqBlock ); else if ( !node.getNext() ) node.remove().insertAfter( bqBlock ); else { node.breakParent( node.getParent() ); processedBlockquoteBlocks.push( node.getNext() ); } // Remember the blockquote node so we can clear it later (if it becomes empty). if ( !bqBlock.getCustomData( 'blockquote_processed' ) ) { processedBlockquoteBlocks.push( bqBlock ); CKEDITOR.dom.element.setMarker( database, bqBlock, 'blockquote_processed', true ); } movedNodes.push( node ); } CKEDITOR.dom.element.clearAllMarkers( database ); // Clear blockquote nodes that have become empty. for ( i = processedBlockquoteBlocks.length - 1; i >= 0; i-- ) { bqBlock = processedBlockquoteBlocks[ i ]; if ( noBlockLeft( bqBlock ) ) bqBlock.remove(); } if ( editor.config.enterMode == CKEDITOR.ENTER_BR ) { var firstTime = true; while ( movedNodes.length ) { node = movedNodes.shift(); if ( node.getName() == 'div' ) { docFrag = new CKEDITOR.dom.documentFragment( editor.document ); var needBeginBr = firstTime && node.getPrevious() && !( node.getPrevious().type == CKEDITOR.NODE_ELEMENT && node.getPrevious().isBlockBoundary() ); if ( needBeginBr ) docFrag.append( editor.document.createElement( 'br' ) ); var needEndBr = node.getNext() && !( node.getNext().type == CKEDITOR.NODE_ELEMENT && node.getNext().isBlockBoundary() ); while ( node.getFirst() ) node.getFirst().remove().appendTo( docFrag ); if ( needEndBr ) docFrag.append( editor.document.createElement( 'br' ) ); docFrag.replace( node ); firstTime = false; } } } } selection.selectBookmarks( bookmarks ); editor.focus(); }, refresh: function( editor, path ) { // Check if inside of blockquote. var firstBlock = path.block || path.blockLimit; this.setState( editor.elementPath( firstBlock ).contains( 'blockquote', 1 ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); }, context: 'blockquote' }; CKEDITOR.plugins.add( 'blockquote', { init: function( editor ) { if ( editor.blockless ) return; editor.addCommand( 'blockquote', commandObject ); editor.ui.addButton && editor.ui.addButton( 'Blockquote', { label: editor.lang.blockquote.toolbar, command: 'blockquote', toolbar: 'blocks,10' }); } }); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @ignore * File overview: Clipboard support. */ // // EXECUTION FLOWS: // -- CTRL+C // * browser's default behaviour // -- CTRL+V // * listen onKey (onkeydown) // * simulate 'beforepaste' for non-IEs on editable // * simulate 'paste' for Fx2/Opera on editable // * listen 'onpaste' on editable ('onbeforepaste' for IE) // * fire 'beforePaste' on editor // * !canceled && getClipboardDataByPastebin // * fire 'paste' on editor // * !canceled && fire 'afterPaste' on editor // -- CTRL+X // * listen onKey (onkeydown) // * fire 'saveSnapshot' on editor // * browser's default behaviour // * deferred second 'saveSnapshot' event // -- Copy command // * tryToCutCopy // * execCommand // * !success && alert // -- Cut command // * fixCut // * tryToCutCopy // * execCommand // * !success && alert // -- Paste command // * fire 'paste' on editable ('beforepaste' for IE) // * !canceled && execCommand 'paste' // * !success && fire 'pasteDialog' on editor // -- Paste from native context menu & menubar // (Fx & Webkits are handled in 'paste' default listner. // Opera cannot be handled at all because it doesn't fire any events // Special treatment is needed for IE, for which is this part of doc) // * listen 'onpaste' // * cancel native event // * fire 'beforePaste' on editor // * !canceled && getClipboardDataByPastebin // * execIECommand( 'paste' ) -> this fires another 'paste' event, so cancel it // * fire 'paste' on editor // * !canceled && fire 'afterPaste' on editor // // // PASTE EVENT - PREPROCESSING: // -- Possible dataValue types: auto, text, html. // -- Possible dataValue contents: // * text (possible \n\r) // * htmlified text (text + br,div,p - no presentional markup & attrs - depends on browser) // * html // -- Possible flags: // * htmlified - if true then content is a HTML even if no markup inside. This flag is set // for content from editable pastebins, because they 'htmlify' pasted content. // // -- Type: auto: // * content: htmlified text -> filter, unify text markup (brs, ps, divs), set type: text // * content: html -> filter, set type: html // -- Type: text: // * content: htmlified text -> filter, unify text markup // * content: html -> filter, strip presentional markup, unify text markup // -- Type: html: // * content: htmlified text -> filter, unify text markup // * content: html -> filter // // -- Phases: // * filtering (priorities 3-5) - e.g. pastefromword filters // * content type sniffing (priority 6) // * markup transformations for text (priority 6) // 'use strict'; (function() { // Register the plugin. CKEDITOR.plugins.add( 'clipboard', { requires: 'dialog', init: function( editor ) { var textificationFilter; initClipboard( editor ); CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) ); editor.on( 'paste', function( evt ) { var data = evt.data.dataValue, blockElements = CKEDITOR.dtd.$block; // Filter webkit garbage. if ( data.indexOf( 'Apple-' ) > -1 ) { // Replace special webkit's   with simple space, because webkit // produces them even for normal spaces. data = data.replace( / <\/span>/gi, ' ' ); // Strip around white-spaces when not in forced 'html' content type. // This spans are created only when pasting plain text into Webkit, // but for safety reasons remove them always. if ( evt.data.type != 'html' ) data = data.replace( /]*>([^<]*)<\/span>/gi, function( all, spaces ) { // Replace tabs with 4 spaces like Fx does. return spaces.replace( /\t/g, '    ' ); }); // This br is produced only when copying & pasting HTML content. if ( data.indexOf( '
    ' ) > -1 ) { evt.data.startsWithEOL = 1; evt.data.preSniffing = 'html'; // Mark as not text. data = data.replace( /
    /, '' ); } // Remove all other classes. data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' ); } // Strip editable that was copied from inside. (#9534) if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) { var tmp, editable_wrapper, wrapper = new CKEDITOR.dom.element( 'div' ); wrapper.setHtml( data ); // Verify for sure and check for nested editor UI parts. (#9675) while ( wrapper.getChildCount() == 1 && ( tmp = wrapper.getFirst() ) && tmp.type == CKEDITOR.NODE_ELEMENT && // Make sure first-child is element. ( tmp.hasClass( 'cke_editable' ) || tmp.hasClass( 'cke_contents' ) ) ) { wrapper = editable_wrapper = tmp; } // If editable wrapper was found strip it and bogus
    (added on FF). if ( editable_wrapper ) data = editable_wrapper.getHtml().replace( /
    $/i, '' ); } if ( CKEDITOR.env.ie ) { //  

    ->

    (br.cke-pasted-remove will be removed later) data = data.replace( /^ (?: |\r\n)?<(\w+)/g, function( match, elementName ) { if ( elementName.toLowerCase() in blockElements ) { evt.data.preSniffing = 'html'; // Mark as not a text. return '<' + elementName; } return match; }); } else if ( CKEDITOR.env.webkit ) { //


    ->


    // We don't mark br, because this situation can happen for htmlified text too. data = data.replace( /<\/(\w+)>

    <\/div>$/, function( match, elementName ) { if ( elementName in blockElements ) { evt.data.endsWithEOL = 1; return ''; } return match; }); } else if ( CKEDITOR.env.gecko ) { // Firefox adds bogus
    when user pasted text followed by space(s). data = data.replace( /(\s)
    $/, '$1' ); } evt.data.dataValue = data; }, null, null, 3 ); editor.on( 'paste', function( evt ) { var dataObj = evt.data, type = dataObj.type, data = dataObj.dataValue, trueType, // Default is 'html'. defaultType = editor.config.clipboard_defaultContentType || 'html'; // If forced type is 'html' we don't need to know true data type. if ( type == 'html' || dataObj.preSniffing == 'html' ) trueType = 'html'; else trueType = recogniseContentType( data ); // Unify text markup. if ( trueType == 'htmlifiedtext' ) data = htmlifiedTextHtmlification( editor.config, data ); // Strip presentional markup & unify text markup. else if ( type == 'text' && trueType == 'html' ) { // Init filter only if needed and cache it. data = htmlTextification( editor.config, data, textificationFilter || ( textificationFilter = getTextificationFilter( editor ) ) ); } if ( dataObj.startsWithEOL ) data = '
    ' + data; if ( dataObj.endsWithEOL ) data += '
    '; if ( type == 'auto' ) type = ( trueType == 'html' || defaultType == 'html' ) ? 'html' : 'text'; dataObj.type = type; dataObj.dataValue = data; delete dataObj.preSniffing; delete dataObj.startsWithEOL; delete dataObj.endsWithEOL; }, null, null, 6 ); // Inserts processed data into the editor at the end of the // events chain. editor.on( 'paste', function( evt ) { var data = evt.data; editor.insertHtml( data.dataValue, data.type ); // Deferr 'afterPaste' so all other listeners for 'paste' will be fired first. setTimeout( function() { editor.fire( 'afterPaste' ); }, 0 ); }, null, null, 1000 ); editor.on( 'pasteDialog', function( evt ) { // TODO it's possible that this setTimeout is not needed any more, // because of changes introduced in the same commit as this comment. // Editor.getClipboardData adds listner to the dialog's events which are // fired after a while (not like 'showDialog'). setTimeout( function() { // Open default paste dialog. editor.openDialog( 'paste', evt.data ); }, 0 ); }); } }); function initClipboard( editor ) { var preventBeforePasteEvent = 0, preventPasteEvent = 0, inReadOnly = 0, // Safari doesn't like 'beforepaste' event - it sometimes doesn't // properly handles ctrl+c. Probably some race-condition between events. // Chrome and Firefox works well with both events, so better to use 'paste' // which will handle pasting from e.g. browsers' menu bars. // IE7/8 doesn't like 'paste' event for which it's throwing random errors. mainPasteEvent = CKEDITOR.env.ie ? 'beforepaste' : 'paste'; addListeners(); addButtonsCommands(); /** * Gets clipboard data by directly accessing the clipboard (IE only) or opening paste dialog. * * editor.getClipboardData( { title: 'Get my data' }, function( data ) { * if ( data ) * alert( data.type + ' ' + data.dataValue ); * } ); * * @member CKEDITOR.editor * @param {Object} options * @param {String} [options.title] Title of paste dialog. * @param {Function} callback Function that will be executed with `data.type` and `data.dataValue` * or `null` if none of the capturing method succeeded. */ editor.getClipboardData = function( options, callback ) { var beforePasteNotCanceled = false, dataType = 'auto', dialogCommited = false; // Options are optional - args shift. if ( !callback ) { callback = options; options = null; } // Listen with maximum priority to handle content before everyone else. // This callback will handle paste event that will be fired if direct // access to the clipboard succeed in IE. editor.on( 'paste', onPaste, null, null, 0 ); // Listen at the end of listeners chain to see if event wasn't canceled // and to retrieve modified data.type. editor.on( 'beforePaste', onBeforePaste, null, null, 1000 ); // getClipboardDataDirectly() will fire 'beforePaste' synchronously, so we can // check if it was canceled and if any listener modified data.type. // If command didn't succeed (only IE allows to access clipboard and only if // user agrees) open and handle paste dialog. if ( getClipboardDataDirectly() === false ) { // Direct access to the clipboard wasn't successful so remove listener. editor.removeListener( 'paste', onPaste ); // If beforePaste was canceled do not open dialog. // Add listeners only if dialog really opened. 'pasteDialog' can be canceled. if ( beforePasteNotCanceled && editor.fire( 'pasteDialog', onDialogOpen ) ) { editor.on( 'pasteDialogCommit', onDialogCommit ); // 'dialogHide' will be fired after 'pasteDialogCommit'. editor.on( 'dialogHide', function( evt ) { evt.removeListener(); evt.data.removeListener( 'pasteDialogCommit', onDialogCommit ); // Because Opera has to wait a while in pasteDialog we have to wait here. setTimeout( function() { // Notify even if user canceled dialog (clicked 'cancel', ESC, etc). if ( !dialogCommited ) callback( null ); }, 10 ); }); } else callback( null ); } function onPaste( evt ) { evt.removeListener(); evt.cancel(); callback( evt.data ); } function onBeforePaste( evt ) { evt.removeListener(); beforePasteNotCanceled = true; dataType = evt.data.type; } function onDialogCommit( evt ) { evt.removeListener(); // Cancel pasteDialogCommit so paste dialog won't automatically fire // 'paste' evt by itself. evt.cancel(); dialogCommited = true; callback( { type: dataType, dataValue: evt.data } ); } function onDialogOpen() { this.customTitle = ( options && options.title ); } }; function addButtonsCommands() { addButtonCommand( 'Cut', 'cut', createCutCopyCmd( 'cut' ), 10, 1 ); addButtonCommand( 'Copy', 'copy', createCutCopyCmd( 'copy' ), 20, 4 ); addButtonCommand( 'Paste', 'paste', createPasteCmd(), 30, 8 ); function addButtonCommand( buttonName, commandName, command, toolbarOrder, ctxMenuOrder ) { var lang = editor.lang.clipboard[ commandName ]; editor.addCommand( commandName, command ); editor.ui.addButton && editor.ui.addButton( buttonName, { label: lang, command: commandName, toolbar: 'clipboard,' + toolbarOrder }); // If the "menu" plugin is loaded, register the menu item. if ( editor.addMenuItems ) { editor.addMenuItem( commandName, { label: lang, command: commandName, group: 'clipboard', order: ctxMenuOrder }); } } } function addListeners() { editor.on( 'key', onKey ); editor.on( 'contentDom', addListenersToEditable ); // For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that. editor.on( 'selectionChange', function( evt ) { inReadOnly = evt.data.selection.getRanges()[ 0 ].checkReadOnly(); setToolbarStates(); }); // If the "contextmenu" plugin is loaded, register the listeners. if ( editor.contextMenu ) { editor.contextMenu.addListener( function( element, selection ) { inReadOnly = selection.getRanges()[ 0 ].checkReadOnly(); return { cut: stateFromNamedCommand( 'Cut' ), copy: stateFromNamedCommand( 'Copy' ), paste: stateFromNamedCommand( 'Paste' ) }; }); } } // Add events listeners to editable. function addListenersToEditable() { var editable = editor.editable(); // We'll be catching all pasted content in one line, regardless of whether // it's introduced by a document command execution (e.g. toolbar buttons) or // user paste behaviors (e.g. CTRL+V). editable.on( mainPasteEvent, function( evt ) { if ( CKEDITOR.env.ie && preventBeforePasteEvent ) return; // If you've just asked yourself why preventPasteEventNow() is not here, but // in listener for CTRL+V and exec method of 'paste' command // you've asked the same question we did. // // THE ANSWER: // // First thing to notice - this answer makes sense only for IE, // because other browsers don't listen for 'paste' event. // // What would happen if we move preventPasteEventNow() here? // For: // * CTRL+V - IE fires 'beforepaste', so we prevent 'paste' and pasteDataFromClipboard(). OK. // * editor.execCommand( 'paste' ) - we fire 'beforepaste', so we prevent // 'paste' and pasteDataFromClipboard() and doc.execCommand( 'Paste' ). OK. // * native context menu - IE fires 'beforepaste', so we prevent 'paste', but unfortunately // on IE we fail with pasteDataFromClipboard() here, because of... we don't know why, but // we just fail, so... we paste nothing. FAIL. // * native menu bar - the same as for native context menu. // // But don't you know any way to distinguish first two cases from last two? // Only one - special flag set in CTRL+V handler and exec method of 'paste' // command. And that's what we did using preventPasteEventNow(). pasteDataFromClipboard( evt ); }); // It's not possible to clearly handle all four paste methods (ctrl+v, native menu bar // native context menu, editor's command) in one 'paste/beforepaste' event in IE. // // For ctrl+v & editor's command it's easy to handle pasting in 'beforepaste' listener, // so we do this. For another two methods it's better to use 'paste' event. // // 'paste' is always being fired after 'beforepaste' (except of weird one on opening native // context menu), so for two methods handled in 'beforepaste' we're canceling 'paste' // using preventPasteEvent state. // // 'paste' event in IE is being fired before getClipboardDataByPastebin executes its callback. // // QUESTION: Why didn't you handle all 4 paste methods in handler for 'paste'? // Wouldn't this just be simpler? // ANSWER: Then we would have to evt.data.preventDefault() only for native // context menu and menu bar pastes. The same with execIECommand(). // That would force us to mark CTRL+V and editor's paste command with // special flag, other than preventPasteEvent. But we still would have to // have preventPasteEvent for the second event fired by execIECommand. // Code would be longer and not cleaner. CKEDITOR.env.ie && editable.on( 'paste', function( evt ) { if ( preventPasteEvent ) return; // Cancel next 'paste' event fired by execIECommand( 'paste' ) // at the end of this callback. preventPasteEventNow(); // Prevent native paste. evt.data.preventDefault(); pasteDataFromClipboard( evt ); // Force IE to paste content into pastebin so pasteDataFromClipboard will work. if ( !execIECommand( 'paste' ) ) editor.openDialog( 'paste' ); }); // [IE] Dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (#7953) if ( CKEDITOR.env.ie ) { editable.on( 'contextmenu', preventBeforePasteEventNow, null, null, 0 ); editable.on( 'beforepaste', function( evt ) { if ( evt.data && !evt.data.$.ctrlKey ) preventBeforePasteEventNow(); }, null, null, 0 ); } editable.on( 'beforecut', function() { !preventBeforePasteEvent && fixCut( editor ); }); // Use editor.document instead of editable in non-IEs for observing mouseup // since editable won't fire the event if selection process started within // iframe and ended out of the editor (#9851). ( CKEDITOR.env.ie ? editable : editor.document.getDocumentElement() ).on( 'mouseup', function() { setTimeout( function() { setToolbarStates(); }, 0 ); }); editable.on( 'keyup', setToolbarStates ); } // Create object representing Cut or Copy commands. function createCutCopyCmd( type ) { return { type: type, canUndo: type == 'cut', // We can't undo copy to clipboard. startDisabled: true, exec: function( data ) { // Attempts to execute the Cut and Copy operations. function tryToCutCopy( type ) { if ( CKEDITOR.env.ie ) return execIECommand( type ); // non-IEs part try { // Other browsers throw an error if the command is disabled. return editor.document.$.execCommand( type, false, null ); } catch ( e ) { return false; } } this.type == 'cut' && fixCut(); var success = tryToCutCopy( this.type ); if ( !success ) alert( editor.lang.clipboard[ this.type + 'Error' ] ); // Show cutError or copyError. return success; } }; } function createPasteCmd() { return { // Snapshots are done manually by editable.insertXXX methods. canUndo: false, async: true, exec: function( editor, data ) { var fire = function( data, withBeforePaste ) { data && firePasteEvents( data.type, data.dataValue, !!withBeforePaste ); editor.fire( 'afterCommandExec', { name: 'paste', command: cmd, returnValue: !!data }); }, cmd = this; // Check data precisely - don't open dialog on empty string. if ( typeof data == 'string' ) fire( { type: 'auto', dataValue: data }, 1 ); else editor.getClipboardData( fire ); } }; } function preventPasteEventNow() { preventPasteEvent = 1; // For safety reason we should wait longer than 0/1ms. // We don't know how long execution of quite complex getClipboardData will take // and in for example 'paste' listner execCommand() (which fires 'paste') is called // after getClipboardData finishes. // Luckily, it's impossible to immediately fire another 'paste' event we want to handle, // because we only handle there native context menu and menu bar. setTimeout( function() { preventPasteEvent = 0; }, 100 ); } function preventBeforePasteEventNow() { preventBeforePasteEvent = 1; setTimeout( function() { preventBeforePasteEvent = 0; }, 10 ); } // Tries to execute any of the paste, cut or copy commands in IE. Returns a // boolean indicating that the operation succeeded. // @param {String} command *LOWER CASED* name of command ('paste', 'cut', 'copy'). function execIECommand( command ) { var doc = editor.document, body = doc.getBody(), enabled = false, onExec = function() { enabled = true; }; // The following seems to be the only reliable way to detect that // clipboard commands are enabled in IE. It will fire the // onpaste/oncut/oncopy events only if the security settings allowed // the command to execute. body.on( command, onExec ); // IE6/7: document.execCommand has problem to paste into positioned element. ( CKEDITOR.env.version > 7 ? doc.$ : doc.$.selection.createRange() )[ 'execCommand' ]( command ); body.removeListener( command, onExec ); return enabled; } function firePasteEvents( type, data, withBeforePaste ) { var eventData = { type: type }; if ( withBeforePaste ) { // Fire 'beforePaste' event so clipboard flavor get customized // by other plugins. if ( !editor.fire( 'beforePaste', eventData ) ) return false; // Event canceled } // The very last guard to make sure the paste has successfully happened. // This check should be done after firing 'beforePaste' because for native paste // 'beforePaste' is by default fired even for empty clipboard. if ( !data ) return false; // Reuse eventData.type because the default one could be changed by beforePaste listeners. eventData.dataValue = data; return editor.fire( 'paste', eventData ); } // Cutting off control type element in IE standards breaks the selection entirely. (#4881) function fixCut() { if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks ) return; var sel = editor.getSelection(), control, range, dummy; if ( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) ) { range = sel.getRanges()[ 0 ]; dummy = editor.document.createText( '' ); dummy.insertBefore( control ); range.setStartBefore( dummy ); range.setEndAfter( control ); sel.selectRanges( [ range ] ); // Clear up the fix if the paste wasn't succeeded. setTimeout( function() { // Element still online? if ( control.getParent() ) { dummy.remove(); sel.selectElement( control ); } }, 0 ); } } // Allow to peek clipboard content by redirecting the // pasting content into a temporary bin and grab the content of it. function getClipboardDataByPastebin( evt, callback ) { var doc = editor.document, editable = editor.editable(), cancel = function( evt ) { evt.cancel(); }, ff3x = CKEDITOR.env.gecko && CKEDITOR.env.version <= 10902; // Avoid recursions on 'paste' event or consequent paste too fast. (#5730) if ( doc.getById( 'cke_pastebin' ) ) return; var sel = editor.getSelection(); var bms = sel.createBookmarks(); // Create container to paste into. // For rich content we prefer to use "body" since it holds // the least possibility to be splitted by pasted content, while this may // breaks the text selection on a frame-less editable, "div" would be // the best one in that case. // In another case on old IEs moving the selection into a "body" paste bin causes error panic. // Body can't be also used for Opera which fills it with
    // what is indistinguishable from pasted
    (copying
    in Opera isn't possible, // but it can be copied from other browser). var pastebin = new CKEDITOR.dom.element( editable.is( 'body' ) && !( CKEDITOR.env.ie || CKEDITOR.env.opera ) ? 'body' : 'div', doc ); pastebin.setAttribute( 'id', 'cke_pastebin' ); // Append bogus to prevent Opera from doing this. (#9522) if ( CKEDITOR.env.opera ) pastebin.appendBogus(); var containerOffset = 0, win = doc.getWindow(); // Seems to be the only way to avoid page scroll in Fx 3.x. if ( ff3x ) { pastebin.insertAfter( bms[ 0 ].startNode ); pastebin.setStyle( 'display', 'inline' ); } else { if ( CKEDITOR.env.webkit ) { // It's better to paste close to the real paste destination, so inherited styles // (which Webkits will try to compensate by styling span) differs less from the destination's one. editable.append( pastebin ); // Style pastebin like .cke_editable, to minimize differences between origin and destination. (#9754) pastebin.addClass( 'cke_editable' ); // Compensate position of offsetParent. containerOffset = ( editable.is( 'body' ) ? editable : CKEDITOR.dom.element.get( pastebin.$.offsetParent ) ).getDocumentPosition().y; } else { // Opera and IE doesn't allow to append to html element. editable.getAscendant( CKEDITOR.env.ie || CKEDITOR.env.opera ? 'body' : 'html', 1 ).append( pastebin ); } pastebin.setStyles({ position: 'absolute', // Position the bin at the top (+10 for safety) of viewport to avoid any subsequent document scroll. top: ( win.getScrollPosition().y - containerOffset + 10 ) + 'px', width: '1px', // Caret has to fit in that height, otherwise browsers like Chrome & Opera will scroll window to show it. // Set height equal to viewport's height - 20px (safety gaps), minimum 1px. height: Math.max( 1, win.getViewPaneSize().height - 20 ) + 'px', overflow: 'hidden', // Reset styles that can mess up pastebin position. margin: 0, padding: 0 }); } // Check if the paste bin now establishes new editing host. var isEditingHost = pastebin.getParent().isReadOnly(); if ( isEditingHost ) { // Hide the paste bin. pastebin.setOpacity( 0 ); // And make it editable. pastebin.setAttribute( 'contenteditable', true ); } // Transparency is not enough since positioned non-editing host always shows // resize handler, pull it off the screen instead. else pastebin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-1000px' ); editor.on( 'selectionChange', cancel, null, null, 0 ); // Temporarily move selection to the pastebin. isEditingHost && pastebin.focus(); var range = new CKEDITOR.dom.range( pastebin ); range.selectNodeContents( pastebin ); var selPastebin = range.select(); // If non-native paste is executed, IE will open security alert and blur editable. // Editable will then lock selection inside itself and after accepting security alert // this selection will be restored. We overwrite stored selection, so it's restored // in pastebin. (#9552) if ( CKEDITOR.env.ie ) { var blurListener = editable.once( 'blur', function( evt ) { editor.lockSelection( selPastebin ); } ); } var scrollTop = CKEDITOR.document.getWindow().getScrollPosition().y; // Wait a while and grab the pasted contents. setTimeout( function() { // Restore main window's scroll position which could have been changed // by browser in cases described in #9771. if ( CKEDITOR.env.webkit || CKEDITOR.env.opera ) CKEDITOR.document[ CKEDITOR.env.webkit ? 'getBody' : 'getDocumentElement' ]().$.scrollTop = scrollTop; // Blur will be fired only on non-native paste. In other case manually remove listener. blurListener && blurListener.removeListener(); // Restore properly the document focus. (#8849) if ( CKEDITOR.env.ie ) editable.focus(); // IE7: selection must go before removing pastebin. (#8691) sel.selectBookmarks( bms ); pastebin.remove(); // Grab the HTML contents. // We need to look for a apple style wrapper on webkit it also adds // a div wrapper if you copy/paste the body of the editor. // Remove hidden div and restore selection. var bogusSpan; if ( CKEDITOR.env.webkit && ( bogusSpan = pastebin.getFirst() ) && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) ) pastebin = bogusSpan; editor.removeListener( 'selectionChange', cancel ); callback( pastebin.getHtml() ); }, 0 ); } // Try to get content directly from clipboard, without native event // being fired before. In other words - synthetically get clipboard data // if it's possible. // mainPasteEvent will be fired, so if forced native paste: // * worked, getClipboardDataByPastebin will grab it, // * didn't work, pastebin will be empty and editor#paste won't be fired. function getClipboardDataDirectly() { if ( CKEDITOR.env.ie ) { // Prevent IE from pasting at the begining of the document. editor.focus(); // Command will be handled by 'beforepaste', but as // execIECommand( 'paste' ) will fire also 'paste' event // we're canceling it. preventPasteEventNow(); // #9247: Lock focus to prevent IE from hiding toolbar for inline editor. var focusManager = editor.focusManager; focusManager.lock(); if ( editor.editable().fire( mainPasteEvent ) && !execIECommand( 'paste' ) ) { focusManager.unlock(); return false; } focusManager.unlock(); } else { try { if ( editor.editable().fire( mainPasteEvent ) && !editor.document.$.execCommand( 'Paste', false, null ) ) { throw 0; } } catch ( e ) { return false; } } return true; } // Listens for some clipboard related keystrokes, so they get customized. // Needs to be bind to keydown event. function onKey( event ) { if ( editor.mode != 'wysiwyg' ) return; switch ( event.data.keyCode ) { // Paste case CKEDITOR.CTRL + 86: // CTRL+V case CKEDITOR.SHIFT + 45: // SHIFT+INS var editable = editor.editable(); // Cancel 'paste' event because ctrl+v is for IE handled // by 'beforepaste'. preventPasteEventNow(); // Simulate 'beforepaste' event for all none-IEs. !CKEDITOR.env.ie && editable.fire( 'beforepaste' ); // Simulate 'paste' event for Opera/Firefox2. if ( CKEDITOR.env.opera || CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) editable.fire( 'paste' ); return; // Cut case CKEDITOR.CTRL + 88: // CTRL+X case CKEDITOR.SHIFT + 46: // SHIFT+DEL // Save Undo snapshot. editor.fire( 'saveSnapshot' ); // Save before cut setTimeout( function() { editor.fire( 'saveSnapshot' ); // Save after cut }, 0 ); } } function pasteDataFromClipboard( evt ) { // Default type is 'auto', but can be changed by beforePaste listeners. var eventData = { type: 'auto' }; // Fire 'beforePaste' event so clipboard flavor get customized by other plugins. // If 'beforePaste' is canceled continue executing getClipboardDataByPastebin and then do nothing // (do not fire 'paste', 'afterPaste' events). This way we can grab all - synthetically // and natively pasted content and prevent its insertion into editor // after canceling 'beforePaste' event. var beforePasteNotCanceled = editor.fire( 'beforePaste', eventData ); getClipboardDataByPastebin( evt, function( data ) { // Clean up. data = data.replace( /]+data-cke-bookmark[^<]*?<\/span>/ig, '' ); // Fire remaining events (without beforePaste) beforePasteNotCanceled && firePasteEvents( eventData.type, data, 0, 1 ); }); } function setToolbarStates() { if ( editor.mode != 'wysiwyg' ) return; var pasteState = stateFromNamedCommand( 'Paste' ); editor.getCommand( 'cut' ).setState( stateFromNamedCommand( 'Cut' ) ); editor.getCommand( 'copy' ).setState( stateFromNamedCommand( 'Copy' ) ); editor.getCommand( 'paste' ).setState( pasteState ); editor.fire( 'pasteState', pasteState ); } function stateFromNamedCommand( command ) { var retval; if ( inReadOnly && command in { Paste:1,Cut:1 } ) return CKEDITOR.TRISTATE_DISABLED; if ( command == 'Paste' ) { // IE Bug: queryCommandEnabled('paste') fires also 'beforepaste(copy/cut)', // guard to distinguish from the ordinary sources (either // keyboard paste or execCommand) (#4874). CKEDITOR.env.ie && ( preventBeforePasteEvent = 1 ); try { // Always return true for Webkit (which always returns false) retval = editor.document.$.queryCommandEnabled( command ) || CKEDITOR.env.webkit; } catch ( er ) {} preventBeforePasteEvent = 0; } // Cut, Copy - check if the selection is not empty else { var sel = editor.getSelection(), ranges = sel.getRanges(); retval = sel.getType() != CKEDITOR.SELECTION_NONE && !( ranges.length == 1 && ranges[ 0 ].collapsed ); } return retval ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; } } // Returns: // * 'htmlifiedtext' if content looks like transformed by browser from plain text. // See clipboard/paste.html TCs for more info. // * 'html' if it is not 'htmlifiedtext'. function recogniseContentType( data ) { if ( CKEDITOR.env.webkit ) { // Plain text or (

    and text inside
    ). if ( !data.match( /^[^<]*$/g ) && !data.match( /^(
    <\/div>|
    [^<]*<\/div>)*$/gi ) ) return 'html'; } else if ( CKEDITOR.env.ie ) { // Text and
    or ( text and
    in

    - paragraphs can be separated by new \r\n ). if ( !data.match( /^([^<]|)*$/gi ) && !data.match( /^(

    ([^<]|)*<\/p>|(\r\n))*$/gi ) ) return 'html'; } else if ( CKEDITOR.env.gecko || CKEDITOR.env.opera ) { // Text or
    . if ( !data.match( /^([^<]|)*$/gi ) ) return 'html'; } else return 'html'; return 'htmlifiedtext'; } // This function transforms what browsers produce when // pasting plain text into editable element (see clipboard/paste.html TCs // for more info) into correct HTML (similar to that produced by text2Html). function htmlifiedTextHtmlification( config, data ) { function repeatParagraphs( repeats ) { // Repeat blocks floor((n+1)/2) times. // Even number of repeats - add
    at the beginning of last

    . return CKEDITOR.tools.repeat( '

    ', ~~ ( repeats / 2 ) ) + ( repeats % 2 == 1 ? '
    ' : '' ); } // Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space. data = data.replace( /\s+/g, ' ' ) // Remove spaces from between tags. .replace( /> +<' ) // Normalize XHTML syntax and upper cased
    tags. .replace( /
    /gi, '
    ' ); // IE - lower cased tags. data = data.replace( /<\/?[A-Z]+>/g, function( match ) { return match.toLowerCase(); }); // Don't touch single lines (no ) - nothing to do here. if ( data.match( /^[^<]$/ ) ) return data; // Webkit. if ( CKEDITOR.env.webkit && data.indexOf( '

    ' ) > -1 ) { // One line break at the beginning - insert
    data = data.replace( /^(
    (
    |)<\/div>)(?!$|(
    (
    |)<\/div>))/g, '
    ' ) // Two or more - reduce number of new lines by one. .replace( /^(
    (
    |)<\/div>){2}(?!$)/g, '
    ' ); // Two line breaks create one paragraph in Webkit. if ( data.match( /
    (
    |)<\/div>/ ) ) { data = '

    ' + data.replace( /(

    (
    |)<\/div>)+/g, function( match ) { return repeatParagraphs( match.split( '
    ' ).length + 1 ); }) + '

    '; } // One line break create br. data = data.replace( /<\/div>
    /g, '
    ' ); // Remove remaining divs. data = data.replace( /<\/?div>/g, '' ); } // Opera and Firefox and enterMode != BR. if ( ( CKEDITOR.env.gecko || CKEDITOR.env.opera ) && config.enterMode != CKEDITOR.ENTER_BR ) { // Remove bogus
    - Fx generates two for one line break. // For two line breaks it still produces two , but it's better to ignore this case than the first one. if ( CKEDITOR.env.gecko ) data = data.replace( /^

    $/, '
    ' ); // This line satisfy edge case when for Opera we have two line breaks //data = data.replace( /) if ( data.indexOf( '

    ' ) > -1 ) { // Two line breaks create one paragraph, three - 2, four - 3, etc. data = '

    ' + data.replace( /(
    ){2,}/g, function( match ) { return repeatParagraphs( match.length / 4 ); }) + '

    '; } } return switchEnterMode( config, data ); } // Filter can be editor dependent. function getTextificationFilter( editor ) { var filter = new CKEDITOR.htmlParser.filter(); // Elements which creates vertical breaks (have vert margins) - took from HTML5 spec. // http://dev.w3.org/html5/markup/Overview.html#toc var replaceWithParaIf = { blockquote:1,dl:1,fieldset:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,ol:1,p:1,table:1,ul:1 }, // All names except of
    . stripInlineIf = CKEDITOR.tools.extend({ br: 0 }, CKEDITOR.dtd.$inline ), // What's finally allowed (cke:br will be removed later). allowedIf = { p:1,br:1,'cke:br':1 }, knownIf = CKEDITOR.dtd, // All names that will be removed (with content). removeIf = CKEDITOR.tools.extend( { area:1,basefont:1,embed:1,iframe:1,map:1,object:1,param:1 }, CKEDITOR.dtd.$nonBodyContent, CKEDITOR.dtd.$cdata ); var flattenTableCell = function( element ) { delete element.name; element.add( new CKEDITOR.htmlParser.text( ' ' ) ); }, // Squash adjacent headers into one.

    A

    B

    ->

    A
    B

    // Empty ones will be removed later. squashHeader = function( element ) { var next = element, br, el; while ( ( next = next.next ) && next.name && next.name.match( /^h\d$/ ) ) { // TODO shitty code - waitin' for htmlParse.element fix. br = new CKEDITOR.htmlParser.element( 'cke:br' ); br.isEmpty = true; element.add( br ); while ( ( el = next.children.shift() ) ) element.add( el ); } }; filter.addRules({ elements: { h1: squashHeader, h2: squashHeader, h3: squashHeader, h4: squashHeader, h5: squashHeader, h6: squashHeader, img: function( element ) { var alt = CKEDITOR.tools.trim( element.attributes.alt || '' ), txt = ' '; // Replace image with its alt if it doesn't look like an url or is empty. if ( alt && !alt.match( /(^http|\.(jpe?g|gif|png))/i ) ) txt = ' [' + alt + '] '; return new CKEDITOR.htmlParser.text( txt ); }, td: flattenTableCell, th: flattenTableCell, $: function( element ) { var initialName = element.name, br; // Remove entirely. if ( removeIf[ initialName ] ) return false; // Remove all attributes. delete element.attributes; // Pass brs. if ( initialName == 'br' ) return element; // Elements that we want to replace with paragraphs. if ( replaceWithParaIf[ initialName ] ) element.name = 'p'; // Elements that we want to strip (tags only, without the content). else if ( stripInlineIf[ initialName ] ) delete element.name; // Surround other known element with and strip tags. else if ( knownIf[ initialName ] ) { // TODO shitty code - waitin' for htmlParse.element fix. br = new CKEDITOR.htmlParser.element( 'cke:br' ); br.isEmpty = true; // Replace hrs (maybe sth else too?) with only one br. if ( CKEDITOR.dtd.$empty[ initialName ] ) return br; element.add( br, 0 ); br = br.clone(); br.isEmpty = true; element.add( br ); delete element.name; } // Final cleanup - if we can still find some not allowed elements then strip their names. if ( !allowedIf[ element.name ] ) delete element.name; return element; } } }); return filter; } function htmlTextification( config, data, filter ) { var fragment = new CKEDITOR.htmlParser.fragment.fromHtml( data ), writer = new CKEDITOR.htmlParser.basicWriter(); fragment.writeHtml( writer, filter ); data = writer.getHtml(); // Cleanup cke:brs. data = data.replace( /\s*(<\/?[a-z:]+ ?\/?>)\s*/g, '$1' ) // Remove spaces around tags. .replace( /(){2,}/g, '' ) // Join multiple adjacent cke:brs .replace( /()(<\/?p>|
    )/g, '$2' ) // Strip cke:brs adjacent to original brs or ps. .replace( /(<\/?p>|
    )()/g, '$1' ) .replace( /<(cke:)?br( \/)?>/g, '
    ' ) // Finally - rename cke:brs to brs and fix
    to
    . .replace( /

    <\/p>/g, '' ); // Remove empty paragraphs. // Fix nested ps. E.g.: //

    A

    B

    C

    D

    E

    F

    G //

    A

    B

    C

    D

    E

    F

    G var nested = 0; data = data.replace( /<\/?p>/g, function( match ) { if ( match == '

    ' ) { if ( ++nested > 1 ) return '

    '; } else { if ( --nested > 0 ) return '

    '; } return match; }).replace( /

    <\/p>/g, '' ); // Step before:

    ->

    . Fix this here. return switchEnterMode( config, data ); } function switchEnterMode( config, data ) { if ( config.enterMode == CKEDITOR.ENTER_BR ) { data = data.replace( /(<\/p>

    )+/g, function( match ) { return CKEDITOR.tools.repeat( '
    ', match.length / 7 * 2 ); }).replace( /<\/?p>/g, '' ); } else if ( config.enterMode == CKEDITOR.ENTER_DIV ) { data = data.replace( /<(\/)?p>/g, '<$1div>' ); } return data; } })(); /** * The default content type is used when pasted data cannot be clearly recognized as HTML or text. * * For example: `'foo'` may come from a plain text editor or a website. It isn't possible to recognize content * type in this case, so default will be used. However, it's clear that `'example text'` is an HTML * and its origin is webpage, email or other rich text editor. * * **Note:** If content type is text, then styles of context of paste are preserved. * * CKEDITOR.config.clipboard_defaultContentType = 'text'; * * @since 4.0 * @cfg {'html'/'text'} [clipboard_defaultContentType='html'] * @member CKEDITOR.config */ /** * Fired when a clipboard operation is about to be taken into the editor. * Listeners can manipulate the data to be pasted before having it effectively * inserted into the document. * * @since 3.1 * @event paste * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. * @param data * @param {String} data.type Type of data in `data.dataValue`. Usually `html` or `text`, but for listeners * with priority less than 6 it may be also `auto`, what means that content type hasn't been recognised yet * (this will be done by content type sniffer that listens with priority 6). * @param {String} data.dataValue HTML to be pasted. */ /** * Internal event to open the Paste dialog. * * @private * @event pasteDialog * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. * @param {Function} [data] Callback that will be passed to {@link CKEDITOR.editor#openDialog}. */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { var template = '= 10900 && !CKEDITOR.env.hc ? '' : '" href="javascript:void(\'{titleJs}\')"' ) + ' title="{title}"' + ' tabindex="-1"' + ' hidefocus="true"' + ' role="button"' + ' aria-labelledby="{id}_label"' + ' aria-haspopup="{hasArrow}"'; // Some browsers don't cancel key events in the keydown but in the // keypress. // TODO: Check if really needed for Gecko+Mac. if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) template += ' onkeypress="return false;"'; // With Firefox, we need to force the button to redraw, otherwise it // will remain in the focus state. if ( CKEDITOR.env.gecko ) template += ' onblur="this.style.cssText = this.style.cssText;"'; template += ' onkeydown="return CKEDITOR.tools.callFunction({keydownFn},event);"' + ' onfocus="return CKEDITOR.tools.callFunction({focusFn},event);" ' + ' onmousedown="return CKEDITOR.tools.callFunction({mousedownFn},event);" ' + ( CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick' ) + // #188 '="CKEDITOR.tools.callFunction({clickFn},this);return false;">' + '{label}' + '{arrowHtml}' + ''; var templateArrow = '' + // BLACK DOWN-POINTING TRIANGLE ( CKEDITOR.env.hc ? '▼' : '' ) + ''; var btnArrowTpl = CKEDITOR.addTemplate( 'buttonArrow', templateArrow ), btnTpl = CKEDITOR.addTemplate( 'button', template ); CKEDITOR.plugins.add( 'button', { beforeInit: function( editor ) { editor.ui.addHandler( CKEDITOR.UI_BUTTON, CKEDITOR.ui.button.handler ); } }); /** * Button UI element. * * @readonly * @property {String} [='button'] * @member CKEDITOR */ CKEDITOR.UI_BUTTON = 'button'; /** * Represents a button UI element. This class should not be called directly. To * create new buttons use {@link CKEDITOR.ui#addButton} instead. * * @class * @constructor Creates a button class instance. * @param {Object} definition The button definition. */ CKEDITOR.ui.button = function( definition ) { CKEDITOR.tools.extend( this, definition, // Set defaults. { title: definition.label, click: definition.click || function( editor ) { editor.execCommand( definition.command ); } }); this._ = {}; }; /** * Represents button handler object. * * @class * @singleton * @extends CKEDITOR.ui.handlerDefinition */ CKEDITOR.ui.button.handler = { /** * Transforms a button definition in a {@link CKEDITOR.ui.button} instance. * * @member CKEDITOR.ui.button.handler * @param {Object} definition * @returns {CKEDITOR.ui.button} */ create: function( definition ) { return new CKEDITOR.ui.button( definition ); } }; /** @class CKEDITOR.ui.button */ CKEDITOR.ui.button.prototype = { /** * Renders the button. * * @param {CKEDITOR.editor} editor The editor instance which this button is * to be used by. * @param {Array} output The output array to which append the HTML relative * to this button. */ render: function( editor, output ) { var env = CKEDITOR.env, id = this._.id = CKEDITOR.tools.getNextId(), stateName = '', command = this.command, // Get the command name. clickFn; this._.editor = editor; var instance = { id: id, button: this, editor: editor, focus: function() { var element = CKEDITOR.document.getById( id ); element.focus(); }, execute: function() { this.button.click( editor ); }, attach: function( editor ) { this.button.attach( editor ); } }; var keydownFn = CKEDITOR.tools.addFunction( function( ev ) { if ( instance.onkey ) { ev = new CKEDITOR.dom.event( ev ); return ( instance.onkey( instance, ev.getKeystroke() ) !== false ); } }); var focusFn = CKEDITOR.tools.addFunction( function( ev ) { var retVal; if ( instance.onfocus ) retVal = ( instance.onfocus( instance, new CKEDITOR.dom.event( ev ) ) !== false ); // FF2: prevent focus event been bubbled up to editor container, which caused unexpected editor focus. if ( CKEDITOR.env.gecko && CKEDITOR.env.version < 10900 ) ev.preventBubble(); return retVal; }); var selLocked = 0; var mousedownFn = CKEDITOR.tools.addFunction( function() { // Opera: lock to prevent loosing editable text selection when clicking on button. if ( CKEDITOR.env.opera ) { var edt = editor.editable(); if ( edt.isInline() && edt.hasFocus ) { editor.lockSelection(); selLocked = 1; } } }); instance.clickFn = clickFn = CKEDITOR.tools.addFunction( function() { // Restore locked selection in Opera. if ( selLocked ) { editor.unlockSelection( 1 ); selLocked = 0; } instance.execute(); }); // Indicate a mode sensitive button. if ( this.modes ) { var modeStates = {}; function updateState() { // "this" is a CKEDITOR.ui.button instance. var mode = editor.mode; if ( mode ) { // Restore saved button state. var state = this.modes[ mode ] ? modeStates[ mode ] != undefined ? modeStates[ mode ] : CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; this.setState( editor.readOnly && !this.readOnly ? CKEDITOR.TRISTATE_DISABLED : state ); } } editor.on( 'beforeModeUnload', function() { if ( editor.mode && this._.state != CKEDITOR.TRISTATE_DISABLED ) modeStates[ editor.mode ] = this._.state; }, this ); editor.on( 'mode', updateState, this ); // If this button is sensitive to readOnly state, update it accordingly. !this.readOnly && editor.on( 'readOnly', updateState, this ); } else if ( command ) { // Get the command instance. command = editor.getCommand( command ); if ( command ) { command.on( 'state', function() { this.setState( command.state ); }, this ); stateName += ( command.state == CKEDITOR.TRISTATE_ON ? 'on' : command.state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' : 'off' ); } } // For button that has text-direction awareness on selection path. if ( this.directional ) { editor.on( 'contentDirChanged', function( evt ) { var el = CKEDITOR.document.getById( this._.id ), icon = el.getFirst(); var pathDir = evt.data; // Make a minor direction change to become style-able for the skin icon. if ( pathDir != editor.lang.dir ) el.addClass( 'cke_' + pathDir ); else el.removeClass( 'cke_ltr' ).removeClass( 'cke_rtl' ); // Inline style update for the plugin icon. icon.setAttribute( 'style', CKEDITOR.skin.getIconStyle( iconName, pathDir == 'rtl', this.icon, this.iconOffset ) ); }, this ); } if ( !command ) stateName += 'off'; var name = this.name || this.command, iconName = name; // Check if we're pointing to an icon defined by another command. (#9555) if ( this.icon && !( /\./ ).test( this.icon ) ) { iconName = this.icon; this.icon = null; } var params = { id: id, name: name, iconName: iconName, label: this.label, cls: this.className || '', state: stateName, title: this.title, titleJs: env.gecko && env.version >= 10900 && !env.hc ? '' : ( this.title || '' ).replace( "'", '' ), hasArrow: this.hasArrow ? 'true' : 'false', keydownFn: keydownFn, mousedownFn: mousedownFn, focusFn: focusFn, clickFn: clickFn, style: CKEDITOR.skin.getIconStyle( iconName, ( editor.lang.dir == 'rtl' ), this.icon, this.iconOffset ), arrowHtml: this.hasArrow ? btnArrowTpl.output() : '' }; btnTpl.output( params, output ); if ( this.onRender ) this.onRender(); return instance; }, /** * @todo */ setState: function( state ) { if ( this._.state == state ) return false; this._.state = state; var element = CKEDITOR.document.getById( this._.id ); if ( element ) { element.setState( state, 'cke_button' ); state == CKEDITOR.TRISTATE_DISABLED ? element.setAttribute( 'aria-disabled', true ) : element.removeAttribute( 'aria-disabled' ); state == CKEDITOR.TRISTATE_ON ? element.setAttribute( 'aria-pressed', true ) : element.removeAttribute( 'aria-pressed' ); return true; } else return false; } }; /** * Adds a button definition to the UI elements list. * * editorInstance.ui.addButton( 'MyBold', { * label: 'My Bold', * command: 'bold', * toolbar: 'basicstyles,1' * } ); * * @member CKEDITOR.ui * @param {String} name The button name. * @param {Object} definition The button definition. */ CKEDITOR.ui.prototype.addButton = function( name, definition ) { this.add( name, CKEDITOR.UI_BUTTON, definition ); }; })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'panelbutton', { requires: 'button', onLoad: function() { function clickFn( editor ) { var _ = this._; if ( _.state == CKEDITOR.TRISTATE_DISABLED ) return; this.createPanel( editor ); if ( _.on ) { _.panel.hide(); return; } _.panel.showBlock( this._.id, this.document.getById( this._.id ), 4 ); } /** * @class * @extends CKEDITOR.ui.button * @todo class and methods */ CKEDITOR.ui.panelButton = CKEDITOR.tools.createClass({ base: CKEDITOR.ui.button, /** * Creates a panelButton class instance. * * @constructor */ $: function( definition ) { // We don't want the panel definition in this object. var panelDefinition = definition.panel || {}; delete definition.panel; this.base( definition ); this.document = ( panelDefinition.parent && panelDefinition.parent.getDocument() ) || CKEDITOR.document; panelDefinition.block = { attributes: panelDefinition.attributes }; panelDefinition.toolbarRelated = true; this.hasArrow = true; this.click = clickFn; this._ = { panelDefinition: panelDefinition }; }, statics: { handler: { create: function( definition ) { return new CKEDITOR.ui.panelButton( definition ); } } }, proto: { createPanel: function( editor ) { var _ = this._; if ( _.panel ) return; var panelDefinition = this._.panelDefinition, panelBlockDefinition = this._.panelDefinition.block, panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(), panel = this._.panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), block = panel.addBlock( _.id, panelBlockDefinition ), me = this; panel.onShow = function() { if ( me.className ) this.element.addClass( me.className + '_panel' ); me.setState( CKEDITOR.TRISTATE_ON ); _.on = 1; me.editorFocus && editor.focus(); if ( me.onOpen ) me.onOpen(); }; panel.onHide = function( preventOnClose ) { if ( me.className ) this.element.getFirst().removeClass( me.className + '_panel' ); me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); _.on = 0; if ( !preventOnClose && me.onClose ) me.onClose(); }; panel.onEscape = function() { panel.hide( 1 ); me.document.getById( _.id ).focus(); }; if ( this.onBlock ) this.onBlock( panel, block ); block.onHide = function() { _.on = 0; me.setState( CKEDITOR.TRISTATE_OFF ); }; } } }); }, beforeInit: function( editor ) { editor.ui.addHandler( CKEDITOR.UI_PANELBUTTON, CKEDITOR.ui.panelButton.handler ); } }); /** * Button UI element. * * @readonly * @property {String} [='panelbutton'] * @member CKEDITOR */ CKEDITOR.UI_PANELBUTTON = 'panelbutton'; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { CKEDITOR.plugins.add( 'panel', { beforeInit: function( editor ) { editor.ui.addHandler( CKEDITOR.UI_PANEL, CKEDITOR.ui.panel.handler ); } }); /** * Panel UI element. * * @readonly * @property {String} [='panel'] * @member CKEDITOR */ CKEDITOR.UI_PANEL = 'panel'; /** * @class * @constructor Creates a panel class instance. * @param {CKEDITOR.dom.document} document * @param {Object} definition */ CKEDITOR.ui.panel = function( document, definition ) { // Copy all definition properties to this object. if ( definition ) CKEDITOR.tools.extend( this, definition ); // Set defaults. CKEDITOR.tools.extend( this, { className: '', css: [] }); this.id = CKEDITOR.tools.getNextId(); this.document = document; this.isFramed = this.forceIFrame || this.css.length; this._ = { blocks: {} }; }; /** * Represents panel handler object. * * @class * @singleton * @extends CKEDITOR.ui.handlerDefinition */ CKEDITOR.ui.panel.handler = { /** * Transforms a panel definition in a {@link CKEDITOR.ui.panel} instance. * * @param {Object} definition * @returns {CKEDITOR.ui.panel} */ create: function( definition ) { return new CKEDITOR.ui.panel( definition ); } }; var panelTpl = CKEDITOR.addTemplate( 'panel', '

    ' ); var frameTpl = CKEDITOR.addTemplate( 'panel-frame', '' ); var frameDocTpl = CKEDITOR.addTemplate( 'panel-frame-inner', '' + '' + '{css}' + '' + '<\/html>' ); /** @class CKEDITOR.ui.panel */ CKEDITOR.ui.panel.prototype = { /** * Renders the combo. * * @param {CKEDITOR.editor} editor The editor instance which this button is * to be used by. * @param {Array} [output] The output array to which append the HTML relative * to this button. */ render: function( editor, output ) { this.getHolderElement = function() { var holder = this._.holder; if ( !holder ) { if ( this.isFramed ) { var iframe = this.document.getById( this.id + '_frame' ), parentDiv = iframe.getParent(), doc = iframe.getFrameDocument(); // Make it scrollable on iOS. (#8308) CKEDITOR.env.iOS && parentDiv.setStyles({ 'overflow': 'scroll', '-webkit-overflow-scrolling': 'touch' }); var onLoad = CKEDITOR.tools.addFunction( CKEDITOR.tools.bind( function( ev ) { this.isLoaded = true; if ( this.onLoad ) this.onLoad(); }, this ) ); doc.write( frameDocTpl.output( CKEDITOR.tools.extend({ css: CKEDITOR.tools.buildStyleHtml( this.css ), onload: 'window.parent.CKEDITOR.tools.callFunction(' + onLoad + ');' }, data ) ) ); var win = doc.getWindow(); // Register the CKEDITOR global. win.$.CKEDITOR = CKEDITOR; // Arrow keys for scrolling is only preventable with 'keypress' event in Opera (#4534). doc.on( 'key' + ( CKEDITOR.env.opera ? 'press' : 'down' ), function( evt ) { var keystroke = evt.data.getKeystroke(), dir = this.document.getById( this.id ).getAttribute( 'dir' ); // Delegate key processing to block. if ( this._.onKeyDown && this._.onKeyDown( keystroke ) === false ) { evt.data.preventDefault(); return; } // ESC/ARROW-LEFT(ltr) OR ARROW-RIGHT(rtl) if ( keystroke == 27 || keystroke == ( dir == 'rtl' ? 39 : 37 ) ) { if ( this.onEscape && this.onEscape( keystroke ) === false ) evt.data.preventDefault(); } }, this ); holder = doc.getBody(); holder.unselectable(); CKEDITOR.env.air && CKEDITOR.tools.callFunction( onLoad ); } else holder = this.document.getById( this.id ); this._.holder = holder; } return holder; }; var data = { editorId: editor.id, id: this.id, langCode: editor.langCode, dir: editor.lang.dir, cls: this.className, frame: '', env: CKEDITOR.env.cssClass, 'z-index': editor.config.baseFloatZIndex + 1 }; if ( this.isFramed ) { data.frame = frameTpl.output({ id: this.id + '_frame', src: 'javascript:void(document.open(),' + ( CKEDITOR.env.isCustomDomain() ? 'document.domain=\'' + document.domain + '\',' : '' ) + 'document.close())">' }); } var html = panelTpl.output( data ); if ( output ) output.push( html ); return html; }, /** * @todo */ addBlock: function( name, block ) { block = this._.blocks[ name ] = block instanceof CKEDITOR.ui.panel.block ? block : new CKEDITOR.ui.panel.block( this.getHolderElement(), block ); if ( !this._.currentBlock ) this.showBlock( name ); return block; }, /** * @todo */ getBlock: function( name ) { return this._.blocks[ name ]; }, /** * @todo */ showBlock: function( name ) { var blocks = this._.blocks, block = blocks[ name ], current = this._.currentBlock; // ARIA role works better in IE on the body element, while on the iframe // for FF. (#8864) var holder = !this.forceIFrame || CKEDITOR.env.ie ? this._.holder : this.document.getById( this.id + '_frame' ); if ( current ) { // Clean up the current block's effects on holder. holder.removeAttributes( current.attributes ); current.hide(); } this._.currentBlock = block; holder.setAttributes( block.attributes ); CKEDITOR.fire( 'ariaWidget', holder ); // Reset the focus index, so it will always go into the first one. block._.focusIndex = -1; this._.onKeyDown = block.onKeyDown && CKEDITOR.tools.bind( block.onKeyDown, block ); block.show(); return block; }, /** * @todo */ destroy: function() { this.element && this.element.remove(); } }; /** * @class * * @todo class and all methods */ CKEDITOR.ui.panel.block = CKEDITOR.tools.createClass({ /** * Creates a block class instances. * * @constructor * @todo */ $: function( blockHolder, blockDefinition ) { this.element = blockHolder.append( blockHolder.getDocument().createElement( 'div', { attributes: { 'tabIndex': -1, 'class': 'cke_panel_block', 'role': 'presentation' }, styles: { display: 'none' } })); // Copy all definition properties to this object. if ( blockDefinition ) CKEDITOR.tools.extend( this, blockDefinition ); if ( !this.attributes.title ) this.attributes.title = this.attributes[ 'aria-label' ]; this.keys = {}; this._.focusIndex = -1; // Disable context menu for panels. this.element.disableContextMenu(); }, _: { /** * Mark the item specified by the index as current activated. */ markItem: function( index ) { if ( index == -1 ) return; var links = this.element.getElementsByTag( 'a' ); var item = links.getItem( this._.focusIndex = index ); // Safari need focus on the iframe window first(#3389), but we need // lock the blur to avoid hiding the panel. if ( CKEDITOR.env.webkit || CKEDITOR.env.opera ) item.getDocument().getWindow().focus(); item.focus(); this.onMark && this.onMark( item ); } }, proto: { show: function() { this.element.setStyle( 'display', '' ); }, hide: function() { if ( !this.onHide || this.onHide.call( this ) !== true ) this.element.setStyle( 'display', 'none' ); }, onKeyDown: function( keystroke ) { var keyAction = this.keys[ keystroke ]; switch ( keyAction ) { // Move forward. case 'next': var index = this._.focusIndex, links = this.element.getElementsByTag( 'a' ), link; while ( ( link = links.getItem( ++index ) ) ) { // Move the focus only if the element is marked with // the _cke_focus and it it's visible (check if it has // width). if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth ) { this._.focusIndex = index; link.focus(); break; } } return false; // Move backward. case 'prev': index = this._.focusIndex; links = this.element.getElementsByTag( 'a' ); while ( index > 0 && ( link = links.getItem( --index ) ) ) { // Move the focus only if the element is marked with // the _cke_focus and it it's visible (check if it has // width). if ( link.getAttribute( '_cke_focus' ) && link.$.offsetWidth ) { this._.focusIndex = index; link.focus(); break; } } return false; case 'click': case 'mouseup': index = this._.focusIndex; link = index >= 0 && this.element.getElementsByTag( 'a' ).getItem( index ); if ( link ) link.$[ keyAction ] ? link.$[ keyAction ]() : link.$[ 'on' + keyAction ](); return false; } return true; } } }); })(); /** * Fired when a panel is added to the document. * * @event ariaWidget * @member CKEDITOR * @param {Object} data The element wrapping the panel. */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'floatpanel', { requires: 'panel' }); (function() { var panels = {}; function getPanel( editor, doc, parentElement, definition, level ) { // Generates the panel key: docId-eleId-skinName-langDir[-uiColor][-CSSs][-level] var key = CKEDITOR.tools.genKey( doc.getUniqueId(), parentElement.getUniqueId(), editor.lang.dir, editor.uiColor || '', definition.css || '', level || '' ), panel = panels[ key ]; if ( !panel ) { panel = panels[ key ] = new CKEDITOR.ui.panel( doc, definition ); panel.element = parentElement.append( CKEDITOR.dom.element.createFromHtml( panel.render( editor ), doc ) ); panel.element.setStyles({ display: 'none', position: 'absolute' }); } return panel; } /** * Represents a floating panel UI element. * * It's reused by rich combos, color combos, menus, etc. * and it renders its content using {@link CKEDITOR.ui.panel}. * * @class * @todo */ CKEDITOR.ui.floatPanel = CKEDITOR.tools.createClass({ /** * Creates a floatPanel class instance. * * @constructor * @param {CKEDITOR.editor} editor * @param {CKEDITOR.dom.element} parentElement * @param {Object} definition Definition of the panel that will be floating. * @param {Number} level */ $: function( editor, parentElement, definition, level ) { definition.forceIFrame = 1; // In case of editor with floating toolbar append panels that should float // to the main UI element. if ( definition.toolbarRelated && editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE ) parentElement = CKEDITOR.document.getById( 'cke_' + editor.name ); var doc = parentElement.getDocument(), panel = getPanel( editor, doc, parentElement, definition, level || 0 ), element = panel.element, iframe = element.getFirst(), that = this; // Disable native browser menu. (#4825) element.disableContextMenu(); // Floating panels are placed outside the main editor UI, so we must // make them application regions as well. (#9543) element.setAttribute( 'role', 'application' ); this.element = element; this._ = { editor: editor, // The panel that will be floating. panel: panel, parentElement: parentElement, definition: definition, document: doc, iframe: iframe, children: [], dir: editor.lang.dir }; editor.on( 'mode', hide ); editor.on( 'resize', hide ); // Window resize doesn't cause hide on blur. (#9800) doc.getWindow().on( 'resize', hide ); // We need a wrapper because events implementation doesn't allow to attach // one listener more than once for the same event on the same object. // Remember that floatPanel#hide is shared between all instances. function hide() { that.hide(); } }, proto: { /** * @todo */ addBlock: function( name, block ) { return this._.panel.addBlock( name, block ); }, /** * @todo */ addListBlock: function( name, multiSelect ) { return this._.panel.addListBlock( name, multiSelect ); }, /** * @todo */ getBlock: function( name ) { return this._.panel.getBlock( name ); }, /** * Shows panel block. * * @param {String} name * @param {CKEDITOR.dom.element} offsetParent Positioned parent. * @param {Number} corner * * * For LTR (left to right) oriented editor: * * `1` = top-left * * `2` = top-right * * `3` = bottom-right * * `4` = bottom-left * * For RTL (right to left): * * `1` = top-right * * `2` = top-left * * `3` = bottom-left * * `4` = bottom-right * * @param {Number} [offsetX=0] * @param {Number} [offsetY=0] * @todo what do exactly these params mean (especially corner)? */ showBlock: function( name, offsetParent, corner, offsetX, offsetY ) { var panel = this._.panel, block = panel.showBlock( name ); this.allowBlur( false ); // Record from where the focus is when open panel. var editable = this._.editor.editable(); this._.returnFocus = editable.hasFocus ? editable : new CKEDITOR.dom.element( CKEDITOR.document.$.activeElement ); var element = this.element, iframe = this._.iframe, // Non IE prefer the event into a window object. focused = CKEDITOR.env.ie ? iframe : new CKEDITOR.dom.window( iframe.$.contentWindow ), doc = element.getDocument(), positionedAncestor = this._.parentElement.getPositionedAncestor(), position = offsetParent.getDocumentPosition( doc ), positionedAncestorPosition = positionedAncestor ? positionedAncestor.getDocumentPosition( doc ) : { x: 0, y: 0 }, rtl = this._.dir == 'rtl', left = position.x + ( offsetX || 0 ) - positionedAncestorPosition.x, top = position.y + ( offsetY || 0 ) - positionedAncestorPosition.y; // Floating panels are off by (-1px, 0px) in RTL mode. (#3438) if ( rtl && ( corner == 1 || corner == 4 ) ) left += offsetParent.$.offsetWidth; else if ( !rtl && ( corner == 2 || corner == 3 ) ) left += offsetParent.$.offsetWidth - 1; if ( corner == 3 || corner == 4 ) top += offsetParent.$.offsetHeight - 1; // Memorize offsetParent by it's ID. this._.panel._.offsetParentId = offsetParent.getId(); element.setStyles({ top: top + 'px', left: 0, display: '' }); // Don't use display or visibility style because we need to // calculate the rendering layout later and focus the element. element.setOpacity( 0 ); // To allow the context menu to decrease back their width element.getFirst().removeStyle( 'width' ); // Report to focus manager. this._.editor.focusManager.add( focused ); // Configure the IFrame blur event. Do that only once. if ( !this._.blurSet ) { // With addEventListener compatible browsers, we must // useCapture when registering the focus/blur events to // guarantee they will be firing in all situations. (#3068, #3222 ) CKEDITOR.event.useCapture = true; focused.on( 'blur', function( ev ) { // As we are using capture to register the listener, // the blur event may get fired even when focusing // inside the window itself, so we must ensure the // target is out of it. if ( !this.allowBlur() || ev.data.getPhase() != CKEDITOR.EVENT_PHASE_AT_TARGET ) return; if ( this.visible && !this._.activeChild ) { // Panel close is caused by user's navigating away the focus, e.g. click outside the panel. // DO NOT restore focus in this case. delete this._.returnFocus; this.hide(); } }, this ); focused.on( 'focus', function() { this._.focused = true; this.hideChild(); this.allowBlur( true ); }, this ); CKEDITOR.event.useCapture = false; this._.blurSet = 1; } panel.onEscape = CKEDITOR.tools.bind( function( keystroke ) { if ( this.onEscape && this.onEscape( keystroke ) === false ) return false; }, this ); CKEDITOR.tools.setTimeout( function() { var panelLoad = CKEDITOR.tools.bind( function() { var target = element; // Reset panel width as the new content can be narrower // than the old one. (#9355) target.removeStyle( 'width' ); if ( block.autoSize ) { var panelDoc = block.element.getDocument(); var width = ( CKEDITOR.env.webkit? block.element : panelDoc.getBody() )[ '$' ].scrollWidth; // Account for extra height needed due to IE quirks box model bug: // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug // (#3426) if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && width > 0 ) width += ( target.$.offsetWidth || 0 ) - ( target.$.clientWidth || 0 ) + 3; // Add some extra pixels to improve the appearance. width += 10; target.setStyle( 'width', width + 'px' ); var height = block.element.$.scrollHeight; // Account for extra height needed due to IE quirks box model bug: // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug // (#3426) if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && height > 0 ) height += ( target.$.offsetHeight || 0 ) - ( target.$.clientHeight || 0 ) + 3; target.setStyle( 'height', height + 'px' ); // Fix IE < 8 visibility. panel._.currentBlock.element.setStyle( 'display', 'none' ).removeStyle( 'display' ); } else target.removeStyle( 'height' ); // Flip panel layout horizontally in RTL with known width. if ( rtl ) left -= element.$.offsetWidth; // Pop the style now for measurement. element.setStyle( 'left', left + 'px' ); /* panel layout smartly fit the viewport size. */ var panelElement = panel.element, panelWindow = panelElement.getWindow(), rect = element.$.getBoundingClientRect(), viewportSize = panelWindow.getViewPaneSize(); // Compensation for browsers that dont support "width" and "height". var rectWidth = rect.width || rect.right - rect.left, rectHeight = rect.height || rect.bottom - rect.top; // Check if default horizontal layout is impossible. var spaceAfter = rtl ? rect.right : viewportSize.width - rect.left, spaceBefore = rtl ? viewportSize.width - rect.right : rect.left; if ( rtl ) { if ( spaceAfter < rectWidth ) { // Flip to show on right. if ( spaceBefore > rectWidth ) left += rectWidth; // Align to window left. else if ( viewportSize.width > rectWidth ) left = left - rect.left; // Align to window right, never cutting the panel at right. else left = left - rect.right + viewportSize.width; } } else if ( spaceAfter < rectWidth ) { // Flip to show on left. if ( spaceBefore > rectWidth ) left -= rectWidth; // Align to window right. else if ( viewportSize.width > rectWidth ) left = left - rect.right + viewportSize.width; // Align to window left, never cutting the panel at left. else left = left - rect.left; } // Check if the default vertical layout is possible. var spaceBelow = viewportSize.height - rect.top, spaceAbove = rect.top; if ( spaceBelow < rectHeight ) { // Flip to show above. if ( spaceAbove > rectHeight ) top -= rectHeight; // Align to window bottom. else if ( viewportSize.height > rectHeight ) top = top - rect.bottom + viewportSize.height; // Align to top, never cutting the panel at top. else top = top - rect.top; } // If IE is in RTL, we have troubles with absolute // position and horizontal scrolls. Here we have a // series of hacks to workaround it. (#6146) if ( CKEDITOR.env.ie ) { var offsetParent = new CKEDITOR.dom.element( element.$.offsetParent ), scrollParent = offsetParent; // Quirks returns , but standards returns . if ( scrollParent.getName() == 'html' ) scrollParent = scrollParent.getDocument().getBody(); if ( scrollParent.getComputedStyle( 'direction' ) == 'rtl' ) { // For IE8, there is not much logic on this, but it works. if ( CKEDITOR.env.ie8Compat ) left -= element.getDocument().getDocumentElement().$.scrollLeft * 2; else left -= ( offsetParent.$.scrollWidth - offsetParent.$.clientWidth ); } } // Trigger the onHide event of the previously active panel to prevent // incorrect styles from being applied (#6170) var innerElement = element.getFirst(), activePanel; if ( ( activePanel = innerElement.getCustomData( 'activePanel' ) ) ) activePanel.onHide && activePanel.onHide.call( this, 1 ); innerElement.setCustomData( 'activePanel', this ); element.setStyles({ top: top + 'px', left: left + 'px' }); element.setOpacity( 1 ); }, this ); panel.isLoaded ? panelLoad() : panel.onLoad = panelLoad; // Set the panel frame focus, so the blur event gets fired. CKEDITOR.tools.setTimeout( function() { this.focus(); // We need this get fired manually because of unfired focus() function. this.allowBlur( true ); this._.editor.fire( 'panelShow', this ); }, 0, this ); }, CKEDITOR.env.air ? 200 : 0, this ); this.visible = 1; if ( this.onShow ) this.onShow.call( this ); }, /** * Restores last focused element or simply focus panel window. */ focus: function() { // Webkit requires to blur any previous focused page element, in // order to properly fire the "focus" event. if ( CKEDITOR.env.webkit ) { var active = CKEDITOR.document.getActive(); !active.equals( this._.iframe ) && active.$.blur(); } // Restore last focused element or simply focus panel window. var focus = this._.lastFocused || this._.iframe.getFrameDocument().getWindow(); focus.focus(); }, /** * @todo */ blur: function() { var doc = this._.iframe.getFrameDocument(), active = doc.getActive(); active.is( 'a' ) && ( this._.lastFocused = active ); }, /** * Hides panel. * * @todo */ hide: function( returnFocus ) { if ( this.visible && ( !this.onHide || this.onHide.call( this ) !== true ) ) { this.hideChild(); // Blur previously focused element. (#6671) CKEDITOR.env.gecko && this._.iframe.getFrameDocument().$.activeElement.blur(); this.element.setStyle( 'display', 'none' ); this.visible = 0; this.element.getFirst().removeCustomData( 'activePanel' ); // Return focus properly. (#6247) var focusReturn = returnFocus && this._.returnFocus; if ( focusReturn ) { // Webkit requires focus moved out panel iframe first. if ( CKEDITOR.env.webkit && focusReturn.type ) focusReturn.getWindow().$.focus(); focusReturn.focus(); } delete this._.lastFocused; this._.editor.fire( 'panelHide', this ); } }, /** * @todo */ allowBlur: function( allow ) // Prevent editor from hiding the panel. #3222. { var panel = this._.panel; if ( allow != undefined ) panel.allowBlur = allow; return panel.allowBlur; }, /** * Shows specified panel as a child of one block of this one. * * @param {CKEDITOR.ui.floatPanel} panel * @param {String} blockName * @param {CKEDITOR.dom.element} offsetParent Positioned parent. * @param {Number} corner * * * For LTR (left to right) oriented editor: * * `1` = top-left * * `2` = top-right * * `3` = bottom-right * * `4` = bottom-left * * For RTL (right to left): * * `1` = top-right * * `2` = top-left * * `3` = bottom-left * * `4` = bottom-right * * @param {Number} [offsetX=0] * @param {Number} [offsetY=0] * @todo */ showAsChild: function( panel, blockName, offsetParent, corner, offsetX, offsetY ) { // Skip reshowing of child which is already visible. if ( this._.activeChild == panel && panel._.panel._.offsetParentId == offsetParent.getId() ) return; this.hideChild(); panel.onHide = CKEDITOR.tools.bind( function() { // Use a timeout, so we give time for this menu to get // potentially focused. CKEDITOR.tools.setTimeout( function() { if ( !this._.focused ) this.hide(); }, 0, this ); }, this ); this._.activeChild = panel; this._.focused = false; panel.showBlock( blockName, offsetParent, corner, offsetX, offsetY ); this.blur(); /* #3767 IE: Second level menu may not have borders */ if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) { setTimeout( function() { panel.element.getChild( 0 ).$.style.cssText += ''; }, 100 ); } }, /** * @todo */ hideChild: function( restoreFocus ) { var activeChild = this._.activeChild; if ( activeChild ) { delete activeChild.onHide; delete this._.activeChild; activeChild.hide(); // At this point focus should be moved back to parent panel. restoreFocus && this.focus(); } } } }); CKEDITOR.on( 'instanceDestroyed', function() { var isLastInstance = CKEDITOR.tools.isEmpty( CKEDITOR.instances ); for ( var i in panels ) { var panel = panels[ i ]; // Safe to destroy it since there're no more instances.(#4241) if ( isLastInstance ) panel.destroy(); // Panel might be used by other instances, just hide them.(#4552) else panel.element.hide(); } // Remove the registration. isLastInstance && ( panels = {} ); } ); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "colorbutton" plugin that makes it possible to assign * text and background colors to editor contents. * */ CKEDITOR.plugins.add( 'colorbutton', { requires: 'panelbutton,floatpanel', init: function( editor ) { var config = editor.config, lang = editor.lang.colorbutton; var clickFn; if ( !CKEDITOR.env.hc ) { addButton( 'TextColor', 'fore', lang.textColorTitle, 10 ); addButton( 'BGColor', 'back', lang.bgColorTitle, 20 ); } function addButton( name, type, title, order ) { var colorBoxId = CKEDITOR.tools.getNextId() + '_colorBox'; editor.ui.add( name, CKEDITOR.UI_PANELBUTTON, { label: title, title: title, modes: { wysiwyg:1 }, editorFocus: 1, toolbar: 'colors,' + order, panel: { css: CKEDITOR.skin.getPath( 'editor' ), attributes: { role: 'listbox', 'aria-label': lang.panelTitle } }, onBlock: function( panel, block ) { block.autoSize = true; block.element.addClass( 'cke_colorblock' ); block.element.setHtml( renderColors( panel, type, colorBoxId ) ); // The block should not have scrollbars (#5933, #6056) block.element.getDocument().getBody().setStyle( 'overflow', 'hidden' ); CKEDITOR.ui.fire( 'ready', this ); var keys = block.keys; var rtl = editor.lang.dir == 'rtl'; keys[ rtl ? 37 : 39 ] = 'next'; // ARROW-RIGHT keys[ 40 ] = 'next'; // ARROW-DOWN keys[ 9 ] = 'next'; // TAB keys[ rtl ? 39 : 37 ] = 'prev'; // ARROW-LEFT keys[ 38 ] = 'prev'; // ARROW-UP keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB keys[ 32 ] = 'click'; // SPACE }, // The automatic colorbox should represent the real color (#6010) onOpen: function() { var selection = editor.getSelection(), block = selection && selection.getStartElement(), path = editor.elementPath( block ), color; // Find the closest block element. block = path.block || path.blockLimit || editor.document.getBody(); // The background color might be transparent. In that case, look up the color in the DOM tree. do { color = block && block.getComputedStyle( type == 'back' ? 'background-color' : 'color' ) || 'transparent'; } while ( type == 'back' && color == 'transparent' && block && ( block = block.getParent() ) ); // The box should never be transparent. if ( !color || color == 'transparent' ) color = '#ffffff'; this._.panel._.iframe.getFrameDocument().getById( colorBoxId ).setStyle( 'background-color', color ); return color; } }); } function renderColors( panel, type, colorBoxId ) { var output = [], colors = config.colorButton_colors.split( ',' ); var clickFn = CKEDITOR.tools.addFunction( function( color, type ) { if ( color == '?' ) { var applyColorStyle = arguments.callee; function onColorDialogClose( evt ) { this.removeListener( 'ok', onColorDialogClose ); this.removeListener( 'cancel', onColorDialogClose ); evt.name == 'ok' && applyColorStyle( this.getContentElement( 'picker', 'selectedColor' ).getValue(), type ); } editor.openDialog( 'colordialog', function() { this.on( 'ok', onColorDialogClose ); this.on( 'cancel', onColorDialogClose ); }); return; } editor.focus(); panel.hide(); editor.fire( 'saveSnapshot' ); // Clean up any conflicting style within the range. editor.removeStyle( new CKEDITOR.style( config[ 'colorButton_' + type + 'Style' ], { color: 'inherit' } ) ); if ( color ) { var colorStyle = config[ 'colorButton_' + type + 'Style' ]; colorStyle.childRule = type == 'back' ? function( element ) { // It's better to apply background color as the innermost style. (#3599) // Except for "unstylable elements". (#6103) return isUnstylable( element ); } : function( element ) { // Fore color style must be applied inside links instead of around it. (#4772,#6908) return !( element.is( 'a' ) || element.getElementsByTag( 'a' ).count() ) || isUnstylable( element ); }; editor.applyStyle( new CKEDITOR.style( colorStyle, { color: color } ) ); } editor.fire( 'saveSnapshot' ); }); // Render the "Automatic" button. output.push( '' + '' + '' + '' + '' + '' + '
    ' + '' + '', lang.auto, '
    ' + '
    ' + '' ); // Render the color boxes. for ( var i = 0; i < colors.length; i++ ) { if ( ( i % 8 ) === 0 ) output.push( '' ); var parts = colors[ i ].split( '/' ), colorName = parts[ 0 ], colorCode = parts[ 1 ] || colorName; // The data can be only a color code (without #) or colorName + color code // If only a color code is provided, then the colorName is the color with the hash // Convert the color from RGB to RRGGBB for better compatibility with IE and . See #5676 if ( !parts[ 1 ] ) colorName = '#' + colorName.replace( /^(.)(.)(.)$/, '$1$1$2$2$3$3' ); var colorLabel = editor.lang.colorbutton.colors[ colorCode ] || colorCode; output.push( '' ); } // Render the "More Colors" button. if ( editor.plugins.colordialog && config.colorButton_enableMore === undefined || config.colorButton_enableMore ) { output.push( '' + '' + '' ); // tr is later in the code. } output.push( '
    ' + '' + '' + '' + '
    ' + '', lang.more, '' + '
    ' ); return output.join( '' ); } function isUnstylable( ele ) { return ( ele.getAttribute( 'contentEditable' ) == 'false' ) || ele.getAttribute( 'data-nostyle' ); } } }); /** * Whether to enable the **More Colors*** button in the color selectors. * * config.colorButton_enableMore = false; * * @cfg {Boolean} [colorButton_enableMore=true] * @member CKEDITOR.config */ /** * Defines the colors to be displayed in the color selectors. This is a string * containing hexadecimal notation for HTML colors, without the `'#'` prefix. * * **Since 3.3:** A color name may optionally be defined by prefixing the entries with * a name and the slash character. For example, `'FontColor1/FF9900'` will be * displayed as the color `#FF9900` in the selector, but will be output as `'FontColor1'`. * * // Brazil colors only. * config.colorButton_colors = '00923E,F8C100,28166F'; * * config.colorButton_colors = 'FontColor1/FF9900,FontColor2/0066CC,FontColor3/F00'; * * @cfg {String} [colorButton_colors=see source] * @member CKEDITOR.config */ CKEDITOR.config.colorButton_colors = '000,800000,8B4513,2F4F4F,008080,000080,4B0082,696969,' + 'B22222,A52A2A,DAA520,006400,40E0D0,0000CD,800080,808080,' + 'F00,FF8C00,FFD700,008000,0FF,00F,EE82EE,A9A9A9,' + 'FFA07A,FFA500,FFFF00,00FF00,AFEEEE,ADD8E6,DDA0DD,D3D3D3,' + 'FFF0F5,FAEBD7,FFFFE0,F0FFF0,F0FFFF,F0F8FF,E6E6FA,FFF'; /** * Stores the style definition that applies the text foreground color. * * // This is actually the default value. * config.colorButton_foreStyle = { * element: 'span', * styles: { color: '#(color)' } * }; * * @cfg [colorButton_foreStyle=see source] * @member CKEDITOR.config */ CKEDITOR.config.colorButton_foreStyle = { element: 'span', styles: { 'color': '#(color)' }, overrides: [ { element: 'font', attributes: { 'color': null } }] }; /** * Stores the style definition that applies the text background color. * * // This is actually the default value. * config.colorButton_backStyle = { * element: 'span', * styles: { 'background-color': '#(color)' } * }; * * @cfg [colorButton_backStyle=see source] * @member CKEDITOR.config */ CKEDITOR.config.colorButton_backStyle = { element: 'span', styles: { 'background-color': '#(color)' } }; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.colordialog = { requires: 'dialog', init: function( editor ) { editor.addCommand( 'colordialog', new CKEDITOR.dialogCommand( 'colordialog' ) ); CKEDITOR.dialog.add( 'colordialog', this.path + 'dialogs/colordialog.js' ); /** * Open up color dialog and to receive the selected color. * * @param {Function} callback The callback when color dialog is closed * @param {String} callback.color The color value received if selected on the dialog. * @param [scope] The scope in which the callback will be bound. * @member CKEDITOR.editor */ editor.getColorFromDialog = function( callback, scope ) { var onClose = function( evt ) { releaseHandlers( this ); var color = evt.name == 'ok' ? this.getValueOf( 'picker', 'selectedColor' ) : null; callback.call( scope, color ); }; var releaseHandlers = function( dialog ) { dialog.removeListener( 'ok', onClose ); dialog.removeListener( 'cancel', onClose ); }; var bindToDialog = function( dialog ) { dialog.on( 'ok', onClose ); dialog.on( 'cancel', onClose ); }; editor.execCommand( 'colordialog' ); if ( editor._.storedDialogs && editor._.storedDialogs.colordialog ) bindToDialog( editor._.storedDialogs.colordialog ); else { CKEDITOR.on( 'dialogDefinition', function( e ) { if ( e.data.name != 'colordialog' ) return; var definition = e.data.definition; e.removeListener(); definition.onLoad = CKEDITOR.tools.override( definition.onLoad, function( orginal ) { return function() { bindToDialog( this ); definition.onLoad = orginal; if ( typeof orginal == 'function' ) orginal.call( this ); }; } ); } ); } }; } }; CKEDITOR.plugins.add( 'colordialog', CKEDITOR.plugins.colordialog ); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { CKEDITOR.plugins.add( 'templates', { requires: 'dialog', init: function( editor ) { CKEDITOR.dialog.add( 'templates', CKEDITOR.getUrl( this.path + 'dialogs/templates.js' ) ); editor.addCommand( 'templates', new CKEDITOR.dialogCommand( 'templates' ) ); editor.ui.addButton && editor.ui.addButton( 'Templates', { label: editor.lang.templates.button, command: 'templates', toolbar: 'doctools,10' }); } }); var templates = {}, loadedTemplatesFiles = {}; CKEDITOR.addTemplates = function( name, definition ) { templates[ name ] = definition; }; CKEDITOR.getTemplates = function( name ) { return templates[ name ]; }; CKEDITOR.loadTemplates = function( templateFiles, callback ) { // Holds the templates files to be loaded. var toLoad = []; // Look for pending template files to get loaded. for ( var i = 0, count = templateFiles.length; i < count; i++ ) { if ( !loadedTemplatesFiles[ templateFiles[ i ] ] ) { toLoad.push( templateFiles[ i ] ); loadedTemplatesFiles[ templateFiles[ i ] ] = 1; } } if ( toLoad.length ) CKEDITOR.scriptLoader.load( toLoad, callback ); else setTimeout( callback, 0 ); }; })(); /** * The templates definition set to use. It accepts a list of names separated by * comma. It must match definitions loaded with the {@link #templates_files} setting. * * config.templates = 'my_templates'; * * @cfg {String} [templates='default'] * @member CKEDITOR.config */ /** * The list of templates definition files to load. * * config.templates_files = [ * '/editor_templates/site_default.js', * 'http://www.example.com/user_templates.js * ]; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.templates_files = [ CKEDITOR.getUrl( 'plugins/templates/templates/default.js' ) ]; /** * Whether the "Replace actual contents" checkbox is checked by default in the * Templates dialog. * * config.templates_replaceContent = false; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.templates_replaceContent = true; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'menu', { requires: 'floatpanel', beforeInit: function( editor ) { var groups = editor.config.menu_groups.split( ',' ), groupsOrder = editor._.menuGroups = {}, menuItems = editor._.menuItems = {}; for ( var i = 0; i < groups.length; i++ ) groupsOrder[ groups[ i ] ] = i + 1; /** * Registers an item group to the editor context menu in order to make it * possible to associate it with menu items later. * * @param {String} name Specify a group name. * @param {Number} [order=100] Define the display sequence of this group * inside the menu. A smaller value gets displayed first. * @member CKEDITOR.editor */ editor.addMenuGroup = function( name, order ) { groupsOrder[ name ] = order || 100; }; /** * Adds an item from the specified definition to the editor context menu. * * @method * @param {String} name The menu item name. * @param {Object} definition The menu item definition. * @member CKEDITOR.editor */ editor.addMenuItem = function( name, definition ) { if ( groupsOrder[ definition.group ] ) menuItems[ name ] = new CKEDITOR.menuItem( this, name, definition ); }; /** * Adds one or more items from the specified definition array to the editor context menu. * * @method * @param {Array} definitions List of definitions for each menu item as if {@link #addMenuItem} is called. * @member CKEDITOR.editor */ editor.addMenuItems = function( definitions ) { for ( var itemName in definitions ) { this.addMenuItem( itemName, definitions[ itemName ] ); } }; /** * Retrieves a particular menu item definition from the editor context menu. * * @method * @param {String} name The name of the desired menu item. * @returns {Object} * @member CKEDITOR.editor */ editor.getMenuItem = function( name ) { return menuItems[ name ]; }; /** * Removes a particular menu item added before from the editor context menu. * * @since 3.6.1 * @method * @param {String} name The name of the desired menu item. * @member CKEDITOR.editor */ editor.removeMenuItem = function( name ) { delete menuItems[ name ]; }; } }); (function() { var menuItemSource = '' + ''; menuItemSource += '' + '' + '' + '' + '' + '{label}' + '' + '{arrowHtml}' + '' + ''; var menuArrowSource = '' + '{label}' + ''; var menuItemTpl = CKEDITOR.addTemplate( 'menuItem', menuItemSource ), menuArrowTpl = CKEDITOR.addTemplate( 'menuArrow', menuArrowSource ); /** * @class * @todo */ CKEDITOR.menu = CKEDITOR.tools.createClass({ $: function( editor, definition ) { definition = this._.definition = definition || {}; this.id = CKEDITOR.tools.getNextId(); this.editor = editor; this.items = []; this._.listeners = []; this._.level = definition.level || 1; var panelDefinition = CKEDITOR.tools.extend( {}, definition.panel, { css: [ CKEDITOR.skin.getPath( 'editor' ) ], level: this._.level - 1, block: {} }); var attrs = panelDefinition.block.attributes = ( panelDefinition.attributes || {} ); // Provide default role of 'menu'. !attrs.role && ( attrs.role = 'menu' ); this._.panelDefinition = panelDefinition; }, _: { onShow: function() { var selection = this.editor.getSelection(), start = selection && selection.getStartElement(), path = this.editor.elementPath(), listeners = this._.listeners; this.removeAll(); // Call all listeners, filling the list of items to be displayed. for ( var i = 0; i < listeners.length; i++ ) { var listenerItems = listeners[ i ]( start, selection, path ); if ( listenerItems ) { for ( var itemName in listenerItems ) { var item = this.editor.getMenuItem( itemName ); if ( item && ( !item.command || this.editor.getCommand( item.command ).state ) ) { item.state = listenerItems[ itemName ]; this.add( item ); } } } } }, onClick: function( item ) { this.hide(); if ( item.onClick ) item.onClick(); else if ( item.command ) this.editor.execCommand( item.command ); }, onEscape: function( keystroke ) { var parent = this.parent; // 1. If it's sub-menu, close it, with focus restored on this. // 2. In case of a top-menu, close it, with focus returned to page. if ( parent ) parent._.panel.hideChild( 1 ); else if ( keystroke == 27 ) this.hide( 1 ); return false; }, onHide: function() { this.onHide && this.onHide(); }, showSubMenu: function( index ) { var menu = this._.subMenu, item = this.items[ index ], subItemDefs = item.getItems && item.getItems(); // If this item has no subitems, we just hide the submenu, if // available, and return back. if ( !subItemDefs ) { // Hide sub menu with focus returned. this._.panel.hideChild( 1 ); return; } // Create the submenu, if not available, or clean the existing // one. if ( menu ) menu.removeAll(); else { menu = this._.subMenu = new CKEDITOR.menu( this.editor, CKEDITOR.tools.extend( {}, this._.definition, { level: this._.level + 1 }, true ) ); menu.parent = this; menu._.onClick = CKEDITOR.tools.bind( this._.onClick, this ); } // Add all submenu items to the menu. for ( var subItemName in subItemDefs ) { var subItem = this.editor.getMenuItem( subItemName ); if ( subItem ) { subItem.state = subItemDefs[ subItemName ]; menu.add( subItem ); } } // Get the element representing the current item. var element = this._.panel.getBlock( this.id ).element.getDocument().getById( this.id + String( index ) ); // Show the submenu. // This timeout is needed to give time for the sub-menu get // focus when JAWS is running. (#9844) setTimeout( function() { menu.show( element, 2 ); },0); } }, proto: { add: function( item ) { // Later we may sort the items, but Array#sort is not stable in // some browsers, here we're forcing the original sequence with // 'order' attribute if it hasn't been assigned. (#3868) if ( !item.order ) item.order = this.items.length; this.items.push( item ); }, removeAll: function() { this.items = []; }, show: function( offsetParent, corner, offsetX, offsetY ) { // Not for sub menu. if ( !this.parent ) { this._.onShow(); // Don't menu with zero items. if ( !this.items.length ) return; } corner = corner || ( this.editor.lang.dir == 'rtl' ? 2 : 1 ); var items = this.items, editor = this.editor, panel = this._.panel, element = this._.element; // Create the floating panel for this menu. if ( !panel ) { panel = this._.panel = new CKEDITOR.ui.floatPanel( this.editor, CKEDITOR.document.getBody(), this._.panelDefinition, this._.level ); panel.onEscape = CKEDITOR.tools.bind( function( keystroke ) { if ( this._.onEscape( keystroke ) === false ) return false; }, this ); panel.onShow = function() { // Menu need CSS resets, compensate class name. var holder = panel._.panel.getHolderElement(); holder.getParent().addClass( 'cke cke_reset_all' ); }; panel.onHide = CKEDITOR.tools.bind( function() { this._.onHide && this._.onHide(); }, this ); // Create an autosize block inside the panel. var block = panel.addBlock( this.id, this._.panelDefinition.block ); block.autoSize = true; var keys = block.keys; keys[ 40 ] = 'next'; // ARROW-DOWN keys[ 9 ] = 'next'; // TAB keys[ 38 ] = 'prev'; // ARROW-UP keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB keys[ ( editor.lang.dir == 'rtl' ? 37 : 39 ) ] = CKEDITOR.env.ie ? 'mouseup' : 'click'; // ARROW-RIGHT/ARROW-LEFT(rtl) keys[ 32 ] = CKEDITOR.env.ie ? 'mouseup' : 'click'; // SPACE CKEDITOR.env.ie && ( keys[ 13 ] = 'mouseup' ); // Manage ENTER, since onclick is blocked in IE (#8041). element = this._.element = block.element; var elementDoc = element.getDocument(); elementDoc.getBody().setStyle( 'overflow', 'hidden' ); elementDoc.getElementsByTag( 'html' ).getItem( 0 ).setStyle( 'overflow', 'hidden' ); this._.itemOverFn = CKEDITOR.tools.addFunction( function( index ) { clearTimeout( this._.showSubTimeout ); this._.showSubTimeout = CKEDITOR.tools.setTimeout( this._.showSubMenu, editor.config.menu_subMenuDelay || 400, this, [ index ] ); }, this ); this._.itemOutFn = CKEDITOR.tools.addFunction( function( index ) { clearTimeout( this._.showSubTimeout ); }, this ); this._.itemClickFn = CKEDITOR.tools.addFunction( function( index ) { var item = this.items[ index ]; if ( item.state == CKEDITOR.TRISTATE_DISABLED ) { this.hide( 1 ); return; } if ( item.getItems ) this._.showSubMenu( index ); else this._.onClick( item ); }, this ); } // Put the items in the right order. sortItems( items ); // Apply the editor mixed direction status to menu. var path = editor.elementPath(), mixedDirCls = ( path && path.direction() != editor.lang.dir ) ? ' cke_mixed_dir_content' : ''; // Build the HTML that composes the menu and its items. var output = [ '' ); // Inject the HTML inside the panel. element.setHtml( output.join( '' ) ); CKEDITOR.ui.fire( 'ready', this ); // Show the panel. if ( this.parent ) this.parent._.panel.showAsChild( panel, this.id, offsetParent, corner, offsetX, offsetY ); else panel.showBlock( this.id, offsetParent, corner, offsetX, offsetY ); editor.fire( 'menuShow', [ panel ] ); }, addListener: function( listenerFn ) { this._.listeners.push( listenerFn ); }, hide: function( returnFocus ) { this._.onHide && this._.onHide(); this._.panel && this._.panel.hide( returnFocus ); } } }); function sortItems( items ) { items.sort( function( itemA, itemB ) { if ( itemA.group < itemB.group ) return -1; else if ( itemA.group > itemB.group ) return 1; return itemA.order < itemB.order ? -1 : itemA.order > itemB.order ? 1 : 0; }); } /** * @class * @todo */ CKEDITOR.menuItem = CKEDITOR.tools.createClass({ $: function( editor, name, definition ) { CKEDITOR.tools.extend( this, definition, // Defaults { order: 0, className: 'cke_menubutton__' + name }); // Transform the group name into its order number. this.group = editor._.menuGroups[ this.group ]; this.editor = editor; this.name = name; }, proto: { render: function( menu, index, output ) { var id = menu.id + String( index ), state = ( typeof this.state == 'undefined' ) ? CKEDITOR.TRISTATE_OFF : this.state; var stateName = state == CKEDITOR.TRISTATE_ON ? 'on' : state == CKEDITOR.TRISTATE_DISABLED ? 'disabled' : 'off'; var hasSubMenu = this.getItems; // ltr: BLACK LEFT-POINTING POINTER // rtl: BLACK RIGHT-POINTING POINTER var arrowLabel = '&#' + ( this.editor.lang.dir == 'rtl' ? '9668' : '9658' ) + ';'; var iconName = this.name; if ( this.icon && !( /\./ ).test( this.icon ) ) iconName = this.icon; var params = { id: id, name: this.name, iconName: iconName, label: this.label, cls: this.className || '', state: stateName, hasPopup: hasSubMenu ? 'true' : 'false', disabled: state == CKEDITOR.TRISTATE_DISABLED, pressed: state == CKEDITOR.TRISTATE_ON, title: this.label, href: 'javascript:void(\'' + ( this.label || '' ).replace( "'" + '' ) + '\')', hoverFn: menu._.itemOverFn, moveOutFn: menu._.itemOutFn, clickFn: menu._.itemClickFn, index: index, iconStyle: CKEDITOR.skin.getIconStyle( iconName, ( this.editor.lang.dir == 'rtl' ), iconName == this.icon ? null : this.icon, this.iconOffset ), arrowHtml: hasSubMenu ? menuArrowTpl.output({ label: arrowLabel } ) : '' }; menuItemTpl.output( params, output ); } } }); })(); /** * The amount of time, in milliseconds, the editor waits before displaying submenu * options when moving the mouse over options that contain submenus, like the * "Cell Properties" entry for tables. * * // Remove the submenu delay. * config.menu_subMenuDelay = 0; * * @cfg {Number} [menu_subMenuDelay=400] * @member CKEDITOR.config */ /** * Fired when a menu is shown. * * @event menuShow * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. * @param {CKEDITOR.ui.panel[]} data */ /** * A comma separated list of items group names to be displayed in the context * menu. The order of items will reflect the order specified in this list if * no priority was defined in the groups. * * config.menu_groups = 'clipboard,table,anchor,link,image'; * * @cfg {String} [menu_groups=see source] * @member CKEDITOR.config */ CKEDITOR.config.menu_groups = 'clipboard,' + 'form,' + 'tablecell,tablecellproperties,tablerow,tablecolumn,table,' + 'anchor,link,image,flash,' + 'checkbox,radio,textfield,hiddenfield,imagebutton,button,select,textarea,div'; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'contextmenu', { requires: 'menu', // Make sure the base class (CKEDITOR.menu) is loaded before it (#3318). onLoad: function() { CKEDITOR.plugins.contextMenu = CKEDITOR.tools.createClass({ base: CKEDITOR.menu, $: function( editor ) { this.base.call( this, editor, { panel: { className: 'cke_menu_panel', attributes: { 'aria-label': editor.lang.contextmenu.options } } }); }, proto: { addTarget: function( element, nativeContextMenuOnCtrl ) { // Opera doesn't support 'contextmenu' event, we have duo approaches employed here: // 1. Inherit the 'button override' hack we introduced in v2 (#4530), while this require the Opera browser // option 'Allow script to detect context menu/right click events' to be always turned on. // 2. Considering the fact that ctrl/meta key is not been occupied // for multiple range selecting (like Gecko), we use this key // combination as a fallback for triggering context-menu. (#4530) if ( CKEDITOR.env.opera && !( 'oncontextmenu' in document.body ) ) { var contextMenuOverrideButton; element.on( 'mousedown', function( evt ) { evt = evt.data; if ( evt.$.button != 2 ) { if ( evt.getKeystroke() == CKEDITOR.CTRL + 1 ) element.fire( 'contextmenu', evt ); return; } if ( nativeContextMenuOnCtrl && ( CKEDITOR.env.mac ? evt.$.metaKey : evt.$.ctrlKey ) ) return; var target = evt.getTarget(); if ( !contextMenuOverrideButton ) { var ownerDoc = target.getDocument(); contextMenuOverrideButton = ownerDoc.createElement( 'input' ); contextMenuOverrideButton.$.type = 'button'; ownerDoc.getBody().append( contextMenuOverrideButton ); } contextMenuOverrideButton.setAttribute( 'style', 'position:absolute;top:' + ( evt.$.clientY - 2 ) + 'px;left:' + ( evt.$.clientX - 2 ) + 'px;width:5px;height:5px;opacity:0.01' ); }); element.on( 'mouseup', function( evt ) { if ( contextMenuOverrideButton ) { contextMenuOverrideButton.remove(); contextMenuOverrideButton = undefined; // Simulate 'contextmenu' event. element.fire( 'contextmenu', evt.data ); } }); } element.on( 'contextmenu', function( event ) { var domEvent = event.data; if ( nativeContextMenuOnCtrl && // Safari on Windows always show 'ctrlKey' as true in 'contextmenu' event, // which make this property unreliable. (#4826) ( CKEDITOR.env.webkit ? holdCtrlKey : ( CKEDITOR.env.mac ? domEvent.$.metaKey : domEvent.$.ctrlKey ) ) ) return; // Cancel the browser context menu. domEvent.preventDefault(); var doc = domEvent.getTarget().getDocument(), offsetParent = domEvent.getTarget().getDocument().getDocumentElement(), fromFrame = !doc.equals( CKEDITOR.document ), scroll = doc.getWindow().getScrollPosition(), offsetX = fromFrame ? domEvent.$.clientX : domEvent.$.pageX || scroll.x + domEvent.$.clientX, offsetY = fromFrame ? domEvent.$.clientY : domEvent.$.pageY || scroll.y + domEvent.$.clientY; CKEDITOR.tools.setTimeout( function() { this.open( offsetParent, null, offsetX, offsetY ); // IE needs a short while to allow selection change before opening menu. (#7908) }, CKEDITOR.env.ie ? 200 : 0, this ); }, this ); if ( CKEDITOR.env.opera ) { // 'contextmenu' event triggered by Windows menu key is unpreventable, // cancel the key event itself. (#6534) element.on( 'keypress', function( evt ) { var domEvent = evt.data; if ( domEvent.$.keyCode === 0 ) domEvent.preventDefault(); }); } if ( CKEDITOR.env.webkit ) { var holdCtrlKey, onKeyDown = function( event ) { holdCtrlKey = CKEDITOR.env.mac ? event.data.$.metaKey : event.data.$.ctrlKey; }, resetOnKeyUp = function() { holdCtrlKey = 0; }; element.on( 'keydown', onKeyDown ); element.on( 'keyup', resetOnKeyUp ); element.on( 'contextmenu', resetOnKeyUp ); } }, open: function( offsetParent, corner, offsetX, offsetY ) { this.editor.focus(); offsetParent = offsetParent || CKEDITOR.document.getDocumentElement(); // #9362: Force selection check to update commands' states in the new context. this.editor.selectionChange( 1 ); this.show( offsetParent, corner, offsetX, offsetY ); } } }); }, beforeInit: function( editor ) { var contextMenu = editor.contextMenu = new CKEDITOR.plugins.contextMenu( editor ); editor.on( 'contentDom', function() { contextMenu.addTarget( editor.editable(), editor.config.browserContextMenuOnCtrl !== false ); }); editor.addCommand( 'contextMenu', { exec: function() { editor.contextMenu.open( editor.document.getBody() ); } }); editor.setKeystroke( CKEDITOR.SHIFT + 121 /*F10*/, 'contextMenu' ); editor.setKeystroke( CKEDITOR.CTRL + CKEDITOR.SHIFT + 121 /*F10*/, 'contextMenu' ); } }); /** * Whether to show the browser native context menu when the *Ctrl* or * *Meta* (Mac) key is pressed on opening the context menu with the * right mouse button click or the *Menu* key. * * config.browserContextMenuOnCtrl = false; * * @since 3.0.2 * @cfg {Boolean} [browserContextMenuOnCtrl=true] * @member CKEDITOR.config */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "div" plugin. It wraps the selected block level elements with a 'div' element with specified styles and attributes. * */ (function() { CKEDITOR.plugins.add( 'div', { requires: 'dialog', init: function( editor ) { if ( editor.blockless ) return; var lang = editor.lang.div; editor.addCommand( 'creatediv', new CKEDITOR.dialogCommand( 'creatediv', { contextSensitive: true, refresh: function( editor, path ) { var context = editor.config.div_wrapTable ? path.root : path.blockLimit; this.setState( 'div' in context.getDtd() ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); } })); editor.addCommand( 'editdiv', new CKEDITOR.dialogCommand( 'editdiv' ) ); editor.addCommand( 'removediv', { exec: function( editor ) { var selection = editor.getSelection(), ranges = selection && selection.getRanges(), range, bookmarks = selection.createBookmarks(), walker, toRemove = []; function findDiv( node ) { var div = CKEDITOR.plugins.div.getSurroundDiv( editor, node ); if ( div && !div.data( 'cke-div-added' ) ) { toRemove.push( div ); div.data( 'cke-div-added' ); } } for ( var i = 0; i < ranges.length; i++ ) { range = ranges[ i ]; if ( range.collapsed ) findDiv( selection.getStartElement() ); else { walker = new CKEDITOR.dom.walker( range ); walker.evaluator = findDiv; walker.lastForward(); } } for ( i = 0; i < toRemove.length; i++ ) toRemove[ i ].remove( true ); selection.selectBookmarks( bookmarks ); } }); editor.ui.addButton && editor.ui.addButton( 'CreateDiv', { label: lang.toolbar, command: 'creatediv', toolbar: 'blocks,50' }); if ( editor.addMenuItems ) { editor.addMenuItems({ editdiv: { label: lang.edit, command: 'editdiv', group: 'div', order: 1 }, removediv: { label: lang.remove, command: 'removediv', group: 'div', order: 5 } }); if ( editor.contextMenu ) { editor.contextMenu.addListener( function( element ) { if ( !element || element.isReadOnly() ) return null; if ( CKEDITOR.plugins.div.getSurroundDiv( editor ) ) { return { editdiv: CKEDITOR.TRISTATE_OFF, removediv: CKEDITOR.TRISTATE_OFF }; } return null; }); } } CKEDITOR.dialog.add( 'creatediv', this.path + 'dialogs/div.js' ); CKEDITOR.dialog.add( 'editdiv', this.path + 'dialogs/div.js' ); } }); CKEDITOR.plugins.div = { getSurroundDiv: function( editor, start ) { var path = editor.elementPath( start ); return editor.elementPath( path.blockLimit ).contains( 'div', 1 ); } }; })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'resize', { init: function( editor ) { var config = editor.config; var spaceId = editor.ui.spaceId( 'resizer' ); // Resize in the same direction of chrome, // which is identical to dir of editor element. (#6614) var resizeDir = editor.element ? editor.element.getDirection( 1 ) : 'ltr'; !config.resize_dir && ( config.resize_dir = 'vertical' ); ( config.resize_maxWidth == undefined ) && ( config.resize_maxWidth = 3000 ); ( config.resize_maxHeight == undefined ) && ( config.resize_maxHeight = 3000 ); ( config.resize_minWidth == undefined ) && ( config.resize_minWidth = 750 ); ( config.resize_minHeight == undefined ) && ( config.resize_minHeight = 250 ); if ( config.resize_enabled !== false ) { var container = null, origin, startSize, resizeHorizontal = ( config.resize_dir == 'both' || config.resize_dir == 'horizontal' ) && ( config.resize_minWidth != config.resize_maxWidth ), resizeVertical = ( config.resize_dir == 'both' || config.resize_dir == 'vertical' ) && ( config.resize_minHeight != config.resize_maxHeight ); function dragHandler( evt ) { var dx = evt.data.$.screenX - origin.x, dy = evt.data.$.screenY - origin.y, width = startSize.width, height = startSize.height, internalWidth = width + dx * ( resizeDir == 'rtl' ? -1 : 1 ), internalHeight = height + dy; if ( resizeHorizontal ) width = Math.max( config.resize_minWidth, Math.min( internalWidth, config.resize_maxWidth ) ); if ( resizeVertical ) height = Math.max( config.resize_minHeight, Math.min( internalHeight, config.resize_maxHeight ) ); // DO NOT impose fixed size with single direction resize. (#6308) editor.resize( resizeHorizontal ? width : null, height ); } function dragEndHandler( evt ) { CKEDITOR.document.removeListener( 'mousemove', dragHandler ); CKEDITOR.document.removeListener( 'mouseup', dragEndHandler ); if ( editor.document ) { editor.document.removeListener( 'mousemove', dragHandler ); editor.document.removeListener( 'mouseup', dragEndHandler ); } } var mouseDownFn = CKEDITOR.tools.addFunction( function( $event ) { if ( !container ) container = editor.getResizable(); startSize = { width: container.$.offsetWidth || 0, height: container.$.offsetHeight || 0 }; origin = { x: $event.screenX, y: $event.screenY }; config.resize_minWidth > startSize.width && ( config.resize_minWidth = startSize.width ); config.resize_minHeight > startSize.height && ( config.resize_minHeight = startSize.height ); CKEDITOR.document.on( 'mousemove', dragHandler ); CKEDITOR.document.on( 'mouseup', dragEndHandler ); if ( editor.document ) { editor.document.on( 'mousemove', dragHandler ); editor.document.on( 'mouseup', dragEndHandler ); } $event.preventDefault && $event.preventDefault(); }); editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( mouseDownFn ); }); editor.on( 'uiSpace', function( event ) { if ( event.data.space == 'bottom' ) { var direction = ''; if ( resizeHorizontal && !resizeVertical ) direction = ' cke_resizer_horizontal'; if ( !resizeHorizontal && resizeVertical ) direction = ' cke_resizer_vertical'; var resizerHtml = '' + // BLACK LOWER RIGHT TRIANGLE (ltr) // BLACK LOWER LEFT TRIANGLE (rtl) ( resizeDir == 'ltr' ? '\u25E2' : '\u25E3' ) + ''; // Always sticks the corner of botttom space. resizeDir == 'ltr' && direction == 'ltr' ? event.data.html += resizerHtml : event.data.html = resizerHtml + event.data.html; } }, editor, null, 100 ); // Toggle the visibility of the resizer when an editor is being maximized or minimized. editor.on( 'maximize', function( event ) { editor.ui.space( 'resizer' )[ event.data == CKEDITOR.TRISTATE_ON ? 'hide' : 'show' ](); }); } } }); /** * The minimum editor width, in pixels, when resizing the editor interface by using the resize handle. * Note: It falls back to editor's actual width if it is smaller than the default value. * * config.resize_minWidth = 500; * * @cfg {Number} [resize_minWidth=750] * @member CKEDITOR.config */ /** * The minimum editor height, in pixels, when resizing the editor interface by using the resize handle. * Note: It falls back to editor's actual height if it is smaller than the default value. * * config.resize_minHeight = 600; * * @cfg {Number} [resize_minHeight=250] * @member CKEDITOR.config */ /** * The maximum editor width, in pixels, when resizing the editor interface by using the resize handle. * * config.resize_maxWidth = 750; * * @cfg {Number} [resize_maxWidth=3000] * @member CKEDITOR.config */ /** * The maximum editor height, in pixels, when resizing the editor interface by using the resize handle. * * config.resize_maxHeight = 600; * * @cfg {Number} [resize_maxHeight=3000] * @member CKEDITOR.config */ /** * Whether to enable the resizing feature. If this feature is disabled, the resize handle will not be visible. * * config.resize_enabled = false; * * @cfg {Boolean} [resize_enabled=true] * @member CKEDITOR.config */ /** * The dimensions for which the editor resizing is enabled. Possible values * are `both`, `vertical`, and `horizontal`. * * config.resize_dir = 'both'; * * @since 3.3 * @cfg {String} [resize_dir='vertical'] * @member CKEDITOR.config */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "toolbar" plugin. Renders the default toolbar interface in * the editor. */ (function() { var toolbox = function() { this.toolbars = []; this.focusCommandExecuted = false; }; toolbox.prototype.focus = function() { for ( var t = 0, toolbar; toolbar = this.toolbars[ t++ ]; ) { for ( var i = 0, item; item = toolbar.items[ i++ ]; ) { if ( item.focus ) { item.focus(); return; } } } }; var commands = { toolbarFocus: { modes: { wysiwyg:1,source:1 }, readOnly: 1, exec: function( editor ) { if ( editor.toolbox ) { editor.toolbox.focusCommandExecuted = true; // Make the first button focus accessible for IE. (#3417) // Adobe AIR instead need while of delay. if ( CKEDITOR.env.ie || CKEDITOR.env.air ) setTimeout( function() { editor.toolbox.focus(); }, 100 ); else editor.toolbox.focus(); } } } }; CKEDITOR.plugins.add( 'toolbar', { requires: 'button', init: function( editor ) { var endFlag; var itemKeystroke = function( item, keystroke ) { var next, toolbar; var rtl = editor.lang.dir == 'rtl', toolbarGroupCycling = editor.config.toolbarGroupCycling; toolbarGroupCycling = toolbarGroupCycling === undefined || toolbarGroupCycling; switch ( keystroke ) { case 9: // TAB case CKEDITOR.SHIFT + 9: // SHIFT + TAB // Cycle through the toolbars, starting from the one // closest to the current item. while ( !toolbar || !toolbar.items.length ) { toolbar = keystroke == 9 ? ( ( toolbar ? toolbar.next : item.toolbar.next ) || editor.toolbox.toolbars[ 0 ] ) : ( ( toolbar ? toolbar.previous : item.toolbar.previous ) || editor.toolbox.toolbars[ editor.toolbox.toolbars.length - 1 ] ); // Look for the first item that accepts focus. if ( toolbar.items.length ) { item = toolbar.items[ endFlag ? ( toolbar.items.length - 1 ) : 0 ]; while ( item && !item.focus ) { item = endFlag ? item.previous : item.next; if ( !item ) toolbar = 0; } } } if ( item ) item.focus(); return false; case rtl ? 37: 39 : // RIGHT-ARROW case 40: // DOWN-ARROW next = item; do { // Look for the next item in the toolbar. next = next.next; // If it's the last item, cycle to the first one. if ( !next && toolbarGroupCycling ) next = item.toolbar.items[ 0 ]; } while ( next && !next.focus ) // If available, just focus it, otherwise focus the // first one. if ( next ) next.focus(); else // Send a TAB. itemKeystroke( item, 9 ); return false; case rtl ? 39: 37 : // LEFT-ARROW case 38: // UP-ARROW next = item; do { // Look for the previous item in the toolbar. next = next.previous; // If it's the first item, cycle to the last one. if ( !next && toolbarGroupCycling ) next = item.toolbar.items[ item.toolbar.items.length - 1 ]; } while ( next && !next.focus ) // If available, just focus it, otherwise focus the // last one. if ( next ) next.focus(); else { endFlag = 1; // Send a SHIFT + TAB. itemKeystroke( item, CKEDITOR.SHIFT + 9 ); endFlag = 0; } return false; case 27: // ESC editor.focus(); return false; case 13: // ENTER case 32: // SPACE item.execute(); return false; } return true; }; editor.on( 'uiSpace', function( event ) { if ( event.data.space == editor.config.toolbarLocation ) { editor.toolbox = new toolbox(); var labelId = CKEDITOR.tools.getNextId(), removeButtons = editor.config.removeButtons; removeButtons = removeButtons && removeButtons.split( ',' ); var output = [ '', editor.lang.toolbar.toolbars, '', '' ]; var expanded = editor.config.toolbarStartupExpanded !== false, groupStarted, pendingSeparator; // If the toolbar collapser will be available, we'll have // an additional container for all toolbars. if ( editor.config.toolbarCanCollapse && editor.elementMode != CKEDITOR.ELEMENT_MODE_INLINE ) output.push( '' : ' style="display:none">' ) ); var toolbars = editor.toolbox.toolbars, toolbar = getToolbarConfig( editor ); for ( var r = 0; r < toolbar.length; r++ ) { var toolbarId, toolbarObj = 0, toolbarName, row = toolbar[ r ], items; // It's better to check if the row object is really // available because it's a common mistake to leave // an extra comma in the toolbar definition // settings, which leads on the editor not loading // at all in IE. (#3983) if ( !row ) continue; if ( groupStarted ) { output.push( '' ); groupStarted = 0; pendingSeparator = 0; } if ( row === '/' ) { output.push( '' ); continue; } items = row.items || row; // Create all items defined for this toolbar. for ( var i = 0; i < items.length; i++ ) { var item, itemName = items[ i ], canGroup; // Ignore items that are configured to be removed. if ( removeButtons && CKEDITOR.tools.indexOf( removeButtons, itemName ) >= 0 ) continue; item = editor.ui.create( itemName ); if ( item ) { if ( item.type == CKEDITOR.UI_SEPARATOR ) { // Do not add the separator immediately. Just save // it be included if we already have something in // the toolbar and if a new item is to be added (later). pendingSeparator = groupStarted && item; continue; } canGroup = item.canGroup !== false; // Initialize the toolbar first, if needed. if ( !toolbarObj ) { // Create the basic toolbar object. toolbarId = CKEDITOR.tools.getNextId(); toolbarObj = { id: toolbarId, items: [] }; toolbarName = row.name && ( editor.lang.toolbar.toolbarGroups[ row.name ] || row.name ); // Output the toolbar opener. output.push( '' ); // If a toolbar name is available, send the voice label. toolbarName && output.push( '', toolbarName, '' ); output.push( '' ); // Add the toolbar to the "editor.toolbox.toolbars" // array. var index = toolbars.push( toolbarObj ) - 1; // Create the next/previous reference. if ( index > 0 ) { toolbarObj.previous = toolbars[ index - 1 ]; toolbarObj.previous.next = toolbarObj; } } if ( canGroup ) { if ( !groupStarted ) { output.push( '' ); groupStarted = 1; } } else if ( groupStarted ) { output.push( '' ); groupStarted = 0; } function addItem( item ) { var itemObj = item.render( editor, output ); index = toolbarObj.items.push( itemObj ) - 1; if ( index > 0 ) { itemObj.previous = toolbarObj.items[ index - 1 ]; itemObj.previous.next = itemObj; } itemObj.toolbar = toolbarObj; itemObj.onkey = itemKeystroke; // Fix for #3052: // Prevent JAWS from focusing the toolbar after document load. itemObj.onfocus = function() { if ( !editor.toolbox.focusCommandExecuted ) editor.focus(); }; } if ( pendingSeparator ) { addItem( pendingSeparator ); pendingSeparator = 0; } addItem( item ); } } if ( groupStarted ) { output.push( '' ); groupStarted = 0; pendingSeparator = 0; } if ( toolbarObj ) output.push( '' ); } if ( editor.config.toolbarCanCollapse ) output.push( '' ); // Not toolbar collapser for inline mode. if ( editor.config.toolbarCanCollapse && editor.elementMode != CKEDITOR.ELEMENT_MODE_INLINE ) { var collapserFn = CKEDITOR.tools.addFunction( function() { editor.execCommand( 'toolbarCollapse' ); }); editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( collapserFn ); }); editor.addCommand( 'toolbarCollapse', { readOnly: 1, exec: function( editor ) { var collapser = editor.ui.space( 'toolbar_collapser' ), toolbox = collapser.getPrevious(), contents = editor.ui.space( 'contents' ), toolboxContainer = toolbox.getParent(), contentHeight = parseInt( contents.$.style.height, 10 ), previousHeight = toolboxContainer.$.offsetHeight, minClass = 'cke_toolbox_collapser_min', collapsed = collapser.hasClass( minClass ); if ( !collapsed ) { toolbox.hide(); collapser.addClass( minClass ); collapser.setAttribute( 'title', editor.lang.toolbar.toolbarExpand ); } else { toolbox.show(); collapser.removeClass( minClass ); collapser.setAttribute( 'title', editor.lang.toolbar.toolbarCollapse ); } // Update collapser symbol. collapser.getFirst().setText( collapsed ? '\u25B2' : // BLACK UP-POINTING TRIANGLE '\u25C0' ); // BLACK LEFT-POINTING TRIANGLE var dy = toolboxContainer.$.offsetHeight - previousHeight; contents.setStyle( 'height', ( contentHeight - dy ) + 'px' ); editor.fire( 'resize' ); }, modes: { wysiwyg:1,source:1 } }); editor.setKeystroke( CKEDITOR.ALT + ( CKEDITOR.env.ie || CKEDITOR.env.webkit ? 189 : 109 ) /*-*/, 'toolbarCollapse' ); output.push( '', '', // BLACK UP-POINTING TRIANGLE '' ); } output.push( '' ); event.data.html += output.join( '' ); } }); editor.on( 'destroy', function() { if ( this.toolbox ) { var toolbars, index = 0, i, items, instance; toolbars = this.toolbox.toolbars; for ( ; index < toolbars.length; index++ ) { items = toolbars[ index ].items; for ( i = 0; i < items.length; i++ ) { instance = items[ i ]; if ( instance.clickFn ) CKEDITOR.tools.removeFunction( instance.clickFn ); if ( instance.keyDownFn ) CKEDITOR.tools.removeFunction( instance.keyDownFn ); } } } }); // Manage editor focus when navigating the toolbar. editor.on( 'uiReady', function() { var toolbox = editor.ui.space( 'toolbox' ); toolbox && editor.focusManager.add( toolbox, 1 ); }); editor.addCommand( 'toolbarFocus', commands.toolbarFocus ); editor.setKeystroke( CKEDITOR.ALT + 121 /*F10*/, 'toolbarFocus' ); editor.ui.add( '-', CKEDITOR.UI_SEPARATOR, {} ); editor.ui.addHandler( CKEDITOR.UI_SEPARATOR, { create: function() { return { render: function( editor, output ) { output.push( '' ); return {}; } }; } }); } }); function getToolbarConfig( editor ) { function buildToolbarConfig() { // Object containing all toolbar groups used by ui items. var lookup = getItemDefinedGroups(); // Take the base for the new toolbar, which is basically a toolbar // definition without items. var toolbar = CKEDITOR.tools.clone( editor.config.toolbarGroups ) || getPrivateToolbarGroups( editor ); // Fill the toolbar groups with the available ui items. for ( var i = 0; i < toolbar.length; i++ ) { var toolbarGroup = toolbar[ i ]; // Skip toolbar break. if ( toolbarGroup == '/' ) continue; // Handle simply group name item. else if ( typeof toolbarGroup == 'string' ) toolbarGroup = toolbar[ i ] = { name: toolbarGroup }; var items, subGroups = toolbarGroup.groups; // Look for items that match sub groups. if ( subGroups ) { for ( var j = 0, sub; j < subGroups.length; j++ ) { sub = subGroups[ j ]; // If any ui item is registered for this subgroup. items = lookup[ sub ]; items && fillGroup( toolbarGroup, items ); } } // Add the main group items as well. items = lookup[ toolbarGroup.name ]; items && fillGroup( toolbarGroup, items ); } return toolbar; } // Returns an object containing all toolbar groups used by ui items. function getItemDefinedGroups() { var groups = {}, itemName, item, itemToolbar, group, order; for ( itemName in editor.ui.items ) { item = editor.ui.items[ itemName ]; itemToolbar = item.toolbar || 'others'; if ( itemToolbar ) { // Break the toolbar property into its parts: "group_name[,order]". itemToolbar = itemToolbar.split( ',' ); group = itemToolbar[ 0 ]; order = parseInt( itemToolbar[ 1 ] || -1, 10 ); // Initialize the group, if necessary. groups[ group ] || ( groups[ group ] = [] ); // Push the data used to build the toolbar later. groups[ group ].push( { name: itemName, order: order} ); } } // Put the items in the right order. for ( group in groups ) { groups[ group ] = groups[ group ].sort( function( a, b ) { return a.order == b.order ? 0 : b.order < 0 ? -1 : a.order < 0 ? 1 : a.order < b.order ? -1 : 1; }); } return groups; } function fillGroup( toolbarGroup, uiItems ) { if ( uiItems.length ) { if ( toolbarGroup.items ) toolbarGroup.items.push( '-' ); else toolbarGroup.items = []; var item; while ( ( item = uiItems.shift() ) ) toolbarGroup.items.push( item.name ); } } var toolbar = editor.config.toolbar; // If it is a string, return the relative "toolbar_name" config. if ( typeof toolbar == 'string' ) toolbar = editor.config[ 'toolbar_' + toolbar ]; // If toolbar hasn't been explicitly defined, build it based on the toolbarGroups. return ( editor.toolbar = toolbar || buildToolbarConfig() ); } /** * Add toolbar group. See {@link CKEDITOR.config#toolbarGroups} for more details. * * **Note:** This method won't modify toolbar groups set explicitly by * {@link CKEDITOR.config#toolbarGroups}. It will extend only default setting. * * @param {String} name Group name. * @param {Number/String} previous Name of group after which this one * should be added or `0` if this group should be the first one. * @param {String} [subgroupOf] Name of parent group. * @member CKEDITOR.ui */ CKEDITOR.ui.prototype.addToolbarGroup = function( name, previous, subgroupOf ) { // The toolbarGroups from the privates is the one we gonna use for automatic toolbar creation. var toolbarGroups = getPrivateToolbarGroups( this.editor ), atStart = previous === 0, newGroup = { name: name }; if ( subgroupOf ) { // Transform the subgroupOf name in the real subgroup object. subgroupOf = CKEDITOR.tools.search( toolbarGroups, function( group ) { return group.name == subgroupOf; }); if ( subgroupOf ) { !subgroupOf.groups && ( subgroupOf.groups = [] ) ; if ( previous ) { // Search the "previous" item and add the new one after it. previous = CKEDITOR.tools.indexOf( subgroupOf.groups, previous ); if ( previous >= 0 ) { subgroupOf.groups.splice( previous + 1, 0, name ); return; } } // If no previous found. if ( atStart ) subgroupOf.groups.splice( 0, 0, name ); else subgroupOf.groups.push( name ); return; } else { // Ignore "previous" if subgroupOf has not been found. previous = null; } } if ( previous ) { // Transform the "previous" name into its index. previous = CKEDITOR.tools.indexOf( toolbarGroups, function( group ) { return group.name == previous; }); } if ( atStart ) toolbarGroups.splice( 0, 0, name ); else if ( typeof previous == 'number' ) toolbarGroups.splice( previous + 1, 0, newGroup ); else toolbarGroups.push( name ); }; function getPrivateToolbarGroups( editor ) { return editor._.toolbarGroups || ( editor._.toolbarGroups = [ { name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] }, { name: 'forms' }, '/', { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align' ] }, { name: 'links' }, { name: 'insert' }, '/', { name: 'styles' }, { name: 'colors' }, { name: 'tools' }, { name: 'others' }, { name: 'about' } ]); } })(); /** * Separator UI element. * * @readonly * @property {String} [='separator'] * @member CKEDITOR */ CKEDITOR.UI_SEPARATOR = 'separator'; /** * The "UI space" to which rendering the toolbar. For the default editor implementation, * the recommended options are `'top'` and `'bottom'`. * * config.toolbarLocation = 'bottom'; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.toolbarLocation = 'top'; /** * The toolbox (alias toolbar) definition. It is a toolbar name or an array of * toolbars (strips), each one being also an array, containing a list of UI items. * * If set to `null`, generate toolbar automatically using all available buttons * and {@link #toolbarGroups} as a toolbar groups layout. * * // Defines a toolbar with only one strip containing the "Source" button, a * // separator and the "Bold" and "Italic" buttons. * config.toolbar = [ * [ 'Source', '-', 'Bold', 'Italic' ] * ]; * * // Similar to example the above, defines a "Basic" toolbar with only one strip containing three buttons. * // Note that this setting is composed by "toolbar_" added by the toolbar name, which in this case is called "Basic". * // This second part of the setting name can be anything. You must use this name in the CKEDITOR.config.toolbar setting, * // so you instruct the editor which toolbar_(name) setting to use. * config.toolbar_Basic = [ * [ 'Source', '-', 'Bold', 'Italic' ] * ]; * // Load toolbar_Name where Name = Basic. * config.toolbar = 'Basic'; * * @cfg {Array/String} [toolbar=null] * @member CKEDITOR.config */ /** * The toolbar groups definition. * * If toolbar layout isn't explicitly defined by {@link #toolbar} setting, then * this setting is used to group all defined buttons (see {@link CKEDITOR.ui#addButton}). * Buttons are associated with toolbar groups by `toolbar` property in their definition objects. * * New groups may be dynamically added during the editor and plugins initialization by * {@link CKEDITOR.ui#addToolbarGroup}. Although only if default setting was used. * * // Default setting. * config.toolbarGroups = [ * { name: 'document', groups: [ 'mode', 'document', 'doctools' ] }, * { name: 'clipboard', groups: [ 'clipboard', 'undo' ] }, * { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ] }, * { name: 'forms' }, * '/', * { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, * { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align' ] }, * { name: 'links' }, * { name: 'insert' }, * '/', * { name: 'styles' }, * { name: 'colors' }, * { name: 'tools' }, * { name: 'others' }, * { name: 'about' } * ]; * * @cfg {Array} [toolbarGroups=see example] * @member CKEDITOR.config */ /** * Whether the toolbar can be collapsed by the user. If disabled, the collapser * button will not be displayed. * * config.toolbarCanCollapse = true; * * @cfg {Boolean} [toolbarCanCollapse=false] * @member CKEDITOR.config */ /** * Whether the toolbar must start expanded when the editor is loaded. * * Setting this option to `false` will affect toolbar only when * {@link #toolbarCanCollapse} is set to `true`: * * config.toolbarCanCollapse = true; * config.toolbarStartupExpanded = false; * * @cfg {Boolean} [toolbarStartupExpanded=true] * @member CKEDITOR.config */ /** * When enabled, makes the arrow keys navigation cycle within the current * toolbar group. Otherwise the arrows will move through all items available in * the toolbar. The *TAB* key will still be used to quickly jump among the * toolbar groups. * * config.toolbarGroupCycling = false; * * @since 3.6 * @cfg {Boolean} [toolbarGroupCycling=true] * @member CKEDITOR.config */ /** * List of toolbar button names that must not be rendered. This will work as * well for non-button toolbar items, like the Font combos. * * config.removeButtons = 'Underline,JustifyCenter'; * * This configuration should not be overused, having * {@link CKEDITOR.config#removePlugins} removing features from the editor. In * some cases though, a single plugin may define a set of toolbar buttons and * removeButtons may be useful when just a few of them are to be removed. * * @cfg {String} [removeButtons] * @member CKEDITOR.config */ /** * Toolbar definition used by the editor. It is crated from the * {@link CKEDITOR.config#toolbar} if it is set or automatically * based on {@link CKEDITOR.config#toolbarGroups}. * * @property {Object} toolbar * @member CKEDITOR.editor */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "elementspath" plugin. It shows all elements in the DOM * parent tree relative to the current selection in the editing area. */ (function() { var commands = { toolbarFocus: { editorFocus: false, readOnly: 1, exec: function( editor ) { var idBase = editor._.elementsPath.idBase; var element = CKEDITOR.document.getById( idBase + '0' ); // Make the first button focus accessible for IE. (#3417) // Adobe AIR instead need while of delay. element && element.focus( CKEDITOR.env.ie || CKEDITOR.env.air ); } } }; var emptyHtml = ' '; var extra = ''; // Some browsers don't cancel key events in the keydown but in the // keypress. // TODO: Check if really needed for Gecko+Mac. if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) extra += ' onkeypress="return false;"'; // With Firefox, we need to force the button to redraw, otherwise it // will remain in the focus state. if ( CKEDITOR.env.gecko ) extra += ' onblur="this.style.cssText = this.style.cssText;"'; var pathItemTpl = CKEDITOR.addTemplate( 'pathItem', '' + '{text}' + '' ); CKEDITOR.plugins.add( 'elementspath', { init: function( editor ) { // Elements path isn't available in inline mode. if ( editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE ) return; var spaceId = editor.ui.spaceId( 'path' ); var spaceElement; var getSpaceElement = function() { if ( !spaceElement ) spaceElement = CKEDITOR.document.getById( spaceId ); return spaceElement; }; var idBase = 'cke_elementspath_' + CKEDITOR.tools.getNextNumber() + '_'; editor._.elementsPath = { idBase: idBase, filters: [] }; editor.on( 'uiSpace', function( event ) { if ( event.data.space == 'bottom' ) { event.data.html += '' + editor.lang.elementspath.eleLabel + '' + '' + emptyHtml + ''; } }); // Register the ui element to the focus manager. editor.on( 'uiReady', function() { var element = editor.ui.space( 'path' ); element && editor.focusManager.add( element, 1 ); }); function onClick( elementIndex ) { editor.focus(); var element = editor._.elementsPath.list[ elementIndex ]; if ( element.equals( editor.editable() ) ) { var range = editor.createRange(); range.selectNodeContents( element ); range.select(); } else editor.getSelection().selectElement( element ); } var onClickHanlder = CKEDITOR.tools.addFunction( onClick ); var onKeyDownHandler = CKEDITOR.tools.addFunction( function( elementIndex, ev ) { var idBase = editor._.elementsPath.idBase, element; ev = new CKEDITOR.dom.event( ev ); var rtl = editor.lang.dir == 'rtl'; switch ( ev.getKeystroke() ) { case rtl ? 39: 37 : // LEFT-ARROW case 9: // TAB element = CKEDITOR.document.getById( idBase + ( elementIndex + 1 ) ); if ( !element ) element = CKEDITOR.document.getById( idBase + '0' ); element.focus(); return false; case rtl ? 37: 39 : // RIGHT-ARROW case CKEDITOR.SHIFT + 9: // SHIFT + TAB element = CKEDITOR.document.getById( idBase + ( elementIndex - 1 ) ); if ( !element ) element = CKEDITOR.document.getById( idBase + ( editor._.elementsPath.list.length - 1 ) ); element.focus(); return false; case 27: // ESC editor.focus(); return false; case 13: // ENTER // Opera case 32: // SPACE onClick( elementIndex ); return false; } return true; }); editor.on( 'selectionChange', function( ev ) { var env = CKEDITOR.env, editable = editor.editable(), selection = ev.data.selection, element = selection.getStartElement(), html = [], elementsList = editor._.elementsPath.list = [], filters = editor._.elementsPath.filters; while ( element ) { var ignore = 0, name; if ( element.data( 'cke-display-name' ) ) name = element.data( 'cke-display-name' ); else if ( element.data( 'cke-real-element-type' ) ) name = element.data( 'cke-real-element-type' ); else name = element.getName(); for ( var i = 0; i < filters.length; i++ ) { var ret = filters[ i ]( element, name ); if ( ret === false ) { ignore = 1; break; } name = ret || name; } if ( !ignore ) { var index = elementsList.push( element ) - 1, label = editor.lang.elementspath.eleTitle.replace( /%1/, name ); var item = pathItemTpl.output({ id: idBase + index, label: label, text: name, jsTitle: 'javascript:void(\'' + name + '\')', index: index, keyDownFn: onKeyDownHandler, clickFn: onClickHanlder }); html.unshift( item ); } if ( element.equals( editable ) ) break; element = element.getParent(); } var space = getSpaceElement(); space.setHtml( html.join( '' ) + emptyHtml ); editor.fire( 'elementsPathUpdate', { space: space } ); }); function empty() { spaceElement && spaceElement.setHtml( emptyHtml ); delete editor._.elementsPath.list; } editor.on( 'readOnly', empty ); editor.on( 'contentDomUnload', empty ); editor.addCommand( 'elementsPathFocus', commands.toolbarFocus ); editor.setKeystroke( CKEDITOR.ALT + 122 /*F11*/, 'elementsPathFocus' ); } }); })(); /** * Fired when the contents of the elementsPath are changed. * * @event elementsPathUpdate * @member CKEDITOR.editor * @param {CKEDITOR.editor} editor This editor instance. * @param data * @param {CKEDITOR.dom.element} data.space The elementsPath container. */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Insert and remove numbered and bulleted lists. */ (function() { var listNodeNames = { ol:1,ul:1 }, emptyTextRegex = /^[\n\r\t ]*$/; var whitespaces = CKEDITOR.dom.walker.whitespaces(), bookmarks = CKEDITOR.dom.walker.bookmark(), nonEmpty = function( node ) { return !( whitespaces( node ) || bookmarks( node ) ); }, blockBogus = CKEDITOR.dom.walker.bogus(); function cleanUpDirection( element ) { var dir, parent, parentDir; if ( ( dir = element.getDirection() ) ) { parent = element.getParent(); while ( parent && !( parentDir = parent.getDirection() ) ) parent = parent.getParent(); if ( dir == parentDir ) element.removeAttribute( 'dir' ); } } // Inheirt inline styles from another element. function inheirtInlineStyles( parent, el ) { var style = parent.getAttribute( 'style' ); // Put parent styles before child styles. style && el.setAttribute( 'style', style.replace( /([^;])$/, '$1;' ) + ( el.getAttribute( 'style' ) || '' ) ); } CKEDITOR.plugins.list = { /** * Convert a DOM list tree into a data structure that is easier to * manipulate. This operation should be non-intrusive in the sense that it * does not change the DOM tree, with the exception that it may add some * markers to the list item nodes when database is specified. * * @member CKEDITOR.plugins.list * @todo params */ listToArray: function( listNode, database, baseArray, baseIndentLevel, grandparentNode ) { if ( !listNodeNames[ listNode.getName() ] ) return []; if ( !baseIndentLevel ) baseIndentLevel = 0; if ( !baseArray ) baseArray = []; // Iterate over all list items to and look for inner lists. for ( var i = 0, count = listNode.getChildCount(); i < count; i++ ) { var listItem = listNode.getChild( i ); // Fixing malformed nested lists by moving it into a previous list item. (#6236) if ( listItem.type == CKEDITOR.NODE_ELEMENT && listItem.getName() in CKEDITOR.dtd.$list ) CKEDITOR.plugins.list.listToArray( listItem, database, baseArray, baseIndentLevel + 1 ); // It may be a text node or some funny stuff. if ( listItem.$.nodeName.toLowerCase() != 'li' ) continue; var itemObj = { 'parent': listNode, indent: baseIndentLevel, element: listItem, contents: [] }; if ( !grandparentNode ) { itemObj.grandparent = listNode.getParent(); if ( itemObj.grandparent && itemObj.grandparent.$.nodeName.toLowerCase() == 'li' ) itemObj.grandparent = itemObj.grandparent.getParent(); } else itemObj.grandparent = grandparentNode; if ( database ) CKEDITOR.dom.element.setMarker( database, listItem, 'listarray_index', baseArray.length ); baseArray.push( itemObj ); for ( var j = 0, itemChildCount = listItem.getChildCount(), child; j < itemChildCount; j++ ) { child = listItem.getChild( j ); if ( child.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ child.getName() ] ) // Note the recursion here, it pushes inner list items with // +1 indentation in the correct order. CKEDITOR.plugins.list.listToArray( child, database, baseArray, baseIndentLevel + 1, itemObj.grandparent ); else itemObj.contents.push( child ); } } return baseArray; }, /** * Convert our internal representation of a list back to a DOM forest. * * @member CKEDITOR.plugins.list * @todo params */ arrayToList: function( listArray, database, baseIndex, paragraphMode, dir ) { if ( !baseIndex ) baseIndex = 0; if ( !listArray || listArray.length < baseIndex + 1 ) return null; var i, doc = listArray[ baseIndex ].parent.getDocument(), retval = new CKEDITOR.dom.documentFragment( doc ), rootNode = null, currentIndex = baseIndex, indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ), currentListItem = null, orgDir, block, paragraphName = ( paragraphMode == CKEDITOR.ENTER_P ? 'p' : 'div' ); while ( 1 ) { var item = listArray[ currentIndex ], itemGrandParent = item.grandparent; orgDir = item.element.getDirection( 1 ); if ( item.indent == indentLevel ) { if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() ) { rootNode = listArray[ currentIndex ].parent.clone( false, 1 ); dir && rootNode.setAttribute( 'dir', dir ); retval.append( rootNode ); } currentListItem = rootNode.append( item.element.clone( 0, 1 ) ); if ( orgDir != rootNode.getDirection( 1 ) ) currentListItem.setAttribute( 'dir', orgDir ); for ( i = 0; i < item.contents.length; i++ ) currentListItem.append( item.contents[ i ].clone( 1, 1 ) ); currentIndex++; } else if ( item.indent == Math.max( indentLevel, 0 ) + 1 ) { // Maintain original direction (#6861). var currDir = listArray[ currentIndex - 1 ].element.getDirection( 1 ), listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode, currDir != orgDir ? orgDir : null ); // If the next block is an
  • with another list tree as the first // child, we'll need to append a filler (
    /NBSP) or the list item // wouldn't be editable. (#6724) if ( !currentListItem.getChildCount() && CKEDITOR.env.ie && !( doc.$.documentMode > 7 ) ) currentListItem.append( doc.createText( '\xa0' ) ); currentListItem.append( listData.listNode ); currentIndex = listData.nextIndex; } else if ( item.indent == -1 && !baseIndex && itemGrandParent ) { if ( listNodeNames[ itemGrandParent.getName() ] ) { currentListItem = item.element.clone( false, true ); if ( orgDir != itemGrandParent.getDirection( 1 ) ) currentListItem.setAttribute( 'dir', orgDir ); } else currentListItem = new CKEDITOR.dom.documentFragment( doc ); // Migrate all children to the new container, // apply the proper text direction. var dirLoose = itemGrandParent.getDirection( 1 ) != orgDir, li = item.element, className = li.getAttribute( 'class' ), style = li.getAttribute( 'style' ); var needsBlock = currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT && ( paragraphMode != CKEDITOR.ENTER_BR || dirLoose || style || className ); var child, count = item.contents.length; for ( i = 0; i < count; i++ ) { child = item.contents[ i ]; if ( child.type == CKEDITOR.NODE_ELEMENT && child.isBlockBoundary() ) { // Apply direction on content blocks. if ( dirLoose && !child.getDirection() ) child.setAttribute( 'dir', orgDir ); inheirtInlineStyles( li, child ); className && child.addClass( className ); } else if ( needsBlock ) { // Establish new block to hold text direction and styles. if ( !block ) { block = doc.createElement( paragraphName ); dirLoose && block.setAttribute( 'dir', orgDir ); } // Copy over styles to new block; style && block.setAttribute( 'style', style ); className && block.setAttribute( 'class', className ); block.append( child.clone( 1, 1 ) ); } currentListItem.append( block || child.clone( 1, 1 ) ); } if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT && currentIndex != listArray.length - 1 ) { var last = currentListItem.getLast(); if ( last && last.type == CKEDITOR.NODE_ELEMENT && last.getAttribute( 'type' ) == '_moz' ) { last.remove(); } if ( !( last = currentListItem.getLast( nonEmpty ) && last.type == CKEDITOR.NODE_ELEMENT && last.getName() in CKEDITOR.dtd.$block ) ) { currentListItem.append( doc.createElement( 'br' ) ); } } var currentListItemName = currentListItem.$.nodeName.toLowerCase(); if ( !CKEDITOR.env.ie && ( currentListItemName == 'div' || currentListItemName == 'p' ) ) currentListItem.appendBogus(); retval.append( currentListItem ); rootNode = null; currentIndex++; } else return null; block = null; if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel ) break; } if ( database ) { var currentNode = retval.getFirst(), listRoot = listArray[ 0 ].parent; while ( currentNode ) { if ( currentNode.type == CKEDITOR.NODE_ELEMENT ) { // Clear marker attributes for the new list tree made of cloned nodes, if any. CKEDITOR.dom.element.clearMarkers( database, currentNode ); // Clear redundant direction attribute specified on list items. if ( currentNode.getName() in CKEDITOR.dtd.$listItem ) cleanUpDirection( currentNode ); } currentNode = currentNode.getNextSourceNode(); } } return { listNode: retval, nextIndex: currentIndex }; } }; function changeListType( editor, groupObj, database, listsCreated ) { // This case is easy... // 1. Convert the whole list into a one-dimensional array. // 2. Change the list type by modifying the array. // 3. Recreate the whole list by converting the array to a list. // 4. Replace the original list with the recreated list. var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ), selectedListItems = []; for ( var i = 0; i < groupObj.contents.length; i++ ) { var itemNode = groupObj.contents[ i ]; itemNode = itemNode.getAscendant( 'li', true ); if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) ) continue; selectedListItems.push( itemNode ); CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true ); } var root = groupObj.root, doc = root.getDocument(), listNode, newListNode; for ( i = 0; i < selectedListItems.length; i++ ) { var listIndex = selectedListItems[ i ].getCustomData( 'listarray_index' ); listNode = listArray[ listIndex ].parent; // Switch to new list node for this particular item. if ( !listNode.is( this.type ) ) { newListNode = doc.createElement( this.type ); // Copy all attributes, except from 'start' and 'type'. listNode.copyAttributes( newListNode, { start:1,type:1 } ); // The list-style-type property should be ignored. newListNode.removeStyle( 'list-style-type' ); listArray[ listIndex ].parent = newListNode; } } var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode ); var child, length = newList.listNode.getChildCount(); for ( i = 0; i < length && ( child = newList.listNode.getChild( i ) ); i++ ) { if ( child.getName() == this.type ) listsCreated.push( child ); } newList.listNode.replace( groupObj.root ); } var headerTagRegex = /^h[1-6]$/; function createList( editor, groupObj, listsCreated ) { var contents = groupObj.contents, doc = groupObj.root.getDocument(), listContents = []; // It is possible to have the contents returned by DomRangeIterator to be the same as the root. // e.g. when we're running into table cells. // In such a case, enclose the childNodes of contents[0] into a
    . if ( contents.length == 1 && contents[ 0 ].equals( groupObj.root ) ) { var divBlock = doc.createElement( 'div' ); contents[ 0 ].moveChildren && contents[ 0 ].moveChildren( divBlock ); contents[ 0 ].append( divBlock ); contents[ 0 ] = divBlock; } // Calculate the common parent node of all content blocks. var commonParent = groupObj.contents[ 0 ].getParent(); for ( var i = 0; i < contents.length; i++ ) commonParent = commonParent.getCommonAncestor( contents[ i ].getParent() ); var useComputedState = editor.config.useComputedState, listDir, explicitDirection; useComputedState = useComputedState === undefined || useComputedState; // We want to insert things that are in the same tree level only, so calculate the contents again // by expanding the selected blocks to the same tree level. for ( i = 0; i < contents.length; i++ ) { var contentNode = contents[ i ], parentNode; while ( ( parentNode = contentNode.getParent() ) ) { if ( parentNode.equals( commonParent ) ) { listContents.push( contentNode ); // Determine the lists's direction. if ( !explicitDirection && contentNode.getDirection() ) explicitDirection = 1; var itemDir = contentNode.getDirection( useComputedState ); if ( listDir !== null ) { // If at least one LI have a different direction than current listDir, we can't have listDir. if ( listDir && listDir != itemDir ) listDir = null; else listDir = itemDir; } break; } contentNode = parentNode; } } if ( listContents.length < 1 ) return; // Insert the list to the DOM tree. var insertAnchor = listContents[ listContents.length - 1 ].getNext(), listNode = doc.createElement( this.type ); listsCreated.push( listNode ); var contentBlock, listItem; while ( listContents.length ) { contentBlock = listContents.shift(); listItem = doc.createElement( 'li' ); // Preserve preformat block and heading structure when converting to list item. (#5335) (#5271) if ( contentBlock.is( 'pre' ) || headerTagRegex.test( contentBlock.getName() ) ) contentBlock.appendTo( listItem ); else { contentBlock.copyAttributes( listItem ); // Remove direction attribute after it was merged into list root. (#7657) if ( listDir && contentBlock.getDirection() ) { listItem.removeStyle( 'direction' ); listItem.removeAttribute( 'dir' ); } contentBlock.moveChildren( listItem ); contentBlock.remove(); } listItem.appendTo( listNode ); } // Apply list root dir only if it has been explicitly declared. if ( listDir && explicitDirection ) listNode.setAttribute( 'dir', listDir ); if ( insertAnchor ) listNode.insertBefore( insertAnchor ); else listNode.appendTo( commonParent ); } function removeList( editor, groupObj, database ) { // This is very much like the change list type operation. // Except that we're changing the selected items' indent to -1 in the list array. var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ), selectedListItems = []; for ( var i = 0; i < groupObj.contents.length; i++ ) { var itemNode = groupObj.contents[ i ]; itemNode = itemNode.getAscendant( 'li', true ); if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) ) continue; selectedListItems.push( itemNode ); CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true ); } var lastListIndex = null; for ( i = 0; i < selectedListItems.length; i++ ) { var listIndex = selectedListItems[ i ].getCustomData( 'listarray_index' ); listArray[ listIndex ].indent = -1; lastListIndex = listIndex; } // After cutting parts of the list out with indent=-1, we still have to maintain the array list // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the // list cannot be converted back to a real DOM list. for ( i = lastListIndex + 1; i < listArray.length; i++ ) { if ( listArray[ i ].indent > listArray[ i - 1 ].indent + 1 ) { var indentOffset = listArray[ i - 1 ].indent + 1 - listArray[ i ].indent; var oldIndent = listArray[ i ].indent; while ( listArray[ i ] && listArray[ i ].indent >= oldIndent ) { listArray[ i ].indent += indentOffset; i++; } i--; } } var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, groupObj.root.getAttribute( 'dir' ) ); // Compensate
    before/after the list node if the surrounds are non-blocks.(#3836) var docFragment = newList.listNode, boundaryNode, siblingNode; function compensateBrs( isStart ) { if ( ( boundaryNode = docFragment[ isStart ? 'getFirst' : 'getLast' ]() ) && !( boundaryNode.is && boundaryNode.isBlockBoundary() ) && ( siblingNode = groupObj.root[ isStart ? 'getPrevious' : 'getNext' ] ( CKEDITOR.dom.walker.invisible( true ) ) ) && !( siblingNode.is && siblingNode.isBlockBoundary( { br:1 } ) ) ) editor.document.createElement( 'br' )[ isStart ? 'insertBefore' : 'insertAfter' ]( boundaryNode ); } compensateBrs( true ); compensateBrs(); docFragment.replace( groupObj.root ); } function listCommand( name, type ) { this.name = name; this.type = type; this.context = type; } var elementType = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ); // Merge child nodes with direction preserved. (#7448) function mergeChildren( from, into, refNode, forward ) { var child, itemDir; while ( ( child = from[ forward ? 'getLast' : 'getFirst' ]( elementType ) ) ) { if ( ( itemDir = child.getDirection( 1 ) ) !== into.getDirection( 1 ) ) child.setAttribute( 'dir', itemDir ); child.remove(); refNode ? child[ forward ? 'insertBefore' : 'insertAfter' ]( refNode ) : into.append( child, forward ); } } listCommand.prototype = { exec: function( editor ) { // Run state check first of all. this.refresh( editor, editor.elementPath() ); var doc = editor.document, config = editor.config, selection = editor.getSelection(), ranges = selection && selection.getRanges( true ); // Midas lists rule #1 says we can create a list even in an empty document. // But DOM iterator wouldn't run if the document is really empty. // So create a paragraph if the document is empty and we're going to create a list. if ( this.state == CKEDITOR.TRISTATE_OFF ) { var editable = editor.editable(); if ( !editable.getFirst( nonEmpty ) ) { config.enterMode == CKEDITOR.ENTER_BR ? editable.appendBogus() : ranges[ 0 ].fixBlock( 1, config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ); selection.selectRanges( ranges ); } // Maybe a single range there enclosing the whole list, // turn on the list state manually(#4129). else { var range = ranges.length == 1 && ranges[ 0 ], enclosedNode = range && range.getEnclosedNode(); if ( enclosedNode && enclosedNode.is && this.type == enclosedNode.getName() ) this.setState( CKEDITOR.TRISTATE_ON ); } } var bookmarks = selection.createBookmarks( true ); // Group the blocks up because there are many cases where multiple lists have to be created, // or multiple lists have to be cancelled. var listGroups = [], database = {}, rangeIterator = ranges.createIterator(), index = 0; while ( ( range = rangeIterator.getNextRange() ) && ++index ) { var boundaryNodes = range.getBoundaryNodes(), startNode = boundaryNodes.startNode, endNode = boundaryNodes.endNode; if ( startNode.type == CKEDITOR.NODE_ELEMENT && startNode.getName() == 'td' ) range.setStartAt( boundaryNodes.startNode, CKEDITOR.POSITION_AFTER_START ); if ( endNode.type == CKEDITOR.NODE_ELEMENT && endNode.getName() == 'td' ) range.setEndAt( boundaryNodes.endNode, CKEDITOR.POSITION_BEFORE_END ); var iterator = range.createIterator(), block; iterator.forceBrBreak = ( this.state == CKEDITOR.TRISTATE_OFF ); while ( ( block = iterator.getNextParagraph() ) ) { // Avoid duplicate blocks get processed across ranges. if ( block.getCustomData( 'list_block' ) ) continue; else CKEDITOR.dom.element.setMarker( database, block, 'list_block', 1 ); var path = editor.elementPath( block ), pathElements = path.elements, pathElementsCount = pathElements.length, listNode = null, processedFlag = 0, blockLimit = path.blockLimit, element; // First, try to group by a list ancestor. for ( var i = pathElementsCount - 1; i >= 0 && ( element = pathElements[ i ] ); i-- ) { if ( listNodeNames[ element.getName() ] && blockLimit.contains( element ) ) // Don't leak outside block limit (#3940). { // If we've encountered a list inside a block limit // The last group object of the block limit element should // no longer be valid. Since paragraphs after the list // should belong to a different group of paragraphs before // the list. (Bug #1309) blockLimit.removeCustomData( 'list_group_object_' + index ); var groupObj = element.getCustomData( 'list_group_object' ); if ( groupObj ) groupObj.contents.push( block ); else { groupObj = { root: element, contents: [ block ] }; listGroups.push( groupObj ); CKEDITOR.dom.element.setMarker( database, element, 'list_group_object', groupObj ); } processedFlag = 1; break; } } if ( processedFlag ) continue; // No list ancestor? Group by block limit, but don't mix contents from different ranges. var root = blockLimit; if ( root.getCustomData( 'list_group_object_' + index ) ) root.getCustomData( 'list_group_object_' + index ).contents.push( block ); else { groupObj = { root: root, contents: [ block ] }; CKEDITOR.dom.element.setMarker( database, root, 'list_group_object_' + index, groupObj ); listGroups.push( groupObj ); } } } // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element. // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking // at the group that's not rooted at lists. So we have three cases to handle. var listsCreated = []; while ( listGroups.length > 0 ) { groupObj = listGroups.shift(); if ( this.state == CKEDITOR.TRISTATE_OFF ) { if ( listNodeNames[ groupObj.root.getName() ] ) changeListType.call( this, editor, groupObj, database, listsCreated ); else createList.call( this, editor, groupObj, listsCreated ); } else if ( this.state == CKEDITOR.TRISTATE_ON && listNodeNames[ groupObj.root.getName() ] ) removeList.call( this, editor, groupObj, database ); } // For all new lists created, merge into adjacent, same type lists. for ( i = 0; i < listsCreated.length; i++ ) mergeListSiblings( listsCreated[ i ] ); // Clean up, restore selection and update toolbar button states. CKEDITOR.dom.element.clearAllMarkers( database ); selection.selectBookmarks( bookmarks ); editor.focus(); }, refresh: function( editor, path ) { var list = path.contains( listNodeNames, 1 ), limit = path.blockLimit || path.root; // 1. Only a single type of list activate. // 2. Do not show list outside of block limit. if ( list && limit.contains( list ) ) this.setState( list.is( this.type ) ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); else this.setState( CKEDITOR.TRISTATE_OFF ); } }; var dtd = CKEDITOR.dtd; var tailNbspRegex = /[\t\r\n ]*(?: |\xa0)$/; // Merge list adjacent, of same type lists. function mergeListSiblings( listNode ) { var mergeSibling; ( mergeSibling = function( rtl ) { var sibling = listNode[ rtl ? 'getPrevious' : 'getNext' ]( nonEmpty ); if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && sibling.is( listNode.getName() ) ) { // Move children order by merge direction.(#3820) mergeChildren( listNode, sibling, null, !rtl ); listNode.remove(); listNode = sibling; } } )(); mergeSibling( 1 ); } function indexOfFirstChildElement( element, tagNameList ) { var child, children = element.children, length = children.length; for ( var i = 0; i < length; i++ ) { child = children[ i ]; if ( child.name && ( child.name in tagNameList ) ) return i; } return length; } // Check if node is block element that recieves text. function isTextBlock( node ) { return node.type == CKEDITOR.NODE_ELEMENT && ( node.getName() in CKEDITOR.dtd.$block || node.getName() in CKEDITOR.dtd.$listItem ) && CKEDITOR.dtd[ node.getName() ][ '#' ]; } // Join visually two block lines. function joinNextLineToCursor( editor, cursor, nextCursor ) { editor.fire( 'saveSnapshot' ); // Merge with previous block's content. nextCursor.enlarge( CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ); var frag = nextCursor.extractContents(); cursor.trim( false, true ); var bm = cursor.createBookmark(); // Kill original bogus; var currentPath = new CKEDITOR.dom.elementPath( cursor.startContainer ), pathBlock = currentPath.block, currentBlock = currentPath.lastElement.getAscendant( 'li', 1 ) || pathBlock, nextPath = new CKEDITOR.dom.elementPath( nextCursor.startContainer ), nextLi = nextPath.contains( CKEDITOR.dtd.$listItem ), nextList = nextPath.contains( CKEDITOR.dtd.$list ), last; // Remove bogus node the current block/pseudo block. if ( pathBlock ) { var bogus = pathBlock.getBogus(); bogus && bogus.remove(); } else if ( nextList ) { last = nextList.getPrevious( nonEmpty ); if ( last && blockBogus( last ) ) last.remove(); } // Kill the tail br in extracted. last = frag.getLast(); if ( last && last.type == CKEDITOR.NODE_ELEMENT && last.is( 'br' ) ) last.remove(); // Insert fragment at the range position. var nextNode = cursor.startContainer.getChild( cursor.startOffset ); if ( nextNode ) frag.insertBefore( nextNode ); else cursor.startContainer.append( frag ); // Move the sub list nested in the next list item. if ( nextLi ) { var sublist = getSubList( nextLi ); if ( sublist ) { // If next line is in the sub list of the current list item. if ( currentBlock.contains( nextLi ) ) { mergeChildren( sublist, nextLi.getParent(), nextLi ); sublist.remove(); } // Migrate the sub list to current list item. else currentBlock.append( sublist ); } } var nextBlock, parent; // Remove any remaining zombies path blocks at the end after line merged. while ( nextCursor.checkStartOfBlock() && nextCursor.checkEndOfBlock() ) { nextPath = nextCursor.startPath(); nextBlock = nextPath.block; // Check if also to remove empty list. if ( nextBlock.is( 'li' ) ) { parent = nextBlock.getParent(); if ( nextBlock.equals( parent.getLast( nonEmpty ) ) && nextBlock.equals( parent.getFirst( nonEmpty ) ) ) nextBlock = parent; } nextCursor.moveToPosition( nextBlock, CKEDITOR.POSITION_BEFORE_START ); nextBlock.remove(); } // Check if need to further merge with the list resides after the merged block. (#9080) var walkerRng = nextCursor.clone(), editable = editor.editable(); walkerRng.setEndAt( editable, CKEDITOR.POSITION_BEFORE_END ); var walker = new CKEDITOR.dom.walker( walkerRng ); walker.evaluator = function( node ) { return nonEmpty( node ) && !blockBogus( node ); }; var next = walker.next(); if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.getName() in CKEDITOR.dtd.$list ) mergeListSiblings( next ); cursor.moveToBookmark( bm ); // Make fresh selection. cursor.select(); editor.fire( 'saveSnapshot' ); } function getSubList( li ) { var last = li.getLast( nonEmpty ); return last && last.type == CKEDITOR.NODE_ELEMENT && last.getName() in listNodeNames ? last : null; } CKEDITOR.plugins.add( 'list', { init: function( editor ) { if ( editor.blockless ) return; // Register commands. editor.addCommand( 'numberedlist', new listCommand( 'numberedlist', 'ol' ) ); editor.addCommand( 'bulletedlist', new listCommand( 'bulletedlist', 'ul' ) ); // Register the toolbar button. if ( editor.ui.addButton ) { editor.ui.addButton( 'NumberedList', { label: editor.lang.list.numberedlist, command: 'numberedlist', directional: true, toolbar: 'list,10' }); editor.ui.addButton( 'BulletedList', { label: editor.lang.list.bulletedlist, command: 'bulletedlist', directional: true, toolbar: 'list,20' }); } // Handled backspace/del key to join list items. (#8248,#9080) editor.on( 'key', function( evt ) { var key = evt.data.keyCode; // DEl/BACKSPACE if ( editor.mode == 'wysiwyg' && key in { 8:1,46:1 } ) { var sel = editor.getSelection(), range = sel.getRanges()[ 0 ], path = range.startPath(); if ( !range.collapsed ) return; path = new CKEDITOR.dom.elementPath( range.startContainer ); var isBackspace = key == 8; var editable = editor.editable(); var walker = new CKEDITOR.dom.walker( range.clone() ); walker.evaluator = function( node ) { return nonEmpty( node ) && !blockBogus( node ); }; // Backspace/Del behavior at the start/end of table is handled in core. walker.guard = function( node, isOut ) { return !( isOut && node.type == CKEDITOR.NODE_ELEMENT && node.is( 'table' ) ); }; var cursor = range.clone(); if ( isBackspace ) { var previous, joinWith; // Join a sub list's first line, with the previous visual line in parent. if ( ( previous = path.contains( listNodeNames ) ) && range.checkBoundaryOfElement( previous, CKEDITOR.START ) && ( previous = previous.getParent() ) && previous.is( 'li' ) && ( previous = getSubList( previous ) ) ) { joinWith = previous; previous = previous.getPrevious( nonEmpty ); // Place cursor before the nested list. cursor.moveToPosition( previous && blockBogus( previous ) ? previous : joinWith, CKEDITOR.POSITION_BEFORE_START ); } // Join any line following a list, with the last visual line of the list. else { walker.range.setStartAt( editable, CKEDITOR.POSITION_AFTER_START ); walker.range.setEnd( range.startContainer, range.startOffset ); previous = walker.previous(); if ( previous && previous.type == CKEDITOR.NODE_ELEMENT && ( previous.getName() in listNodeNames || previous.is( 'li' ) ) ) { if ( !previous.is( 'li' ) ) { walker.range.selectNodeContents( previous ); walker.reset(); walker.evaluator = isTextBlock; previous = walker.previous(); } joinWith = previous; // Place cursor at the end of previous block. cursor.moveToElementEditEnd( joinWith ); } } if ( joinWith ) { joinNextLineToCursor( editor, cursor, range ); evt.cancel(); } else { var list = path.contains( listNodeNames ); // Backspace pressed at the start of list outdents the first list item. (#9129) if ( list && range.checkBoundaryOfElement( list, CKEDITOR.START ) ) { li = list.getFirst( nonEmpty ); if ( range.checkBoundaryOfElement( li, CKEDITOR.START ) ) { previous = list.getPrevious( nonEmpty ); // Only if the list item contains a sub list, do nothing but // simply move cursor backward one character. if ( getSubList( li ) ) { if ( previous ) { range.moveToElementEditEnd( previous ); range.select(); } evt.cancel(); } else { editor.execCommand( 'outdent' ); evt.cancel(); } } } } } else { var next, nextLine, li = path.contains( 'li' ); if ( li ) { walker.range.setEndAt( editable, CKEDITOR.POSITION_BEFORE_END ); var last = li.getLast( nonEmpty ); var block = last && isTextBlock( last ) ? last : li; // Indicate cursor at the visual end of an list item. var isAtEnd = 0; next = walker.next(); // When list item contains a sub list. if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.getName() in listNodeNames && next.equals( last ) ) { isAtEnd = 1; // Move to the first item in sub list. next = walker.next(); } // Right at the end of list item. else if ( range.checkBoundaryOfElement( block, CKEDITOR.END ) ) isAtEnd = 1; if ( isAtEnd && next ) { // Put cursor range there. nextLine = range.clone(); nextLine.moveToElementEditStart( next ); joinNextLineToCursor( editor, cursor, nextLine ); evt.cancel(); } } else { // Handle Del key pressed before the list. walker.range.setEndAt( editable, CKEDITOR.POSITION_BEFORE_END ); next = walker.next(); if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.is( listNodeNames ) ) { // The start
  • next = next.getFirst( nonEmpty ); // Simply remove the current empty block, move cursor to the // subsequent list. if ( path.block && range.checkStartOfBlock() && range.checkEndOfBlock() ) { path.block.remove(); range.moveToElementEditStart( next ); range.select(); evt.cancel(); } // Preventing the default (merge behavior), but simply move // the cursor one character forward if subsequent list item // contains sub list. else if ( getSubList( next ) ) { range.moveToElementEditStart( next ); range.select(); evt.cancel(); } // Merge the first list item with the current line. else { nextLine = range.clone(); nextLine.moveToElementEditStart( next ); joinNextLineToCursor( editor, cursor, nextLine ); evt.cancel(); } } } } // The backspace/del could potentially put cursor at a bad position, // being it handled or not, check immediately the selection to have it fixed. setTimeout( function() { editor.selectionChange( 1 ); } ); } }); } }); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Increse and decrease indent commands. */ (function() { var listNodeNames = { ol:1,ul:1 }, isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ); function indentCommand( editor, name ) { this.name = name; this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0; if ( this.useIndentClasses ) { this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' ); this.indentClassMap = {}; for ( var i = 0; i < editor.config.indentClasses.length; i++ ) this.indentClassMap[ editor.config.indentClasses[ i ] ] = i + 1; } this.startDisabled = name == 'outdent'; } // Returns the CSS property to be used for identing a given element. function getIndentCssProperty( element, dir ) { return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right'; } function isListItem( node ) { return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' ); } indentCommand.prototype = { // It applies to a "block-like" context. context: 'p', refresh: function( editor, path ) { var list = path && path.contains( listNodeNames ), firstBlock = path.block || path.blockLimit; if ( list ) this.setState( CKEDITOR.TRISTATE_OFF ); else if ( !this.useIndentClasses && this.name == 'indent' ) this.setState( CKEDITOR.TRISTATE_OFF ); else if ( !firstBlock ) this.setState( CKEDITOR.TRISTATE_DISABLED ); else if ( this.useIndentClasses ) { var indentClass = firstBlock.$.className.match( this.classNameRegex ), indentStep = 0; if ( indentClass ) { indentClass = indentClass[ 1 ]; indentStep = this.indentClassMap[ indentClass ]; } if ( ( this.name == 'outdent' && !indentStep ) || ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ) ) this.setState( CKEDITOR.TRISTATE_DISABLED ); else this.setState( CKEDITOR.TRISTATE_OFF ); } else { var indent = parseInt( firstBlock.getStyle( getIndentCssProperty( firstBlock ) ), 10 ); if ( isNaN( indent ) ) indent = 0; if ( indent <= 0 ) this.setState( CKEDITOR.TRISTATE_DISABLED ); else this.setState( CKEDITOR.TRISTATE_OFF ); } }, exec: function( editor ) { var self = this, database = {}; function indentList( listNode ) { // Our starting and ending points of the range might be inside some blocks under a list item... // So before playing with the iterator, we need to expand the block to include the list items. var startContainer = range.startContainer, endContainer = range.endContainer; while ( startContainer && !startContainer.getParent().equals( listNode ) ) startContainer = startContainer.getParent(); while ( endContainer && !endContainer.getParent().equals( listNode ) ) endContainer = endContainer.getParent(); if ( !startContainer || !endContainer ) return; // Now we can iterate over the individual items on the same tree depth. var block = startContainer, itemsToMove = [], stopFlag = false; while ( !stopFlag ) { if ( block.equals( endContainer ) ) stopFlag = true; itemsToMove.push( block ); block = block.getNext(); } if ( itemsToMove.length < 1 ) return; // Do indent or outdent operations on the array model of the list, not the // list's DOM tree itself. The array model demands that it knows as much as // possible about the surrounding lists, we need to feed it the further // ancestor node that is still a list. var listParents = listNode.getParents( true ); for ( var i = 0; i < listParents.length; i++ ) { if ( listParents[ i ].getName && listNodeNames[ listParents[ i ].getName() ] ) { listNode = listParents[ i ]; break; } } var indentOffset = self.name == 'indent' ? 1 : -1, startItem = itemsToMove[ 0 ], lastItem = itemsToMove[ itemsToMove.length - 1 ]; // Convert the list DOM tree into a one dimensional array. var listArray = CKEDITOR.plugins.list.listToArray( listNode, database ); // Apply indenting or outdenting on the array. var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent; for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ ) { listArray[ i ].indent += indentOffset; // Make sure the newly created sublist get a brand-new element of the same type. (#5372) if ( indentOffset > 0 ) { var listRoot = listArray[ i ].parent; listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() ); } } for ( i = lastItem.getCustomData( 'listarray_index' ) + 1; i < listArray.length && listArray[ i ].indent > baseIndent; i++ ) listArray[ i ].indent += indentOffset; // Convert the array back to a DOM forest (yes we might have a few subtrees now). // And replace the old list with the new forest. var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, listNode.getDirection() ); // Avoid nested
  • after outdent even they're visually same, // recording them for later refactoring.(#3982) if ( self.name == 'outdent' ) { var parentLiElement; if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) ) { var children = newList.listNode.getChildren(), pendingLis = [], count = children.count(), child; for ( i = count - 1; i >= 0; i-- ) { if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' ) ) pendingLis.push( child ); } } } if ( newList ) newList.listNode.replace( listNode ); // Move the nested
  • to be appeared after the parent. if ( pendingLis && pendingLis.length ) { for ( i = 0; i < pendingLis.length; i++ ) { var li = pendingLis[ i ], followingList = li; // Nest preceding
      /
        inside current
      1. if any. while ( ( followingList = followingList.getNext() ) && followingList.is && followingList.getName() in listNodeNames ) { // IE requires a filler NBSP for nested list inside empty list item, // otherwise the list item will be inaccessiable. (#4476) if ( CKEDITOR.env.ie && !li.getFirst( function( node ) { return isNotWhitespaces( node ) && isNotBookmark( node ); })) li.append( range.document.createText( '\u00a0' ) ); li.append( followingList ); } li.insertAfter( parentLiElement ); } } } function indentBlock() { var iterator = range.createIterator(), enterMode = editor.config.enterMode; iterator.enforceRealBlocks = true; iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR; var block; while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) ) indentElement( block ); } function indentElement( element, dir ) { if ( element.getCustomData( 'indent_processed' ) ) return false; if ( self.useIndentClasses ) { // Transform current class name to indent step index. var indentClass = element.$.className.match( self.classNameRegex ), indentStep = 0; if ( indentClass ) { indentClass = indentClass[ 1 ]; indentStep = self.indentClassMap[ indentClass ]; } // Operate on indent step index, transform indent step index back to class // name. if ( self.name == 'outdent' ) indentStep--; else indentStep++; if ( indentStep < 0 ) return false; indentStep = Math.min( indentStep, editor.config.indentClasses.length ); indentStep = Math.max( indentStep, 0 ); element.$.className = CKEDITOR.tools.ltrim( element.$.className.replace( self.classNameRegex, '' ) ); if ( indentStep > 0 ) element.addClass( editor.config.indentClasses[ indentStep - 1 ] ); } else { var indentCssProperty = getIndentCssProperty( element, dir ), currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 ); if ( isNaN( currentOffset ) ) currentOffset = 0; var indentOffset = editor.config.indentOffset || 40; currentOffset += ( self.name == 'indent' ? 1 : -1 ) * indentOffset; if ( currentOffset < 0 ) return false; currentOffset = Math.max( currentOffset, 0 ); currentOffset = Math.ceil( currentOffset / indentOffset ) * indentOffset; element.setStyle( indentCssProperty, currentOffset ? currentOffset + ( editor.config.indentUnit || 'px' ) : '' ); if ( element.getAttribute( 'style' ) === '' ) element.removeAttribute( 'style' ); } CKEDITOR.dom.element.setMarker( database, element, 'indent_processed', 1 ); return true; } var selection = editor.getSelection(), bookmarks = selection.createBookmarks( 1 ), ranges = selection && selection.getRanges( 1 ), range; var iterator = ranges.createIterator(); while ( ( range = iterator.getNextRange() ) ) { var rangeRoot = range.getCommonAncestor(), nearestListBlock = rangeRoot; while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ nearestListBlock.getName() ] ) ) nearestListBlock = nearestListBlock.getParent(); // Avoid having selection enclose the entire list. (#6138) // [
        • ...
        ] =>
        • [...]
        if ( !nearestListBlock ) { var selectedNode = range.getEnclosedNode(); if ( selectedNode && selectedNode.type == CKEDITOR.NODE_ELEMENT && selectedNode.getName() in listNodeNames ) { range.setStartAt( selectedNode, CKEDITOR.POSITION_AFTER_START ); range.setEndAt( selectedNode, CKEDITOR.POSITION_BEFORE_END ); nearestListBlock = selectedNode; } } // Avoid selection anchors under list root. //
          [
        • ...
        • ]
        =>
        • [...]
        if ( nearestListBlock && range.startContainer.type == CKEDITOR.NODE_ELEMENT && range.startContainer.getName() in listNodeNames ) { var walker = new CKEDITOR.dom.walker( range ); walker.evaluator = isListItem; range.startContainer = walker.next(); } if ( nearestListBlock && range.endContainer.type == CKEDITOR.NODE_ELEMENT && range.endContainer.getName() in listNodeNames ) { walker = new CKEDITOR.dom.walker( range ); walker.evaluator = isListItem; range.endContainer = walker.previous(); } if ( nearestListBlock ) { var firstListItem = nearestListBlock.getFirst( isListItem ), hasMultipleItems = !!firstListItem.getNext( isListItem ), rangeStart = range.startContainer, indentWholeList = firstListItem.equals( rangeStart ) || firstListItem.contains( rangeStart ); // Indent the entire list if cursor is inside the first list item. (#3893) // Only do that for indenting or when using indent classes or when there is something to outdent. (#6141) if ( !( indentWholeList && ( self.name == 'indent' || self.useIndentClasses || parseInt( nearestListBlock.getStyle( getIndentCssProperty( nearestListBlock ) ), 10 ) ) && indentElement( nearestListBlock, !hasMultipleItems && firstListItem.getDirection() ) ) ) indentList( nearestListBlock ); } else indentBlock(); } // Clean up the markers. CKEDITOR.dom.element.clearAllMarkers( database ); editor.forceNextSelectionCheck(); selection.selectBookmarks( bookmarks ); } }; CKEDITOR.plugins.add( 'indent', { // TODO: Remove this dependency. requires: 'list', onLoad: function() { // [IE6/7] Raw lists are using margin instead of padding for visual indentation in wysiwyg mode. (#3893) if ( CKEDITOR.env.ie6Compat || CKEDITOR.env.ie7Compat ) { CKEDITOR.addCss( ".cke_editable ul," + ".cke_editable ol" + "{" + " margin-left: 0px;" + " padding-left: 40px;" + "}" ); } }, init: function( editor ) { if ( editor.blockless ) return; // Register commands. var indent = editor.addCommand( 'indent', new indentCommand( editor, 'indent' ) ), outdent = editor.addCommand( 'outdent', new indentCommand( editor, 'outdent' ) ); if ( editor.ui.addButton ) { // Register the toolbar buttons. editor.ui.addButton( 'Indent', { label: editor.lang.indent.indent, command: 'indent', directional: true, toolbar: 'indent,20' }); editor.ui.addButton( 'Outdent', { label: editor.lang.indent.outdent, command: 'outdent', directional: true, toolbar: 'indent,10' }); } // Register dirChanged listener. editor.on( 'dirChanged', function( e ) { var range = editor.createRange(); range.setStartBefore( e.data.node ); range.setEndAfter( e.data.node ); var walker = new CKEDITOR.dom.walker( range ), node; while ( ( node = walker.next() ) ) { if ( node.type == CKEDITOR.NODE_ELEMENT ) { // A child with the defined dir is to be ignored. if ( !node.equals( e.data.node ) && node.getDirection() ) { range.setStartAfter( node ); walker = new CKEDITOR.dom.walker( range ); continue; } // Switch alignment classes. var classes = editor.config.indentClasses; if ( classes ) { var suffix = ( e.data.dir == 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ]; for ( var i = 0; i < classes.length; i++ ) { if ( node.hasClass( classes[ i ] + suffix[ 0 ] ) ) { node.removeClass( classes[ i ] + suffix[ 0 ] ); node.addClass( classes[ i ] + suffix[ 1 ] ); } } } // Switch the margins. var marginLeft = node.getStyle( 'margin-right' ), marginRight = node.getStyle( 'margin-left' ); marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' ); marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' ); } } }); } }); })(); /** * Size of each indentation step. * * config.indentOffset = 4; * * @cfg {Number} [indentOffset=40] * @member CKEDITOR.config */ /** * Unit for the indentation style. * * config.indentUnit = 'em'; * * @cfg {String} [indentUnit='px'] * @member CKEDITOR.config */ /** * List of classes to use for indenting the contents. If it's `null`, no classes will be used * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used. * * // Use the classes 'Indent1', 'Indent2', 'Indent3' * config.indentClasses = ['Indent1', 'Indent2', 'Indent3']; * * @cfg {Array} [indentClasses=null] * @member CKEDITOR.config */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { CKEDITOR.plugins.add( 'enterkey', { // TODO: should not depend on a particular format plugin. requires: 'indent', init: function( editor ) { editor.addCommand( 'enter', { modes:{wysiwyg:1 }, editorFocus: false, exec: function( editor ) { enter( editor ); } }); editor.addCommand( 'shiftEnter', { modes:{wysiwyg:1 }, editorFocus: false, exec: function( editor ) { shiftEnter( editor ); } }); editor.setKeystroke( [ [ 13, 'enter' ], [ CKEDITOR.SHIFT + 13, 'shiftEnter' ] ] ); } }); var whitespaces = CKEDITOR.dom.walker.whitespaces(), bookmark = CKEDITOR.dom.walker.bookmark(); CKEDITOR.plugins.enterkey = { enterBlock: function( editor, mode, range, forceMode ) { // Get the range for the current selection. range = range || getRange( editor ); // We may not have valid ranges to work on, like when inside a // contenteditable=false element. if ( !range ) return; var doc = range.document; var atBlockStart = range.checkStartOfBlock(), atBlockEnd = range.checkEndOfBlock(), path = editor.elementPath( range.startContainer ), block = path.block; // Exit the list when we're inside an empty list item block. (#5376) if ( atBlockStart && atBlockEnd ) { // Exit the list when we're inside an empty list item block. (#5376) if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) ) { editor.execCommand( 'outdent' ); return; } if ( block && block.getParent().is( 'blockquote' ) ) { block.breakParent( block.getParent() ); // If we were at the start of
        , there will be an empty element before it now. if ( !block.getPrevious().getFirst( CKEDITOR.dom.walker.invisible( 1 ) ) ) block.getPrevious().remove(); // If we were at the end of
        , there will be an empty element after it now. if ( !block.getNext().getFirst( CKEDITOR.dom.walker.invisible( 1 ) ) ) block.getNext().remove(); range.moveToElementEditStart( block ); range.select(); return; } } // Don't split
         if we're in the middle of it, act as shift enter key.
        			else if ( block && block.is( 'pre' ) ) {
        				if ( !atBlockEnd ) {
        					enterBr( editor, mode, range, forceMode );
        					return;
        				}
        			}
        
        			// Determine the block element to be used.
        			var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' );
        
        			// Split the range.
        			var splitInfo = range.splitBlock( blockTag );
        
        			if ( !splitInfo )
        				return;
        
        			// Get the current blocks.
        			var previousBlock = splitInfo.previousBlock,
        				nextBlock = splitInfo.nextBlock;
        
        			var isStartOfBlock = splitInfo.wasStartOfBlock,
        				isEndOfBlock = splitInfo.wasEndOfBlock;
        
        			var node;
        
        			// If this is a block under a list item, split it as well. (#1647)
        			if ( nextBlock ) {
        				node = nextBlock.getParent();
        				if ( node.is( 'li' ) ) {
        					nextBlock.breakParent( node );
        					nextBlock.move( nextBlock.getNext(), 1 );
        				}
        			} else if ( previousBlock && ( node = previousBlock.getParent() ) && node.is( 'li' ) ) {
        				previousBlock.breakParent( node );
        				node = previousBlock.getNext();
        				range.moveToElementEditStart( node );
        				previousBlock.move( previousBlock.getPrevious() );
        			}
        
        			// If we have both the previous and next blocks, it means that the
        			// boundaries were on separated blocks, or none of them where on the
        			// block limits (start/end).
        			if ( !isStartOfBlock && !isEndOfBlock ) {
        				// If the next block is an 
      2. with another list tree as the first // child, we'll need to append a filler (
        /NBSP) or the list item // wouldn't be editable. (#1420) if ( nextBlock.is( 'li' ) ) { var walkerRange = range.clone(); walkerRange.selectNodeContents( nextBlock ); var walker = new CKEDITOR.dom.walker( walkerRange ); walker.evaluator = function( node ) { return !( bookmark( node ) || whitespaces( node ) || node.type == CKEDITOR.NODE_ELEMENT && node.getName() in CKEDITOR.dtd.$inline && !( node.getName() in CKEDITOR.dtd.$empty ) ); }; node = walker.next(); if ( node && node.type == CKEDITOR.NODE_ELEMENT && node.is( 'ul', 'ol' ) ) { ( CKEDITOR.env.ie ? doc.createText( '\xa0' ) : doc.createElement( 'br' ) ).insertBefore( node ); } } // Move the selection to the end block. if ( nextBlock ) range.moveToElementEditStart( nextBlock ); } else { var newBlock, newBlockDir; if ( previousBlock ) { // Do not enter this block if it's a header tag, or we are in // a Shift+Enter (#77). Create a new block element instead // (later in the code). if ( previousBlock.is( 'li' ) || !( headerTagRegex.test( previousBlock.getName() ) || previousBlock.is( 'pre' ) ) ) { // Otherwise, duplicate the previous block. newBlock = previousBlock.clone(); } } else if ( nextBlock ) newBlock = nextBlock.clone(); if ( !newBlock ) { // We have already created a new list item. (#6849) if ( node && node.is( 'li' ) ) newBlock = node; else { newBlock = doc.createElement( blockTag ); if ( previousBlock && ( newBlockDir = previousBlock.getDirection() ) ) newBlock.setAttribute( 'dir', newBlockDir ); } } // Force the enter block unless we're talking of a list item. else if ( forceMode && !newBlock.is( 'li' ) ) newBlock.renameNode( blockTag ); // Recreate the inline elements tree, which was available // before hitting enter, so the same styles will be available in // the new block. var elementPath = splitInfo.elementPath; if ( elementPath ) { for ( var i = 0, len = elementPath.elements.length; i < len; i++ ) { var element = elementPath.elements[ i ]; if ( element.equals( elementPath.block ) || element.equals( elementPath.blockLimit ) ) break; if ( CKEDITOR.dtd.$removeEmpty[ element.getName() ] ) { element = element.clone(); newBlock.moveChildren( element ); newBlock.append( element ); } } } if ( !CKEDITOR.env.ie ) newBlock.appendBogus(); if ( !newBlock.getParent() ) range.insertNode( newBlock ); // list item start number should not be duplicated (#7330), but we need // to remove the attribute after it's onto the DOM tree because of old IEs (#7581). newBlock.is( 'li' ) && newBlock.removeAttribute( 'value' ); // This is tricky, but to make the new block visible correctly // we must select it. // The previousBlock check has been included because it may be // empty if we have fixed a block-less space (like ENTER into an // empty table cell). if ( CKEDITOR.env.ie && isStartOfBlock && ( !isEndOfBlock || !previousBlock.getChildCount() ) ) { // Move the selection to the new block. range.moveToElementEditStart( isEndOfBlock ? previousBlock : newBlock ); range.select(); } // Move the selection to the new block. range.moveToElementEditStart( isStartOfBlock && !isEndOfBlock ? nextBlock : newBlock ); } range.select(); range.scrollIntoView(); }, enterBr: function( editor, mode, range, forceMode ) { // Get the range for the current selection. range = range || getRange( editor ); // We may not have valid ranges to work on, like when inside a // contenteditable=false element. if ( !range ) return; var doc = range.document; // Determine the block element to be used. var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); var isEndOfBlock = range.checkEndOfBlock(); var elementPath = new CKEDITOR.dom.elementPath( editor.getSelection().getStartElement() ); var startBlock = elementPath.block, startBlockTag = startBlock && elementPath.block.getName(); var isPre = false; if ( !forceMode && startBlockTag == 'li' ) { enterBlock( editor, mode, range, forceMode ); return; } // If we are at the end of a header block. if ( !forceMode && isEndOfBlock && headerTagRegex.test( startBlockTag ) ) { var newBlock, newBlockDir; if ( ( newBlockDir = startBlock.getDirection() ) ) { newBlock = doc.createElement( 'div' ); newBlock.setAttribute( 'dir', newBlockDir ); newBlock.insertAfter( startBlock ); range.setStart( newBlock, 0 ); } else { // Insert a
        after the current paragraph. doc.createElement( 'br' ).insertAfter( startBlock ); // A text node is required by Gecko only to make the cursor blink. if ( CKEDITOR.env.gecko ) doc.createText( '' ).insertAfter( startBlock ); // IE has different behaviors regarding position. range.setStartAt( startBlock.getNext(), CKEDITOR.env.ie ? CKEDITOR.POSITION_BEFORE_START : CKEDITOR.POSITION_AFTER_START ); } } else { var lineBreak; // IE<8 prefers text node as line-break inside of
         (#4711).
        				if ( startBlockTag == 'pre' && CKEDITOR.env.ie && CKEDITOR.env.version < 8 )
        					lineBreak = doc.createText( '\r' );
        				else
        					lineBreak = doc.createElement( 'br' );
        
        				range.deleteContents();
        				range.insertNode( lineBreak );
        
        				// IE has different behavior regarding position.
        				if ( CKEDITOR.env.ie )
        					range.setStartAt( lineBreak, CKEDITOR.POSITION_AFTER_END );
        				else {
        					// A text node is required by Gecko only to make the cursor blink.
        					// We need some text inside of it, so the bogus 
        is properly // created. doc.createText( '\ufeff' ).insertAfter( lineBreak ); // If we are at the end of a block, we must be sure the bogus node is available in that block. if ( isEndOfBlock ) lineBreak.getParent().appendBogus(); // Now we can remove the text node contents, so the caret doesn't // stop on it. lineBreak.getNext().$.nodeValue = ''; range.setStartAt( lineBreak.getNext(), CKEDITOR.POSITION_AFTER_START ); } } // This collapse guarantees the cursor will be blinking. range.collapse( true ); range.select(); range.scrollIntoView(); } }; var plugin = CKEDITOR.plugins.enterkey, enterBr = plugin.enterBr, enterBlock = plugin.enterBlock, headerTagRegex = /^h[1-6]$/; function shiftEnter( editor ) { // Only effective within document. if ( editor.mode != 'wysiwyg' ) return false; // On SHIFT+ENTER: // 1. We want to enforce the mode to be respected, instead // of cloning the current block. (#77) return enter( editor, editor.config.shiftEnterMode, 1 ); } function enter( editor, mode, forceMode ) { forceMode = editor.config.forceEnterMode || forceMode; // Only effective within document. if ( editor.mode != 'wysiwyg' ) return false; if ( !mode ) mode = editor.config.enterMode; // Check path block specialities: // 1. Cannot be a un-splittable element, e.g. table caption; // 2. Must not be the editable element itself. (blockless) var path = editor.elementPath(); if ( !path.isContextFor( 'p' ) ) { mode = CKEDITOR.ENTER_BR; forceMode = 1; } editor.fire( 'saveSnapshot' ); // Save undo step. if ( mode == CKEDITOR.ENTER_BR ) enterBr( editor, mode, null, forceMode ); else enterBlock( editor, mode, null, forceMode ); editor.fire( 'saveSnapshot' ); return true; } function getRange( editor ) { // Get the selection ranges. var ranges = editor.getSelection().getRanges( true ); // Delete the contents of all ranges except the first one. for ( var i = ranges.length - 1; i > 0; i-- ) { ranges[ i ].deleteContents(); } // Return the first range. return ranges[ 0 ]; } })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { // Base HTML entities. var htmlbase = 'nbsp,gt,lt,amp'; var entities = // Latin-1 Entities 'quot,iexcl,cent,pound,curren,yen,brvbar,sect,uml,copy,ordf,laquo,' + 'not,shy,reg,macr,deg,plusmn,sup2,sup3,acute,micro,para,middot,' + 'cedil,sup1,ordm,raquo,frac14,frac12,frac34,iquest,times,divide,' + // Symbols 'fnof,bull,hellip,prime,Prime,oline,frasl,weierp,image,real,trade,' + 'alefsym,larr,uarr,rarr,darr,harr,crarr,lArr,uArr,rArr,dArr,hArr,' + 'forall,part,exist,empty,nabla,isin,notin,ni,prod,sum,minus,lowast,' + 'radic,prop,infin,ang,and,or,cap,cup,int,there4,sim,cong,asymp,ne,' + 'equiv,le,ge,sub,sup,nsub,sube,supe,oplus,otimes,perp,sdot,lceil,' + 'rceil,lfloor,rfloor,lang,rang,loz,spades,clubs,hearts,diams,' + // Other Special Characters 'circ,tilde,ensp,emsp,thinsp,zwnj,zwj,lrm,rlm,ndash,mdash,lsquo,' + 'rsquo,sbquo,ldquo,rdquo,bdquo,dagger,Dagger,permil,lsaquo,rsaquo,' + 'euro'; // Latin Letters Entities var latin = 'Agrave,Aacute,Acirc,Atilde,Auml,Aring,AElig,Ccedil,Egrave,Eacute,' + 'Ecirc,Euml,Igrave,Iacute,Icirc,Iuml,ETH,Ntilde,Ograve,Oacute,Ocirc,' + 'Otilde,Ouml,Oslash,Ugrave,Uacute,Ucirc,Uuml,Yacute,THORN,szlig,' + 'agrave,aacute,acirc,atilde,auml,aring,aelig,ccedil,egrave,eacute,' + 'ecirc,euml,igrave,iacute,icirc,iuml,eth,ntilde,ograve,oacute,ocirc,' + 'otilde,ouml,oslash,ugrave,uacute,ucirc,uuml,yacute,thorn,yuml,' + 'OElig,oelig,Scaron,scaron,Yuml'; // Greek Letters Entities. var greek = 'Alpha,Beta,Gamma,Delta,Epsilon,Zeta,Eta,Theta,Iota,Kappa,Lambda,Mu,' + 'Nu,Xi,Omicron,Pi,Rho,Sigma,Tau,Upsilon,Phi,Chi,Psi,Omega,alpha,' + 'beta,gamma,delta,epsilon,zeta,eta,theta,iota,kappa,lambda,mu,nu,xi,' + 'omicron,pi,rho,sigmaf,sigma,tau,upsilon,phi,chi,psi,omega,thetasym,' + 'upsih,piv'; // Create a mapping table between one character and its entity form from a list of entity names. // @param reverse {Boolean} Whether to create a reverse map from the entity string form to an actual character. function buildTable( entities, reverse ) { var table = {}, regex = []; // Entities that the browsers DOM don't transform to the final char // automatically. var specialTable = { nbsp: '\u00A0', // IE | FF shy: '\u00AD', // IE gt: '\u003E', // IE | FF | -- | Opera lt: '\u003C', // IE | FF | Safari | Opera amp: '\u0026', // ALL apos: '\u0027', // IE quot: '\u0022' // IE }; entities = entities.replace( /\b(nbsp|shy|gt|lt|amp|apos|quot)(?:,|$)/g, function( match, entity ) { var org = reverse ? '&' + entity + ';' : specialTable[ entity ], result = reverse ? specialTable[ entity ] : '&' + entity + ';'; table[ org ] = result; regex.push( org ); return ''; }); if ( !reverse && entities ) { // Transforms the entities string into an array. entities = entities.split( ',' ); // Put all entities inside a DOM element, transforming them to their // final chars. var div = document.createElement( 'div' ), chars; div.innerHTML = '&' + entities.join( ';&' ) + ';'; chars = div.innerHTML; div = null; // Add all chars to the table. for ( var i = 0; i < chars.length; i++ ) { var charAt = chars.charAt( i ); table[ charAt ] = '&' + entities[ i ] + ';'; regex.push( charAt ); } } table.regex = regex.join( reverse ? '|' : '' ); return table; } CKEDITOR.plugins.add( 'entities', { afterInit: function( editor ) { var config = editor.config; var dataProcessor = editor.dataProcessor, htmlFilter = dataProcessor && dataProcessor.htmlFilter; if ( htmlFilter ) { // Mandatory HTML base entities. var selectedEntities = []; if ( config.basicEntities !== false ) selectedEntities.push( htmlbase ); if ( config.entities ) { if ( selectedEntities.length ) selectedEntities.push( entities ); if ( config.entities_latin ) selectedEntities.push( latin ); if ( config.entities_greek ) selectedEntities.push( greek ); if ( config.entities_additional ) selectedEntities.push( config.entities_additional ); } var entitiesTable = buildTable( selectedEntities.join( ',' ) ); // Create the Regex used to find entities in the text, leave it matches nothing if entities are empty. var entitiesRegex = entitiesTable.regex ? '[' + entitiesTable.regex + ']' : 'a^'; delete entitiesTable.regex; if ( config.entities && config.entities_processNumerical ) entitiesRegex = '[^ -~]|' + entitiesRegex; entitiesRegex = new RegExp( entitiesRegex, 'g' ); function getEntity( character ) { return config.entities_processNumerical == 'force' || !entitiesTable[ character ] ? '&#' + character.charCodeAt( 0 ) + ';' : entitiesTable[ character ]; } // Decode entities that the browsers has transformed // at first place. var baseEntitiesTable = buildTable( [ htmlbase, 'shy' ].join( ',' ), true ), baseEntitiesRegex = new RegExp( baseEntitiesTable.regex, 'g' ); function getChar( character ) { return baseEntitiesTable[ character ]; } htmlFilter.addRules({ text: function( text ) { return text.replace( baseEntitiesRegex, getChar ).replace( entitiesRegex, getEntity ); } }); } } }); })(); /** * Whether to escape basic HTML entities in the document, including: * * * `nbsp` * * `gt` * * `lt` * * `amp` * * **Note:** It should not be subject to change unless when outputting a non-HTML data format like BBCode. * * config.basicEntities = false; * * @cfg {Boolean} [basicEntities=true] * @member CKEDITOR.config */ CKEDITOR.config.basicEntities = true; /** * Whether to use HTML entities in the output. * * config.entities = false; * * @cfg {Boolean} [entities=true] * @member CKEDITOR.config */ CKEDITOR.config.entities = true; /** * Whether to convert some Latin characters (Latin alphabet No. 1, ISO 8859-1) * to HTML entities. The list of entities can be found in the * [W3C HTML 4.01 Specification, section 24.2.1](http://www.w3.org/TR/html4/sgml/entities.html#h-24.2.1). * * config.entities_latin = false; * * @cfg {Boolean} [entities_latin=true] * @member CKEDITOR.config */ CKEDITOR.config.entities_latin = true; /** * Whether to convert some symbols, mathematical symbols, and Greek letters to * HTML entities. This may be more relevant for users typing text written in Greek. * The list of entities can be found in the * [W3C HTML 4.01 Specification, section 24.3.1(http://www.w3.org/TR/html4/sgml/entities.html#h-24.3.1). * * config.entities_greek = false; * * @cfg {Boolean} [entities_greek=true] * @member CKEDITOR.config */ CKEDITOR.config.entities_greek = true; /** * Whether to convert all remaining characters not included in the ASCII * character table to their relative decimal numeric representation of HTML entity. * When set to `force`, it will convert all entities into this format. * * For example the phrase `'This is Chinese: 汉语.'` is output * as `'This is Chinese: 汉语.'` * * config.entities_processNumerical = true; * config.entities_processNumerical = 'force'; // Converts from ' ' into ' '; * * @cfg {Boolean/String} [entities_processNumerical=false] * @member CKEDITOR.config */ /** * A comma separated list of additional entities to be used. Entity names * or numbers must be used in a form that excludes the `'&'` prefix and the `';'` ending. * * config.entities_additional = '#1049'; // Adds Cyrillic capital letter Short I (Й). * * @cfg {String} [entities_additional='#39' (The single quote (') character)] * @member CKEDITOR.config */ CKEDITOR.config.entities_additional = '#39'; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'popup' ); CKEDITOR.tools.extend( CKEDITOR.editor.prototype, { /** * Opens Browser in a popup. The `width` and `height` parameters accept * numbers (pixels) or percent (of screen size) values. * * @member CKEDITOR.editor * @param {String} url The url of the external file browser. * @param {Number/String} [width='80%'] Popup window width. * @param {Number/String} [height='70%'] Popup window height. * @param {String} [options='location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes'] * Popup window features. */ popup: function( url, width, height, options ) { width = width || '80%'; height = height || '70%'; if ( typeof width == 'string' && width.length > 1 && width.substr( width.length - 1, 1 ) == '%' ) width = parseInt( window.screen.width * parseInt( width, 10 ) / 100, 10 ); if ( typeof height == 'string' && height.length > 1 && height.substr( height.length - 1, 1 ) == '%' ) height = parseInt( window.screen.height * parseInt( height, 10 ) / 100, 10 ); if ( width < 640 ) width = 640; if ( height < 420 ) height = 420; var top = parseInt( ( window.screen.height - height ) / 2, 10 ), left = parseInt( ( window.screen.width - width ) / 2, 10 ); options = ( options || 'location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes' ) + ',width=' + width + ',height=' + height + ',top=' + top + ',left=' + left; var popupWindow = window.open( '', null, options, true ); // Blocked by a popup blocker. if ( !popupWindow ) return false; try { // Chrome is problematic with moveTo/resizeTo, but it's not really needed here (#8855). var ua = navigator.userAgent.toLowerCase(); if ( ua.indexOf( ' chrome/' ) == -1 ) { popupWindow.moveTo( left, top ); popupWindow.resizeTo( width, height ); } popupWindow.focus(); popupWindow.location.href = url; } catch ( e ) { popupWindow = window.open( url, null, options, true ); } return true; } }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "filebrowser" plugin that adds support for file uploads and * browsing. * * When a file is uploaded or selected inside the file browser, its URL is * inserted automatically into a field defined in the filebrowser * attribute. In order to specify a field that should be updated, pass the tab ID and * the element ID, separated with a colon.

        * * Example 1: (Browse) * *
         * {
         * 	type : 'button',
         * 	id : 'browse',
         * 	filebrowser : 'tabId:elementId',
         * 	label : editor.lang.common.browseServer
         * }
         * 
        * * If you set the filebrowser attribute for an element other than * the fileButton, the Browse action will be triggered.

        * * Example 2: (Quick Upload) * *
         * {
         * 	type : 'fileButton',
         * 	id : 'uploadButton',
         * 	filebrowser : 'tabId:elementId',
         * 	label : editor.lang.common.uploadSubmit,
         * 	'for' : [ 'upload', 'upload' ]
         * }
         * 
        * * If you set the filebrowser attribute for a fileButton * element, the QuickUpload action will be executed.

        * * The filebrowser plugin also supports more advanced configuration performed through * a JavaScript object. * * The following settings are supported: * *
          *
        • actionBrowse or QuickUpload.
        • *
        • target – the field to update in the tabId:elementId format.
        • *
        • params – additional arguments to be passed to the server connector (optional).
        • *
        • onSelect – a function to execute when the file is selected/uploaded (optional).
        • *
        • url – the URL to be called (optional).
        • *
        * * Example 3: (Quick Upload) * *
         * {
         * 	type : 'fileButton',
         * 	label : editor.lang.common.uploadSubmit,
         * 	id : 'buttonId',
         * 	filebrowser :
         * 	{
         * 		action : 'QuickUpload', // required
         * 		target : 'tab1:elementId', // required
         * 		params : // optional
         * 		{
         * 			type : 'Files',
         * 			currentFolder : '/folder/'
         * 		},
         * 		onSelect : function( fileUrl, errorMessage ) // optional
         * 		{
         * 			// Do not call the built-in selectFuntion.
         * 			// return false;
         * 		}
         * 	},
         * 	'for' : [ 'tab1', 'myFile' ]
         * }
         * 
        * * Suppose you have a file element with an ID of myFile, a text * field with an ID of elementId and a fileButton. * If the filebowser.url attribute is not specified explicitly, * the form action will be set to filebrowser[DialogWindowName]UploadUrl * or, if not specified, to filebrowserUploadUrl. Additional parameters * from the params object will be added to the query string. It is * possible to create your own uploadHandler and cancel the built-in * updateTargetElement command.

        * * Example 4: (Browse) * *
         * {
         * 	type : 'button',
         * 	id : 'buttonId',
         * 	label : editor.lang.common.browseServer,
         * 	filebrowser :
         * 	{
         * 		action : 'Browse',
         * 		url : '/ckfinder/ckfinder.html&type=Images',
         * 		target : 'tab1:elementId'
         * 	}
         * }
         * 
        * * In this example, when the button is pressed, the file browser will be opened in a * popup window. If you do not specify the filebrowser.url attribute, * filebrowser[DialogName]BrowseUrl or * filebrowserBrowseUrl will be used. After selecting a file in the file * browser, an element with an ID of elementId will be updated. Just * like in the third example, a custom onSelect function may be defined. */ (function() { // Adds (additional) arguments to given url. // // @param {String} // url The url. // @param {Object} // params Additional parameters. function addQueryString( url, params ) { var queryString = []; if ( !params ) return url; else { for ( var i in params ) queryString.push( i + "=" + encodeURIComponent( params[ i ] ) ); } return url + ( ( url.indexOf( "?" ) != -1 ) ? "&" : "?" ) + queryString.join( "&" ); } // Make a string's first character uppercase. // // @param {String} // str String. function ucFirst( str ) { str += ''; var f = str.charAt( 0 ).toUpperCase(); return f + str.substr( 1 ); } // The onlick function assigned to the 'Browse Server' button. Opens the // file browser and updates target field when file is selected. // // @param {CKEDITOR.event} // evt The event object. function browseServer( evt ) { var dialog = this.getDialog(); var editor = dialog.getParentEditor(); editor._.filebrowserSe = this; var width = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowWidth' ] || editor.config.filebrowserWindowWidth || '80%'; var height = editor.config[ 'filebrowser' + ucFirst( dialog.getName() ) + 'WindowHeight' ] || editor.config.filebrowserWindowHeight || '70%'; var params = this.filebrowser.params || {}; params.CKEditor = editor.name; params.CKEditorFuncNum = editor._.filebrowserFn; if ( !params.langCode ) params.langCode = editor.langCode; var url = addQueryString( this.filebrowser.url, params ); // TODO: V4: Remove backward compatibility (#8163). editor.popup( url, width, height, editor.config.filebrowserWindowFeatures || editor.config.fileBrowserWindowFeatures ); } // The onlick function assigned to the 'Upload' button. Makes the final // decision whether form is really submitted and updates target field when // file is uploaded. // // @param {CKEDITOR.event} // evt The event object. function uploadFile( evt ) { var dialog = this.getDialog(); var editor = dialog.getParentEditor(); editor._.filebrowserSe = this; // If user didn't select the file, stop the upload. if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getInputElement().$.value ) return false; if ( !dialog.getContentElement( this[ 'for' ][ 0 ], this[ 'for' ][ 1 ] ).getAction() ) return false; return true; } // Setups the file element. // // @param {CKEDITOR.ui.dialog.file} // fileInput The file element used during file upload. // @param {Object} // filebrowser Object containing filebrowser settings assigned to // the fileButton associated with this file element. function setupFileElement( editor, fileInput, filebrowser ) { var params = filebrowser.params || {}; params.CKEditor = editor.name; params.CKEditorFuncNum = editor._.filebrowserFn; if ( !params.langCode ) params.langCode = editor.langCode; fileInput.action = addQueryString( filebrowser.url, params ); fileInput.filebrowser = filebrowser; } // Traverse through the content definition and attach filebrowser to // elements with 'filebrowser' attribute. // // @param String // dialogName Dialog name. // @param {CKEDITOR.dialog.definitionObject} // definition Dialog definition. // @param {Array} // elements Array of {@link CKEDITOR.dialog.definition.content} // objects. function attachFileBrowser( editor, dialogName, definition, elements ) { var element, fileInput; for ( var i in elements ) { element = elements[ i ]; if ( element.type == 'hbox' || element.type == 'vbox' || element.type == 'fieldset' ) attachFileBrowser( editor, dialogName, definition, element.children ); if ( !element.filebrowser ) continue; if ( typeof element.filebrowser == 'string' ) { var fb = { action: ( element.type == 'fileButton' ) ? 'QuickUpload' : 'Browse', target: element.filebrowser }; element.filebrowser = fb; } if ( element.filebrowser.action == 'Browse' ) { var url = element.filebrowser.url; if ( url === undefined ) { url = editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'BrowseUrl' ]; if ( url === undefined ) url = editor.config.filebrowserBrowseUrl; } if ( url ) { element.onClick = browseServer; element.filebrowser.url = url; element.hidden = false; } } else if ( element.filebrowser.action == 'QuickUpload' && element[ 'for' ] ) { url = element.filebrowser.url; if ( url === undefined ) { url = editor.config[ 'filebrowser' + ucFirst( dialogName ) + 'UploadUrl' ]; if ( url === undefined ) url = editor.config.filebrowserUploadUrl; } if ( url ) { var onClick = element.onClick; element.onClick = function( evt ) { // "element" here means the definition object, so we need to find the correct // button to scope the event call var sender = evt.sender; if ( onClick && onClick.call( sender, evt ) === false ) return false; return uploadFile.call( sender, evt ); }; element.filebrowser.url = url; element.hidden = false; setupFileElement( editor, definition.getContents( element[ 'for' ][ 0 ] ).get( element[ 'for' ][ 1 ] ), element.filebrowser ); } } } } // Updates the target element with the url of uploaded/selected file. // // @param {String} // url The url of a file. function updateTargetElement( url, sourceElement ) { var dialog = sourceElement.getDialog(); var targetElement = sourceElement.filebrowser.target || null; // If there is a reference to targetElement, update it. if ( targetElement ) { var target = targetElement.split( ':' ); var element = dialog.getContentElement( target[ 0 ], target[ 1 ] ); if ( element ) { element.setValue( url ); dialog.selectPage( target[ 0 ] ); } } } // Returns true if filebrowser is configured in one of the elements. // // @param {CKEDITOR.dialog.definitionObject} // definition Dialog definition. // @param String // tabId The tab id where element(s) can be found. // @param String // elementId The element id (or ids, separated with a semicolon) to check. function isConfigured( definition, tabId, elementId ) { if ( elementId.indexOf( ";" ) !== -1 ) { var ids = elementId.split( ";" ); for ( var i = 0; i < ids.length; i++ ) { if ( isConfigured( definition, tabId, ids[ i ] ) ) return true; } return false; } var elementFileBrowser = definition.getContents( tabId ).get( elementId ).filebrowser; return ( elementFileBrowser && elementFileBrowser.url ); } function setUrl( fileUrl, data ) { var dialog = this._.filebrowserSe.getDialog(), targetInput = this._.filebrowserSe[ 'for' ], onSelect = this._.filebrowserSe.filebrowser.onSelect; if ( targetInput ) dialog.getContentElement( targetInput[ 0 ], targetInput[ 1 ] ).reset(); if ( typeof data == 'function' && data.call( this._.filebrowserSe ) === false ) return; if ( onSelect && onSelect.call( this._.filebrowserSe, fileUrl, data ) === false ) return; // The "data" argument may be used to pass the error message to the editor. if ( typeof data == 'string' && data ) alert( data ); if ( fileUrl ) updateTargetElement( fileUrl, this._.filebrowserSe ); } CKEDITOR.plugins.add( 'filebrowser', { requires: 'popup', init: function( editor, pluginPath ) { editor._.filebrowserFn = CKEDITOR.tools.addFunction( setUrl, editor ); editor.on( 'destroy', function() { CKEDITOR.tools.removeFunction( this._.filebrowserFn ); }); } }); CKEDITOR.on( 'dialogDefinition', function( evt ) { var definition = evt.data.definition, element; // Associate filebrowser to elements with 'filebrowser' attribute. for ( var i in definition.contents ) { if ( ( element = definition.contents[ i ] ) ) { attachFileBrowser( evt.editor, evt.data.name, definition, element.elements ); if ( element.hidden && element.filebrowser ) { element.hidden = !isConfigured( definition, element[ 'id' ], element.filebrowser ); } } } }); })(); /** * The location of an external file browser that should be launched when the **Browse Server** * button is pressed. If configured, the **Browse Server** button will appear in the * **Link**, **Image**, and **Flash** dialog windows. * * See the [File Browser/Uploader](http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader\)) documentation. * * config.filebrowserBrowseUrl = '/browser/browse.php'; * * @since 3.0 * @cfg {String} [filebrowserBrowseUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The location of the script that handles file uploads. * If set, the **Upload** tab will appear in the **Link**, **Image**, * and **Flash** dialog windows. * * See the [File Browser/Uploader](http://docs.cksource.com/CKEditor_3.x/Developers_Guide/File_Browser_(Uploader\)) documentation. * * config.filebrowserUploadUrl = '/uploader/upload.php'; * * @since 3.0 * @cfg {String} [filebrowserUploadUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The location of an external file browser that should be launched when the **Browse Server** * button is pressed in the **Image** dialog window. * * If not set, CKEditor will use {@link CKEDITOR.config#filebrowserBrowseUrl}. * * config.filebrowserImageBrowseUrl = '/browser/browse.php?type=Images'; * * @since 3.0 * @cfg {String} [filebrowserImageBrowseUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The location of an external file browser that should be launched when the **Browse Server** * button is pressed in the **Flash** dialog window. * * If not set, CKEditor will use {@link CKEDITOR.config#filebrowserBrowseUrl}. * * config.filebrowserFlashBrowseUrl = '/browser/browse.php?type=Flash'; * * @since 3.0 * @cfg {String} [filebrowserFlashBrowseUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The location of the script that handles file uploads in the **Image** dialog window. * * If not set, CKEditor will use {@link CKEDITOR.config#filebrowserUploadUrl}. * * config.filebrowserImageUploadUrl = '/uploader/upload.php?type=Images'; * * @since 3.0 * @cfg {String} [filebrowserImageUploadUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The location of the script that handles file uploads in the **Flash** dialog window. * * If not set, CKEditor will use {@link CKEDITOR.config#filebrowserUploadUrl}. * * config.filebrowserFlashUploadUrl = '/uploader/upload.php?type=Flash'; * * @since 3.0 * @cfg {String} filebrowserFlashUploadUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The location of an external file browser that should be launched when the **Browse Server** * button is pressed in the **Link** tab of the **Image** dialog window. * * If not set, CKEditor will use {@link CKEDITOR.config#filebrowserBrowseUrl}. * * config.filebrowserImageBrowseLinkUrl = '/browser/browse.php'; * * @since 3.2 * @cfg {String} [filebrowserImageBrowseLinkUrl='' (empty string = disabled)] * @member CKEDITOR.config */ /** * The features to use in the file browser popup window. * * config.filebrowserWindowFeatures = 'resizable=yes,scrollbars=no'; * * @since 3.4.1 * @cfg {String} [filebrowserWindowFeatures='location=no,menubar=no,toolbar=no,dependent=yes,minimizable=no,modal=yes,alwaysRaised=yes,resizable=yes,scrollbars=yes'] * @member CKEDITOR.config */ /** * The width of the file browser popup window. It can be a number denoting a value in * pixels or a percent string. * * config.filebrowserWindowWidth = 750; * * config.filebrowserWindowWidth = '50%'; * * @cfg {Number/String} [filebrowserWindowWidth='80%'] * @member CKEDITOR.config */ /** * The height of the file browser popup window. It can be a number denoting a value in * pixels or a percent string. * * config.filebrowserWindowHeight = 580; * * config.filebrowserWindowHeight = '50%'; * * @cfg {Number/String} [filebrowserWindowHeight='70%'] * @member CKEDITOR.config */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'find', { requires: 'dialog', init: function( editor ) { var findCommand = editor.addCommand( 'find', new CKEDITOR.dialogCommand( 'find' ) ); findCommand.canUndo = false; findCommand.readOnly = 1; var replaceCommand = editor.addCommand( 'replace', new CKEDITOR.dialogCommand( 'replace' ) ); replaceCommand.canUndo = false; if ( editor.ui.addButton ) { editor.ui.addButton( 'Find', { label: editor.lang.find.find, command: 'find', toolbar: 'find,10' }); editor.ui.addButton( 'Replace', { label: editor.lang.find.replace, command: 'replace', toolbar: 'find,20' }); } CKEDITOR.dialog.add( 'find', this.path + 'dialogs/find.js' ); CKEDITOR.dialog.add( 'replace', this.path + 'dialogs/find.js' ); } }); /** * Defines the style to be used to highlight results with the find dialog. * * // Highlight search results with blue on yellow. * config.find_highlight = { * element: 'span', * styles: { 'background-color': '#ff0', color: '#00f' } * }; * * @cfg * @member CKEDITOR.config */ CKEDITOR.config.find_highlight = { element: 'span', styles: { 'background-color': '#004', color: '#fff' } }; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { var cssStyle = CKEDITOR.htmlParser.cssStyle, cssLength = CKEDITOR.tools.cssLength; var cssLengthRegex = /^((?:\d*(?:\.\d+))|(?:\d+))(.*)?$/i; // Replacing the former CSS length value with the later one, with // adjustment to the length unit. function replaceCssLength( length1, length2 ) { var parts1 = cssLengthRegex.exec( length1 ), parts2 = cssLengthRegex.exec( length2 ); // Omit pixel length unit when necessary, // e.g. replaceCssLength( 10, '20px' ) -> 20 if ( parts1 ) { if ( !parts1[ 2 ] && parts2[ 2 ] == 'px' ) return parts2[ 1 ]; if ( parts1[ 2 ] == 'px' && !parts2[ 2 ] ) return parts2[ 1 ] + 'px'; } return length2; } var htmlFilterRules = { elements: { $: function( element ) { var attributes = element.attributes, realHtml = attributes && attributes[ 'data-cke-realelement' ], realFragment = realHtml && new CKEDITOR.htmlParser.fragment.fromHtml( decodeURIComponent( realHtml ) ), realElement = realFragment && realFragment.children[ 0 ]; // Width/height in the fake object are subjected to clone into the real element. if ( realElement && element.attributes[ 'data-cke-resizable' ] ) { var styles = new cssStyle( element ).rules, realAttrs = realElement.attributes, width = styles.width, height = styles.height; width && ( realAttrs.width = replaceCssLength( realAttrs.width, width ) ); height && ( realAttrs.height = replaceCssLength( realAttrs.height, height ) ); } return realElement; } } }; var plugin = CKEDITOR.plugins.add( 'fakeobjects', { afterInit: function( editor ) { var dataProcessor = editor.dataProcessor, htmlFilter = dataProcessor && dataProcessor.htmlFilter; if ( htmlFilter ) htmlFilter.addRules( htmlFilterRules ); } }); /** * @member CKEDITOR.editor * @todo */ CKEDITOR.editor.prototype.createFakeElement = function( realElement, className, realElementType, isResizable ) { var lang = this.lang.fakeobjects, label = lang[ realElementType ] || lang.unknown; var attributes = { 'class': className, 'data-cke-realelement': encodeURIComponent( realElement.getOuterHtml() ), 'data-cke-real-node-type': realElement.type, alt: label, title: label, align: realElement.getAttribute( 'align' ) || '' }; // Do not set "src" on high-contrast so the alt text is displayed. (#8945) if ( !CKEDITOR.env.hc ) attributes.src = CKEDITOR.getUrl( plugin.path + 'images/spacer.gif' ); if ( realElementType ) attributes[ 'data-cke-real-element-type' ] = realElementType; if ( isResizable ) { attributes[ 'data-cke-resizable' ] = isResizable; var fakeStyle = new cssStyle(); var width = realElement.getAttribute( 'width' ), height = realElement.getAttribute( 'height' ); width && ( fakeStyle.rules.width = cssLength( width ) ); height && ( fakeStyle.rules.height = cssLength( height ) ); fakeStyle.populate( attributes ); } return this.document.createElement( 'img', { attributes: attributes } ); }; /** * @member CKEDITOR.editor * @todo */ CKEDITOR.editor.prototype.createFakeParserElement = function( realElement, className, realElementType, isResizable ) { var lang = this.lang.fakeobjects, label = lang[ realElementType ] || lang.unknown, html; var writer = new CKEDITOR.htmlParser.basicWriter(); realElement.writeHtml( writer ); html = writer.getHtml(); var attributes = { 'class': className, 'data-cke-realelement': encodeURIComponent( html ), 'data-cke-real-node-type': realElement.type, alt: label, title: label, align: realElement.attributes.align || '' }; // Do not set "src" on high-contrast so the alt text is displayed. (#8945) if ( !CKEDITOR.env.hc ) attributes.src = CKEDITOR.getUrl( plugin.path + 'images/spacer.gif' ); if ( realElementType ) attributes[ 'data-cke-real-element-type' ] = realElementType; if ( isResizable ) { attributes[ 'data-cke-resizable' ] = isResizable; var realAttrs = realElement.attributes, fakeStyle = new cssStyle(); var width = realAttrs.width, height = realAttrs.height; width != undefined && ( fakeStyle.rules.width = cssLength( width ) ); height != undefined && ( fakeStyle.rules.height = cssLength( height ) ); fakeStyle.populate( attributes ); } return new CKEDITOR.htmlParser.element( 'img', attributes ); }; /** * @member CKEDITOR.editor * @todo */ CKEDITOR.editor.prototype.restoreRealElement = function( fakeElement ) { if ( fakeElement.data( 'cke-real-node-type' ) != CKEDITOR.NODE_ELEMENT ) return null; var element = CKEDITOR.dom.element.createFromHtml( decodeURIComponent( fakeElement.data( 'cke-realelement' ) ), this.document ); if ( fakeElement.data( 'cke-resizable' ) ) { var width = fakeElement.getStyle( 'width' ), height = fakeElement.getStyle( 'height' ); width && element.setAttribute( 'width', replaceCssLength( element.getAttribute( 'width' ), width ) ); height && element.setAttribute( 'height', replaceCssLength( element.getAttribute( 'height' ), height ) ); } return element; }; })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { var flashFilenameRegex = /\.swf(?:$|\?)/i; function isFlashEmbed( element ) { var attributes = element.attributes; return ( attributes.type == 'application/x-shockwave-flash' || flashFilenameRegex.test( attributes.src || '' ) ); } function createFakeElement( editor, realElement ) { return editor.createFakeParserElement( realElement, 'cke_flash', 'flash', true ); } CKEDITOR.plugins.add( 'flash', { requires: 'dialog,fakeobjects', onLoad: function() { CKEDITOR.addCss( 'img.cke_flash' + '{' + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/placeholder.png' ) + ');' + 'background-position: center center;' + 'background-repeat: no-repeat;' + 'border: 1px solid #a9a9a9;' + 'width: 80px;' + 'height: 80px;' + '}' ); }, init: function( editor ) { editor.addCommand( 'flash', new CKEDITOR.dialogCommand( 'flash' ) ); editor.ui.addButton && editor.ui.addButton( 'Flash', { label: editor.lang.common.flash, command: 'flash', toolbar: 'insert,20' }); CKEDITOR.dialog.add( 'flash', this.path + 'dialogs/flash.js' ); // If the "menu" plugin is loaded, register the menu items. if ( editor.addMenuItems ) { editor.addMenuItems({ flash: { label: editor.lang.flash.properties, command: 'flash', group: 'flash' } }); } editor.on( 'doubleclick', function( evt ) { var element = evt.data.element; if ( element.is( 'img' ) && element.data( 'cke-real-element-type' ) == 'flash' ) evt.data.dialog = 'flash'; }); // If the "contextmenu" plugin is loaded, register the listeners. if ( editor.contextMenu ) { editor.contextMenu.addListener( function( element, selection ) { if ( element && element.is( 'img' ) && !element.isReadOnly() && element.data( 'cke-real-element-type' ) == 'flash' ) return { flash: CKEDITOR.TRISTATE_OFF }; }); } }, afterInit: function( editor ) { var dataProcessor = editor.dataProcessor, dataFilter = dataProcessor && dataProcessor.dataFilter; if ( dataFilter ) { dataFilter.addRules({ elements: { 'cke:object': function( element ) { var attributes = element.attributes, classId = attributes.classid && String( attributes.classid ).toLowerCase(); if ( !classId && !isFlashEmbed( element ) ) { // Look for the inner for ( var i = 0; i < element.children.length; i++ ) { if ( element.children[ i ].name == 'cke:embed' ) { if ( !isFlashEmbed( element.children[ i ] ) ) return null; return createFakeElement( editor, element ); } } return null; } return createFakeElement( editor, element ); }, 'cke:embed': function( element ) { if ( !isFlashEmbed( element ) ) return null; return createFakeElement( editor, element ); } } }, 5 ); } } }); })(); CKEDITOR.tools.extend( CKEDITOR.config, { /** * Save as `` tag only. This tag is unrecommended. * * @cfg {Boolean} [flashEmbedTagOnly=false] * @member CKEDITOR.config */ flashEmbedTagOnly: false, /** * Add `` tag as alternative: ``. * * @cfg {Boolean} [flashAddEmbedTag=false] * @member CKEDITOR.config */ flashAddEmbedTag: true, /** * Use {@link #flashEmbedTagOnly} and {@link #flashAddEmbedTag} values on edit. * * @cfg {Boolean} [flashConvertOnEdit=false] * @member CKEDITOR.config */ flashConvertOnEdit: false }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { var floatSpaceTpl = CKEDITOR.addTemplate( 'floatcontainer', '' + '
        ' + '' + '
        ' + '
  • ' ); CKEDITOR.plugins.add( 'floatingspace', { init: function( editor ) { editor.on( 'contentDom', function() { attach( editor ); }); } }); var win = CKEDITOR.document.getWindow(); var pixelate = CKEDITOR.tools.cssLength; function scrollOffset( side ) { var pageOffset = side == 'left' ? 'pageXOffset' : 'pageYOffset', docScrollOffset = side == 'left' ? 'scrollLeft' : 'scrollTop'; return ( pageOffset in win.$ ) ? win.$[ pageOffset ] : CKEDITOR.document.$.documentElement[ docScrollOffset ]; } function attach( editor ) { // Indicates the vertical aligning mode. var mode; // Allow minor adjustments of the float space from custom configs. var config = editor.config; var dockedOffsetX = config.floatSpaceDockedOffsetX || 0, dockedOffsetY = config.floatSpaceDockedOffsetY || 0, pinnedOffsetX = config.floatSpacePinnedOffsetX || 0, pinnedOffsetY = config.floatSpacePinnedOffsetY || 0; var layout = function( evt ) { // Update the float space position. function updatePos( pos, prop, val ) { floatSpace.setStyle( prop, pixelate( val ) ); floatSpace.setStyle( 'position', pos ); } // Change the current mode and update float space position accordingly. function changeMode( newMode ) { var editorPos = editable.getDocumentPosition(); switch ( newMode ) { case 'top': updatePos( 'absolute', 'top', editorPos.y - spaceHeight - dockedOffsetY ); break; case 'pin': updatePos( 'fixed', 'top', pinnedOffsetY ); break; case 'bottom': updatePos( 'absolute', 'top', editorPos.y + ( editorRect.height || editorRect.bottom - editorRect.top ) + dockedOffsetY ); break; } mode = newMode; } // Show up the space on focus gain. evt.name == 'focus' && floatSpace.show(); // Reset the horizontal position for below measurement. floatSpace.removeStyle( 'left' ); floatSpace.removeStyle( 'right' ); // Compute the screen position from the TextRectangle object would // be very simple, even though the "width"/"height" property is not // available for all, it's safe to figure that out from the rest. // http://help.dottoro.com/ljgupwlp.php var editable = editor.editable(), spaceRect = floatSpace.getClientRect(), editorRect = editable.getClientRect(), spaceHeight = spaceRect.height, pageScrollX = scrollOffset( 'left' ); // We initialize it as pin mode. if ( !mode ) { mode = 'pin'; changeMode( 'pin' ); // Call for a refresh to the actual layout. layout( evt ); return; } // Pin the space element while page scrolls down to pull it off the view port. else if ( mode == 'top' && spaceRect.top < pinnedOffsetY ) changeMode( 'pin' ); else if ( mode == 'pin' ) { // Restore into docked top from pin. if ( editorRect.top > dockedOffsetY + spaceHeight ) changeMode( 'top' ); // Docked the space below editable when page scrolls down and the space masks // the final few lines of the content. else if ( editorRect.bottom - spaceRect.bottom < spaceHeight ) changeMode( 'bottom' ); } else if ( mode == 'bottom' ) { // Jump to top mode. ( with pin mode skipped) if ( editorRect.top > dockedOffsetY + spaceHeight ) changeMode( 'top' ); // Restore into pin mode from docked bottom. else if ( editorRect.bottom > 2 * spaceHeight + pinnedOffsetY ) changeMode( 'pin' ); } var viewRect = win.getViewPaneSize(); var mid = viewRect.width / 2; var alignSide = ( editorRect.left > 0 && editorRect.right < viewRect.width && editorRect.width > spaceRect.width ) ? ( editor.config.contentsLangDirection == 'rtl' ? 'right' : 'left' ) : ( mid - editorRect.left > editorRect.right - mid ? 'left' : 'right' ), offset; // (#9769) If viewport width is less than space width, // make sure space never cross the left boundary of the viewport. // In other words: top-left corner of the space is always visible. if ( spaceRect.width > viewRect.width ) { alignSide = 'left'; offset = 0; } else { if ( alignSide == 'left' ) { // If the space rect fits into viewport, align it // to the left edge of editor: // // +------------------------ Viewport -+ // | | // | +------------- Space -+ | // | | | | // | +---------------------+ | // | +------------------ Editor -+ | // | | | | // if ( editorRect.left > 0 ) offset = editorRect.left; // If the left part of the editor is cut off by the left // edge of the viewport, stick the space to the viewport: // // +------------------------ Viewport -+ // | | // +---------------- Space -+ | // | | | // +------------------------+ | // +----|------------- Editor -+ | // | | | | // else offset = 0; } else { // If the space rect fits into viewport, align it // to the right edge of editor: // // +------------------------ Viewport -+ // | | // | +------------- Space -+ | // | | | | // | +---------------------+ | // | +------------------ Editor -+ | // | | | | // if ( editorRect.right < viewRect.width ) offset = viewRect.width - editorRect.right; // If the right part of the editor is cut off by the right // edge of the viewport, stick the space to the viewport: // // +------------------------ Viewport -+ // | | // | +------------- Space -+ // | | | // | +---------------------+ // | +-----------------|- Editor -+ // | | | | // else offset = 0; } // (#9769) Finally, stick the space to the opposite side of // the viewport when it's cut off horizontally on the left/right // side like below. // // This trick reveals cut off space in some edge cases and // hence it improves accessibility. // // +------------------------ Viewport -+ // | | // | +--------------------|-- Space -+ // | | | | // | +--------------------|----------+ // | +------- Editor -+ | // | | | | // // becomes: // // +------------------------ Viewport -+ // | | // | +----------------------- Space -+ // | | | // | +-------------------------------+ // | +------- Editor -+ | // | | | | // if ( offset + spaceRect.width > viewRect.width ) { alignSide = alignSide == 'left' ? 'right' : 'left'; offset = 0; } } // Pin mode is fixed, so don't include scroll-x. // (#9903) For mode is "top" or "bottom", add opposite scroll-x for right-aligned space. var scroll = mode == 'pin' ? 0 : alignSide == 'left' ? pageScrollX : -pageScrollX; floatSpace.setStyle( alignSide, pixelate( ( mode == 'pin' ? pinnedOffsetX : dockedOffsetX ) + offset + scroll ) ); }; var body = CKEDITOR.document.getBody(); var vars = { id : editor.id, name: editor.name, langDir: editor.lang.dir, langCode: editor.langCode }; // Get the HTML for the predefined spaces. var topHtml = editor.fire( 'uiSpace', { space: 'top', html: '' } ).html; if ( topHtml ) { var floatSpace = body.append( CKEDITOR.dom.element.createFromHtml( floatSpaceTpl.output( CKEDITOR.tools.extend({ topId: editor.ui.spaceId( 'top' ), content: topHtml, style: 'display:none;z-index:' + ( editor.config.baseFloatZIndex - 1 ) }, vars ) ) ) ); // There's no need for the floatSpace to be selectable. floatSpace.unselectable(); // Prevent clicking on non-buttons area of the space from blurring editor. floatSpace.on( 'mousedown', function( evt ) { evt = evt.data; if ( !evt.getTarget().hasAscendant( 'a', 1 ) ) evt.preventDefault(); }); editor.on( 'focus', function( evt ) { layout( evt ); win.on( 'scroll', layout ); win.on( 'resize', layout ); }); editor.on( 'blur', function() { floatSpace.hide(); win.removeListener( 'scroll', layout ); win.removeListener( 'resize', layout ); }); editor.on( 'destroy', function() { win.removeListener( 'scroll', layout ); win.removeListener( 'resize', layout ); floatSpace.clearCustomData(); floatSpace.remove(); }); // Handle initial focus. if ( editor.focusManager.hasFocus ) floatSpace.show(); // Register this UI space to the focus manager. editor.focusManager.add( floatSpace, 1 ); } } })(); /** * Along with {@link #floatSpaceDockedOffsetY} it defines the * amount of offset (in pixels) between float space and the editable left/right * boundaries when space element is docked at either side of the editable. * * config.floatSpaceDockedOffsetX = 10; * * @cfg {Number} [floatSpaceDockedOffsetX=0] * @member CKEDITOR.config */ /** * Along with {@link #floatSpaceDockedOffsetX} it defines the * amount of offset (in pixels) between float space and the editable top/bottom * boundaries when space element is docked at either side of the editable. * * config.floatSpaceDockedOffsetY = 10; * * @cfg {Number} [floatSpaceDockedOffsetY=0] * @member CKEDITOR.config */ /** * Along with {@link #floatSpacePinnedOffsetY} it defines the * amount of offset (in pixels) between float space and the view port boundaries * when space element is pinned. * * config.floatSpacePinnedOffsetX = 20; * * @cfg {Number} [floatSpacePinnedOffsetX=0] * @member CKEDITOR.config */ /** * Along with {@link #floatSpacePinnedOffsetX} it defines the * amount of offset (in pixels) between float space and the view port boundaries * when space element is pinned. * * config.floatSpacePinnedOffsetY = 20; * * @cfg {Number} [floatSpacePinnedOffsetY=0] * @member CKEDITOR.config */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'listblock', { requires: 'panel', onLoad: function() { var list = CKEDITOR.addTemplate( 'panel-list', '' ), listItem = CKEDITOR.addTemplate( 'panel-list-item', '' ), listGroup = CKEDITOR.addTemplate( 'panel-list-group', '

    {label}

    ' ); CKEDITOR.ui.panel.prototype.addListBlock = function( name, definition ) { return this.addBlock( name, new CKEDITOR.ui.listBlock( this.getHolderElement(), definition ) ); }; CKEDITOR.ui.listBlock = CKEDITOR.tools.createClass({ base: CKEDITOR.ui.panel.block, $: function( blockHolder, blockDefinition ) { blockDefinition = blockDefinition || {}; var attribs = blockDefinition.attributes || ( blockDefinition.attributes = {} ); ( this.multiSelect = !!blockDefinition.multiSelect ) && ( attribs[ 'aria-multiselectable' ] = true ); // Provide default role of 'listbox'. !attribs.role && ( attribs.role = 'listbox' ); // Call the base contructor. this.base.apply( this, arguments ); var keys = this.keys; keys[ 40 ] = 'next'; // ARROW-DOWN keys[ 9 ] = 'next'; // TAB keys[ 38 ] = 'prev'; // ARROW-UP keys[ CKEDITOR.SHIFT + 9 ] = 'prev'; // SHIFT + TAB keys[ 32 ] = CKEDITOR.env.ie ? 'mouseup' : 'click'; // SPACE CKEDITOR.env.ie && ( keys[ 13 ] = 'mouseup' ); // Manage ENTER, since onclick is blocked in IE (#8041). this._.pendingHtml = []; this._.pendingList = []; this._.items = {}; this._.groups = {}; }, _: { close: function() { if ( this._.started ) { var output = list.output({ items: this._.pendingList.join( '' ) } ); this._.pendingList = []; this._.pendingHtml.push( output ); delete this._.started; } }, getClick: function() { if ( !this._.click ) { this._.click = CKEDITOR.tools.addFunction( function( value ) { var marked = this.toggle( value ); if ( this.onClick ) this.onClick( value, marked ); }, this ); } return this._.click; } }, proto: { add: function( value, html, title ) { var id = CKEDITOR.tools.getNextId(); if ( !this._.started ) { this._.started = 1; this._.size = this._.size || 0; } this._.items[ value ] = id; var data = { id: id, val: value, onclick: CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick', clickFn: this._.getClick(), title: title || value, text: html || value }; this._.pendingList.push( listItem.output( data ) ); }, startGroup: function( title ) { this._.close(); var id = CKEDITOR.tools.getNextId(); this._.groups[ title ] = id; this._.pendingHtml.push( listGroup.output({ id: id, label: title } ) ); }, commit: function() { this._.close(); this.element.appendHtml( this._.pendingHtml.join( '' ) ); delete this._.size; this._.pendingHtml = []; }, toggle: function( value ) { var isMarked = this.isMarked( value ); if ( isMarked ) this.unmark( value ); else this.mark( value ); return !isMarked; }, hideGroup: function( groupTitle ) { var group = this.element.getDocument().getById( this._.groups[ groupTitle ] ), list = group && group.getNext(); if ( group ) { group.setStyle( 'display', 'none' ); if ( list && list.getName() == 'ul' ) list.setStyle( 'display', 'none' ); } }, hideItem: function( value ) { this.element.getDocument().getById( this._.items[ value ] ).setStyle( 'display', 'none' ); }, showAll: function() { var items = this._.items, groups = this._.groups, doc = this.element.getDocument(); for ( var value in items ) { doc.getById( items[ value ] ).setStyle( 'display', '' ); } for ( var title in groups ) { var group = doc.getById( groups[ title ] ), list = group.getNext(); group.setStyle( 'display', '' ); if ( list && list.getName() == 'ul' ) list.setStyle( 'display', '' ); } }, mark: function( value ) { if ( !this.multiSelect ) this.unmarkAll(); var itemId = this._.items[ value ], item = this.element.getDocument().getById( itemId ); item.addClass( 'cke_selected' ); this.element.getDocument().getById( itemId + '_option' ).setAttribute( 'aria-selected', true ); this.onMark && this.onMark( item ); }, unmark: function( value ) { var doc = this.element.getDocument(), itemId = this._.items[ value ], item = doc.getById( itemId ); item.removeClass( 'cke_selected' ); doc.getById( itemId + '_option' ).removeAttribute( 'aria-selected' ); this.onUnmark && this.onUnmark( item ); }, unmarkAll: function() { var items = this._.items, doc = this.element.getDocument(); for ( var value in items ) { var itemId = items[ value ]; doc.getById( itemId ).removeClass( 'cke_selected' ); doc.getById( itemId + '_option' ).removeAttribute( 'aria-selected' ); } this.onUnmark && this.onUnmark(); }, isMarked: function( value ) { return this.element.getDocument().getById( this._.items[ value ] ).hasClass( 'cke_selected' ); }, focus: function( value ) { this._.focusIndex = -1; if ( value ) { var selected = this.element.getDocument().getById( this._.items[ value ] ).getFirst(); var links = this.element.getElementsByTag( 'a' ), link, i = -1; while ( ( link = links.getItem( ++i ) ) ) { if ( link.equals( selected ) ) { this._.focusIndex = i; break; } } setTimeout( function() { selected.focus(); }, 0 ); } } } }); } }); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'richcombo', { requires: 'floatpanel,listblock,button', beforeInit: function( editor ) { editor.ui.addHandler( CKEDITOR.UI_RICHCOMBO, CKEDITOR.ui.richCombo.handler ); } }); (function() { var template = '' + '{label}' + '= 10900 && !CKEDITOR.env.hc ? '' : '" href="javascript:void(\'{titleJs}\')"' ) + ' hidefocus="true"' + ' role="button"' + ' aria-labelledby="{id}_label"' + ' aria-haspopup="true"'; // Some browsers don't cancel key events in the keydown but in the // keypress. // TODO: Check if really needed for Gecko+Mac. if ( CKEDITOR.env.opera || ( CKEDITOR.env.gecko && CKEDITOR.env.mac ) ) template += ' onkeypress="return false;"'; // With Firefox, we need to force the button to redraw, otherwise it // will remain in the focus state. if ( CKEDITOR.env.gecko ) template += ' onblur="this.style.cssText = this.style.cssText;"'; template += ' onkeydown="return CKEDITOR.tools.callFunction({keydownFn},event,this);"' + ' onmousedown="return CKEDITOR.tools.callFunction({mousedownFn},event);" ' + ' onfocus="return CKEDITOR.tools.callFunction({focusFn},event);" ' + ( CKEDITOR.env.ie ? 'onclick="return false;" onmouseup' : 'onclick' ) + // #188 '="CKEDITOR.tools.callFunction({clickFn},this);return false;">' + '{label}' + '' + '' + // BLACK DOWN-POINTING TRIANGLE ( CKEDITOR.env.hc ? '▼' : CKEDITOR.env.air ? ' ' : '' ) + '' + '' + '' + ''; var rcomboTpl = CKEDITOR.addTemplate( 'combo', template ); /** * Button UI element. * * @readonly * @property {String} [='richcombo'] * @member CKEDITOR */ CKEDITOR.UI_RICHCOMBO = 'richcombo'; /** * @class * @todo */ CKEDITOR.ui.richCombo = CKEDITOR.tools.createClass({ $: function( definition ) { // Copy all definition properties to this object. CKEDITOR.tools.extend( this, definition, // Set defaults. { // The combo won't participate in toolbar grouping. canGroup: false, title: definition.label, modes: { wysiwyg:1 }, editorFocus: 1 }); // We don't want the panel definition in this object. var panelDefinition = this.panel || {}; delete this.panel; this.id = CKEDITOR.tools.getNextNumber(); this.document = ( panelDefinition.parent && panelDefinition.parent.getDocument() ) || CKEDITOR.document; panelDefinition.className = 'cke_combopanel'; panelDefinition.block = { multiSelect: panelDefinition.multiSelect, attributes: panelDefinition.attributes }; panelDefinition.toolbarRelated = true; this._ = { panelDefinition: panelDefinition, items: {} }; }, proto: { renderHtml: function( editor ) { var output = []; this.render( editor, output ); return output.join( '' ); }, /** * Renders the combo. * @param {CKEDITOR.editor} editor The editor instance which this button is * to be used by. * @param {Array} output The output array to which append the HTML relative * to this button. * @example */ render: function( editor, output ) { var env = CKEDITOR.env; var id = 'cke_' + this.id; var clickFn = CKEDITOR.tools.addFunction( function( el ) { // Restore locked selection in Opera. if ( selLocked ) { editor.unlockSelection( 1 ); selLocked = 0; } instance.execute( el ); }, this ); var combo = this; var instance = { id: id, combo: this, focus: function() { var element = CKEDITOR.document.getById( id ).getChild( 1 ); element.focus(); }, execute: function( el ) { var _ = combo._; if ( _.state == CKEDITOR.TRISTATE_DISABLED ) return; combo.createPanel( editor ); if ( _.on ) { _.panel.hide(); return; } combo.commit(); var value = combo.getValue(); if ( value ) _.list.mark( value ); else _.list.unmarkAll(); _.panel.showBlock( combo.id, new CKEDITOR.dom.element( el ), 4 ); }, clickFn: clickFn }; function updateState() { var state = this.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED; this.setState( editor.readOnly && !this.readOnly ? CKEDITOR.TRISTATE_DISABLED : state ); this.setValue( '' ); } editor.on( 'mode', updateState, this ); // If this combo is sensitive to readOnly state, update it accordingly. !this.readOnly && editor.on( 'readOnly', updateState, this ); var keyDownFn = CKEDITOR.tools.addFunction( function( ev, element ) { ev = new CKEDITOR.dom.event( ev ); var keystroke = ev.getKeystroke(); switch ( keystroke ) { case 13: // ENTER case 32: // SPACE case 40: // ARROW-DOWN // Show panel CKEDITOR.tools.callFunction( clickFn, element ); break; default: // Delegate the default behavior to toolbar button key handling. instance.onkey( instance, keystroke ); } // Avoid subsequent focus grab on editor document. ev.preventDefault(); }); var focusFn = CKEDITOR.tools.addFunction( function() { instance.onfocus && instance.onfocus(); }); var selLocked = 0; var mouseDownFn = CKEDITOR.tools.addFunction( function() { // Opera: lock to prevent loosing editable text selection when clicking on button. if ( CKEDITOR.env.opera ) { var edt = editor.editable(); if ( edt.isInline() && edt.hasFocus ) { editor.lockSelection(); selLocked = 1; } } }); // For clean up instance.keyDownFn = keyDownFn; var params = { id: id, name: this.name || this.command, label: this.label, title: this.title, cls: this.className || '', titleJs: env.gecko && env.version >= 10900 && !env.hc ? '' : ( this.title || '' ).replace( "'", '' ), keydownFn: keyDownFn, mousedownFn: mouseDownFn, focusFn: focusFn, clickFn: clickFn }; rcomboTpl.output( params, output ); if ( this.onRender ) this.onRender(); return instance; }, createPanel: function( editor ) { if ( this._.panel ) return; var panelDefinition = this._.panelDefinition, panelBlockDefinition = this._.panelDefinition.block, panelParentElement = panelDefinition.parent || CKEDITOR.document.getBody(), namedPanelCls = 'cke_combopanel__' + this.name, panel = new CKEDITOR.ui.floatPanel( editor, panelParentElement, panelDefinition ), list = panel.addListBlock( this.id, panelBlockDefinition ), me = this; panel.onShow = function() { this.element.addClass( namedPanelCls ); me.setState( CKEDITOR.TRISTATE_ON ); list.focus( !list.multiSelect && me.getValue() ); me._.on = 1; me.editorFocus && editor.focus(); if ( me.onOpen ) me.onOpen(); }; panel.onHide = function( preventOnClose ) { this.element.removeClass( namedPanelCls ); me.setState( me.modes && me.modes[ editor.mode ] ? CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED ); me._.on = 0; if ( !preventOnClose && me.onClose ) me.onClose(); }; panel.onEscape = function() { // Hide drop-down with focus returned. panel.hide( 1 ); }; list.onClick = function( value, marked ) { if ( me.onClick ) me.onClick.call( me, value, marked ); panel.hide(); }; this._.panel = panel; this._.list = list; panel.getBlock( this.id ).onHide = function() { me._.on = 0; me.setState( CKEDITOR.TRISTATE_OFF ); }; if ( this.init ) this.init(); }, setValue: function( value, text ) { this._.value = value; var textElement = this.document.getById( 'cke_' + this.id + '_text' ); if ( textElement ) { if ( !( value || text ) ) { text = this.label; textElement.addClass( 'cke_combo_inlinelabel' ); } else textElement.removeClass( 'cke_combo_inlinelabel' ); textElement.setText( typeof text != 'undefined' ? text : value ); } }, getValue: function() { return this._.value || ''; }, unmarkAll: function() { this._.list.unmarkAll(); }, mark: function( value ) { this._.list.mark( value ); }, hideItem: function( value ) { this._.list.hideItem( value ); }, hideGroup: function( groupTitle ) { this._.list.hideGroup( groupTitle ); }, showAll: function() { this._.list.showAll(); }, add: function( value, html, text ) { this._.items[ value ] = text || value; this._.list.add( value, html, text ); }, startGroup: function( title ) { this._.list.startGroup( title ); }, commit: function() { if ( !this._.committed ) { this._.list.commit(); this._.committed = 1; CKEDITOR.ui.fire( 'ready', this ); } this._.committed = 1; }, setState: function( state ) { if ( this._.state == state ) return; var el = this.document.getById( 'cke_' + this.id ); el.setState( state, 'cke_combo' ); state == CKEDITOR.TRISTATE_DISABLED ? el.setAttribute( 'aria-disabled', true ) : el.removeAttribute( 'aria-disabled' ); this._.state = state; }, enable: function() { if ( this._.state == CKEDITOR.TRISTATE_DISABLED ) this.setState( this._.lastState ); }, disable: function() { if ( this._.state != CKEDITOR.TRISTATE_DISABLED ) { this._.lastState = this._.state; this.setState( CKEDITOR.TRISTATE_DISABLED ); } } }, /** * Represents richCombo handler object. * * @class CKEDITOR.ui.richCombo.handler * @singleton * @extends CKEDITOR.ui.handlerDefinition */ statics: { handler: { /** * Transforms a richCombo definition in a {@link CKEDITOR.ui.richCombo} instance. * * @param {Object} definition * @returns {CKEDITOR.ui.richCombo} */ create: function( definition ) { return new CKEDITOR.ui.richCombo( definition ); } } } }); /** * @member CKEDITOR.ui * @param {String} * @param {Object} definition * @todo */ CKEDITOR.ui.prototype.addRichCombo = function( name, definition ) { this.add( name, CKEDITOR.UI_RICHCOMBO, definition ); }; })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { function addCombo( editor, comboName, styleType, lang, entries, defaultLabel, styleDefinition, order ) { var config = editor.config; // Gets the list of fonts from the settings. var names = entries.split( ';' ), values = []; // Create style objects for all fonts. var styles = {}; for ( var i = 0; i < names.length; i++ ) { var parts = names[ i ]; if ( parts ) { parts = parts.split( '/' ); var vars = {}, name = names[ i ] = parts[ 0 ]; vars[ styleType ] = values[ i ] = parts[ 1 ] || name; styles[ name ] = new CKEDITOR.style( styleDefinition, vars ); styles[ name ]._.definition.name = name; } else names.splice( i--, 1 ); } editor.ui.addRichCombo( comboName, { label: lang.label, title: lang.panelTitle, toolbar: 'styles,' + order, panel: { css: [ CKEDITOR.skin.getPath( 'editor' ) ].concat( config.contentsCss ), multiSelect: false, attributes: { 'aria-label': lang.panelTitle } }, init: function() { this.startGroup( lang.panelTitle ); for ( var i = 0; i < names.length; i++ ) { var name = names[ i ]; // Add the tag entry to the panel list. this.add( name, styles[ name ].buildPreview(), name ); } }, onClick: function( value ) { editor.focus(); editor.fire( 'saveSnapshot' ); var style = styles[ value ]; editor[ this.getValue() == value ? 'removeStyle' : 'applyStyle' ]( style ); editor.fire( 'saveSnapshot' ); }, onRender: function() { editor.on( 'selectionChange', function( ev ) { var currentValue = this.getValue(); var elementPath = ev.data.path, elements = elementPath.elements; // For each element into the elements path. for ( var i = 0, element; i < elements.length; i++ ) { element = elements[ i ]; // Check if the element is removable by any of // the styles. for ( var value in styles ) { if ( styles[ value ].checkElementMatch( element, true ) ) { if ( value != currentValue ) this.setValue( value ); return; } } } // If no styles match, just empty it. this.setValue( '', defaultLabel ); }, this ); } }); } CKEDITOR.plugins.add( 'font', { requires: 'richcombo', init: function( editor ) { var config = editor.config; addCombo( editor, 'Font', 'family', editor.lang.font, config.font_names, config.font_defaultLabel, config.font_style, 30 ); addCombo( editor, 'FontSize', 'size', editor.lang.font.fontSize, config.fontSize_sizes, config.fontSize_defaultLabel, config.fontSize_style, 40 ); } }); })(); /** * The list of fonts names to be displayed in the Font combo in the toolbar. * Entries are separated by semi-colons (`';'`), while it's possible to have more * than one font for each entry, in the HTML way (separated by comma). * * A display name may be optionally defined by prefixing the entries with the * name and the slash character. For example, `'Arial/Arial, Helvetica, sans-serif'` * will be displayed as `'Arial'` in the list, but will be outputted as * `'Arial, Helvetica, sans-serif'`. * * config.font_names = * 'Arial/Arial, Helvetica, sans-serif;' + * 'Times New Roman/Times New Roman, Times, serif;' + * 'Verdana'; * * config.font_names = 'Arial;Times New Roman;Verdana'; * * @cfg {String} [font_names=see source] * @member CKEDITOR.config */ CKEDITOR.config.font_names = 'Arial/Arial, Helvetica, sans-serif;' + 'Comic Sans MS/Comic Sans MS, cursive;' + 'Courier New/Courier New, Courier, monospace;' + 'Georgia/Georgia, serif;' + 'Lucida Sans Unicode/Lucida Sans Unicode, Lucida Grande, sans-serif;' + 'Tahoma/Tahoma, Geneva, sans-serif;' + 'Times New Roman/Times New Roman, Times, serif;' + 'Trebuchet MS/Trebuchet MS, Helvetica, sans-serif;' + 'Verdana/Verdana, Geneva, sans-serif'; /** * The text to be displayed in the Font combo is none of the available values * matches the current cursor position or text selection. * * // If the default site font is Arial, we may making it more explicit to the end user. * config.font_defaultLabel = 'Arial'; * * @cfg {String} [font_defaultLabel=''] * @member CKEDITOR.config */ CKEDITOR.config.font_defaultLabel = ''; /** * The style definition to be used to apply the font in the text. * * // This is actually the default value for it. * config.font_style = { * element: 'span', * styles: { 'font-family': '#(family)' }, * overrides: [ { element: 'font', attributes: { 'face': null } } ] * }; * * @cfg {Object} [font_style=see example] * @member CKEDITOR.config */ CKEDITOR.config.font_style = { element: 'span', styles: { 'font-family': '#(family)' }, overrides: [ { element: 'font', attributes: { 'face': null } }] }; /** * The list of fonts size to be displayed in the Font Size combo in the * toolbar. Entries are separated by semi-colons (`';'`). * * Any kind of "CSS like" size can be used, like `'12px'`, `'2.3em'`, `'130%'`, * `'larger'` or `'x-small'`. * * A display name may be optionally defined by prefixing the entries with the * name and the slash character. For example, `'Bigger Font/14px'` will be * displayed as `'Bigger Font'` in the list, but will be outputted as `'14px'`. * * config.fontSize_sizes = '16/16px;24/24px;48/48px;'; * * config.fontSize_sizes = '12px;2.3em;130%;larger;x-small'; * * config.fontSize_sizes = '12 Pixels/12px;Big/2.3em;30 Percent More/130%;Bigger/larger;Very Small/x-small'; * * @cfg {String} [fontSize_sizes=see source] * @member CKEDITOR.config */ CKEDITOR.config.fontSize_sizes = '8/8px;9/9px;10/10px;11/11px;12/12px;14/14px;16/16px;18/18px;20/20px;22/22px;24/24px;26/26px;28/28px;36/36px;48/48px;72/72px'; /** * The text to be displayed in the Font Size combo is none of the available * values matches the current cursor position or text selection. * * // If the default site font size is 12px, we may making it more explicit to the end user. * config.fontSize_defaultLabel = '12px'; * * @cfg {String} [fontSize_defaultLabel=''] * @member CKEDITOR.config */ CKEDITOR.config.fontSize_defaultLabel = ''; /** * The style definition to be used to apply the font size in the text. * * // This is actually the default value for it. * config.fontSize_style = { * element: 'span', * styles: { 'font-size': '#(size)' }, * overrides: [ { element :'font', attributes: { 'size': null } } ] * }; * * @cfg {Object} [fontSize_style=see example] * @member CKEDITOR.config */ CKEDITOR.config.fontSize_style = { element: 'span', styles: { 'font-size': '#(size)' }, overrides: [ { element: 'font', attributes: { 'size': null } }] }; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Forms Plugin */ CKEDITOR.plugins.add( 'forms', { requires: 'dialog,fakeobjects', onLoad: function() { CKEDITOR.addCss( '.cke_editable form' + '{' + 'border: 1px dotted #FF0000;' + 'padding: 2px;' + '}\n' ); CKEDITOR.addCss( 'img.cke_hidden' + '{' + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/hiddenfield.gif' ) + ');' + 'background-position: center center;' + 'background-repeat: no-repeat;' + 'border: 1px solid #a9a9a9;' + 'width: 16px !important;' + 'height: 16px !important;' + '}' ); }, init: function( editor ) { var lang = editor.lang, order = 0, textfieldTypes = { email:1,password:1,search:1,tel:1,text:1,url:1 }; // All buttons use the same code to register. So, to avoid // duplications, let's use this tool function. var addButtonCommand = function( buttonName, commandName, dialogFile ) { var def = {}; commandName == 'form' && ( def.context = 'form' ); editor.addCommand( commandName, new CKEDITOR.dialogCommand( commandName, def ) ); editor.ui.addButton && editor.ui.addButton( buttonName, { label: lang.common[ buttonName.charAt( 0 ).toLowerCase() + buttonName.slice( 1 ) ], command: commandName, toolbar: 'forms,' + ( order += 10 ) }); CKEDITOR.dialog.add( commandName, dialogFile ); }; var dialogPath = this.path + 'dialogs/'; !editor.blockless && addButtonCommand( 'Form', 'form', dialogPath + 'form.js' ); addButtonCommand( 'Checkbox', 'checkbox', dialogPath + 'checkbox.js' ); addButtonCommand( 'Radio', 'radio', dialogPath + 'radio.js' ); addButtonCommand( 'TextField', 'textfield', dialogPath + 'textfield.js' ); addButtonCommand( 'Textarea', 'textarea', dialogPath + 'textarea.js' ); addButtonCommand( 'Select', 'select', dialogPath + 'select.js' ); addButtonCommand( 'Button', 'button', dialogPath + 'button.js' ); // If the "image" plugin is loaded. var imagePlugin = CKEDITOR.plugins.get( 'image' ); imagePlugin && addButtonCommand( 'ImageButton', 'imagebutton', CKEDITOR.plugins.getPath( 'image' ) + 'dialogs/image.js' ); addButtonCommand( 'HiddenField', 'hiddenfield', dialogPath + 'hiddenfield.js' ); // If the "menu" plugin is loaded, register the menu items. if ( editor.addMenuItems ) { var items = { checkbox: { label: lang.forms.checkboxAndRadio.checkboxTitle, command: 'checkbox', group: 'checkbox' }, radio: { label: lang.forms.checkboxAndRadio.radioTitle, command: 'radio', group: 'radio' }, textfield: { label: lang.forms.textfield.title, command: 'textfield', group: 'textfield' }, hiddenfield: { label: lang.forms.hidden.title, command: 'hiddenfield', group: 'hiddenfield' }, imagebutton: { label: lang.image.titleButton, command: 'imagebutton', group: 'imagebutton' }, button: { label: lang.forms.button.title, command: 'button', group: 'button' }, select: { label: lang.forms.select.title, command: 'select', group: 'select' }, textarea: { label: lang.forms.textarea.title, command: 'textarea', group: 'textarea' } }; !editor.blockless && ( items.form = { label: lang.forms.form.menu, command: 'form', group: 'form' }); editor.addMenuItems( items ); } // If the "contextmenu" plugin is loaded, register the listeners. if ( editor.contextMenu ) { !editor.blockless && editor.contextMenu.addListener( function( element, selection, path ) { var form = path.contains( 'form', 1 ); if ( form && !form.isReadOnly() ) return { form: CKEDITOR.TRISTATE_OFF }; }); editor.contextMenu.addListener( function( element ) { if ( element && !element.isReadOnly() ) { var name = element.getName(); if ( name == 'select' ) return { select: CKEDITOR.TRISTATE_OFF }; if ( name == 'textarea' ) return { textarea: CKEDITOR.TRISTATE_OFF }; if ( name == 'input' ) { var type = element.getAttribute( 'type' ) || 'text'; switch ( type ) { case 'button': case 'submit': case 'reset': return { button: CKEDITOR.TRISTATE_OFF }; case 'checkbox': return { checkbox: CKEDITOR.TRISTATE_OFF }; case 'radio': return { radio: CKEDITOR.TRISTATE_OFF }; case 'image': return imagePlugin ? { imagebutton: CKEDITOR.TRISTATE_OFF } : null; } if ( textfieldTypes[ type ] ) return { textfield: CKEDITOR.TRISTATE_OFF }; } if ( name == 'img' && element.data( 'cke-real-element-type' ) == 'hiddenfield' ) return { hiddenfield: CKEDITOR.TRISTATE_OFF }; } }); } editor.on( 'doubleclick', function( evt ) { var element = evt.data.element; if ( !editor.blockless && element.is( 'form' ) ) evt.data.dialog = 'form'; else if ( element.is( 'select' ) ) evt.data.dialog = 'select'; else if ( element.is( 'textarea' ) ) evt.data.dialog = 'textarea'; else if ( element.is( 'img' ) && element.data( 'cke-real-element-type' ) == 'hiddenfield' ) evt.data.dialog = 'hiddenfield'; else if ( element.is( 'input' ) ) { var type = element.getAttribute( 'type' ) || 'text'; switch ( type ) { case 'button': case 'submit': case 'reset': evt.data.dialog = 'button'; break; case 'checkbox': evt.data.dialog = 'checkbox'; break; case 'radio': evt.data.dialog = 'radio'; break; case 'image': evt.data.dialog = 'imagebutton'; break; } if ( textfieldTypes[ type ] ) evt.data.dialog = 'textfield'; } }); }, afterInit: function( editor ) { var dataProcessor = editor.dataProcessor, htmlFilter = dataProcessor && dataProcessor.htmlFilter, dataFilter = dataProcessor && dataProcessor.dataFilter; // Cleanup certain IE form elements default values. if ( CKEDITOR.env.ie ) { htmlFilter && htmlFilter.addRules({ elements: { input: function( input ) { var attrs = input.attributes, type = attrs.type; // Old IEs don't provide type for Text inputs #5522 if ( !type ) attrs.type = 'text'; if ( type == 'checkbox' || type == 'radio' ) attrs.value == 'on' && delete attrs.value; } } }); } if ( dataFilter ) { dataFilter.addRules({ elements: { input: function( element ) { if ( element.attributes.type == 'hidden' ) return editor.createFakeParserElement( element, 'cke_hidden', 'hiddenfield' ); } } }); } } }); if ( CKEDITOR.env.ie ) { CKEDITOR.dom.element.prototype.hasAttribute = CKEDITOR.tools.override( CKEDITOR.dom.element.prototype.hasAttribute, function( original ) { return function( name ) { var $attr = this.$.attributes.getNamedItem( name ); if ( this.getName() == 'input' ) { switch ( name ) { case 'class': return this.$.className.length > 0; case 'checked': return !!this.$.checked; case 'value': var type = this.getAttribute( 'type' ); return type == 'checkbox' || type == 'radio' ? this.$.value != 'on' : this.$.value; } } return original.apply( this, arguments ); }; }); } /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'format', { requires: 'richcombo', init: function( editor ) { if ( editor.blockless ) return; var config = editor.config, lang = editor.lang.format; // Gets the list of tags from the settings. var tags = config.format_tags.split( ';' ); // Create style objects for all defined styles. var styles = {}; for ( var i = 0; i < tags.length; i++ ) { var tag = tags[ i ]; styles[ tag ] = new CKEDITOR.style( config[ 'format_' + tag ] ); styles[ tag ]._.enterMode = editor.config.enterMode; } editor.ui.addRichCombo( 'Format', { label: lang.label, title: lang.panelTitle, toolbar: 'styles,20', panel: { css: [ CKEDITOR.skin.getPath( 'editor' ) ].concat( config.contentsCss ), multiSelect: false, attributes: { 'aria-label': lang.panelTitle } }, init: function() { this.startGroup( lang.panelTitle ); for ( var tag in styles ) { var label = lang[ 'tag_' + tag ]; // Add the tag entry to the panel list. this.add( tag, styles[ tag ].buildPreview( label ), label ); } }, onClick: function( value ) { editor.focus(); editor.fire( 'saveSnapshot' ); var style = styles[ value ], elementPath = editor.elementPath(); editor[ style.checkActive( elementPath ) ? 'removeStyle' : 'applyStyle' ]( style ); // Save the undo snapshot after all changes are affected. (#4899) setTimeout( function() { editor.fire( 'saveSnapshot' ); }, 0 ); }, onRender: function() { editor.on( 'selectionChange', function( ev ) { var currentTag = this.getValue(), elementPath = ev.data.path, isEnabled = !editor.readOnly && elementPath.isContextFor( 'p' ); // Disable the command when selection path is "blockless". this[ isEnabled ? 'enable' : 'disable' ](); if ( isEnabled ) { for ( var tag in styles ) { if ( styles[ tag ].checkActive( elementPath ) ) { if ( tag != currentTag ) this.setValue( tag, editor.lang.format[ 'tag_' + tag ] ); return; } } // If no styles match, just empty it. this.setValue( '' ); } }, this ); } }); } }); /** * A list of semi colon separated style names (by default tags) representing * the style definition for each entry to be displayed in the Format combo in * the toolbar. Each entry must have its relative definition configuration in a * setting named `'format_(tagName)'`. For example, the `'p'` entry has its * definition taken from `config.format_p`. * * config.format_tags = 'p;h2;h3;pre'; * * @cfg {String} [format_tags='p;h1;h2;h3;h4;h5;h6;pre;address;div'] * @member CKEDITOR.config */ CKEDITOR.config.format_tags = 'p;h1;h2;h3;h4;h5;h6;pre;address;div'; /** * The style definition to be used to apply the `'Normal'` format. * * config.format_p = { element : 'p', attributes : { 'class' : 'normalPara' } }; * * @cfg {Object} [format_p={ element: 'p' }] * @member CKEDITOR.config */ CKEDITOR.config.format_p = { element: 'p' }; /** * The style definition to be used to apply the `'Normal (DIV)'` format. * * config.format_div = { element : 'div', attributes : { 'class' : 'normalDiv' } }; * * @cfg {Object} [format_div={ element: 'div' }] * @member CKEDITOR.config */ CKEDITOR.config.format_div = { element: 'div' }; /** * The style definition to be used to apply the `'Formatted'` format. * * config.format_pre = { element: 'pre', attributes: { 'class': 'code' } }; * * @cfg {Object} [format_pre={ element: 'pre' }] * @member CKEDITOR.config */ CKEDITOR.config.format_pre = { element: 'pre' }; /** * The style definition to be used to apply the `'Address'` format. * * config.format_address = { element: 'address', attributes: { 'class': 'styledAddress' } }; * * @cfg {Object} [format_address={ element: 'address' }] * @member CKEDITOR.config */ CKEDITOR.config.format_address = { element: 'address' }; /** * The style definition to be used to apply the ``'Heading 1'` format. * * config.format_h1 = { element: 'h1', attributes: { 'class': 'contentTitle1' } }; * * @cfg {Object} [format_h1={ element: 'h1' }] * @member CKEDITOR.config */ CKEDITOR.config.format_h1 = { element: 'h1' }; /** * The style definition to be used to apply the `'Heading 1'` format. * * config.format_h2 = { element: 'h2', attributes: { 'class': 'contentTitle2' } }; * * @cfg {Object} [format_h2={ element: 'h2' }] * @member CKEDITOR.config */ CKEDITOR.config.format_h2 = { element: 'h2' }; /** * The style definition to be used to apply the `'Heading 1'` format. * * config.format_h3 = { element: 'h3', attributes: { 'class': 'contentTitle3' } }; * * @cfg {Object} [format_h3={ element: 'h3' }] * @member CKEDITOR.config */ CKEDITOR.config.format_h3 = { element: 'h3' }; /** * The style definition to be used to apply the `'Heading 1'` format. * * config.format_h4 = { element: 'h4', attributes: { 'class': 'contentTitle4' } }; * * @cfg {Object} [format_h4={ element: 'h4' }] * @member CKEDITOR.config */ CKEDITOR.config.format_h4 = { element: 'h4' }; /** * The style definition to be used to apply the `'Heading 1'` format. * * config.format_h5 = { element: 'h5', attributes: { 'class': 'contentTitle5' } }; * * @cfg {Object} [format_h5={ element: 'h5' }] * @member CKEDITOR.config */ CKEDITOR.config.format_h5 = { element: 'h5' }; /** * The style definition to be used to apply the `'Heading 1'` format. * * config.format_h6 = { element: 'h6', attributes: { 'class': 'contentTitle6' } }; * * @cfg {Object} [format_h6={ element: 'h6' }] * @member CKEDITOR.config */ CKEDITOR.config.format_h6 = { element: 'h6' }; /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ CKEDITOR.plugins.add( 'htmlwriter', { init: function( editor ) { var writer = new CKEDITOR.htmlWriter(); writer.forceSimpleAmpersand = editor.config.forceSimpleAmpersand; writer.indentationChars = editor.config.dataIndentationChars || '\t'; // Overwrite default basicWriter initialized in hmtlDataProcessor constructor. editor.dataProcessor.writer = writer; } }); /** * Class used to write HTML data. * * var writer = new CKEDITOR.htmlWriter(); * writer.openTag( 'p' ); * writer.attribute( 'class', 'MyClass' ); * writer.openTagClose( 'p' ); * writer.text( 'Hello' ); * writer.closeTag( 'p' ); * alert( writer.getHtml() ); // '

    Hello

    ' * * @class * @extends CKEDITOR.htmlParser.basicWriter */ CKEDITOR.htmlWriter = CKEDITOR.tools.createClass({ base: CKEDITOR.htmlParser.basicWriter, /** * Creates a htmlWriter class instance. * * @constructor */ $: function() { // Call the base contructor. this.base(); /** * The characters to be used for each identation step. * * // Use tab for indentation. * editorInstance.dataProcessor.writer.indentationChars = '\t'; */ this.indentationChars = '\t'; /** * The characters to be used to close "self-closing" elements, like `
    ` or ``. * * // Use HTML4 notation for self-closing elements. * editorInstance.dataProcessor.writer.selfClosingEnd = '>'; */ this.selfClosingEnd = ' />'; /** * The characters to be used for line breaks. * * // Use CRLF for line breaks. * editorInstance.dataProcessor.writer.lineBreakChars = '\r\n'; */ this.lineBreakChars = '\n'; this.sortAttributes = 1; this._.indent = 0; this._.indentation = ''; // Indicate preformatted block context status. (#5789) this._.inPre = 0; this._.rules = {}; var dtd = CKEDITOR.dtd; for ( var e in CKEDITOR.tools.extend( {}, dtd.$nonBodyContent, dtd.$block, dtd.$listItem, dtd.$tableContent ) ) { this.setRules( e, { indent: !dtd[ e ][ '#' ], breakBeforeOpen: 1, breakBeforeClose: !dtd[ e ][ '#' ], breakAfterClose: 1, needsSpace: ( e in dtd.$block ) && !( e in { li:1,dt:1,dd:1 } ) }); } this.setRules( 'br', { breakAfterOpen:1 } ); this.setRules( 'title', { indent: 0, breakAfterOpen: 0 }); this.setRules( 'style', { indent: 0, breakBeforeClose: 1 }); this.setRules( 'pre', { breakAfterOpen: 1, // Keep line break after the opening tag indent: 0 // Disable indentation on
    .
    		});
    	},
    
    	proto: {
    		/**
    		 * Writes the tag opening part for a opener tag.
    		 *
    		 *		// Writes ''.
    		 *		writer.openTagClose( 'p', false );
    		 *
    		 *		// Writes ' />'.
    		 *		writer.openTagClose( 'br', true );
    		 *
    		 * @param {String} tagName The element name for this tag.
    		 * @param {Boolean} isSelfClose Indicates that this is a self-closing tag,
    		 * like `
    ` or ``. */ openTagClose: function( tagName, isSelfClose ) { var rules = this._.rules[ tagName ]; if ( isSelfClose ) { this._.output.push( this.selfClosingEnd ); if ( rules && rules.breakAfterClose ) this._.needsSpace = rules.needsSpace; } else { this._.output.push( '>' ); if ( rules && rules.indent ) this._.indentation += this.indentationChars; } if ( rules && rules.breakAfterOpen ) this.lineBreak(); tagName == 'pre' && ( this._.inPre = 1 ); }, /** * Writes an attribute. This function should be called after opening the * tag with {@link #openTagClose}. * * // Writes ' class="MyClass"'. * writer.attribute( 'class', 'MyClass' ); * * @param {String} attName The attribute name. * @param {String} attValue The attribute value. */ attribute: function( attName, attValue ) { if ( typeof attValue == 'string' ) { this.forceSimpleAmpersand && ( attValue = attValue.replace( /&/g, '&' ) ); // Browsers don't always escape special character in attribute values. (#4683, #4719). attValue = CKEDITOR.tools.htmlEncodeAttr( attValue ); } this._.output.push( ' ', attName, '="', attValue, '"' ); }, /** * Writes a closer tag. * * // Writes '

    '. * writer.closeTag( 'p' ); * * @param {String} tagName The element name for this tag. */ closeTag: function( tagName ) { var rules = this._.rules[ tagName ]; if ( rules && rules.indent ) this._.indentation = this._.indentation.substr( this.indentationChars.length ); if ( this._.indent ) this.indentation(); // Do not break if indenting. else if ( rules && rules.breakBeforeClose ) { this.lineBreak(); this.indentation(); } this._.output.push( '' ); tagName == 'pre' && ( this._.inPre = 0 ); if ( rules && rules.breakAfterClose ) { this.lineBreak(); this._.needsSpace = rules.needsSpace; } this._.afterCloser = 1; }, /** * Writes text. * * // Writes 'Hello Word'. * writer.text( 'Hello Word' ); * * @param {String} text The text value */ text: function( text ) { if ( this._.indent ) { this.indentation(); !this._.inPre && ( text = CKEDITOR.tools.ltrim( text ) ); } this._.output.push( text ); }, /** * Writes a comment. * * // Writes "". * writer.comment( ' My comment ' ); * * @param {String} comment The comment text. */ comment: function( comment ) { if ( this._.indent ) this.indentation(); this._.output.push( '' ); }, /** * Writes a line break. It uses the {@link #lineBreakChars} property for it. * * // Writes '\n' (e.g.). * writer.lineBreak(); */ lineBreak: function() { if ( !this._.inPre && this._.output.length > 0 ) this._.output.push( this.lineBreakChars ); this._.indent = 1; }, /** * Writes the current indentation chars. It uses the {@link #indentationChars} * property, repeating it for the current indentation steps. * * // Writes '\t' (e.g.). * writer.indentation(); */ indentation: function() { if ( !this._.inPre && this._.indentation ) this._.output.push( this._.indentation ); this._.indent = 0; }, /** * Empties the current output buffer. It also brings back the default * values of the writer flags. * * writer.reset(); */ reset: function() { this._.output = []; this._.indent = 0; this._.indentation = ''; this._.afterCloser = 0; this._.inPre = 0; }, /** * Sets formatting rules for a give element. The possible rules are: * * * `indent`: indent the element contents. * * `breakBeforeOpen`: break line before the opener tag for this element. * * `breakAfterOpen`: break line after the opener tag for this element. * * `breakBeforeClose`: break line before the closer tag for this element. * * `breakAfterClose`: break line after the closer tag for this element. * * All rules default to `false`. Each call to the function overrides * already present rules, leaving the undefined untouched. * * By default, all elements available in the {@link CKEDITOR.dtd#$block}, * {@link CKEDITOR.dtd#$listItem} and {@link CKEDITOR.dtd#$tableContent} * lists have all the above rules set to `true`. Additionaly, the `
    ` * element has the `breakAfterOpen` set to `true`. * * // Break line before and after "img" tags. * writer.setRules( 'img', { * breakBeforeOpen: true * breakAfterOpen: true * } ); * * // Reset the rules for the "h1" tag. * writer.setRules( 'h1', {} ); * * @param {String} tagName The element name to which set the rules. * @param {Object} rules An object containing the element rules. */ setRules: function( tagName, rules ) { var currentRules = this._.rules[ tagName ]; if ( currentRules ) CKEDITOR.tools.extend( currentRules, rules, true ); else this._.rules[ tagName ] = rules; } } }); /** * Whether to force using `'&'` instead of `'&'` in elements attributes * values, it's not recommended to change this setting for compliance with the * W3C XHTML 1.0 standards ([C.12, XHTML 1.0](http://www.w3.org/TR/xhtml1/#C_12)). * * // Use `'&'` instead of `'&'` * CKEDITOR.config.forceSimpleAmpersand = true; * * @cfg {Boolean} [forceSimpleAmpersand=false] * @member CKEDITOR.config */ /** * The characters to be used for indenting the HTML produced by the editor. * Using characters different than `' '` (space) and `'\t'` (tab) is definitely * a bad idea as it'll mess the code. * * // No indentation. * CKEDITOR.config.dataIndentationChars = ''; * * // Use two spaces for indentation. * CKEDITOR.config.dataIndentationChars = ' '; * * @cfg {String} [dataIndentationChars='\t'] * @member CKEDITOR.config */ /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview Horizontal Rule plugin. */ (function() { var horizontalruleCmd = { canUndo: false, // The undo snapshot will be handled by 'insertElement'. exec: function( editor ) { var hr = editor.document.createElement( 'hr' ); editor.insertElement( hr ); } }; var pluginName = 'horizontalrule'; // Register a plugin named "horizontalrule". CKEDITOR.plugins.add( pluginName, { init: function( editor ) { if ( editor.blockless ) return; editor.addCommand( pluginName, horizontalruleCmd ); editor.ui.addButton && editor.ui.addButton( 'HorizontalRule', { label: editor.lang.horizontalrule.toolbar, command: pluginName, toolbar: 'insert,40' }); } }); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ (function() { CKEDITOR.plugins.add( 'iframe', { requires: 'dialog,fakeobjects', onLoad: function() { CKEDITOR.addCss( 'img.cke_iframe' + '{' + 'background-image: url(' + CKEDITOR.getUrl( this.path + 'images/placeholder.png' ) + ');' + 'background-position: center center;' + 'background-repeat: no-repeat;' + 'border: 1px solid #a9a9a9;' + 'width: 80px;' + 'height: 80px;' + '}' ); }, init: function( editor ) { var pluginName = 'iframe', lang = editor.lang.iframe; CKEDITOR.dialog.add( pluginName, this.path + 'dialogs/iframe.js' ); editor.addCommand( pluginName, new CKEDITOR.dialogCommand( pluginName ) ); editor.ui.addButton && editor.ui.addButton( 'Iframe', { label: lang.toolbar, command: pluginName, toolbar: 'insert,80' }); editor.on( 'doubleclick', function( evt ) { var element = evt.data.element; if ( element.is( 'img' ) && element.data( 'cke-real-element-type' ) == 'iframe' ) evt.data.dialog = 'iframe'; }); if ( editor.addMenuItems ) { editor.addMenuItems({ iframe: { label: lang.title, command: 'iframe', group: 'image' } }); } // If the "contextmenu" plugin is loaded, register the listeners. if ( editor.contextMenu ) { editor.contextMenu.addListener( function( element, selection ) { if ( element && element.is( 'img' ) && element.data( 'cke-real-element-type' ) == 'iframe' ) return { iframe: CKEDITOR.TRISTATE_OFF }; }); } }, afterInit: function( editor ) { var dataProcessor = editor.dataProcessor, dataFilter = dataProcessor && dataProcessor.dataFilter; if ( dataFilter ) { dataFilter.addRules({ elements: { iframe: function( element ) { return editor.createFakeParserElement( element, 'cke_iframe', 'iframe', true ); } } }); } } }); })(); /** * Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.html or http://ckeditor.com/license */ /** * @fileOverview The "wysiwygarea" plugin. It registers the "wysiwyg" editing * mode, which handles the main editing area space. */ (function() { CKEDITOR.plugins.add( 'wysiwygarea', { init: function( editor ) { editor.addMode( 'wysiwyg', function( callback ) { var iframe = CKEDITOR.document.createElement( 'iframe' ); iframe.setStyles({ width: '100%', height: '100%' } ); iframe.addClass( 'cke_wysiwyg_frame cke_reset' ); var contentSpace = editor.ui.space( 'contents' ); contentSpace.append( iframe ); var src = 'document.open();' + // The document domain must be set any time we // call document.open(). ( isCustomDomain ? ( 'document.domain="' + document.domain + '";' ) : '' ) + 'document.close();'; // With IE, the custom domain has to be taken care at first, // for other browers, the 'src' attribute should be left empty to // trigger iframe's 'load' event. src = CKEDITOR.env.air ? 'javascript:void(0)' : CKEDITOR.env.ie ? 'javascript:void(function(){' + encodeURIComponent( src ) + '}())' : ''; // Asynchronous iframe loading is only required in IE>8 and Gecko (other reasons probably). // Do not use it on WebKit as it'll break the browser-back navigation. var useOnloadEvent = CKEDITOR.env.ie || CKEDITOR.env.gecko; if ( useOnloadEvent ) iframe.on( 'load', onLoad ); var frameLabel = [ editor.lang.editor, editor.name ].join( ',' ), frameDesc = editor.lang.common.editorHelp; if ( CKEDITOR.env.ie ) frameLabel += ', ' + frameDesc; var labelId = CKEDITOR.tools.getNextId(), desc = CKEDITOR.dom.element.createFromHtml( '' + frameDesc + '' ); contentSpace.append( desc, 1 ); // Remove the ARIA description. editor.on( 'beforeModeUnload', function( evt ) { evt.removeListener(); desc.remove(); }); iframe.setAttributes({ frameBorder: 0, 'aria-describedby' : labelId, title: frameLabel, src: src, tabIndex: editor.tabIndex, allowTransparency: 'true' }); // Execute onLoad manually for all non IE||Gecko browsers. !useOnloadEvent && onLoad(); if ( CKEDITOR.env.webkit ) { // Webkit: iframe size doesn't auto fit well. (#7360) var onResize = function() { // Hide the iframe to get real size of the holder. (#8941) contentSpace.setStyle( 'width', '100%' ); iframe.hide(); iframe.setSize( 'width', contentSpace.getSize( 'width' ) ); contentSpace.removeStyle( 'width' ); iframe.show(); }; iframe.setCustomData( 'onResize', onResize ); CKEDITOR.document.getWindow().on( 'resize', onResize ); } editor.fire( 'ariaWidget', iframe ); function onLoad( evt ) { evt && evt.removeListener(); editor.editable( new framedWysiwyg( editor, iframe.$.contentWindow.document.body ) ); editor.setData( editor.getData( 1 ), callback ); } }); } }); // Support for custom document.domain in IE. var isCustomDomain = CKEDITOR.env.isCustomDomain(); function onDomReady( win ) { var editor = this.editor, doc = win.document, body = doc.body; // Remove helper scripts from the DOM. var script = doc.getElementById( 'cke_actscrpt' ); script && script.parentNode.removeChild( script ); script = doc.getElementById( 'cke_shimscrpt' ); script && script.parentNode.removeChild( script ); if ( CKEDITOR.env.gecko ) { // Force Gecko to change contentEditable from false to true on domReady // (because it's previously set to true on iframe's body creation). // Otherwise del/backspace and some other editable features will be broken in Fx <4 // See: #107 and https://bugzilla.mozilla.org/show_bug.cgi?id=440916 body.contentEditable = false; // Remove any leading
    which is between the and the comment. // This one fixes Firefox 3.6 bug: the browser inserts a leading
    // on document.write if the body has contenteditable="true". if ( CKEDITOR.env.version < 20000 ) { body.innerHTML = body.innerHTML.replace( /^.*/, '' ); // The above hack messes up the selection in FF36. // To clean this up, manually select collapsed range that // starts within the body. setTimeout( function() { var range = new CKEDITOR.dom.range( new CKEDITOR.dom.document( doc ) ); range.setStart( new CKEDITOR.dom.node( body ), 0 ); editor.getSelection().selectRanges( [ range ] ); }, 0 ); } } body.contentEditable = true; if ( CKEDITOR.env.ie ) { // Don't display the focus border. body.hideFocus = true; // Disable and re-enable the body to avoid IE from // taking the editing focus at startup. (#141 / #523) body.disabled = true; body.removeAttribute( 'disabled' ); } delete this._.isLoadingData; // Play the magic to alter element reference to the reloaded one. this.$ = body; doc = new CKEDITOR.dom.document( doc ); this.setup(); if ( CKEDITOR.env.ie ) { doc.getDocumentElement().addClass( doc.$.compatMode ); // Prevent IE from leaving new paragraph after deleting all contents in body. (#6966) editor.config.enterMode != CKEDITOR.ENTER_P && doc.on( 'selectionchange', function() { var body = doc.getBody(), sel = editor.getSelection(), range = sel && sel.getRanges()[ 0 ]; if ( range && body.getHtml().match( /^

     <\/p>$/i ) && range.startContainer.equals( body ) ) { // Avoid the ambiguity from a real user cursor position. setTimeout( function() { range = editor.getSelection().getRanges()[ 0 ]; if ( !range.startContainer.equals( 'body' ) ) { body.getFirst().remove( 1 ); range.moveToElementEditEnd( body ); range.select(); } }, 0 ); } }); } // Gecko needs a key event to 'wake up' editing when the document is // empty. (#3864, #5781) CKEDITOR.env.gecko && CKEDITOR.tools.setTimeout( activateEditing, 0, this, editor ); // ## START : disableNativeTableHandles and disableObjectResizing settings. // Enable dragging of position:absolute elements in IE. try { editor.document.$.execCommand( '2D-position', false, true ); } catch ( e ) {} // IE, Opera and Safari may not support it and throw errors. try { editor.document.$.execCommand( 'enableInlineTableEditing', false, !editor.config.disableNativeTableHandles ); } catch ( e ) {} if ( editor.config.disableObjectResizing ) { try { this.getDocument().$.execCommand( 'enableObjectResizing', false, false ); } catch ( e ) { // For browsers in which the above method failed, we can cancel the resizing on the fly (#4208) this.attachListener( this, CKEDITOR.env.ie ? 'resizestart' : 'resize', function( evt ) { evt.data.preventDefault(); }); } } if ( CKEDITOR.env.gecko || CKEDITOR.env.ie && editor.document.$.compatMode == 'CSS1Compat' ) { this.attachListener( this, 'keydown', function( evt ) { var keyCode = evt.data.getKeystroke(); // PageUp OR PageDown if ( keyCode == 33 || keyCode == 34 ) { // PageUp/PageDown scrolling is broken in document // with standard doctype, manually fix it. (#4736) if ( CKEDITOR.env.ie ) { setTimeout( function() { editor.getSelection().scrollIntoView(); }, 0 ); } // Page up/down cause editor selection to leak // outside of editable thus we try to intercept // the behavior, while it affects only happen // when editor contents are not overflowed. (#7955) else if ( editor.window.$.innerHeight > this.$.offsetHeight ) { var range = editor.createRange(); range[ keyCode == 33 ? 'moveToElementEditStart' : 'moveToElementEditEnd' ]( this ); range.select(); evt.data.preventDefault(); } } }); } if ( CKEDITOR.env.ie ) { // [IE] Iframe will still keep the selection when blurred, if // focus is moved onto a non-editing host, e.g. link or button, but // it becomes a problem for the object type selection, since the resizer // handler attached on it will mark other part of the UI, especially // for the dialog. (#8157) // [IE<8 & Opera] Even worse For old IEs, the cursor will not vanish even if // the selection has been moved to another text input in some cases. (#4716) // // Now the range restore is disabled, so we simply force IE to clean // up the selection before blur. this.attachListener( doc, 'blur', function() { // Error proof when the editor is not visible. (#6375) try { doc.$.selection.empty(); } catch ( er ) {} }); } // ## END var title = editor.document.getElementsByTag( 'title' ).getItem( 0 ); title.data( 'cke-title', editor.document.$.title ); // [IE] JAWS will not recognize the aria label we used on the iframe // unless the frame window title string is used as the voice label, // backup the original one and restore it on output. if ( CKEDITOR.env.ie ) editor.document.$.title = this._.docTitle; CKEDITOR.tools.setTimeout( function() { editor.fire( 'contentDom' ); if ( this._.isPendingFocus ) { editor.focus(); this._.isPendingFocus = false; } setTimeout( function() { editor.fire( 'dataReady' ); }, 0 ); // IE BUG: IE might have rendered the iframe with invisible contents. // (#3623). Push some inconsequential CSS style changes to force IE to // refresh it. // // Also, for some unknown reasons, short timeouts (e.g. 100ms) do not // fix the problem. :( if ( CKEDITOR.env.ie ) { setTimeout( function() { if ( editor.document ) { var $body = editor.document.$.body; $body.runtimeStyle.marginBottom = '0px'; $body.runtimeStyle.marginBottom = ''; } }, 1000 ); } }, 0, this ); } var framedWysiwyg = CKEDITOR.tools.createClass({ $: function( editor ) { this.base.apply( this, arguments ); this._.frameLoadedHandler = CKEDITOR.tools.addFunction( function( win ) { // Avoid opening design mode in a frame window thread, // which will cause host page scrolling.(#4397) CKEDITOR.tools.setTimeout( onDomReady, 0, this, win ); }, this ); this._.docTitle = this.getWindow().getFrame().getAttribute( 'title' ); }, base: CKEDITOR.editable, proto: { setData: function( data, isSnapshot ) { var editor = this.editor; if ( isSnapshot ) this.setHtml( data ); else { this._.isLoadingData = true; editor._.dataStore = { id:1 }; var config = editor.config, fullPage = config.fullPage, docType = config.docType; // Build the additional stuff to be included into . var headExtra = CKEDITOR.tools.buildStyleHtml( iframeCssFixes() ) .replace( /