JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
vanilla ckeditor-3.4b
[ckeditor.git] / _source / plugins / indent / plugin.js
index 80fc71d..e60a29e 100644 (file)
@@ -53,7 +53,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                }\r
                else\r
                {\r
-                       var indent = parseInt( firstBlock.getStyle( this.indentCssProperty ), 10 );\r
+                       var indent = parseInt( firstBlock.getStyle( getIndentCssProperty( firstBlock ) ), 10 );\r
                        if ( isNaN( indent ) )\r
                                indent = 0;\r
                        if ( indent <= 0 )\r
@@ -62,260 +62,274 @@ For licensing, see LICENSE.html or http://ckeditor.com/license
                }\r
        }\r
 \r
-       function indentList( editor, range, listNode )\r
+       function indentCommand( editor, name )\r
        {\r
-               // Our starting and ending points of the range might be inside some blocks under a list item...\r
-               // So before playing with the iterator, we need to expand the block to include the list items.\r
-               var startContainer = range.startContainer,\r
-                       endContainer = range.endContainer;\r
-               while ( startContainer && !startContainer.getParent().equals( listNode ) )\r
-                       startContainer = startContainer.getParent();\r
-               while ( endContainer && !endContainer.getParent().equals( listNode ) )\r
-                       endContainer = endContainer.getParent();\r
-\r
-               if ( !startContainer || !endContainer )\r
-                       return;\r
-\r
-               // Now we can iterate over the individual items on the same tree depth.\r
-               var block = startContainer,\r
-                       itemsToMove = [],\r
-                       stopFlag = false;\r
-               while ( !stopFlag )\r
-               {\r
-                       if ( block.equals( endContainer ) )\r
-                               stopFlag = true;\r
-                       itemsToMove.push( block );\r
-                       block = block.getNext();\r
-               }\r
-               if ( itemsToMove.length < 1 )\r
-                       return;\r
-\r
-               // Do indent or outdent operations on the array model of the list, not the\r
-               // list's DOM tree itself. The array model demands that it knows as much as\r
-               // possible about the surrounding lists, we need to feed it the further\r
-               // ancestor node that is still a list.\r
-               var listParents = listNode.getParents( true );\r
-               for ( var i = 0 ; i < listParents.length ; i++ )\r
+               this.name = name;\r
+               this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0;\r
+               if ( this.useIndentClasses )\r
                {\r
-                       if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] )\r
-                       {\r
-                               listNode = listParents[i];\r
-                               break;\r
-                       }\r
+                       this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' );\r
+                       this.indentClassMap = {};\r
+                       for ( var i = 0 ; i < editor.config.indentClasses.length ; i++ )\r
+                               this.indentClassMap[ editor.config.indentClasses[i] ] = i + 1;\r
                }\r
-               var indentOffset = this.name == 'indent' ? 1 : -1,\r
-                       startItem = itemsToMove[0],\r
-                       lastItem = itemsToMove[ itemsToMove.length - 1 ],\r
-                       database = {};\r
-\r
-               // Convert the list DOM tree into a one dimensional array.\r
-               var listArray = CKEDITOR.plugins.list.listToArray( listNode, database );\r
 \r
-               // Apply indenting or outdenting on the array.\r
-               var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;\r
-               for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ )\r
-               {\r
-                       listArray[ i ].indent += indentOffset;\r
-                       // Make sure the newly created sublist get a brand-new element of the same type. (#5372)\r
-                       var listRoot = listArray[ i ].parent;\r
-                       listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() );\r
-               }\r
+               this.startDisabled = name == 'outdent';\r
+       }\r
 \r
-               for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ;\r
-                               i < listArray.length && listArray[i].indent > baseIndent ; i++ )\r
-                       listArray[i].indent += indentOffset;\r
+       // Returns the CSS property to be used for identing a given element.\r
+       function getIndentCssProperty( element )\r
+       {\r
+               return element.getComputedStyle( 'direction' ) == 'ltr' ? 'margin-left' : 'margin-right';\r
+       }\r
 \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 newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 );\r
+       function isListItem( node )\r
+       {\r
+               return node.type = CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
+       }\r
 \r
