JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.6.3
[ckeditor.git] / _source / plugins / indent / plugin.js
index 4c1406e..1e3ec49 100644 (file)
@@ -1,5 +1,5 @@
 /*\r
-Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved.\r
+Copyright (c) 2003-2012, CKSource - Frederico Knabben. All rights reserved.\r
 For licensing, see LICENSE.html or http://ckeditor.com/license\r
 */\r
 \r
@@ -9,33 +9,28 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
 (function()\r
 {\r
-       var listNodeNames = { ol : 1, ul : 1 };\r
-\r
-       var isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),\r
+       var listNodeNames = { ol : 1, ul : 1 },\r
+               isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ),\r
                isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true );\r
 \r
-       function setState( editor, state )\r
-       {\r
-               editor.getCommand( this.name ).setState( state );\r
-       }\r
-\r
        function onSelectionChange( evt )\r
        {\r
-               var editor = evt.editor;\r
+               if ( evt.editor.readOnly )\r
+                       return null;\r
 \r
-               var elementPath = evt.data.path,\r
-                               list = elementPath && elementPath.contains( listNodeNames );\r
+               var editor = evt.editor,\r
+                       elementPath = evt.data.path,\r
+                       list = elementPath && elementPath.contains( listNodeNames ),\r
+                       firstBlock = elementPath.block || elementPath.blockLimit;\r
 \r
                if ( list )\r
-                               return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );\r
+                               return this.setState( CKEDITOR.TRISTATE_OFF );\r
 \r
                if ( !this.useIndentClasses && this.name == 'indent' )\r
-                       return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );\r
+                       return this.setState( CKEDITOR.TRISTATE_OFF );\r
 \r
-               var path = evt.data.path,\r
-                       firstBlock = path.block || path.blockLimit;\r
                if ( !firstBlock )\r
-                       return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );\r
+                       return this.setState( CKEDITOR.TRISTATE_DISABLED );\r
 \r
                if ( this.useIndentClasses )\r
                {\r
@@ -48,8 +43,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        }\r
                        if ( ( this.name == 'outdent' && !indentStep ) ||\r
                                        ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ) )\r
-                               return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );\r
-                       return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );\r
+                               return this.setState( CKEDITOR.TRISTATE_DISABLED );\r
+                       return this.setState( CKEDITOR.TRISTATE_OFF );\r
                }\r
                else\r
                {\r
@@ -57,8 +52,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                        if ( isNaN( indent ) )\r
                                indent = 0;\r
                        if ( indent <= 0 )\r
-                               return setState.call( this, editor, CKEDITOR.TRISTATE_DISABLED );\r
-                       return setState.call( this, editor, CKEDITOR.TRISTATE_OFF );\r
+                               return this.setState( CKEDITOR.TRISTATE_DISABLED );\r
+                       return this.setState( CKEDITOR.TRISTATE_OFF );\r
                }\r
        }\r
 \r
@@ -78,14 +73,14 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
        }\r
 \r
        // Returns the CSS property to be used for identing a given element.\r
-       function getIndentCssProperty( element )\r
+       function getIndentCssProperty( element, dir )\r
        {\r
-               return element.getComputedStyle( 'direction' ) == 'ltr' ? 'margin-left' : 'margin-right';\r
+               return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right';\r
        }\r
 \r
        function isListItem( node )\r
        {\r
-               return node.type = CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
+               return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
        }\r
 \r
        indentCommand.prototype = {\r
@@ -157,8 +152,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                                // Convert the array back to a DOM forest (yes we might have a few subtrees now).\r
                                // And replace the old list with the new forest.\r
-                               var newListDir = listNode.getAttribute( 'dir' ) || listNode.getStyle( 'direction' );\r
-                               var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, newListDir );\r
+                               var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, listNode.getDirection() );\r
 \r
                                // Avoid nested <li> after outdent even they're visually same,\r
                                // recording them for later refactoring.(#3982)\r
@@ -216,11 +210,11 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                iterator.enforceRealBlocks = true;\r
                                iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;\r
                                var block;\r
-                               while ( ( block = iterator.getNextParagraph() ) )\r
+                               while ( ( block = iterator.getNextParagraph( enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ) ) )\r
                                        indentElement( block );\r
                        }\r
 \r
