Changeset 869


Ignore:
Timestamp:
09/24/2007 01:06:59 PM (7 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 – 2012 CKSource – Frederico Knabben. All rights reserved. | Terms of use | Privacy policy