-               // Avoid nested <li> after outdent even they're visually same,\r
-               // recording them for later refactoring.(#3982)\r
-               if ( this.name == 'outdent' )\r
+       indentCommand.prototype = {\r
+               exec : function( editor )\r
                {\r
-                       var parentLiElement;\r
-                       if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) )\r
-                       {\r
-                               var children = newList.listNode.getChildren(),\r
-                                       pendingLis = [],\r
-                                       count = children.count(),\r
-                                       child;\r
+                       var self = this, database = {};\r
 \r
-                               for ( i = count - 1 ; i >= 0 ; i-- )\r
+                       function indentList( listNode )\r
+                       {\r
+                               // Our starting and ending points of the range might be inside some blocks under a list item...\r
+                               // So before playing with the iterator, we need to expand the block to include the list items.\r
+                               var startContainer = range.startContainer,\r
+                                       endContainer = range.endContainer;\r
+                               while ( startContainer && !startContainer.getParent().equals( listNode ) )\r
+                                       startContainer = startContainer.getParent();\r
+                               while ( endContainer && !endContainer.getParent().equals( listNode ) )\r
+                                       endContainer = endContainer.getParent();\r
+\r
+                               if ( !startContainer || !endContainer )\r
+                                       return;\r
+\r
+                               // Now we can iterate over the individual items on the same tree depth.\r
+                               var block = startContainer,\r
+                                       itemsToMove = [],\r
+                                       stopFlag = false;\r
+                               while ( !stopFlag )\r
                                {\r
-                                       if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' )  )\r
-                                               pendingLis.push( child );\r
+                                       if ( block.equals( endContainer ) )\r
+                                               stopFlag = true;\r
+                                       itemsToMove.push( block );\r
+                                       block = block.getNext();\r
                                }\r
-                       }\r
-               }\r
-\r
-               if ( newList )\r
-                       newList.listNode.replace( listNode );\r
+                               if ( itemsToMove.length < 1 )\r
+                                       return;\r
+\r
+                               // Do indent or outdent operations on the array model of the list, not the\r
+                               // list's DOM tree itself. The array model demands that it knows as much as\r
+                               // possible about the surrounding lists, we need to feed it the further\r
+                               // ancestor node that is still a list.\r
+                               var listParents = listNode.getParents( true );\r
+                               for ( var i = 0 ; i < listParents.length ; i++ )\r
+                               {\r
+                                       if ( listParents[i].getName && listNodeNames[ listParents[i].getName() ] )\r
+                                       {\r
+                                               listNode = listParents[i];\r
+                                               break;\r
+                                       }\r
+                               }\r
+                               var indentOffset = self.name == 'indent' ? 1 : -1,\r
+                                       startItem = itemsToMove[0],\r
+                                       lastItem = itemsToMove[ itemsToMove.length - 1 ];\r
 \r
-               // Move the nested <li> to be appeared after the parent.\r
-               if ( pendingLis && pendingLis.length )\r
-               {\r
-                       for (  i = 0; i < pendingLis.length ; i++ )\r
-                       {\r
-                               var li = pendingLis[ i ],\r
-                                       followingList = li;\r
+                               // Convert the list DOM tree into a one dimensional array.\r
+                               var listArray = CKEDITOR.plugins.list.listToArray( listNode, database );\r
 \r
-                               // Nest preceding <ul>/<ol> inside current <li> if any.\r
-                               while ( ( followingList = followingList.getNext() ) &&\r
-                                          followingList.is &&\r
-                                          followingList.getName() in listNodeNames )\r
+                               // Apply indenting or outdenting on the array.\r
+                               var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent;\r
+                               for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ )\r
                                {\r
-                                       // IE requires a filler NBSP for nested list inside empty list item,\r
-                                       // otherwise the list item will be inaccessiable. (#4476)\r
-                                       if ( CKEDITOR.env.ie && !li.getFirst( function( node ){ return isNotWhitespaces( node ) && isNotBookmark( node ); } ) )\r
-                                               li.append( range.document.createText( '\u00a0' ) );\r
-\r
-                                       li.append( followingList );\r
+                                       listArray[ i ].indent += indentOffset;\r
+                                       // Make sure the newly created sublist get a brand-new element of the same type. (#5372)\r
+                                       var listRoot = listArray[ i ].parent;\r
+                                       listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() );\r
                                }\r
 \r
-                               li.insertAfter( parentLiElement );\r
-                       }\r
-               }\r
-\r
-               // Clean up the markers.\r
-               CKEDITOR.dom.element.clearAllMarkers( database );\r
-       }\r
+                               for ( i = lastItem.getCustomData( 'listarray_index' ) + 1 ;\r
+                                               i < listArray.length && listArray[i].indent > baseIndent ; i++ )\r
+                                       listArray[i].indent += indentOffset;\r
 \r
-       function indentBlock( editor, range )\r
-       {\r
-               var iterator = range.createIterator(),\r
-                       enterMode = editor.config.enterMode;\r
-               iterator.enforceRealBlocks = true;\r
-               iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;\r
-               var block;\r
-               while ( ( block = iterator.getNextParagraph() ) )\r
-                       indentElement.call( this, editor, block );\r
-       }\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 newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, 0 );\r
 \r