-                       function indentElement( element )\r
+                       function indentElement( element, dir )\r
                        {\r
                                if ( element.getCustomData( 'indent_processed' ) )\r
                                        return false;\r
@@ -248,91 +242,95 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
 \r
                                        indentStep = Math.min( indentStep, editor.config.indentClasses.length );\r
                                        indentStep = Math.max( indentStep, 0 );\r
-                                       var className = CKEDITOR.tools.ltrim( element.$.className.replace( self.classNameRegex, '' ) );\r
-                                       if ( indentStep < 1 )\r
-                                               element.$.className = className;\r
-                                       else\r
+                                       element.$.className = CKEDITOR.tools.ltrim( element.$.className.replace( self.classNameRegex, '' ) );\r
+                                       if ( indentStep > 0 )\r
                                                element.addClass( editor.config.indentClasses[ indentStep - 1 ] );\r
                                }\r
                                else\r
                                {\r
-                                       var indentCssProperty = getIndentCssProperty( element );\r
-                                       var currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 );\r
+                                       var indentCssProperty = getIndentCssProperty( element, dir ),\r
+                                               currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 );\r
                                        if ( isNaN( currentOffset ) )\r
                                                currentOffset = 0;\r
-                                       currentOffset += ( self.name == 'indent' ? 1 : -1 ) * editor.config.indentOffset;\r
+                                       var indentOffset = editor.config.indentOffset || 40;\r
+                                       currentOffset += ( self.name == 'indent' ? 1 : -1 ) * indentOffset;\r
 \r
                                        if ( currentOffset < 0 )\r
                                                return false;\r
 \r
                                        currentOffset = Math.max( currentOffset, 0 );\r
-                                       currentOffset = Math.ceil( currentOffset / editor.config.indentOffset ) * editor.config.indentOffset;\r
-                                       element.setStyle( indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' );\r
+                                       currentOffset = Math.ceil( currentOffset / indentOffset ) * indentOffset;\r
+                                       element.setStyle( indentCssProperty, currentOffset ? currentOffset + ( editor.config.indentUnit || 'px' ) : '' );\r
                                        if ( element.getAttribute( 'style' ) === '' )\r
                                                element.removeAttribute( 'style' );\r
                                }\r
 \r
-                               CKEDITOR.dom.element.setMarker( database, element, 'indent_processed', true );\r
+                               CKEDITOR.dom.element.setMarker( database, element, 'indent_processed', 1 );\r
                                return true;\r
                        }\r
 \r
                        var selection = editor.getSelection(),\r
-                               bookmarks = selection.createBookmarks( true ),\r
-                               ranges = selection && selection.getRanges( true ),\r
+                               bookmarks = selection.createBookmarks( 1 ),\r
+                               ranges = selection && selection.getRanges( 1 ),\r
                                range;\r
 \r
