/** * @license 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