-       function indentElement( editor, element )\r
-               {\r
-                       if ( this.useIndentClasses )\r
-                       {\r
-                               // Transform current class name to indent step index.\r
-                       var indentClass = element.$.className.match( this.classNameRegex ),\r
-                                       indentStep = 0;\r
-                               if ( indentClass )\r
+                               // Avoid nested <li> after outdent even they're visually same,\r
+                               // recording them for later refactoring.(#3982)\r
+                               if ( self.name == 'outdent' )\r
                                {\r
-                                       indentClass = indentClass[1];\r
-                                       indentStep = this.indentClassMap[ indentClass ];\r
+                                       var parentLiElement;\r
+                                       if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) )\r
+                                       {\r
+                                               var children = newList.listNode.getChildren(),\r
+                                                       pendingLis = [],\r
+                                                       count = children.count(),\r
+                                                       child;\r
+\r
+                                               for ( i = count - 1 ; i >= 0 ; i-- )\r
+                                               {\r
+                                                       if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' )  )\r
+                                                               pendingLis.push( child );\r
+                                               }\r
+                                       }\r
                                }\r
 \r
-                               // Operate on indent step index, transform indent step index back to class\r
-                               // name.\r
-                               if ( this.name == 'outdent' )\r
-                                       indentStep--;\r
-                               else\r
-                                       indentStep++;\r
-\r
-                       if ( indentStep < 0 )\r
-                               return false;\r
+                               if ( newList )\r
+                                       newList.listNode.replace( listNode );\r
 \r
-                               indentStep = Math.min( indentStep, editor.config.indentClasses.length );\r
-                               indentStep = Math.max( indentStep, 0 );\r
-                       var className = CKEDITOR.tools.ltrim( element.$.className.replace( this.classNameRegex, '' ) );\r
-                               if ( indentStep < 1 )\r
-                               element.$.className = className;\r
-                               else\r
-                               element.addClass( editor.config.indentClasses[ indentStep - 1 ] );\r
+                               // Move the nested <li> to be appeared after the parent.\r
+                               if ( pendingLis && pendingLis.length )\r
+                               {\r
+                                       for (  i = 0; i < pendingLis.length ; i++ )\r
+                                       {\r
+                                               var li = pendingLis[ i ],\r
+                                                       followingList = li;\r
+\r
+                                               // Nest preceding <ul>/<ol> inside current <li> if any.\r
+                                               while ( ( followingList = followingList.getNext() ) &&\r
+                                                          followingList.is &&\r
+                                                          followingList.getName() in listNodeNames )\r
+                                               {\r
+                                                       // IE requires a filler NBSP for nested list inside empty list item,\r
+                                                       // otherwise the list item will be inaccessiable. (#4476)\r
+                                                       if ( CKEDITOR.env.ie && !li.getFirst( function( node ){ return isNotWhitespaces( node ) && isNotBookmark( node ); } ) )\r
+                                                               li.append( range.document.createText( '\u00a0' ) );\r
+\r
+                                                       li.append( followingList );\r
+                                               }\r
+\r
+                                               li.insertAfter( parentLiElement );\r
+                                       }\r
+                               }\r
                        }\r
-                       else\r
+\r
+                       function indentBlock()\r
                        {\r
-                       var currentOffset = parseInt( element.getStyle( this.indentCssProperty ), 10 );\r
-                               if ( isNaN( currentOffset ) )\r
-                                       currentOffset = 0;\r
-                               currentOffset += ( this.name == 'indent' ? 1 : -1 ) * editor.config.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( this.indentCssProperty, currentOffset ? currentOffset + editor.config.indentUnit : '' );\r
-                       if ( element.getAttribute( 'style' ) === '' )\r
-                               element.removeAttribute( 'style' );\r
+                               var iterator = range.createIterator(),\r
+                                       enterMode = editor.config.enterMode;\r
+                               iterator.enforceRealBlocks = true;\r
+                               iterator.enlargeBr = enterMode != CKEDITOR.ENTER_BR;\r
+                               var block;\r
+                               while ( ( block = iterator.getNextParagraph() ) )\r
+                                       indentElement( block );\r
                        }\r
 \r