-                       var skipBookmarks = function( node )\r
-                       {\r
-                               return ! node.hasAttribute( '_cke_bookmark' );\r
-                       };\r
 \r
                        var iterator = ranges.createIterator();\r
                        while ( ( range = iterator.getNextRange() ) )\r
                        {\r
-                               // Do not indent body. (#6138)\r
-                               range.shrink( CKEDITOR.SHRINK_ELEMENT );\r
-                               if ( range.endContainer.getName() == 'body' )\r
-                                       range.setEndAt( range.endContainer.getLast( skipBookmarks ), CKEDITOR.POSITION_BEFORE_END );\r
-\r
-                               var startContainer = range.startContainer,\r
-                                       endContainer = range.endContainer,\r
-                                       rangeRoot = range.getCommonAncestor(),\r
+                               var rangeRoot = range.getCommonAncestor(),\r
                                        nearestListBlock = rangeRoot;\r
 \r
                                while ( nearestListBlock && !( nearestListBlock.type == CKEDITOR.NODE_ELEMENT &&\r
                                        listNodeNames[ nearestListBlock.getName() ] ) )\r
                                        nearestListBlock = nearestListBlock.getParent();\r
 \r
+                               // Avoid having selection enclose the entire list. (#6138)\r
+                               // [<ul><li>...</li></ul>] =><ul><li>[...]</li></ul>\r
+                               if ( !nearestListBlock )\r
+                               {\r
+                                       var selectedNode = range.getEnclosedNode();\r
+                                       if ( selectedNode\r
+                                               && selectedNode.type == CKEDITOR.NODE_ELEMENT\r
+                                               && selectedNode.getName() in listNodeNames)\r
+                                       {\r
+                                               range.setStartAt( selectedNode, CKEDITOR.POSITION_AFTER_START );\r
+                                               range.setEndAt( selectedNode, CKEDITOR.POSITION_BEFORE_END );\r
+                                               nearestListBlock = selectedNode;\r
+                                       }\r
+                               }\r
+\r
                                // Avoid selection anchors under list root.\r
                                // <ul>[<li>...</li>]</ul> =>   <ul><li>[...]</li></ul>\r
-                               if ( nearestListBlock && startContainer.type == CKEDITOR.NODE_ELEMENT\r
-                                       && startContainer.getName() in listNodeNames )\r
+                               if ( nearestListBlock && range.startContainer.type == CKEDITOR.NODE_ELEMENT\r
+                                       && range.startContainer.getName() in listNodeNames )\r
                                {\r
                                        var walker = new CKEDITOR.dom.walker( range );\r
                                        walker.evaluator = isListItem;\r
                                        range.startContainer = walker.next();\r
                                }\r
 \r
-                               if ( nearestListBlock && endContainer.type == CKEDITOR.NODE_ELEMENT\r
-                                       && endContainer.getName() in listNodeNames )\r
+                               if ( nearestListBlock && range.endContainer.type == CKEDITOR.NODE_ELEMENT\r
+                                       && range.endContainer.getName() in listNodeNames )\r
                                {\r
                                        walker = new CKEDITOR.dom.walker( range );\r
                                        walker.evaluator = isListItem;\r
                                        range.endContainer = walker.previous();\r
                                }\r
 \r
-                               if ( nearestListBlock  )\r
+                               if ( nearestListBlock )\r
                                {\r
-                                       var firstListItem = nearestListBlock.getFirst( function( node )\r
-                                               {\r
-                                                       return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
-                                               }),\r
+                                       var firstListItem = nearestListBlock.getFirst( isListItem ),\r
+                                               hasMultipleItems = !!firstListItem.getNext( isListItem ),\r
                                                rangeStart = range.startContainer,\r
                                                indentWholeList = firstListItem.equals( rangeStart ) || firstListItem.contains( rangeStart );\r
 \r
-                                       // Indent the entire list if  cursor is inside the first list item. (#3893)\r
-                                       if ( !( indentWholeList && indentElement( nearestListBlock ) ) )\r
-                                               indentList( nearestListBlock );\r
+                                       // Indent the entire list if cursor is inside the first list item. (#3893)\r
+                                       // Only do that for indenting or when using indent classes or when there is something to outdent. (#6141)\r
+                                       if ( !( indentWholeList &&\r
+                                               ( self.name == 'indent' || self.useIndentClasses || parseInt( nearestListBlock.getStyle( getIndentCssProperty( nearestListBlock ) ), 10 ) ) &&\r
+                                                       indentElement( nearestListBlock, !hasMultipleItems && firstListItem.getDirection() ) ) )\r
+                                                               indentList( nearestListBlock );\r
                                }\r
                                else\r
                                        indentBlock();\r
@@ -351,10 +349,8 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                init : function( editor )\r
                {\r
                        // Register commands.\r
-                       var indent = new indentCommand( editor, 'indent' ),\r
-                               outdent = new indentCommand( editor, 'outdent' );\r
-                       editor.addCommand( 'indent', indent );\r
-                       editor.addCommand( 'outdent', outdent );\r
+                       var indent = editor.addCommand( 'indent', new indentCommand( editor, 'indent' ) ),\r
+                               outdent = editor.addCommand( 'outdent', new indentCommand( editor, 'outdent' ) );\r
 \r
                        // Register the toolbar buttons.\r
                        editor.ui.addButton( 'Indent',\r
@@ -382,46 +378,81 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                                        "       padding-left: 40px;" +\r
                                        "}" );\r
                        }\r
-               },\r
 \r
