/** * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved. * For licensing, see LICENSE.md or http://ckeditor.com/license */ /** * @fileOverview Handles the indentation of block elements. */ ( function() { 'use strict'; var $listItem = CKEDITOR.dtd.$listItem, $list = CKEDITOR.dtd.$list, TRISTATE_DISABLED = CKEDITOR.TRISTATE_DISABLED, TRISTATE_OFF = CKEDITOR.TRISTATE_OFF; CKEDITOR.plugins.add( 'indentblock', { requires: 'indent', init: function( editor ) { var globalHelpers = CKEDITOR.plugins.indent, classes = editor.config.indentClasses; // Register commands. globalHelpers.registerCommands( editor, { indentblock: new commandDefinition( editor, 'indentblock', true ), outdentblock: new commandDefinition( editor, 'outdentblock' ) } ); function commandDefinition() { globalHelpers.specificDefinition.apply( this, arguments ); this.allowedContent = { 'div h1 h2 h3 h4 h5 h6 ol p pre ul': { // Do not add elements, but only text-align style if element is validated by other rule. propertiesOnly: true, styles: !classes ? 'margin-left,margin-right' : null, classes: classes || null } }; if ( this.enterBr ) this.allowedContent.div = true; this.requiredContent = ( this.enterBr ? 'div' : 'p' ) + ( classes ? '(' + classes.join( ',' ) + ')' : '{margin-left}' ); this.jobs = { '20': { refresh: function( editor, path ) { var firstBlock = path.block || path.blockLimit; // Switch context from somewhere inside list item to list item, // if not found just assign self (doing nothing). if ( !firstBlock.is( $listItem ) ) { var ascendant = firstBlock.getAscendant( $listItem ); firstBlock = ( ascendant && path.contains( ascendant ) ) || firstBlock; } // Switch context from list item to list // because indentblock can indent entire list // but not a single list element. if ( firstBlock.is( $listItem ) ) firstBlock = firstBlock.getParent(); // [-] Context in the path or ENTER_BR // // Don't try to indent if the element is out of // this plugin's scope. This assertion is omitted // if ENTER_BR is in use since there may be no block // in the path. if ( !this.enterBr && !this.getContext( path ) ) return TRISTATE_DISABLED; else if ( classes ) { // [+] Context in the path or ENTER_BR // [+] IndentClasses // // If there are indentation classes, check if reached // the highest level of indentation. If so, disable // the command. if ( indentClassLeft.call( this, firstBlock, classes ) ) return TRISTATE_OFF; else return TRISTATE_DISABLED; } else { // [+] Context in the path or ENTER_BR // [-] IndentClasses // [+] Indenting // // No indent-level limitations due to indent classes. // Indent-like command can always be executed. if ( this.isIndent ) return TRISTATE_OFF; // [+] Context in the path or ENTER_BR // [-] IndentClasses // [-] Indenting // [-] Block in the path // // No block in path. There's no element to apply indentation // so disable the command. else if ( !firstBlock ) return TRISTATE_DISABLED; // [+] Context in the path or ENTER_BR // [-] IndentClasses // [-] Indenting // [+] Block in path. // // Not using indentClasses but there is firstBlock. // We can calculate current indentation level and // try to increase/decrease it. else { return CKEDITOR[ ( getIndent( firstBlock ) || 0 ) <= 0 ? 'TRISTATE_DISABLED' : 'TRISTATE_OFF' ]; } } }, exec: function( editor ) { var selection = editor.getSelection(), range = selection && selection.getRanges()[ 0 ], nearestListBlock; // If there's some list in the path, then it will be // a full-list indent by increasing or decreasing margin property. if ( ( nearestListBlock = editor.elementPath().contains( $list ) ) ) indentElement.call( this, nearestListBlock, classes ); // If no list in the path, use iterator to indent all the possible // paragraphs in the range, creating them if necessary. else { var iterator = range.createIterator(), enterMode = editor.config.enterMode, block; iterator.enforceRealBlocks = true; iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR; while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) ) { if ( !block.isReadOnly() ) indentElement.call( this, block, classes ); } } return true; } } }; } CKEDITOR.tools.extend( commandDefinition.prototype, globalHelpers.specificDefinition.prototype, { // Elements that, if in an elementpath, will be handled by this // command. They restrict the scope of the plugin. context: { div: 1, dl: 1, h1: 1, h2: 1, h3: 1, h4: 1, h5: 1, h6: 1, ul: 1, ol: 1, p: 1, pre: 1, table: 1 }, // A regex built on config#indentClasses to detect whether an // element has some indentClass or not. classNameRegex: classes ? new RegExp( '(?:^|\\s+)(' + classes.join( '|' ) + ')(?=$|\\s)' ) : null } ); } } ); // Generic indentation procedure for indentation of any element // either with margin property or config#indentClass. function indentElement( element, classes, dir ) { if ( element.getCustomData( 'indent_processed' ) ) return; var editor = this.editor, isIndent = this.isIndent; if ( classes ) { // Transform current class f to indent step index. var indentClass = element.$.className.match( this.classNameRegex ), indentStep = 0; if ( indentClass ) { indentClass = indentClass[ 1 ]; indentStep = CKEDITOR.tools.indexOf( classes, indentClass ) + 1; } // Operate on indent step index, transform indent step index // back to class name. if ( ( indentStep += isIndent ? 1 : -1 ) < 0 ) return; indentStep = Math.min( indentStep, classes.length ); indentStep = Math.max( indentStep, 0 ); element.$.className = CKEDITOR.tools.ltrim( element.$.className.replace( this.classNameRegex, '' ) ); if ( indentStep > 0 ) element.addClass( classes[ indentStep - 1 ] ); } else { var indentCssProperty = getIndentCss( element, dir ), currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 ), indentOffset = editor.config.indentOffset || 40; if ( isNaN( currentOffset ) ) currentOffset = 0; currentOffset += ( isIndent ? 1 : -1 ) * indentOffset; if ( currentOffset < 0 ) return; 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( this.database, element, 'indent_processed', 1 ); return; } // Method that checks if current indentation level for an element // reached the limit determined by config#indentClasses. function indentClassLeft( node, classes ) { var indentClass = node.$.className.match( this.classNameRegex ), isIndent = this.isIndent; // If node has one of the indentClasses: // * If it holds the topmost indentClass, then // no more classes have left. // * If it holds any other indentClass, it can use the next one // or the previous one. // * Outdent is always possible. We can remove indentClass. if ( indentClass ) return isIndent ? indentClass[ 1 ] != classes.slice( -1 ) : true; // If node has no class which belongs to indentClasses, // then it is at 0-level. It can be indented but not outdented. else return isIndent; } // Determines indent CSS property for an element according to // what is the direction of such element. It can be either `margin-left` // or `margin-right`. function getIndentCss( element, dir ) { return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right'; } // Return the numerical indent value of margin-left|right of an element, // considering element's direction. If element has no margin specified, // NaN is returned. function getIndent( element ) { return parseInt( element.getStyle( getIndentCss( element ) ), 10 ); } } )(); /** * A list of classes to use for indenting the contents. If set to `null`, no classes will be used * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used. * * // Use the 'Indent1', 'Indent2', 'Indent3' classes. * config.indentClasses = ['Indent1', 'Indent2', 'Indent3']; * * @cfg {Array} [indentClasses=null] * @member CKEDITOR.config */ /** * The size in {@link CKEDITOR.config#indentUnit indentation units} of each indentation step. * * config.indentOffset = 4; * * @cfg {Number} [indentOffset=40] * @member CKEDITOR.config */ /** * The unit used for {@link CKEDITOR.config#indentOffset indentation offset}. * * config.indentUnit = 'em'; * * @cfg {String} [indentUnit='px'] * @member CKEDITOR.config */