-               return true;\r
-               }\r
+                       function indentElement( element )\r
+                       {\r
+                               if ( element.getCustomData( 'indent_processed' ) )\r
+                                       return false;\r
 \r
-       function indentCommand( editor, name )\r
-       {\r
-               this.name = name;\r
-               this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0;\r
-               if ( this.useIndentClasses )\r
-               {\r
-                       this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' );\r
-                       this.indentClassMap = {};\r
-                       for ( var i = 0 ; i < editor.config.indentClasses.length ; i++ )\r
-                               this.indentClassMap[ editor.config.indentClasses[i] ] = i + 1;\r
-               }\r
-               else\r
-                       this.indentCssProperty = editor.config.contentsLangDirection == 'ltr' ? 'margin-left' : 'margin-right';\r
-               this.startDisabled = name == 'outdent';\r
-       }\r
+                               if ( self.useIndentClasses )\r
+                               {\r
+                                       // Transform current class name to indent step index.\r
+                                       var indentClass = element.$.className.match( self.classNameRegex ),\r
+                                                       indentStep = 0;\r
+                                       if ( indentClass )\r
+                                       {\r
+                                               indentClass = indentClass[1];\r
+                                               indentStep = self.indentClassMap[ indentClass ];\r
+                                       }\r
+\r
+                                       // Operate on indent step index, transform indent step index back to class\r
+                                       // name.\r
+                                       if ( self.name == 'outdent' )\r
+                                               indentStep--;\r
+                                       else\r
+                                               indentStep++;\r
+\r
+                                       if ( indentStep < 0 )\r
+                                               return false;\r
+\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.addClass( editor.config.indentClasses[ indentStep - 1 ] );\r
+                               }\r
+                               else\r
+                               {\r
+                                       var indentCssProperty = getIndentCssProperty( element );\r
+                                       var currentOffset = parseInt( element.getStyle( indentCssProperty ), 10 );\r
+                                       if ( isNaN( currentOffset ) )\r
+                                               currentOffset = 0;\r
+                                       currentOffset += ( self.name == 'indent' ? 1 : -1 ) * editor.config.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
+                                       if ( element.getAttribute( 'style' ) === '' )\r
+                                               element.removeAttribute( 'style' );\r
+                               }\r
 \r
-       function isListItem( node )\r
-       {\r
-               return node.type = CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
-       }\r
+                               CKEDITOR.dom.element.setMarker( database, element, 'indent_processed', true );\r
+                               return true;\r
+                       }\r
 \r
-       indentCommand.prototype = {\r
-               exec : function( editor )\r
-               {\r
                        var selection = editor.getSelection(),\r
-                               range = selection && selection.getRanges()[0];\r
-\r
-                       var startContainer = range.startContainer,\r
-                               endContainer = range.endContainer,\r
-                               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 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
-                       {\r
-                               var walker = new CKEDITOR.dom.walker( range );\r
-                               walker.evaluator = isListItem;\r
-                               range.startContainer = walker.next();\r
-                       }\r
+                               bookmarks = selection.createBookmarks( true ),\r
+                               ranges = selection && selection.getRanges( true ),\r
+                               range;\r
 \r
-                       if ( nearestListBlock && endContainer.type == CKEDITOR.NODE_ELEMENT\r
-                               && endContainer.getName() in listNodeNames )\r
+                       var iterator = ranges.createIterator();\r
+                       while ( ( range = iterator.getNextRange() ) )\r
                        {\r
-                               walker = new CKEDITOR.dom.walker( range );\r
-                               walker.evaluator = isListItem;\r
-                               range.endContainer = walker.previous();\r
-                       }\r
+                               var startContainer = range.startContainer,\r
+                                       endContainer = range.endContainer,\r
+                                       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 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
+                               {\r
+                                       var walker = new CKEDITOR.dom.walker( range );\r
+                                       walker.evaluator = isListItem;\r
+                                       range.startContainer = walker.next();\r
+                               }\r
 \r
-                       var bookmarks = selection.createBookmarks( true );\r
+                               if ( nearestListBlock && endContainer.type == CKEDITOR.NODE_ELEMENT\r
+                                       && 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
-                       {\r
-                               var firstListItem = nearestListBlock.getFirst( function( node )\r
-                                       {\r
-                                               return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' );\r
-                                       }),\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.call( this, editor, nearestListBlock ) ) )\r
-                               indentList.call( this, editor, range, 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
+                                               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
+                               }\r
+                               else\r
+                                       indentBlock();\r
                        }\r
-                       else\r
-                               indentBlock.call( this, editor, range );\r
 \r
-                       editor.focus();\r
+                       // Clean up the markers.\r
+                       CKEDITOR.dom.element.clearAllMarkers( database );\r
+\r
                        editor.forceNextSelectionCheck();\r
                        selection.selectBookmarks( bookmarks );\r
                }\r