-               requires : [ 'domiterator', 'list' ]\r
-       } );\r
-})();\r
+                       // Register dirChanged listener.\r
+                       editor.on( 'dirChanged', function( e )\r
+                       {\r
+                               var range = new CKEDITOR.dom.range( editor.document );\r
+                               range.setStartBefore( e.data.node );\r
+                               range.setEndAfter( e.data.node );\r
 \r
-CKEDITOR.tools.extend( CKEDITOR.config,\r
-       {\r
-               indentOffset : 40,\r
-               indentUnit : 'px',\r
-               indentClasses : null\r
-       });\r
+                               var walker = new CKEDITOR.dom.walker( range ),\r
+                                       node;\r
 \r
-/**\r
- * Size of each indentation step\r
- * @type Number\r
- * @example\r
- * config.indentOffset = 40;\r
- */\r
+                               while ( ( node = walker.next() ) )\r
+                               {\r
+                                       if ( node.type == CKEDITOR.NODE_ELEMENT )\r
+                                       {\r
+                                               // A child with the defined dir is to be ignored.\r
+                                               if ( !node.equals( e.data.node ) && node.getDirection() )\r
+                                               {\r
+                                                       range.setStartAfter( node );\r
+                                                       walker = new CKEDITOR.dom.walker( range );\r
+                                                       continue;\r
+                                               }\r
 \r
- /**\r
- * Unit for the indentation style\r
- * @type String\r
- * @example\r
- * config.indentUnit = 'px';\r
- */\r
+                                               // Switch alignment classes.\r
+                                               var classes = editor.config.indentClasses;\r
+                                               if ( classes )\r
+                                               {\r
+                                                       var suffix = ( e.data.dir == 'ltr' ) ? [ '_rtl', '' ] : [ '', '_rtl' ];\r
+                                                       for ( var i = 0; i < classes.length; i++ )\r
+                                                       {\r
+                                                               if ( node.hasClass( classes[ i ] + suffix[ 0 ] ) )\r
+                                                               {\r
+                                                                       node.removeClass( classes[ i ] + suffix[ 0 ] );\r
+                                                                       node.addClass( classes[ i ] + suffix[ 1 ] );\r
+                                                               }\r
+                                                       }\r
+                                               }\r
 \r
- /**\r
- * List of classes to use for indenting the contents.\r
- * @type Array\r
- * @example\r
- * // Don't use classes for indenting. (this is the default value)\r
- * config.indentClasses = null;\r
- * @example\r
- * // Use the classes 'Indent1', 'Indent2', 'Indent3'\r
- * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];\r
- */\r
+                                               // Switch the margins.\r
+                                               var marginLeft = node.getStyle( 'margin-right' ),\r
+                                                       marginRight = node.getStyle( 'margin-left' );\r
+\r
+                                               marginLeft ? node.setStyle( 'margin-left', marginLeft ) : node.removeStyle( 'margin-left' );\r
+                                               marginRight ? node.setStyle( 'margin-right', marginRight ) : node.removeStyle( 'margin-right' );\r
+                                       }\r
+                               }\r
+                       });\r
+\r
+                       editor.on( 'key', function( evt )\r
+                       {\r
+                               // Backspace at the beginning of  list item should outdent it.\r
+                               if ( editor.mode == 'wysiwyg' && evt.data.keyCode == 8 )\r
+                               {\r
+                                       var sel = editor.getSelection(),\r
+                                               range = sel.getRanges()[ 0 ],\r
+                                               li;\r
+\r
+                                       if ( range.collapsed &&\r
+                                                ( li = range.startContainer.getAscendant( 'li', 1 ) ) &&\r
+                                                range.checkBoundaryOfElement( li, CKEDITOR.START ) )\r
+                                       {\r
+                                               editor.execCommand( 'outdent' );\r
+                                               evt.cancel();\r
+                                       }\r
+                               }\r
+                       });\r
+               },\r
+\r
+               requires : [ 'domiterator', 'list' ]\r
+       } );\r
+})();\r
 \r
 /**\r
  * Size of each indentation step\r
+ * @name CKEDITOR.config.indentOffset\r
  * @type Number\r
  * @default 40\r
  * @example\r
@@ -430,6 +461,7 @@ CKEDITOR.tools.extend( CKEDITOR.config,
 \r
  /**\r
  * Unit for the indentation style\r
+ * @name CKEDITOR.config.indentUnit\r
  * @type String\r
  * @default 'px'\r
  * @example\r
@@ -439,8 +471,9 @@ CKEDITOR.tools.extend( CKEDITOR.config,
  /**\r
  * List of classes to use for indenting the contents. If it's null, no classes will be used\r
  * and instead the {@link #indentUnit} and {@link #indentOffset} properties will be used.\r
+ * @name CKEDITOR.config.indentClasses\r
  * @type Array\r
- * default null\r
+ * @default null\r
  * @example\r
  * // Use the classes 'Indent1', 'Indent2', 'Indent3'\r
  * config.indentClasses = ['Indent1', 'Indent2', 'Indent3'];\r