diff --git a/lam/templates/lib/extra/ckeditor/core/_bootstrap.js b/lam/templates/lib/extra/ckeditor/core/_bootstrap.js new file mode 100644 index 00000000..d6802af5 --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/_bootstrap.js @@ -0,0 +1,74 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview API initialization code. + */ + +( function() { + // Disable HC detection in WebKit. (#5429) + if ( CKEDITOR.env.webkit ) + CKEDITOR.env.hc = false; + else { + // Check whether high contrast is active by creating a colored border. + var hcDetect = CKEDITOR.dom.element.createFromHtml( '
', CKEDITOR.document ); + + hcDetect.appendTo( CKEDITOR.document.getHead() ); + + // Update CKEDITOR.env. + // Catch exception needed sometimes for FF. (#4230) + try { + var top = hcDetect.getComputedStyle( 'border-top-color' ), + right = hcDetect.getComputedStyle( 'border-right-color' ); + + // We need to check if getComputedStyle returned any value, because on FF + // it returnes empty string if CKEditor is loaded in hidden iframe. (#11121) + CKEDITOR.env.hc = !!( top && top == right ); + } catch ( e ) { + CKEDITOR.env.hc = false; + } + + hcDetect.remove(); + } + + if ( CKEDITOR.env.hc ) + CKEDITOR.env.cssClass += ' cke_hc'; + + // Initially hide UI spaces when relevant skins are loading, later restored by skin css. + CKEDITOR.document.appendStyleText( '.cke{visibility:hidden;}' ); + + // Mark the editor as fully loaded. + CKEDITOR.status = 'loaded'; + CKEDITOR.fireOnce( 'loaded' ); + + // Process all instances created by the "basic" implementation. + var pending = CKEDITOR._.pending; + if ( pending ) { + delete CKEDITOR._.pending; + + for ( var i = 0; i < pending.length; i++ ) { + CKEDITOR.editor.prototype.constructor.apply( pending[ i ][ 0 ], pending[ i ][ 1 ] ); + CKEDITOR.add( pending[ i ][ 0 ] ); + } + } +} )(); + +/** + * Indicates that CKEditor is running on a High Contrast environment. + * + * if ( CKEDITOR.env.hc ) + * alert( 'You\'re running on High Contrast mode. The editor interface will get adapted to provide you a better experience.' ); + * + * @property {Boolean} hc + * @member CKEDITOR.env + */ + +/** + * Fired when a CKEDITOR core object is fully loaded and ready for interaction. + * + * @event loaded + * @member CKEDITOR + */ diff --git a/lam/templates/lib/extra/ckeditor/core/ckeditor.js b/lam/templates/lib/extra/ckeditor/core/ckeditor.js new file mode 100644 index 00000000..aaa37d35 --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/ckeditor.js @@ -0,0 +1,204 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview Contains the third and last part of the {@link CKEDITOR} object + * definition. + */ + +/** @class CKEDITOR */ + +// Remove the CKEDITOR.loadFullCore reference defined on ckeditor_basic. +delete CKEDITOR.loadFullCore; + +/** + * Stores references to all editor instances created. The name of the properties + * in this object correspond to instance names, and their values contain the + * {@link CKEDITOR.editor} object representing them. + * + * alert( CKEDITOR.instances.editor1.name ); // 'editor1' + * + * @property {Object} + */ +CKEDITOR.instances = {}; + +/** + * The document of the window storing the CKEDITOR object. + * + * alert( CKEDITOR.document.getBody().getName() ); // 'body' + * + * @property {CKEDITOR.dom.document} + */ +CKEDITOR.document = new CKEDITOR.dom.document( document ); + +/** + * Adds an editor instance to the global {@link CKEDITOR} object. This function + * is available for internal use mainly. + * + * @param {CKEDITOR.editor} editor The editor instance to be added. + */ +CKEDITOR.add = function( editor ) { + CKEDITOR.instances[ editor.name ] = editor; + + editor.on( 'focus', function() { + if ( CKEDITOR.currentInstance != editor ) { + CKEDITOR.currentInstance = editor; + CKEDITOR.fire( 'currentInstance' ); + } + } ); + + editor.on( 'blur', function() { + if ( CKEDITOR.currentInstance == editor ) { + CKEDITOR.currentInstance = null; + CKEDITOR.fire( 'currentInstance' ); + } + } ); + + CKEDITOR.fire( 'instance', null, editor ); +}; + +/** + * Removes an editor instance from the global {@link CKEDITOR} object. This function + * is available for internal use only. External code must use {@link CKEDITOR.editor#method-destroy}. + * + * @private + * @param {CKEDITOR.editor} editor The editor instance to be removed. + */ +CKEDITOR.remove = function( editor ) { + delete CKEDITOR.instances[ editor.name ]; +}; + +( function() { + var tpls = {}; + + /** + * Adds a named {@link CKEDITOR.template} instance to be reused among all editors. + * This will return the existing one if a template with same name is already + * defined. Additionally, it fires the "template" event to allow template source customization. + * + * @param {String} name The name which identifies a UI template. + * @param {String} source The source string for constructing this template. + * @returns {CKEDITOR.template} The created template instance. + */ + CKEDITOR.addTemplate = function( name, source ) { + var tpl = tpls[ name ]; + if ( tpl ) + return tpl; + + // Make it possible to customize the template through event. + var params = { name: name, source: source }; + CKEDITOR.fire( 'template', params ); + + return ( tpls[ name ] = new CKEDITOR.template( params.source ) ); + }; + + /** + * Retrieves a defined template created with {@link CKEDITOR#addTemplate}. + * + * @param {String} name The template name. + */ + CKEDITOR.getTemplate = function( name ) { + return tpls[ name ]; + }; +} )(); + +( function() { + var styles = []; + + /** + * Adds CSS rules to be appended to the editor document. + * This method is mostly used by plugins to add custom styles to the editor + * document. For basic content styling the `contents.css` file should be + * used instead. + * + * **Note:** This function should be called before the creation of editor instances. + * + * // Add styles for all headings inside editable contents. + * CKEDITOR.addCss( '.cke_editable h1,.cke_editable h2,.cke_editable h3 { border-bottom: 1px dotted red }' ); + * + * @param {String} css The style rules to be appended. + * @see CKEDITOR.config#contentsCss + */ + CKEDITOR.addCss = function( css ) { + styles.push( css ); + }; + + /** + * Returns a string will all CSS rules passed to the {@link CKEDITOR#addCss} method. + * + * @returns {String} A string containing CSS rules. + */ + CKEDITOR.getCss = function() { + return styles.join( '\n' ); + }; +} )(); + +// Perform global clean up to free as much memory as possible +// when there are no instances left +CKEDITOR.on( 'instanceDestroyed', function() { + if ( CKEDITOR.tools.isEmpty( this.instances ) ) + CKEDITOR.fire( 'reset' ); +} ); + +// Load the bootstrap script. +CKEDITOR.loader.load( '_bootstrap' ); // %REMOVE_LINE% + +// Tri-state constants. +/** + * Used to indicate the ON or ACTIVE state. + * + * @readonly + * @property {Number} [=1] + */ +CKEDITOR.TRISTATE_ON = 1; + +/** + * Used to indicate the OFF or INACTIVE state. + * + * @readonly + * @property {Number} [=2] + */ +CKEDITOR.TRISTATE_OFF = 2; + +/** + * Used to indicate the DISABLED state. + * + * @readonly + * @property {Number} [=0] + */ +CKEDITOR.TRISTATE_DISABLED = 0; + +/** + * The editor which is currently active (has user focus). + * + * function showCurrentEditorName() { + * if ( CKEDITOR.currentInstance ) + * alert( CKEDITOR.currentInstance.name ); + * else + * alert( 'Please focus an editor first.' ); + * } + * + * @property {CKEDITOR.editor} currentInstance + * @see CKEDITOR#event-currentInstance + */ + +/** + * Fired when the CKEDITOR.currentInstance object reference changes. This may + * happen when setting the focus on different editor instances in the page. + * + * var editor; // A variable to store a reference to the current editor. + * CKEDITOR.on( 'currentInstance', function() { + * editor = CKEDITOR.currentInstance; + * } ); + * + * @event currentInstance + */ + +/** + * Fired when the last instance has been destroyed. This event is used to perform + * global memory cleanup. + * + * @event reset + */ diff --git a/lam/templates/lib/extra/ckeditor/core/ckeditor_base.js b/lam/templates/lib/extra/ckeditor/core/ckeditor_base.js new file mode 100644 index 00000000..06979317 --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/ckeditor_base.js @@ -0,0 +1,315 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md 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.js file. + +// window.CKEDITOR||(window.CKEDITOR=function(){var b={timestamp:"",version:"%VERSION%",revision:"%REV%",rnd:Math.floor(900*Math.random())+100,_:{pending:[]},status:"unloaded",basePath:function(){var a=window.CKEDITOR_BASEPATH||"";if(!a)for(var b=document.getElementsByTagName("script"),c=0;c 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 && path.slice( 0, 2 ) != '//' ) { + // 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 ) diff --git a/lam/templates/lib/extra/ckeditor/core/ckeditor_basic.js b/lam/templates/lib/extra/ckeditor/core/ckeditor_basic.js new file mode 100644 index 00000000..efdb2456 --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/ckeditor_basic.js @@ -0,0 +1,94 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md 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'; + script.src = CKEDITOR.basePath + 'ckeditor_source.js'; // %REMOVE_LINE% + + 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'; + } )(); +} diff --git a/lam/templates/lib/extra/ckeditor/core/command.js b/lam/templates/lib/extra/ckeditor/core/command.js new file mode 100644 index 00000000..e9c98863 --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/command.js @@ -0,0 +1,271 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * Represents a command that can be executed on an editor instance. + * + * var command = new CKEDITOR.command( editor, { + * exec: function( editor ) { + * alert( editor.document.getBody().getHtml() ); + * } + * } ); + * + * @class + * @mixins CKEDITOR.event + * @constructor Creates a command class instance. + * @param {CKEDITOR.editor} editor The editor instance this command will be + * related to. + * @param {CKEDITOR.commandDefinition} commandDefinition The command + * definition. + */ +CKEDITOR.command = function( editor, commandDefinition ) { + /** + * Lists UI items that are associated to this command. This list can be + * used to interact with the UI on command execution (by the execution code + * itself, for example). + * + * alert( 'Number of UI items associated to this command: ' + command.uiItems.length ); + */ + this.uiItems = []; + + /** + * Executes the command. + * + * command.exec(); // The command gets executed. + * + * @param {Object} [data] Any data to pass to the command. Depends on the + * command implementation and requirements. + * @returns {Boolean} A boolean indicating that the command has been successfully executed. + */ + this.exec = function( data ) { + if ( this.state == CKEDITOR.TRISTATE_DISABLED || !this.checkAllowed() ) + return false; + + if ( this.editorFocus ) // Give editor focus if necessary (#4355). + editor.focus(); + + if ( this.fire( 'exec' ) === false ) + return true; + + return ( commandDefinition.exec.call( this, editor, data ) !== false ); + }; + + /** + * Explicitly update the status of the command, by firing the {@link CKEDITOR.command#event-refresh} event, + * as well as invoke the {@link CKEDITOR.commandDefinition#refresh} method if defined, this method + * is to allow different parts of the editor code to contribute in command status resolution. + * + * @param {CKEDITOR.editor} editor The editor instance. + * @param {CKEDITOR.dom.elementPath} path + */ + this.refresh = function( editor, path ) { + // Do nothing is we're on read-only and this command doesn't support it. + // We don't need to disabled the command explicitely here, because this + // is already done by the "readOnly" event listener. + if ( !this.readOnly && editor.readOnly ) + return true; + + // Disable commands that are not allowed in the current selection path context. + if ( this.context && !path.isContextFor( this.context ) ) { + this.disable(); + return true; + } + + // Disable commands that are not allowed by the active filter. + if ( !this.checkAllowed( true ) ) { + this.disable(); + return true; + } + + // Make the "enabled" state a default for commands enabled from start. + if ( !this.startDisabled ) + this.enable(); + + // Disable commands which shouldn't be enabled in this mode. + if ( this.modes && !this.modes[ editor.mode ] ) + this.disable(); + + if ( this.fire( 'refresh', { editor: editor, path: path } ) === false ) + return true; + + return ( commandDefinition.refresh && commandDefinition.refresh.apply( this, arguments ) !== false ); + }; + + var allowed; + + /** + * Checks whether this command is allowed by the active allowed + * content filter ({@link CKEDITOR.editor#activeFilter}). This means + * that if command implements {@link CKEDITOR.feature} interface it will be tested + * by the {@link CKEDITOR.filter#checkFeature} method. + * + * @since 4.1 + * @param {Boolean} [noCache] Skip cache for example due to active filter change. Since CKEditor 4.2. + * @returns {Boolean} Whether this command is allowed. + */ + this.checkAllowed = function( noCache ) { + if ( !noCache && typeof allowed == 'boolean' ) + return allowed; + + return allowed = editor.activeFilter.checkFeature( this ); + }; + + CKEDITOR.tools.extend( this, commandDefinition, { + /** + * The editor modes within which the command can be executed. The + * execution will have no action if the current mode is not listed + * in this property. + * + * // Enable the command in both WYSIWYG and Source modes. + * command.modes = { wysiwyg:1,source:1 }; + * + * // Enable the command in Source mode only. + * command.modes = { source:1 }; + * + * @see CKEDITOR.editor#mode + */ + modes: { wysiwyg: 1 }, + + /** + * Indicates that the editor will get the focus before executing + * the command. + * + * // Do not force the editor to have focus when executing the command. + * command.editorFocus = false; + * + * @property {Boolean} [=true] + */ + editorFocus: 1, + + /** + * Indicates that this command is sensible to the selection context. + * If `true`, the {@link CKEDITOR.command#method-refresh} method will be + * called for this command on the {@link CKEDITOR.editor#event-selectionChange} event. + * + * @property {Boolean} [=false] + */ + contextSensitive: !!commandDefinition.context, + + /** + * Indicates the editor state. Possible values are: + * + * * {@link CKEDITOR#TRISTATE_DISABLED}: the command is + * disabled. It's execution will have no effect. Same as {@link #disable}. + * * {@link CKEDITOR#TRISTATE_ON}: the command is enabled + * and currently active in the editor (for context sensitive commands, for example). + * * {@link CKEDITOR#TRISTATE_OFF}: the command is enabled + * and currently inactive in the editor (for context sensitive commands, for example). + * + * Do not set this property directly, using the {@link #setState} method instead. + * + * if ( command.state == CKEDITOR.TRISTATE_DISABLED ) + * alert( 'This command is disabled' ); + * + * @property {Number} [=CKEDITOR.TRISTATE_DISABLED] + */ + state: CKEDITOR.TRISTATE_DISABLED + } ); + + // Call the CKEDITOR.event constructor to initialize this instance. + CKEDITOR.event.call( this ); +}; + +CKEDITOR.command.prototype = { + /** + * Enables the command for execution. The command state (see + * {@link CKEDITOR.command#property-state}) available before disabling it is restored. + * + * command.enable(); + * command.exec(); // Execute the command. + */ + enable: function() { + if ( this.state == CKEDITOR.TRISTATE_DISABLED && this.checkAllowed() ) + this.setState( ( !this.preserveState || ( typeof this.previousState == 'undefined' ) ) ? CKEDITOR.TRISTATE_OFF : this.previousState ); + }, + + /** + * Disables the command for execution. The command state (see + * {@link CKEDITOR.command#property-state}) will be set to {@link CKEDITOR#TRISTATE_DISABLED}. + * + * command.disable(); + * command.exec(); // "false" - Nothing happens. + */ + disable: function() { + this.setState( CKEDITOR.TRISTATE_DISABLED ); + }, + + /** + * Sets the command state. + * + * command.setState( CKEDITOR.TRISTATE_ON ); + * command.exec(); // Execute the command. + * command.setState( CKEDITOR.TRISTATE_DISABLED ); + * command.exec(); // 'false' - Nothing happens. + * command.setState( CKEDITOR.TRISTATE_OFF ); + * command.exec(); // Execute the command. + * + * @param {Number} newState The new state. See {@link #property-state}. + * @returns {Boolean} Returns `true` if the command state changed. + */ + setState: function( newState ) { + // Do nothing if there is no state change. + if ( this.state == newState ) + return false; + + if ( newState != CKEDITOR.TRISTATE_DISABLED && !this.checkAllowed() ) + return false; + + this.previousState = this.state; + + // Set the new state. + this.state = newState; + + // Fire the "state" event, so other parts of the code can react to the + // change. + this.fire( 'state' ); + + return true; + }, + + /** + * Toggles the on/off (active/inactive) state of the command. This is + * mainly used internally by context sensitive commands. + * + * command.toggleState(); + */ + toggleState: function() { + if ( this.state == CKEDITOR.TRISTATE_OFF ) + this.setState( CKEDITOR.TRISTATE_ON ); + else if ( this.state == CKEDITOR.TRISTATE_ON ) + this.setState( CKEDITOR.TRISTATE_OFF ); + } +}; + +CKEDITOR.event.implementOn( CKEDITOR.command.prototype ); + +/** + * Indicates the previous command state. + * + * alert( command.previousState ); + * + * @property {Number} previousState + * @see #state + */ + +/** + * Fired when the command state changes. + * + * command.on( 'state', function() { + * // Alerts the new state. + * alert( this.state ); + * } ); + * + * @event state + */ + + /** + * @event refresh + * @todo + */ diff --git a/lam/templates/lib/extra/ckeditor/core/commanddefinition.js b/lam/templates/lib/extra/ckeditor/core/commanddefinition.js new file mode 100644 index 00000000..18609bc0 --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/commanddefinition.js @@ -0,0 +1,139 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview Defines the "virtual" {@link CKEDITOR.commandDefinition} class, + * which contains the defintion of a command. This file is for + * documentation purposes only. + */ + +/** + * Virtual class that illustrates the features of command objects to be + * passed to the {@link CKEDITOR.editor#addCommand} function. + * + * @class CKEDITOR.commandDefinition + * @abstract + */ + +/** + * The function to be fired when the commend is executed. + * + * editorInstance.addCommand( 'sample', { + * exec: function( editor ) { + * alert( 'Executing a command for the editor name "' + editor.name + '"!' ); + * } + * } ); + * + * @method exec + * @param {CKEDITOR.editor} editor The editor within which run the command. + * @param {Object} [data] Additional data to be used to execute the command. + * @returns {Boolean} Whether the command has been successfully executed. + * Defaults to `true`, if nothing is returned. + */ + +/** + * Whether the command need to be hooked into the redo/undo system. + * + * editorInstance.addCommand( 'alertName', { + * exec: function( editor ) { + * alert( editor.name ); + * }, + * canUndo: false // No support for undo/redo. + * } ); + * + * @property {Boolean} [canUndo=true] + */ + +/** + * Whether the command is asynchronous, which means that the + * {@link CKEDITOR.editor#event-afterCommandExec} event will be fired by the + * command itself manually, and that the return value of this command is not to + * be returned by the {@link #exec} function. + * + * editorInstance.addCommand( 'loadOptions', { + * exec: function( editor ) { + * // Asynchronous operation below. + * CKEDITOR.ajax.loadXml( 'data.xml', function() { + * editor.fire( 'afterCommandExec' ); + * } ); + * }, + * async: true // The command need some time to complete after exec function returns. + * } ); + * + * @property {Boolean} [async=false] + */ + +/** + * Whether the command should give focus to the editor before execution. + * + * editorInstance.addCommand( 'maximize', { + * exec: function( editor ) { + * // ... + * }, + * editorFocus: false // The command doesn't require focusing the editing document. + * } ); + * + * @property {Boolean} [editorFocus=true] + * @see CKEDITOR.command#editorFocus + */ + + +/** + * Whether the command state should be set to {@link CKEDITOR#TRISTATE_DISABLED} on startup. + * + * editorInstance.addCommand( 'unlink', { + * exec: function( editor ) { + * // ... + * }, + * startDisabled: true // Command is unavailable until selection is inside a link. + * } ); + * + * @property {Boolean} [startDisabled=false] + */ + +/** + * Indicates that this command is sensible to the selection context. + * If `true`, the {@link CKEDITOR.command#method-refresh} method will be + * called for this command on selection changes, with a single parameter + * representing the current elements path. + * + * @property {Boolean} [contextSensitive=true] + */ + +/** + * Defined by command definition a function to determinate the command state, it will be invoked + * when editor has it's `states` or `selection` changed. + * + * **Note:** The function provided must be calling {@link CKEDITOR.command#setState} in all circumstance, + * if it is intended to update the command state. + * + * @method refresh + * @param {CKEDITOR.editor} editor + * @param {CKEDITOR.dom.elementPath} path + */ + +/** + * Sets the element name used to reflect the command state on selection changes. + * If the selection is in a place where the element is not allowed, the command + * will be disabled. + * Setting this property overrides {@link #contextSensitive} to `true`. + * + * @property {Boolean} [context=true] + */ + +/** + * The editor modes within which the command can be executed. The execution + * will have no action if the current mode is not listed in this property. + * + * editorInstance.addCommand( 'link', { + * exec: function( editor ) { + * // ... + * }, + * modes: { wysiwyg:1 } // Command is available in wysiwyg mode only. + * } ); + * + * @property {Object} [modes={ wysiwyg:1 }] + * @see CKEDITOR.command#modes + */ diff --git a/lam/templates/lib/extra/ckeditor/core/config.js b/lam/templates/lib/extra/ckeditor/core/config.js new file mode 100644 index 00000000..dbdcaf1d --- /dev/null +++ b/lam/templates/lib/extra/ckeditor/core/config.js @@ -0,0 +1,383 @@ +/** + * @license Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +/** + * @fileOverview Defines the {@link CKEDITOR.config} object that stores the + * default configuration settings. + */ + +/** + * Used in conjunction with {@link CKEDITOR.config#enterMode} + * and {@link CKEDITOR.config#shiftEnterMode} configuration + * settings to make the editor produce `

` tags when + * using the *Enter* key. + * + * @readonly + * @property {Number} [=1] + * @member CKEDITOR + */ +CKEDITOR.ENTER_P = 1; + +/** + * Used in conjunction with {@link CKEDITOR.config#enterMode} + * and {@link CKEDITOR.config#shiftEnterMode} configuration + * settings to make the editor produce `
` tags when + * using the *Enter* key. + * + * @readonly + * @property {Number} [=2] + * @member CKEDITOR + */ +CKEDITOR.ENTER_BR = 2; + +/** + * Used in conjunction with {@link CKEDITOR.config#enterMode} + * and {@link CKEDITOR.config#shiftEnterMode} configuration + * settings to make the editor produce `

` tags when + * using the *Enter* key. + * + * @readonly + * @property {Number} [=3] + * @member CKEDITOR + */ +CKEDITOR.ENTER_DIV = 3; + +/** + * Stores default configuration settings. Changes to this object are + * reflected in all editor instances, if not specified otherwise for a particular + * instance. + * + * @class + * @singleton + */ +CKEDITOR.config = { + /** + * The URL path for the custom configuration file to be loaded. If not + * overloaded with inline configuration, it defaults to the `config.js` + * file present in the root of the CKEditor installation directory. + * + * CKEditor will recursively load custom configuration files defined inside + * other custom configuration files. + * + * // Load a specific configuration file. + * CKEDITOR.replace( 'myfield', { customConfig: '/myconfig.js' } ); + * + * // Do not load any custom configuration file. + * CKEDITOR.replace( 'myfield', { customConfig: '' } ); + * + * @cfg {String} [="/config.js"] + */ + customConfig: 'config.js', + + /** + * Whether the replaced element (usually a ` + data = protectElements( data, protectTextareaRegex ); + + // Before anything, we must protect the URL attributes as the + // browser may changing them when setting the innerHTML later in + // the code. + data = protectAttributes( data ); + + // Protect elements than can't be set inside a DIV. E.g. IE removes + // style tags from innerHTML. (#3710) + data = protectElements( data, protectElementsRegex ); + + // Certain elements has problem to go through DOM operation, protect + // them by prefixing 'cke' namespace. (#3591) + data = protectElementsNames( data ); + + // All none-IE browsers ignore self-closed custom elements, + // protecting them into open-close. (#3591) + data = protectSelfClosingElements( data ); + + // Compensate one leading line break after
 open as browsers
+			// eat it up. (#5789)
+			data = protectPreFormatted( data );
+
+			// There are attributes which may execute JavaScript code inside fixBin.
+			// Encode them greedily. They will be unprotected right after getting HTML from fixBin. (#10)
+			data = protectInsecureAttributes( data );
+
+			var fixBin = evtData.context || editor.editable().getName(),
+				isPre;
+
+			// Old IEs loose formats when load html into 
.
+			if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 && fixBin == 'pre' ) {
+				fixBin = 'div';
+				data = '
' + data + '
'; + isPre = 1; + } + + // Call the browser to help us fixing a possibly invalid HTML + // structure. + var el = editor.document.createElement( fixBin ); + // Add fake character to workaround IE comments bug. (#3801) + el.setHtml( 'a' + data ); + data = el.getHtml().substr( 1 ); + + // Restore shortly protected attribute names. + data = data.replace( new RegExp( 'data-cke-' + CKEDITOR.rnd + '-', 'ig' ), '' ); + + isPre && ( data = data.replace( /^
|<\/pre>$/gi, '' ) );
+
+			// Unprotect "some" of the protected elements at this point.
+			data = unprotectElementNames( data );
+
+			data = unprotectElements( data );
+
+			// Restore the comments that have been protected, in this way they
+			// can be properly filtered.
+			data = unprotectRealComments( data );
+
+			// Now use our parser to make further fixes to the structure, as
+			// well as apply the filter.
+			evtData.dataValue = CKEDITOR.htmlParser.fragment.fromHtml(
+				data, evtData.context, evtData.fixForBody === false ? false : getFixBodyTag( evtData.enterMode, editor.config.autoParagraph ) );
+		}, null, null, 5 );
+
+		// Filter incoming "data".
+		// Add element filter before htmlDataProcessor.dataFilter when purifying input data to correct html.
+		editor.on( 'toHtml', function( evt ) {
+			if ( evt.data.filter.applyTo( evt.data.dataValue, true, evt.data.dontFilter, evt.data.enterMode ) )
+				editor.fire( 'dataFiltered' );
+		}, null, null, 6 );
+
+		editor.on( 'toHtml', function( evt ) {
+			evt.data.dataValue.filterChildren( that.dataFilter, true );
+		}, null, null, 10 );
+
+		editor.on( 'toHtml', function( evt ) {
+			var evtData = evt.data,
+				data = evtData.dataValue,
+				writer = new CKEDITOR.htmlParser.basicWriter();
+
+			data.writeChildrenHtml( writer );
+			data = writer.getHtml( true );
+
+			// Protect the real comments again.
+			evtData.dataValue = protectRealComments( data );
+		}, null, null, 15 );
+
+
+		editor.on( 'toDataFormat', function( evt ) {
+			var data = evt.data.dataValue;
+
+			// #10854 - we need to strip leading blockless 
which FF adds + // automatically when editable contains only non-editable content. + // We do that for every browser (so it's a constant behavior) and + // not in BR mode, in which chance of valid leading blockless
is higher. + if ( evt.data.enterMode != CKEDITOR.ENTER_BR ) + data = data.replace( /^
/i, '' ); + + evt.data.dataValue = CKEDITOR.htmlParser.fragment.fromHtml( + data, evt.data.context, getFixBodyTag( evt.data.enterMode, editor.config.autoParagraph ) ); + }, null, null, 5 ); + + editor.on( 'toDataFormat', function( evt ) { + evt.data.dataValue.filterChildren( that.htmlFilter, true ); + }, null, null, 10 ); + + // Transform outcoming "data". + // Add element filter after htmlDataProcessor.htmlFilter when preparing output data HTML. + editor.on( 'toDataFormat', function( evt ) { + evt.data.filter.applyTo( evt.data.dataValue, false, true ); + }, null, null, 11 ); + + editor.on( 'toDataFormat', function( evt ) { + var data = evt.data.dataValue, + writer = that.writer; + + writer.reset(); + data.writeChildrenHtml( writer ); + data = writer.getHtml( true ); + + // Restore those non-HTML protected source. (#4475,#4880) + data = unprotectRealComments( data ); + data = unprotectSource( data, editor ); + + evt.data.dataValue = data; + }, null, null, 15 ); + }; + + CKEDITOR.htmlDataProcessor.prototype = { + /** + * Processes the input (potentially malformed) HTML to a purified form which + * is suitable for using in the WYSIWYG editable. + * + * This method fires the {@link CKEDITOR.editor#toHtml} event which makes it possible + * to hook into the process at various stages. + * + * **Note:** Since CKEditor 4.3 the signature of this method changed and all options + * are now grouped in one `options` object. Previously `context`, `fixForBody` and `dontFilter` + * were passed separately. + * + * @param {String} data The raw data. + * @param {Object} [options] The options object. + * @param {String} [options.context] The tag name of a context element within which + * the input is to be processed, default to be the editable element. + * If `null` is passed, then data will be parsed without context (as children of {@link CKEDITOR.htmlParser.fragment}). + * See {@link CKEDITOR.htmlParser.fragment#fromHtml} for more details. + * @param {Boolean} [options.fixForBody=true] Whether to trigger the auto paragraph for non-block contents. + * @param {CKEDITOR.filter} [options.filter] When specified, instead of using the {@link CKEDITOR.editor#filter main filter}, + * passed instance will be used to filter the content. + * @param {Boolean} [options.dontFilter] Do not filter data with {@link CKEDITOR.filter} (note: transformations + * will be still applied). + * @param {Number} [options.enterMode] When specified it will be used instead of the {@link CKEDITOR.editor#enterMode main enterMode}. + * @returns {String} + */ + toHtml: function( data, options, fixForBody, dontFilter ) { + var editor = this.editor, + context, filter, enterMode; + + // Typeof null == 'object', so check truthiness of options too. + if ( options && typeof options == 'object' ) { + context = options.context; + fixForBody = options.fixForBody; + dontFilter = options.dontFilter; + filter = options.filter; + enterMode = options.enterMode; + } + // Backward compatibility. Since CKEDITOR 4.3 every option was a separate argument. + else + context = options; + + // Fall back to the editable as context if not specified. + if ( !context && context !== null ) + context = editor.editable().getName(); + + return editor.fire( 'toHtml', { + dataValue: data, + context: context, + fixForBody: fixForBody, + dontFilter: dontFilter, + filter: filter || editor.filter, + enterMode: enterMode || editor.enterMode + } ).dataValue; + }, + + /** + * See {@link CKEDITOR.dataProcessor#toDataFormat}. + * + * This method fires the {@link CKEDITOR.editor#toDataFormat} event which makes it possible + * to hook into the process at various steps. + * + * @param {String} html + * @param {Object} [options] The options object. + * @param {String} [options.context] The tag name of a context element within which + * the input is to be processed, default to be the editable element. + * @param {CKEDITOR.filter} [options.filter] When specified, instead of using the {@link CKEDITOR.editor#filter main filter}, + * passed instance will be used to apply content transformations to the content. + * @param {Number} [options.enterMode] When specified it will be used instead of the {@link CKEDITOR.editor#enterMode main enterMode}. + * @returns {String} + */ + toDataFormat: function( html, options ) { + var context, filter, enterMode; + + // Do not shorten this to `options && options.xxx`, because + // falsy `options` will be passed instead of undefined. + if ( options ) { + context = options.context; + filter = options.filter; + enterMode = options.enterMode; + } + + // Fall back to the editable as context if not specified. + if ( !context && context !== null ) + context = this.editor.editable().getName(); + + return this.editor.fire( 'toDataFormat', { + dataValue: html, + filter: filter || this.editor.filter, + context: context, + enterMode: enterMode || this.editor.enterMode + } ).dataValue; + } + }; + + // Produce a set of filtering rules that handles bogus and filler node at the + // end of block/pseudo block, in the following consequence: + // 1. elements: - this filter removes any bogus node, then check + // if it's an empty block that requires a filler. + // 2. elements:
- After cleaned with bogus, this filter checks the real + // line-break BR to compensate a filler after it. + // + // Terms definitions: + // filler: An element that's either
or &NBSP; at the end of block that established line height. + // bogus: Whenever a filler is proceeded with inline content, it becomes a bogus which is subjected to be removed. + // + // Various forms of the filler: + // In output HTML: Filler should be consistently &NBSP;
at the end of block is always considered as bogus. + // In Wysiwyg HTML: Browser dependent - see env.needsBrFiller. Either BR for when needsBrFiller is true, or &NBSP; otherwise. + //
is NEVER considered as bogus when needsBrFiller is true. + function createBogusAndFillerRules( editor, type ) { + function createFiller( isOutput ) { + return isOutput || CKEDITOR.env.needsNbspFiller ? + new CKEDITOR.htmlParser.text( '\xa0' ) : + new CKEDITOR.htmlParser.element( 'br', { 'data-cke-bogus': 1 } ); + } + + // This text block filter, remove any bogus and create the filler on demand. + function blockFilter( isOutput, fillEmptyBlock ) { + + return function( block ) { + + // DO NOT apply the filer if it's a fragment node. + if ( block.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) + return; + + cleanBogus( block ); + + // [Opera] it's mandatory for the filler to present inside of empty block when in WYSIWYG. + if ( ( ( CKEDITOR.env.opera && !isOutput ) || + ( typeof fillEmptyBlock == 'function' ? fillEmptyBlock( block ) !== false : fillEmptyBlock ) ) && + isEmptyBlockNeedFiller( block ) ) + { + block.add( createFiller( isOutput ) ); + } + }; + } + + // Append a filler right after the last line-break BR, found at the end of block. + function brFilter( isOutput ) { + return function( br ) { + + // DO NOT apply the filer if parent's a fragment node. + if ( br.parent.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) + return; + + var attrs = br.attributes; + // Dismiss BRs that are either bogus or eol marker. + if ( 'data-cke-bogus' in attrs || + 'data-cke-eol' in attrs ) { + delete attrs [ 'data-cke-bogus' ]; + return; + } + + // Judge the tail line-break BR, and to insert bogus after it. + var next = getNext( br ), previous = getPrevious( br ); + + if ( !next && isBlockBoundary( br.parent ) ) + append( br.parent, createFiller( isOutput ) ); + else if ( isBlockBoundary( next ) && previous && !isBlockBoundary( previous ) ) + createFiller( isOutput ).insertBefore( next ); + }; + } + + // Determinate whether this node is potentially a bogus node. + function maybeBogus( node, atBlockEnd ) { + + // BR that's not from IE<11 DOM, except for a EOL marker. + if ( !( isOutput && !CKEDITOR.env.needsBrFiller ) && + node.type == CKEDITOR.NODE_ELEMENT && node.name == 'br' && + !node.attributes[ 'data-cke-eol' ] ) + return true; + + var match; + // NBSP, possibly. + if ( node.type == CKEDITOR.NODE_TEXT && + ( match = node.value.match( tailNbspRegex ) ) ) + { + // We need to separate tail NBSP out of a text node, for later removal. + if ( match.index ) { + ( new CKEDITOR.htmlParser.text( node.value.substring( 0, match.index ) ) ).insertBefore( node ); + node.value = match[ 0 ]; + } + + // From IE<11 DOM, at the end of a text block, or before block boundary. + if ( !CKEDITOR.env.needsBrFiller && isOutput && ( !atBlockEnd || node.parent.name in textBlockTags ) ) + return true; + + // From the output. + if ( !isOutput ) { + var previous = node.previous; + + // Following a line-break at the end of block. + if ( previous && previous.name == 'br' ) + return true; + + // Or a single NBSP between two blocks. + if ( !previous || isBlockBoundary( previous ) ) + return true; + } + } + + return false; + } + + // Removes all bogus inside of this block, and to convert fillers into the proper form. + function cleanBogus( block ) { + var bogus = []; + var last = getLast( block ), node, previous; + if ( last ) { + + // Check for bogus at the end of this block. + // e.g.

foo

+ maybeBogus( last, 1 ) && bogus.push( last ); + + while ( last ) { + + // Check for bogus at the end of any pseudo block contained. + if ( isBlockBoundary( last ) && + ( node = getPrevious( last ) ) && + maybeBogus( node ) ) + { + // Bogus must have inline proceeding, instead single BR between two blocks, + // is considered as filler, e.g.


+ if ( ( previous = getPrevious( node ) ) && !isBlockBoundary( previous ) ) + bogus.push( node ); + // Convert the filler into appropriate form. + else { + createFiller( isOutput ).insertAfter( node ); + node.remove(); + } + } + + last = last.previous; + } + } + + // Now remove all bogus collected from above. + for ( var i = 0 ; i < bogus.length ; i++ ) + bogus[ i ].remove(); + } + + // Judge whether it's an empty block that requires a filler node. + function isEmptyBlockNeedFiller( block ) { + + // DO NOT fill empty editable in IE<11. + if ( !isOutput && !CKEDITOR.env.needsBrFiller && block.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) + return false; + + // 1. For IE version >=8, empty blocks are displayed correctly themself in wysiwiyg; + // 2. For the rest, at least table cell and list item need no filler space. (#6248) + if ( !isOutput && !CKEDITOR.env.needsBrFiller && + ( document.documentMode > 7 || + block.name in CKEDITOR.dtd.tr || + block.name in CKEDITOR.dtd.$listItem ) ) { + return false; + } + + var last = getLast( block ); + return !last || block.name == 'form' && last.name == 'input' ; + } + + var rules = { elements: {} }; + var isOutput = type == 'html'; + + // Build the list of text blocks. + var textBlockTags = CKEDITOR.tools.extend( {}, blockLikeTags ); + for ( var i in textBlockTags ) { + if ( !( '#' in dtd[ i ] ) ) + delete textBlockTags[ i ]; + } + + for ( i in textBlockTags ) + rules.elements[ i ] = blockFilter( isOutput, editor.config.fillEmptyBlocks !== false ); + + // Editable element is to be checked separately. + rules.root = blockFilter( isOutput ); + rules.elements.br = brFilter( isOutput ); + return rules; + } + + function getFixBodyTag( enterMode, autoParagraph ) { + return ( enterMode != CKEDITOR.ENTER_BR && autoParagraph !== false ) ? enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'p' : false; + } + + // Regex to scan for   at the end of blocks, which are actually placeholders. + // Safari transforms the   to \xa0. (#4172) + var tailNbspRegex = /(?: |\xa0)$/; + + var protectedSourceMarker = '{cke_protected}'; + + function getLast( node ) { + var last = node.children[ node.children.length - 1 ]; + while ( last && isEmpty( last ) ) + last = last.previous; + return last; + } + + function getNext( node ) { + var next = node.next; + while ( next && isEmpty( next ) ) + next = next.next; + return next; + } + + function getPrevious( node ) { + var previous = node.previous; + while ( previous && isEmpty( previous ) ) + previous = previous.previous; + return previous; + } + + // Judge whether the node is an ghost node to be ignored, when traversing. + function isEmpty( node ) { + return node.type == CKEDITOR.NODE_TEXT && + !CKEDITOR.tools.trim( node.value ) || + node.type == CKEDITOR.NODE_ELEMENT && + node.attributes[ 'data-cke-bookmark' ]; + } + + // Judge whether the node is a block-like element. + function isBlockBoundary( node ) { + return node && + ( node.type == CKEDITOR.NODE_ELEMENT && node.name in blockLikeTags || + node.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT ); + } + + function append( parent, node ) { + var last = parent.children[ parent.children.length -1 ]; + parent.children.push( node ); + node.parent = parent; + if ( last ) { + last.next = node; + node.previous = last; + } + } + + function getNodeIndex( node ) { + return node.parent ? node.getIndex() : -1; + } + + var dtd = CKEDITOR.dtd, + // Define orders of table elements. + tableOrder = [ 'caption', 'colgroup', 'col', 'thead', 'tfoot', 'tbody' ], + // List of all block elements. + blockLikeTags = CKEDITOR.tools.extend( {}, dtd.$blockLimit, dtd.$block ); + + // + // DATA filter rules ------------------------------------------------------ + // + + var defaultDataFilterRulesEditableOnly = { + elements: { + input: protectReadOnly, + textarea: protectReadOnly + } + }; + + // These rules will also be applied to non-editable content. + var defaultDataFilterRulesForAll = { + attributeNames: [ + // Event attributes (onXYZ) must not be directly set. They can become + // active in the editing area (IE|WebKit). + [ ( /^on/ ), 'data-cke-pa-on' ], + + // Don't let some old expando enter editor. Concerns only IE8, + // but for consistency remove on all browsers. + [ ( /^data-cke-expando$/ ), '' ] + ] + }; + + // Disable form elements editing mode provided by some browsers. (#5746) + function protectReadOnly( element ) { + var attrs = element.attributes; + + // We should flag that the element was locked by our code so + // it'll be editable by the editor functions (#6046). + if ( attrs.contenteditable != 'false' ) + attrs[ 'data-cke-editable' ] = attrs.contenteditable ? 'true' : 1; + + attrs.contenteditable = 'false'; + } + + // + // HTML filter rules ------------------------------------------------------ + // + + var defaultHtmlFilterRulesEditableOnly = { + elements: { + embed: function( element ) { + var parent = element.parent; + + // If the is child of a , copy the width + // and height attributes from it. + if ( parent && parent.name == 'object' ) { + var parentWidth = parent.attributes.width, + parentHeight = parent.attributes.height; + if ( parentWidth ) + element.attributes.width = parentWidth; + if ( parentHeight ) + element.attributes.height = parentHeight; + } + }, + + // Remove empty link but not empty anchor. (#3829) + a: function( element ) { + if ( !( element.children.length || element.attributes.name || element.attributes[ 'data-cke-saved-name' ] ) ) + return false; + } + } + }; + + // These rules will also be applied to non-editable content. + var defaultHtmlFilterRulesForAll = { + elementNames: [ + // Remove the "cke:" namespace prefix. + [ ( /^cke:/ ), '' ], + + // Ignore tags. + [ ( /^\?xml:namespace$/ ), '' ] + ], + + attributeNames: [ + // Attributes saved for changes and protected attributes. + [ ( /^data-cke-(saved|pa)-/ ), '' ], + + // All "data-cke-" attributes are to be ignored. + [ ( /^data-cke-.*/ ), '' ], + + [ 'hidefocus', '' ] + ], + + elements: { + $: function( element ) { + var attribs = element.attributes; + + if ( attribs ) { + // Elements marked as temporary are to be ignored. + if ( attribs[ 'data-cke-temp' ] ) + return false; + + // Remove duplicated attributes - #3789. + var attributeNames = [ 'name', 'href', 'src' ], + savedAttributeName; + for ( var i = 0; i < attributeNames.length; i++ ) { + savedAttributeName = 'data-cke-saved-' + attributeNames[ i ]; + savedAttributeName in attribs && ( delete attribs[ attributeNames[ i ] ] ); + } + } + + return element; + }, + + // The contents of table should be in correct order (#4809). + table: function( element ) { + // Clone the array as it would become empty during the sort call. + var children = element.children.slice( 0 ); + children.sort( function( node1, node2 ) { + var index1, index2; + + // Compare in the predefined order. + if ( node1.type == CKEDITOR.NODE_ELEMENT && + node2.type == node1.type ) { + index1 = CKEDITOR.tools.indexOf( tableOrder, node1.name ); + index2 = CKEDITOR.tools.indexOf( tableOrder, node2.name ); + } + + // Make sure the sort is stable, if no order can be established above. + if ( !( index1 > -1 && index2 > -1 && index1 != index2 ) ) { + index1 = getNodeIndex( node1 ); + index2 = getNodeIndex( node2 ); + } + + return index1 > index2 ? 1 : -1; + } ); + }, + + // Restore param elements into self-closing. + param: function( param ) { + param.children = []; + param.isEmpty = true; + return param; + }, + + // Remove dummy span in webkit. + span: function( element ) { + if ( element.attributes[ 'class' ] == 'Apple-style-span' ) + delete element.name; + }, + + html: function( element ) { + delete element.attributes.contenteditable; + delete element.attributes[ 'class' ]; + }, + + body: function( element ) { + delete element.attributes.spellcheck; + delete element.attributes.contenteditable; + }, + + style: function( element ) { + var child = element.children[ 0 ]; + if ( child && child.value ) + child.value = CKEDITOR.tools.trim( child.value ); + + if ( !element.attributes.type ) + element.attributes.type = 'text/css'; + }, + + title: function( element ) { + var titleText = element.children[ 0 ]; + + // Append text-node to title tag if not present (i.e. non-IEs) (#9882). + !titleText && append( element, titleText = new CKEDITOR.htmlParser.text() ); + + // Transfer data-saved title to title tag. + titleText.value = element.attributes[ 'data-cke-title' ] || ''; + }, + + input: unprotectReadyOnly, + textarea: unprotectReadyOnly + }, + + attributes: { + 'class': function( value, element ) { + // Remove all class names starting with "cke_". + return CKEDITOR.tools.ltrim( value.replace( /(?:^|\s+)cke_[^\s]*/g, '' ) ) || false; + } + } + }; + + if ( CKEDITOR.env.ie ) { + // IE outputs style attribute in capital letters. We should convert + // them back to lower case, while not hurting the values (#5930) + defaultHtmlFilterRulesForAll.attributes.style = function( value, element ) { + return value.replace( /(^|;)([^\:]+)/g, function( match ) { + return match.toLowerCase(); + } ); + }; + } + + // Disable form elements editing mode provided by some browsers. (#5746) + function unprotectReadyOnly( element ) { + var attrs = element.attributes; + switch ( attrs[ 'data-cke-editable' ] ) { + case 'true': + attrs.contenteditable = 'true'; + break; + case '1': + delete attrs.contenteditable; + break; + } + } + + // + // Preprocessor filters --------------------------------------------------- + // + + var protectElementRegex = /<(a|area|img|input|source)\b([^>]*)>/gi, + // Be greedy while looking for protected attributes. This will let us avoid an unfortunate + // situation when "nested attributes", which may appear valid, are also protected. + // I.e. if we consider the following HTML: + // + // + // + // then the "non-greedy match" returns: + // + // 'href' => '"X"' // It's wrong! Href is not an attribute of . + // + // while greedy match returns: + // + // 'data-x' => '<a href="X"' + // + // which, can be easily filtered out (#11508). + protectAttributeRegex = /([\w-]+)\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|(?:[^ "'>]+))/gi, + protectAttributeNameRegex = /^(href|src|name)$/i; + + // Note: we use lazy star '*?' to prevent eating everything up to the last occurrence of or . + var protectElementsRegex = /(?:])[^>]*>[\s\S]*?<\/style>)|(?:<(:?link|meta|base)[^>]*>)/gi, + protectTextareaRegex = /(])[^>]*>)([\s\S]*?)(?:<\/textarea>)/gi, + encodedElementsRegex = /([^<]*)<\/cke:encoded>/gi; + + var protectElementNamesRegex = /(<\/?)((?:object|embed|param|html|body|head|title)[^>]*>)/gi, + unprotectElementNamesRegex = /(<\/?)cke:((?:html|body|head|title)[^>]*>)/gi; + + var protectSelfClosingRegex = /]*?)\/?>(?!\s*<\/cke:\1)/gi; + + function protectAttributes( html ) { + return html.replace( protectElementRegex, function( element, tag, attributes ) { + return '<' + tag + attributes.replace( protectAttributeRegex, function( fullAttr, attrName ) { + // Avoid corrupting the inline event attributes (#7243). + // We should not rewrite the existed protected attributes, e.g. clipboard content from editor. (#5218) + if ( protectAttributeNameRegex.test( attrName ) && attributes.indexOf( 'data-cke-saved-' + attrName ) == -1 ) + return ' data-cke-saved-' + fullAttr + ' data-cke-' + CKEDITOR.rnd + '-' + fullAttr; + + return fullAttr; + } ) + '>'; + } ); + } + + function protectElements( html, regex ) { + return html.replace( regex, function( match, tag, content ) { + // Encode < and > in textarea because this won't be done by a browser, since + // textarea will be protected during passing data through fix bin. + if ( match.indexOf( '/g, '>' ) + ''; + + return '' + encodeURIComponent( match ) + ''; + } ); + } + + function unprotectElements( html ) { + return html.replace( encodedElementsRegex, function( match, encoded ) { + return decodeURIComponent( encoded ); + } ); + } + + function protectElementsNames( html ) { + return html.replace( protectElementNamesRegex, '$1cke:$2' ); + } + + function unprotectElementNames( html ) { + return html.replace( unprotectElementNamesRegex, '$1$2' ); + } + + function protectSelfClosingElements( html ) { + return html.replace( protectSelfClosingRegex, '' ); + } + + function protectPreFormatted( html ) { + return CKEDITOR.env.opera ? html : html.replace( /(]*>)(\r\n|\n)/g, '$1$2$2' ); + } + + function protectRealComments( html ) { + return html.replace( //g, function( match ) { + return ''; + } ); + } + + // Replace all "on\w{3,}" strings which are not: + // * opening tags - e.g. ` (tested in "false positive 1"), + // * part of other attribute - e.g. `data-onfoo` or `fonfoo`. + function protectInsecureAttributes( html ) { + return html.replace( /([^a-z0-9<\-])(on\w{3,})(?!>)/gi, '$1data-cke-' + CKEDITOR.rnd + '-$2' ); + } + + function unprotectRealComments( html ) { + return html.replace( //g, function( match, data ) { + return decodeURIComponent( data ); + } ); + } + + function unprotectSource( html, editor ) { + var store = editor._.dataStore; + + return html.replace( //g, function( match, data ) { + return decodeURIComponent( data ); + } ).replace( /\{cke_protected_(\d+)\}/g, function( match, id ) { + return store && store[ id ] || ''; + } ); + } + + function protectSource( data, editor ) { + var protectedHtml = [], + protectRegexes = editor.config.protectedSource, + store = editor._.dataStore || ( editor._.dataStore = { id: 1 } ), + tempRegex = /<\!--\{cke_temp(comment)?\}(\d*?)-->/g; + + var regexes = [ + // Script tags will also be forced to be protected, otherwise + // IE will execute them. + ( //gi ), + + //