Changeset 869


Ignore:
Timestamp:
09/24/07 13:06:59 (8 years ago)
Author:
martinkou
Message:

Fix for #1178 and #1267 : Reimplemented the insert list/remove lists command from scratch without depending on buggy browser commands.

Location:
FCKeditor/trunk/editor/_source
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • FCKeditor/trunk/editor/_source/commandclasses/fckindentcommands.js

    r848 r869  
    229229
    230230                // Convert the list DOM tree into a one dimensional array.
    231                 var listArray = FCKDomTools.ListToArray( listNode, null, null, markerObj ) ;
     231                var listArray = FCKDomTools.ListToArray( listNode, markerObj ) ;
    232232
    233233                // Apply indenting or outdenting on the array.
  • FCKeditor/trunk/editor/_source/commandclasses/fcklistcommands.js

    r771 r869  
    2222 */
    2323
    24 var FCKListCommand = function( name )
     24var FCKListCommand = function( name, tagName )
    2525{
    2626        this.Name = name ;
     27        this.TagName = tagName ;
    2728}
    2829
     
    3132        GetState : function()
    3233        {
    33                 return FCK.GetNamedCommandState( this.Name ) ;
     34                // Disabled if not WYSIWYG.
     35                if ( FCK.EditMode != FCK_EDITMODE_WYSIWYG || ! FCK.EditorWindow )
     36                        return FCK_TRISTATE_DISABLED ;
     37
     38                // We'll use the style system's convention to determine list state here...
     39                // If the starting block is a descendant of an <ol> or <ul> node, then we're in a list.
     40                var startContainer = FCKSelection.GetBoundaryParentElement( true ) ;
     41                var listNode = FCKTools.GetElementAscensor( startContainer, this.TagName ) ;
     42                if ( listNode )
     43                        return FCK_TRISTATE_ON ;
     44                else
     45                        return FCK_TRISTATE_OFF ;
    3446        },
    3547
    3648        Execute : function()
    3749        {
    38                 if ( FCKBrowserInfo.IsIE && this.GetState() == FCK_TRISTATE_OFF )
    39                 {
    40                         // IE does not split selected <br> tags when it is making lists. (See #428)
    41                         // So, pre-split the blocks for IE.
    42                         var range = new FCKDomRange( FCK.EditorWindow ) ;
    43                         range.MoveToSelection() ;
    44                         var startNode = range._Range.startContainer ;
    45                         var endNode = range._Range.endContainer ;
    46                         if ( startNode.nodeType == 1 )
    47                         {
    48                                 if ( startNode.firstChild )
    49                                 {
    50                                         if ( startNode.childNodes.length <= range._Range.startOffset )
    51                                                 startNode = startNode.lastChild ;
    52                                         else
    53                                                 startNode = startNode.childNodes[ range._Range.startOffset ] ;
    54                                 }
    55                         }
    56                         if ( endNode.nodeType == 1 )
    57                         {
    58                                 if ( endNode.firstChild )
    59                                 {
    60                                         if ( endNode.childNodes.length <= range._Range.endOffset )
    61                                                 endNode = endNode.lastChild ;
    62                                         else
    63                                                 endNode = endNode.childNodes[ range._Range.endOffset ] ;
    64                                 }
    65                         }
    66 
    67                         var brNodes = [] ;
    68                         var curNode = startNode ;
    69                         while ( curNode && curNode != endNode )
    70                         {
    71                                 if ( curNode.nodeType == 1 && curNode.tagName.toLowerCase() == 'br' )
    72                                         brNodes.push( curNode ) ;
    73                                 curNode = FCKTools.GetNextNode( curNode ) ;
    74                         }
    75 
    76                         for ( var i = brNodes.length - 1 ; i >= 0 ; i-- )
    77                         {
    78                                 range.SetStart( brNodes[i], 3 ) ;
    79                                 range.SetEnd( brNodes[i], 3 ) ;
    80                                 brNodes[i].parentNode.removeChild( brNodes[i] ) ;
    81                                 range.SplitBlock() ;
    82                         }
    83 
    84                         range.SetStart( startNode, 1 ) ;
    85                         range.SetEnd( endNode, 4 ) ;
    86                         range.Select() ;
    87                 }
    88                 FCK.ExecuteNamedCommand( this.Name ) ;
     50                FCKUndo.SaveUndoStep() ;
     51
     52                var range = new FCKDomRange( FCK.EditorWindow ) ;
     53                range.MoveToSelection() ;
     54                var bookmark = range.CreateBookmark() ;
     55
     56                // Group the blocks up because there are many cases where multiple lists have to be created,
     57                // or multiple lists have to be cancelled.
     58                var listGroups = [] ;
     59                var markerObj = {} ;
     60                var iterator = new FCKDomRangeIterator( range ) ;
     61                var block ;
     62                var state = this.GetState() ;
     63                iterator.ForceBrBreak = ( state == FCK_TRISTATE_OFF ) ;
     64                var nextRangeExists = true ;
     65                var rangeQueue = null ;
     66                while ( nextRangeExists )
     67                {
     68                        while ( ( block = iterator.GetNextParagraph() ) )
     69                        {
     70                                var path = new FCKElementPath( block ) ;
     71                                var listNode = null ;
     72                                var processedFlag = false ;
     73
     74                                // First, try to group by a list ancestor.
     75                                for ( var i = path.Elements.length - 1 ; i >= 0 ; i-- )
     76                                {
     77                                        var el = path.Elements[i] ;
     78                                        if ( el.nodeName.IEquals( ['ol', 'ul'] ) )
     79                                        {
     80                                                var groupObj = el._FCK_ListGroupObject ;
     81                                                if ( groupObj )
     82                                                        groupObj.contents.push( block ) ;
     83                                                else
     84                                                {
     85                                                        groupObj = { 'root' : el, 'contents' : [ block ] } ;
     86                                                        listGroups.push( groupObj ) ;
     87                                                        FCKDomTools.SetElementMarker( markerObj, el, '_FCK_ListGroupObject', groupObj ) ;
     88                                                }
     89                                                processedFlag = true ;
     90                                                break ;
     91                                        }
     92                                }
     93
     94                                if ( processedFlag )
     95                                        continue ;
     96
     97                                // No list ancestor? Group by block limit.
     98                                var root = path.BlockLimit ;
     99                                if ( root._FCK_ListGroupObject )
     100                                        root._FCK_ListGroupObject.contents.push( block ) ;
     101                                else
     102                                {
     103                                        var groupObj = { 'root' : root, 'contents' : [ block ] } ;
     104                                        FCKDomTools.SetElementMarker( markerObj, root, '_FCK_ListGroupObject', groupObj ) ;
     105                                        listGroups.push( groupObj ) ;
     106                                }
     107                        }
     108
     109                        if ( FCKBrowserInfo.IsIE )
     110                                nextRangeExists = false ;
     111                        else
     112                        {
     113                                if ( rangeQueue == null )
     114                                {
     115                                        rangeQueue = [] ;
     116                                        var selectionObject = FCK.EditorWindow.getSelection() ;
     117                                        if ( selectionObject && listGroups.length == 0 )
     118                                                rangeQueue.push( selectionObject.getRangeAt( 0 ) ) ;
     119                                        for ( var i = 1 ; selectionObject && i < selectionObject.rangeCount ; i++ )
     120                                                rangeQueue.push( selectionObject.getRangeAt( i ) ) ;
     121                                }
     122                                if ( rangeQueue.length < 1 )
     123                                        nextRangeExists = false ;
     124                                else
     125                                {
     126                                        var internalRange = FCKW3CRange.CreateFromRange( FCK.EditorDocument, rangeQueue.shift() ) ;
     127                                        range._Range = internalRange ;
     128                                        range._UpdateElementInfo() ;
     129                                        if ( range.StartNode.nodeName.IEquals( 'td' ) )
     130                                                range.SetStart( range.StartNode, 1 ) ;
     131                                        if ( range.EndNode.nodeName.IEquals( 'td' ) )
     132                                                range.SetEnd( range.EndNode, 2 ) ;
     133                                        iterator = new FCKDomRangeIterator( range ) ;
     134                                        iterator.ForceBrBreak = ( state == FCK_TRISTATE_OFF ) ;
     135                                }
     136                        }
     137                }
     138
     139                // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element.
     140                // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking
     141                // at the group that's not rooted at lists. So we have three cases to handle.
     142                var listsCreated = [] ;
     143                while ( listGroups.length > 0 )
     144                {
     145                        var groupObj = listGroups.shift() ;
     146                        if ( state == FCK_TRISTATE_OFF )
     147                        {
     148                                if ( groupObj.root.nodeName.IEquals( ['ul', 'ol'] ) )
     149                                        this._ChangeListType( groupObj, markerObj, listsCreated ) ;
     150                                else
     151                                        this._CreateList( groupObj, listsCreated ) ;
     152                        }
     153                        else if ( state == FCK_TRISTATE_ON && groupObj.root.nodeName.IEquals( ['ul', 'ol'] ) )
     154                                this._RemoveList( groupObj, markerObj ) ;
     155                }
     156
     157                // For all new lists created, merge adjacent, same type lists.
     158                while ( listsCreated.length > 0 )
     159                {
     160                        var listNode = listsCreated.shift() ;
     161                        var stopFlag = false ;
     162                        var currentNode = listNode ;
     163                        while ( ! stopFlag )
     164                        {
     165                                currentNode = currentNode.nextSibling ;
     166                                if ( currentNode && currentNode.nodeType == 3 && currentNode.nodeValue.search( /^[\n\r\t ]*$/ ) == 0  )
     167                                        continue ;
     168                                stopFlag = true ;
     169                        }
     170                       
     171                        if ( currentNode && currentNode.nodeName.IEquals( this.TagName ) )
     172                        {
     173                                currentNode.parentNode.removeChild( currentNode ) ;
     174                                while ( currentNode.firstChild )
     175                                        listNode.appendChild( currentNode.removeChild( currentNode.firstChild ) ) ;
     176                        }
     177                }
     178
     179                // Clean up, restore selection and update toolbar button states.
     180                FCKDomTools.ClearElementMarkers( markerObj ) ;
     181                range.MoveToBookmark( bookmark ) ;
     182                range.Select() ;
     183                FCK.Events.FireEvent( 'OnSelectionChange' ) ;
     184        },
     185
     186        _ChangeListType : function( groupObj, markerObj, listsCreated )
     187        {
     188                // This case is easy...
     189                // 1. Convert the whole list into a one-dimensional array.
     190                // 2. Change the list type by modifying the array.
     191                // 3. Recreate the whole list by converting the array to a list.
     192                // 4. Replace the original list with the recreated list.
     193                var listArray = FCKDomTools.ListToArray( groupObj.root, markerObj ) ;
     194                var selectedListItems = [] ;
     195                for ( var i = 0 ; i < groupObj.contents.length ; i++ )
     196                {
     197                        var itemNode = groupObj.contents[i] ;
     198                        itemNode = FCKTools.GetElementAscensor( itemNode, 'li' ) ;
     199                        if ( ! itemNode || itemNode._FCK_ListItem_Processed )
     200                                continue ;
     201                        selectedListItems.push( itemNode ) ;
     202                        FCKDomTools.SetElementMarker( markerObj, itemNode, '_FCK_ListItem_Processed', true ) ;
     203                }
     204                var fakeParent = groupObj.root.ownerDocument.createElement( this.TagName ) ;
     205                for ( var i = 0 ; i < selectedListItems.length ; i++ )
     206                {
     207                        var listIndex = selectedListItems[i]._FCK_ListArray_Index ;
     208                        listArray[listIndex].parent = fakeParent ;
     209                }
     210                var newList = FCKDomTools.ArrayToList( listArray ) ;
     211                if ( newList.listNode.lastChild.nodeName.IEquals( this.TagName) )
     212                        listsCreated.push( newList.listNode.lastChild ) ;
     213                groupObj.root.parentNode.replaceChild( newList.listNode, groupObj.root ) ;
     214        },
     215
     216        _CreateList : function( groupObj, listsCreated )
     217        {
     218                var contents = groupObj.contents ;
     219                var doc = groupObj.root.ownerDocument ;
     220                var listContents = [] ;
     221
     222                // It is possible to have the contents returned by DomRangeIterator to be the same as the root.
     223                // e.g. when we're running into table cells.
     224                // In such a case, enclose the childNodes of contents[0] into a <div>.
     225                if ( contents.length == 1 && contents[0] == groupObj.root )
     226                {
     227                        var divBlock = doc.createElement( 'div' );
     228                        while ( contents[0].firstChild )
     229                                divBlock.appendChild( contents[0].removeChild( contents[0].firstChild ) ) ;
     230                        contents[0].appendChild( divBlock ) ;
     231                        contents[0] = divBlock ;
     232                }
     233
     234                // Calculate the common parent node of all content blocks.
     235                var commonParent = groupObj.contents[0].parentNode ;
     236                for ( var i = 0 ; i < contents.length ; i++ )
     237                        commonParent = FCKDomTools.GetCommonParents( commonParent, contents[i].parentNode ).pop() ;
     238
     239                // We want to insert things that are in the same tree level only, so calculate the contents again
     240                // by expanding the selected blocks to the same tree level.
     241                for ( var i = 0 ; i < contents.length ; i++ )
     242                {
     243                        var contentNode = contents[i] ;
     244                        while ( contentNode.parentNode )
     245                        {
     246                                if ( contentNode.parentNode == commonParent )
     247                                {
     248                                        listContents.push( contentNode ) ;
     249                                        break ;
     250                                }
     251                                contentNode = contentNode.parentNode ;
     252                        }
     253                }
     254
     255                if ( listContents.length < 1 )
     256                        return ;
     257
     258                // Insert the list to the DOM tree.
     259                var insertAnchor = listContents[listContents.length - 1].nextSibling ;
     260                var listNode = doc.createElement( this.TagName ) ;
     261                listsCreated.push( listNode ) ;
     262                while ( listContents.length )
     263                {
     264                        var contentBlock = listContents.shift() ;
     265                        var docFrag = doc.createDocumentFragment() ;
     266                        while ( contentBlock.firstChild )
     267                                docFrag.appendChild( contentBlock.removeChild( contentBlock.firstChild ) ) ;
     268                        contentBlock.parentNode.removeChild( contentBlock ) ;
     269                        var listItem = doc.createElement( 'li' ) ;
     270                        listItem.appendChild( docFrag ) ;
     271                        listNode.appendChild( listItem ) ;
     272                }
     273                commonParent.insertBefore( listNode, insertAnchor ) ;
     274        },
     275
     276        _RemoveList : function( groupObj, markerObj )
     277        {
     278                // This is very much like the change list type operation.
     279                // Except that we're changing the selected items' indent to -1 in the list array.
     280                var listArray = FCKDomTools.ListToArray( groupObj.root, markerObj ) ;
     281                var selectedListItems = [] ;
     282                for ( var i = 0 ; i < groupObj.contents.length ; i++ )
     283                {
     284                        var itemNode = groupObj.contents[i] ;
     285                        itemNode = FCKTools.GetElementAscensor( itemNode, 'li' ) ;
     286                        if ( ! itemNode || itemNode._FCK_ListItem_Processed )
     287                                continue ;
     288                        selectedListItems.push( itemNode ) ;
     289                        FCKDomTools.SetElementMarker( markerObj, itemNode, '_FCK_ListItem_Processed', true ) ;
     290                }
     291               
     292                var lastListIndex = null ;
     293                for ( var i = 0 ; i < selectedListItems.length ; i++ )
     294                {
     295                        var listIndex = selectedListItems[i]._FCK_ListArray_Index ;
     296                        listArray[listIndex].indent = -1 ;
     297                        lastListIndex = listIndex ;
     298                }
     299
     300                // After cutting parts of the list out with indent=-1, we still have to maintain the array list
     301                // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the
     302                // list cannot be converted back to a real DOM list.
     303                for ( var i = lastListIndex + 1; i < listArray.length ; i++ )
     304                {
     305                        if ( listArray[i].indent > listArray[i-1].indent + 1 )
     306                        {
     307                                var indentOffset = listArray[i-1].indent + 1 - listArray[i].indent ;
     308                                var oldIndent = listArray[i].indent ;
     309                                while ( listArray[i] && listArray[i].indent >= oldIndent)
     310                                {
     311                                        listArray[i].indent += indentOffset ;
     312                                        i++ ;
     313                                }
     314                                i-- ;
     315                        }
     316                }
     317
     318                var newList = FCKDomTools.ArrayToList( listArray ) ;
     319                // If groupObj.root is the last element in its parent, or its nextSibling is a <br>, then we should
     320                // not add a <br> after the final item. So, check for the cases and trim the <br>.
     321                if ( groupObj.root.nextSibling == null || groupObj.root.nextSibling.nodeName.IEquals( 'br' ) )
     322                {
     323                        if ( newList.listNode.lastChild.nodeName.IEquals( 'br' ) )
     324                                newList.listNode.removeChild( newList.listNode.lastChild ) ;
     325                }
     326                groupObj.root.parentNode.replaceChild( newList.listNode, groupObj.root ) ;
    89327        }
    90328};
  • FCKeditor/trunk/editor/_source/internals/fckcommands.js

    r850 r869  
    131131
    132132                case 'SelectAll'                        : oCommand = new FCKSelectAllCommand() ; break ;
    133                 case 'InsertOrderedList'        : oCommand = new FCKListCommand( 'insertorderedlist' ) ; break ;
    134                 case 'InsertUnorderedList'      : oCommand = new FCKListCommand( 'insertunorderedlist' ) ; break ;
     133                case 'InsertOrderedList'        : oCommand = new FCKListCommand( 'insertorderedlist', 'ol' ) ; break ;
     134                case 'InsertUnorderedList'      : oCommand = new FCKListCommand( 'insertunorderedlist', 'ul' ) ; break ;
    135135                case 'ShowBlocks' : oCommand = new FCKShowBlockCommand( 'ShowBlocks', FCKConfig.StartupShowBlocks ? FCK_TRISTATE_ON : FCK_TRISTATE_OFF ) ; break ;
    136136
  • FCKeditor/trunk/editor/_source/internals/fckdomtools.js

    r865 r869  
    610610        // This operation should be non-intrusive in the sense that it does not change the DOM tree,
    611611        // with the exception that it may add some markers to the list item nodes when markerObj is specified.
    612         ListToArray : function( listNode, baseArray, baseIndentLevel, markerObj )
     612        ListToArray : function( listNode, markerObj, baseArray, baseIndentLevel, grandparentNode )
    613613        {
    614614                if ( ! listNode.nodeName.IEquals( ['ul', 'ol'] ) )
     
    625625                        if ( ! listItem.nodeName.IEquals( 'li' ) )
    626626                                continue ;
    627                         var itemObj = { 'grandparent' : listNode.parentNode, 'parent' : listNode, 'indent' : baseIndentLevel, 'contents' : [] } ;
    628                         if ( itemObj.grandparent && itemObj.grandparent.nodeName.IEquals( 'li' ) )
    629                                 itemObj.grandparent = itemObj.grandparent.parentNode ;
     627                        var itemObj = { 'parent' : listNode, 'indent' : baseIndentLevel, 'contents' : [] } ;
     628                        if ( ! grandparentNode )
     629                        {
     630                                itemObj.grandparent = listNode.parentNode ;
     631                                if ( itemObj.grandparent && itemObj.grandparent.nodeName.IEquals( 'li' ) )
     632                                        itemObj.grandparent = itemObj.grandparent.parentNode ;
     633                        }
     634                        else
     635                                itemObj.grandparent = grandparentNode ;
    630636                        if ( markerObj )
    631637                                this.SetElementMarker( markerObj, listItem, '_FCK_ListArray_Index', baseArray.length ) ;
     
    637643                                        // Note the recursion here, it pushes inner list items with +1 indentation in the correct
    638644                                        // order.
    639                                         this.ListToArray( child, baseArray, baseIndentLevel + 1, markerObj ) ;
     645                                        this.ListToArray( child, markerObj, baseArray, baseIndentLevel + 1, itemObj.grandparent ) ;
    640646                                else
    641647                                        itemObj.contents.push( child ) ;
     
    687693                                else
    688694                                {
    689                                         if ( FCKConfig.EnterMode.IEquals( ['div', 'p'] ) )
     695                                        if ( FCKConfig.EnterMode.IEquals( ['div', 'p'] ) && ! item.grandparent.nodeName.IEquals( 'td' ) )
    690696                                                currentListItem = doc.createElement( FCKConfig.EnterMode ) ;
    691697                                        else
Note: See TracChangeset for help on using the changeset viewer.
© 2003 – 2015 CKSource – Frederico Knabben. All rights reserved. | Terms of use | Privacy policy