/* global ToolbarConfigurator */ 'use strict'; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create if ( typeof Object.create != 'function' ) { ( function() { var F = function() {}; Object.create = function( o ) { if ( arguments.length > 1 ) { throw Error( 'Second argument not supported' ); } if ( o === null ) { throw Error( 'Cannot set a null [[Prototype]]' ); } if ( typeof o != 'object' ) { throw TypeError( 'Argument must be an object' ); } F.prototype = o; return new F(); }; } )(); } // Copy of the divarea plugin (with some enhancements), so we always have some editable mode, regardless of the build's config. CKEDITOR.plugins.add( 'toolbarconfiguratorarea', { // Use afterInit to override wysiwygarea's mode. May still fail to override divarea, but divarea is nice. afterInit: function( editor ) { editor.addMode( 'wysiwyg', function( callback ) { var editingBlock = CKEDITOR.dom.element.createFromHtml( '
' ); var contentSpace = editor.ui.space( 'contents' ); contentSpace.append( editingBlock ); editingBlock = editor.editable( editingBlock ); editingBlock.detach = CKEDITOR.tools.override( editingBlock.detach, function( org ) { return function() { org.apply( this, arguments ); this.remove(); }; } ); editor.setData( editor.getData( 1 ), callback ); editor.fire( 'contentDom' ); } ); // Additions to the divarea. // Speed up data processing. editor.dataProcessor.toHtml = function( html ) { return html; }; editor.dataProcessor.toDataFormat = function( html ) { return html; }; // End of the additions. } } ); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys if ( !Object.keys ) { Object.keys = ( function() { var hasOwnProperty = Object.prototype.hasOwnProperty, hasDontEnumBug = !( { toString: null } ).propertyIsEnumerable( 'toString' ), dontEnums = [ 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor' ], dontEnumsLength = dontEnums.length; return function( obj ) { if ( typeof obj !== 'object' && ( typeof obj !== 'function' || obj === null ) ) throw new TypeError( 'Object.keys called on non-object' ); var result = [], prop, i; for ( prop in obj ) { if ( hasOwnProperty.call( obj, prop ) ) result.push( prop ); } if ( hasDontEnumBug ) { for ( i = 0; i < dontEnumsLength; i++ ) { if ( hasOwnProperty.call( obj, dontEnums[ i ] ) ) result.push( dontEnums[ i ] ); } } return result; }; }() ); } ( function() { /** * @class ToolbarConfigurator.AbstractToolbarModifier * @param {String} editorId An id of modified editor * @constructor */ function AbstractToolbarModifier( editorId, cfg ) { this.cfg = cfg || {}; this.hidden = false; this.editorId = editorId; this.fullToolbarEditor = new ToolbarConfigurator.FullToolbarEditor(); this.mainContainer = null; this.originalConfig = null; this.actualConfig = null; this.waitForReady = false; this.isEditableVisible = false; this.toolbarContainer = null; this.toolbarButtons = []; } // Expose the class. ToolbarConfigurator.AbstractToolbarModifier = AbstractToolbarModifier; /** * @param {String} config */ AbstractToolbarModifier.prototype.setConfig = function( config ) { this._onInit( undefined, config, true ); }; /** * @param {Function} [callback] */ AbstractToolbarModifier.prototype.init = function( callback ) { var that = this; this.mainContainer = new CKEDITOR.dom.element( 'div' ); if ( this.fullToolbarEditor.editorInstance !== null ) { throw 'Only one instance of ToolbarModifier is allowed'; } if ( !this.editorInstance ) { // Do not refresh yet, let's wait for the full toolbar editor (see below). this._createEditor( false ); } this.editorInstance.once( 'loaded', function() { that.fullToolbarEditor.init( function() { that._onInit( callback ); if ( typeof that.onRefresh == 'function' ) { that.onRefresh(); } }, that.editorInstance.config ); } ); return this.mainContainer; }; /** * Called editor initialization finished. * * @param {Function} callback * @param {String} [actualConfig] * @private */ AbstractToolbarModifier.prototype._onInit = function( callback, actualConfig ) { this.originalConfig = this.editorInstance.config; if ( !actualConfig ) { this.actualConfig = JSON.parse( JSON.stringify( this.originalConfig ) ); } else { this.actualConfig = JSON.parse( actualConfig ); } if ( !this.actualConfig.toolbarGroups && !this.actualConfig.toolbar ) { this.actualConfig.toolbarGroups = getDefaultToolbarGroups( this.editorInstance ); } if ( typeof callback === 'function' ) callback( this.mainContainer ); // Here we are going to keep only `name` and `groups` data from editor `toolbar` property. function getDefaultToolbarGroups( editor ) { var toolbarGroups = editor.toolbar, copy = []; var max = toolbarGroups.length; for ( var i = 0; i < max; i++ ) { var group = toolbarGroups[ i ]; if ( typeof group == 'string' ) { copy.push( group ); // separator } else { copy.push( { name: group.name, groups: group.groups ? group.groups.slice() : [] } ); } } return copy; } }; /** * Creates DOM structure of tool. * * @returns {CKEDITOR.dom.element} * @private */ AbstractToolbarModifier.prototype._createModifier = function() { this.mainContainer.addClass( 'unselectable' ); if ( this.modifyContainer ) { this.modifyContainer.remove(); } this.modifyContainer = new CKEDITOR.dom.element( 'div' ); this.modifyContainer.addClass( 'toolbarModifier' ); this.mainContainer.append( this.modifyContainer ); return this.mainContainer; }; /** * Find editable area in CKEditor instance DOM container * * @returns {CKEDITOR.dom.element} */ AbstractToolbarModifier.prototype.getEditableArea = function() { var selector = ( '#' + this.editorInstance.id + '_contents' ); return this.editorInstance.container.findOne( selector ); }; /** * Hide editable area in modified editor by sets its height to 0. * * @private */ AbstractToolbarModifier.prototype._hideEditable = function() { var area = this.getEditableArea(); this.isEditableVisible = false; this.lastEditableAreaHeight = area.getStyle( 'height' ); area.setStyle( 'height', '0' ); }; /** * Show editable area in modified editor. * * @private */ AbstractToolbarModifier.prototype._showEditable = function() { this.isEditableVisible = true; this.getEditableArea().setStyle( 'height', this.lastEditableAreaHeight || 'auto' ); }; /** * Toggle editable area visibility. * * @private */ AbstractToolbarModifier.prototype._toggleEditable = function() { if ( this.isEditableVisible ) this._hideEditable(); else this._showEditable(); }; /** * Usually called when configuration changes. * * @private */ AbstractToolbarModifier.prototype._refreshEditor = function() { var that = this, status = this.editorInstance.status; // Wait for ready only once. if ( this.waitForReady ) return; // Not ready. if ( status == 'unloaded' || status == 'loaded' ) { this.waitForReady = true; this.editorInstance.once( 'instanceReady', function() { refresh(); }, this ); // Ready or destroyed. } else { refresh(); } function refresh() { that.editorInstance.destroy(); that._createEditor( true, that.getActualConfig() ); that.waitForReady = false; } }; /** * Creates editor that can be used to present the toolbar configuration. * * @private */ AbstractToolbarModifier.prototype._createEditor = function( doRefresh, configOverrides ) { var that = this; this.editorInstance = CKEDITOR.replace( this.editorId ); this.editorInstance.on( 'configLoaded', function() { var config = that.editorInstance.config; if ( configOverrides ) { CKEDITOR.tools.extend( config, configOverrides, true ); } AbstractToolbarModifier.extendPluginsConfig( config ); } ); // Prevent creating any other space than the top one. this.editorInstance.on( 'uiSpace', function( evt ) { if ( evt.data.space != 'top' ) { evt.stop(); } }, null, null, -999 ); this.editorInstance.once( 'loaded', function() { var btns = that.editorInstance.ui.instances; for ( var i in btns ) { if ( btns[ i ] ) { btns[ i ].click = empty; btns[ i ].onClick = empty; } } if ( !that.isEditableVisible ) { that._hideEditable(); } if ( that.currentActive && that.currentActive.name ) { that._highlightGroup( that.currentActive.name ); } if ( that.hidden ) { that.hideUI(); } else { that.showUI(); } if ( doRefresh && ( typeof that.onRefresh === 'function' ) ) { that.onRefresh(); } } ); function empty() {} }; /** * Always returns copy of config. * * @returns {Object} */ AbstractToolbarModifier.prototype.getActualConfig = function() { return JSON.parse( JSON.stringify( this.actualConfig ) ); }; /** * Creates toolbar in tool. * * @private */ AbstractToolbarModifier.prototype._createToolbar = function() { if ( !this.toolbarButtons.length ) { return; } this.toolbarContainer = new CKEDITOR.dom.element( 'div' ); this.toolbarContainer.addClass( 'toolbar' ); var max = this.toolbarButtons.length; for ( var i = 0; i < max; i += 1 ) { this._createToolbarBtn( this.toolbarButtons[ i ] ); } }; /** * Create toolbar button and add it to toolbar container * * @param {Object} cfg * @returns {CKEDITOR.dom.element} * @private */ AbstractToolbarModifier.prototype._createToolbarBtn = function( cfg ) { var btnText = ( typeof cfg.text === 'string' ? cfg.text : cfg.text.inactive ), btn = ToolbarConfigurator.FullToolbarEditor.createButton( btnText, cfg.cssClass ); this.toolbarContainer.append( btn ); btn.data( 'group', cfg.group ); btn.addClass( cfg.position ); btn.on( 'click', function() { cfg.clickCallback.call( this, btn, cfg ); }, this ); return btn; }; /** * @private * @param {Object} config */ AbstractToolbarModifier.prototype._fixGroups = function( config ) { var groups = config.toolbarGroups || []; var max = groups.length; for ( var i = 0; i < max; i += 1 ) { var currentGroup = groups[ i ]; // separator, in config, is in raw format // need to make it more sophisticated to keep unique id // for each one if ( currentGroup == '/' ) { currentGroup = groups[ i ] = {}; currentGroup.type = 'separator'; currentGroup.name = ( 'separator' + CKEDITOR.tools.getNextNumber() ); continue; } // sometimes subgroups are not set (basic package), so need to // create them artifically currentGroup.groups = currentGroup.groups || []; // when there is no subgroup with same name like its parent name // then it have to be added artificially // in order to maintain consistency between user interface and config if ( CKEDITOR.tools.indexOf( currentGroup.groups, currentGroup.name ) == -1 ) { this.editorInstance.ui.addToolbarGroup( currentGroup.name, currentGroup.groups[ currentGroup.groups.length - 1 ], currentGroup.name ); currentGroup.groups.push( currentGroup.name ); } this._fixSubgroups( currentGroup ); } }; /** * Transform subgroup string to object literal * with keys: {String} name and {Number} totalBtns * Please note: this method modify Object provided in first argument * * input: * [ * { groups: [ 'nameOne', 'nameTwo' ] } * ] * * output: * [ * { groups: [ { name: 'nameOne', totalBtns: 3 }, { name: 'nameTwo', totalBtns: 5 } ] } * ] * * @param {Object} group * @private */ AbstractToolbarModifier.prototype._fixSubgroups = function( group ) { var subGroups = group.groups; var max = subGroups.length; for ( var i = 0; i < max; i += 1 ) { var subgroupName = subGroups[ i ]; subGroups[ i ] = { name: subgroupName, totalBtns: ToolbarConfigurator.ToolbarModifier.getTotalSubGroupButtonsNumber( subgroupName, this.fullToolbarEditor ) }; } }; /** * Same as JSON.stringify method but returned string is in one line * * @param {Object} json * @param {Object} opts * @param {Boolean} opts.addSpaces * @param {Boolean} opts.noQuotesOnKey * @param {Boolean} opts.singleQuotes * @returns {Object} */ AbstractToolbarModifier.stringifyJSONintoOneLine = function( json, opts ) { opts = opts || {}; var stringJSON = JSON.stringify( json, null, '' ); // IE8 make new line characters stringJSON = stringJSON.replace( /\n/g, '' ); if ( opts.addSpaces ) { stringJSON = stringJSON.replace( /(\{|:|,|\[|\])/g, function( sentence ) { return sentence + ' '; } ); stringJSON = stringJSON.replace( /(\])/g, function( sentence ) { return ' ' + sentence; } ); } if ( opts.noQuotesOnKey ) { stringJSON = stringJSON.replace( /"(\w*)":/g, function( sentence, word ) { return word + ':'; } ); } if ( opts.singleQuotes ) { stringJSON = stringJSON.replace( /\"/g, '\'' ); } return stringJSON; }; /** * Hide toolbar configurator */ AbstractToolbarModifier.prototype.hideUI = function() { this.hidden = true; this.mainContainer.hide(); if ( this.editorInstance.container ) { this.editorInstance.container.hide(); } }; /** * Show toolbar configurator */ AbstractToolbarModifier.prototype.showUI = function() { this.hidden = false; this.mainContainer.show(); if ( this.editorInstance.container ) { this.editorInstance.container.show(); } }; /** * Extends plugins setttings in the specified config with settings useful for * the toolbar configurator. * * @static */ AbstractToolbarModifier.extendPluginsConfig = function( config ) { var extraPlugins = config.extraPlugins; // Enable the special, lightweight area to replace wysiwygarea. config.extraPlugins = ( extraPlugins ? extraPlugins + ',' : '' ) + 'toolbarconfiguratorarea'; }; } )();