| 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 ) ; |