| | 1 | /* |
| | 2 | Copyright (c) 2003-2009, CKSource - Frederico Knabben. All rights reserved. |
| | 3 | For licensing, see LICENSE.html or http://ckeditor.com/license |
| | 4 | */ |
| | 5 | |
| | 6 | /** |
| | 7 | * @file Insert and remove numbered and bulleted lists. |
| | 8 | */ |
| | 9 | |
| | 10 | (function() |
| | 11 | { |
| | 12 | var listNodeNames = { ol : 1, ul : 1 }, |
| | 13 | emptyTextRegex = /^[\n\r\t ]*$/; |
| | 14 | |
| | 15 | CKEDITOR.plugins.list = { |
| | 16 | /* |
| | 17 | * Convert a DOM list tree into a data structure that is easier to |
| | 18 | * manipulate. This operation should be non-intrusive in the sense that it |
| | 19 | * does not change the DOM tree, with the exception that it may add some |
| | 20 | * markers to the list item nodes when database is specified. |
| | 21 | */ |
| | 22 | listToArray : function( listNode, database, baseArray, baseIndentLevel, grandparentNode ) |
| | 23 | { |
| | 24 | if ( !listNodeNames[ listNode.getName() ] ) |
| | 25 | return []; |
| | 26 | |
| | 27 | if ( !baseIndentLevel ) |
| | 28 | baseIndentLevel = 0; |
| | 29 | if ( !baseArray ) |
| | 30 | baseArray = []; |
| | 31 | |
| | 32 | // Iterate over all list items to get their contents and look for inner lists. |
| | 33 | for ( var i = 0, count = listNode.getChildCount() ; i < count ; i++ ) |
| | 34 | { |
| | 35 | var listItem = listNode.getChild( i ); |
| | 36 | |
| | 37 | // It may be a text node or some funny stuff. |
| | 38 | if ( listItem.$.nodeName.toLowerCase() != 'li' ) |
| | 39 | continue; |
| | 40 | var itemObj = { 'parent' : listNode, indent : baseIndentLevel, contents : [] }; |
| | 41 | if ( !grandparentNode ) |
| | 42 | { |
| | 43 | itemObj.grandparent = listNode.getParent(); |
| | 44 | if ( itemObj.grandparent && itemObj.grandparent.$.nodeName.toLowerCase() == 'li' ) |
| | 45 | itemObj.grandparent = itemObj.grandparent.getParent(); |
| | 46 | } |
| | 47 | else |
| | 48 | itemObj.grandparent = grandparentNode; |
| | 49 | |
| | 50 | if ( database ) |
| | 51 | CKEDITOR.dom.element.setMarker( database, listItem, 'listarray_index', baseArray.length ); |
| | 52 | baseArray.push( itemObj ); |
| | 53 | |
| | 54 | for ( var j = 0, itemChildCount = listItem.getChildCount() ; j < itemChildCount ; j++ ) |
| | 55 | { |
| | 56 | var child = listItem.getChild( j ); |
| | 57 | if ( child.type == CKEDITOR.NODE_ELEMENT && listNodeNames[ child.getName() ] ) |
| | 58 | // Note the recursion here, it pushes inner list items with |
| | 59 | // +1 indentation in the correct order. |
| | 60 | CKEDITOR.plugins.list.listToArray( child, database, baseArray, baseIndentLevel + 1, itemObj.grandparent ); |
| | 61 | else |
| | 62 | itemObj.contents.push( child ); |
| | 63 | } |
| | 64 | } |
| | 65 | return baseArray; |
| | 66 | }, |
| | 67 | |
| | 68 | // Convert our internal representation of a list back to a DOM forest. |
| | 69 | arrayToList : function( listArray, database, baseIndex, paragraphMode ) |
| | 70 | { |
| | 71 | if ( !baseIndex ) |
| | 72 | baseIndex = 0; |
| | 73 | if ( !listArray || listArray.length < baseIndex + 1 ) |
| | 74 | return null; |
| | 75 | var doc = listArray[ baseIndex ].parent.getDocument(), |
| | 76 | retval = new CKEDITOR.dom.documentFragment( doc ), |
| | 77 | rootNode = null, |
| | 78 | currentIndex = baseIndex, |
| | 79 | indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ), |
| | 80 | currentListItem = null; |
| | 81 | while ( true ) |
| | 82 | { |
| | 83 | var item = listArray[ currentIndex ]; |
| | 84 | if ( item.indent == indentLevel ) |
| | 85 | { |
| | 86 | if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() ) |
| | 87 | { |
| | 88 | rootNode = listArray[ currentIndex ].parent.clone( false ); |
| | 89 | retval.append( rootNode ); |
| | 90 | } |
| | 91 | currentListItem = rootNode.append( doc.createElement( 'li' ) ); |
| | 92 | for ( var i = 0 ; i < item.contents.length ; i++ ) |
| | 93 | currentListItem.append( item.contents[i].clone( true ) ); |
| | 94 | currentIndex++; |
| | 95 | } |
| | 96 | else if ( item.indent == Math.max( indentLevel, 0 ) + 1 ) |
| | 97 | { |
| | 98 | var listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode ); |
| | 99 | currentListItem.append( listData.listNode ); |
| | 100 | currentIndex = listData.nextIndex; |
| | 101 | } |
| | 102 | else if ( item.indent == -1 && baseIndex == 0 && item.grandparent ) |
| | 103 | { |
| | 104 | var currentListItem; |
| | 105 | if ( listNodeNames[ item.grandparent.getName() ] ) |
| | 106 | currentListItem = doc.createElement( 'li' ); |
| | 107 | else |
| | 108 | { |
| | 109 | if ( paragraphMode != 'br' && item.grandparent.getName() != 'td' ) |
| | 110 | currentListItem = doc.createElement( paragraphMode ); |
| | 111 | else |
| | 112 | currentListItem = new CKEDITOR.dom.documentFragment( doc ); |
| | 113 | } |
| | 114 | |
| | 115 | for ( var i = 0 ; i < item.contents.length ; i++ ) |
| | 116 | currentListItem.append( item.contents[i].clone( true ) ); |
| | 117 | |
| | 118 | if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT ) |
| | 119 | { |
| | 120 | if ( currentListItem.getLast() |
| | 121 | && currentListItem.getLast().type == CKEDITOR.NODE_ELEMENT |
| | 122 | && currentListItem.getLast().getAttribute( 'type' ) == '_moz' ) |
| | 123 | currentListItem.getLast().remove(); |
| | 124 | currentListItem.append( doc.createElement( 'br' ) ); |
| | 125 | } |
| | 126 | |
| | 127 | if ( currentListItem.getName() == paragraphMode && currentListItem.$.firstChild ) |
| | 128 | { |
| | 129 | currentListItem.trim(); |
| | 130 | var firstChild = currentListItem.getFirst(); |
| | 131 | if ( firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.isBlockBoundary() ) |
| | 132 | { |
| | 133 | var tmp = new CKEDITOR.dom.documentFragment( doc ); |
| | 134 | currentListItem.moveChildren( tmp ); |
| | 135 | currentListItem = tmp; |
| | 136 | } |
| | 137 | } |
| | 138 | |
| | 139 | var currentListItemName = currentListItem.$.nodeName.toLowerCase(); |
| | 140 | if ( !CKEDITOR.env.ie && currentListItemName == 'div' || currentListItemName == 'p' ) |
| | 141 | currentListItem.append( doc.createElement( 'br' ) ); |
| | 142 | retval.append( currentListItem ); |
| | 143 | rootNode = null; |
| | 144 | currentIndex++; |
| | 145 | } |
| | 146 | else |
| | 147 | return null; |
| | 148 | |
| | 149 | if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel ) |
| | 150 | break; |
| | 151 | } |
| | 152 | |
| | 153 | // Clear marker attributes for the new list tree made of cloned nodes, if any. |
| | 154 | if ( database ) |
| | 155 | { |
| | 156 | var currentNode = retval.getFirst(); |
| | 157 | while ( currentNode ) |
| | 158 | { |
| | 159 | if ( currentNode.type == CKEDITOR.NODE_ELEMENT ) |
| | 160 | CKEDITOR.dom.element.clearMarkers( database, currentNode ); |
| | 161 | currentNode = currentNode.getNextSourceNode(); |
| | 162 | } |
| | 163 | } |
| | 164 | |
| | 165 | return { listNode : retval, nextIndex : currentIndex }; |
| | 166 | } |
| | 167 | }; |
| | 168 | |
| | 169 | function setState( editor, state ) |
| | 170 | { |
| | 171 | var command = editor.getCommand( this.name ); |
| | 172 | command.state = state; |
| | 173 | command.fire( 'state' ); |
| | 174 | } |
| | 175 | |
| | 176 | function onSelectionChange( evt ) |
| | 177 | { |
| | 178 | var elements = evt.data.path.elements; |
| | 179 | |
| | 180 | for ( var i = 0 ; i < elements.length ; i++ ) |
| | 181 | { |
| | 182 | if ( listNodeNames[ elements[i].getName() ] ) |
| | 183 | return setState.call( this, evt.editor, |
| | 184 | this.type == elements[i].getName() ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); |
| | 185 | } |
| | 186 | |
| | 187 | setState.call( this, evt.editor, CKEDITOR.TRISTATE_OFF ); |
| | 188 | } |
| | 189 | |
| | 190 | function changeListType( editor, groupObj, database, listsCreated ) |
| | 191 | { |
| | 192 | // This case is easy... |
| | 193 | // 1. Convert the whole list into a one-dimensional array. |
| | 194 | // 2. Change the list type by modifying the array. |
| | 195 | // 3. Recreate the whole list by converting the array to a list. |
| | 196 | // 4. Replace the original list with the recreated list. |
| | 197 | var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ), |
| | 198 | selectedListItems = []; |
| | 199 | |
| | 200 | for ( var i = 0 ; i < groupObj.contents.length ; i++ ) |
| | 201 | { |
| | 202 | var itemNode = groupObj.contents[i]; |
| | 203 | itemNode = itemNode.getAscendant( 'li', true ); |
| | 204 | if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) ) |
| | 205 | continue; |
| | 206 | selectedListItems.push( itemNode ); |
| | 207 | CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true ); |
| | 208 | } |
| | 209 | |
| | 210 | var fakeParent = groupObj.root.getDocument().createElement( this.type ); |
| | 211 | for ( var i = 0 ; i < selectedListItems.length ; i++ ) |
| | 212 | { |
| | 213 | var listIndex = selectedListItems[i].getCustomData( 'listarray_index' ); |
| | 214 | listArray[listIndex].parent = fakeParent; |
| | 215 | } |
| | 216 | var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode ); |
| | 217 | for ( var i = 0, length = newList.listNode.getChildCount(), child ; |
| | 218 | i < length && ( child = newList.listNode.getChild( i ) ) ; i++ ) |
| | 219 | { |
| | 220 | if ( child.getName() == this.type ) |
| | 221 | listsCreated.push( child ); |
| | 222 | } |
| | 223 | newList.listNode.replace( groupObj.root ); |
| | 224 | } |
| | 225 | |
| | 226 | function createList( editor, groupObj, listsCreated ) |
| | 227 | { |
| | 228 | var contents = groupObj.contents, |
| | 229 | doc = groupObj.root.getDocument(), |
| | 230 | listContents = []; |
| | 231 | |
| | 232 | // It is possible to have the contents returned by DomRangeIterator to be the same as the root. |
| | 233 | // e.g. when we're running into table cells. |
| | 234 | // In such a case, enclose the childNodes of contents[0] into a <div>. |
| | 235 | if ( contents.length == 1 && contents[0].equals( groupObj.root ) ) |
| | 236 | { |
| | 237 | var divBlock = doc.createElement( 'div' ); |
| | 238 | contents[0].moveChildren && contents[0].moveChildren( divBlock ); |
| | 239 | contents[0].append( divBlock ); |
| | 240 | contents[0] = divBlock; |
| | 241 | } |
| | 242 | |
| | 243 | // Calculate the common parent node of all content blocks. |
| | 244 | var commonParent = groupObj.contents[0].getParent(); |
| | 245 | for ( var i = 0 ; i < contents.length ; i++ ) |
| | 246 | commonParent = commonParent.getCommonAncestor( contents[i].getParent() ); |
| | 247 | |
| | 248 | // We want to insert things that are in the same tree level only, so calculate the contents again |
| | 249 | // by expanding the selected blocks to the same tree level. |
| | 250 | for ( var i = 0 ; i < contents.length ; i++ ) |
| | 251 | { |
| | 252 | var contentNode = contents[i], |
| | 253 | parentNode; |
| | 254 | while ( ( parentNode = contentNode.getParent() ) ) |
| | 255 | { |
| | 256 | if ( parentNode.equals( commonParent ) ) |
| | 257 | { |
| | 258 | listContents.push( contentNode ); |
| | 259 | break; |
| | 260 | } |
| | 261 | contentNode = parentNode; |
| | 262 | } |
| | 263 | } |
| | 264 | |
| | 265 | if ( listContents.length < 1 ) |
| | 266 | return; |
| | 267 | |
| | 268 | // Insert the list to the DOM tree. |
| | 269 | var insertAnchor = listContents[ listContents.length - 1 ].getNext(), |
| | 270 | listNode = doc.createElement( this.type ); |
| | 271 | |
| | 272 | listsCreated.push( listNode ); |
| | 273 | while ( listContents.length ) |
| | 274 | { |
| | 275 | var contentBlock = listContents.shift(), |
| | 276 | listItem = doc.createElement( 'li' ); |
| | 277 | contentBlock.moveChildren( listItem ); |
| | 278 | contentBlock.remove(); |
| | 279 | listItem.appendTo( listNode ); |
| | 280 | } |
| | 281 | if ( insertAnchor ) |
| | 282 | listNode.insertBefore( insertAnchor ); |
| | 283 | else |
| | 284 | listNode.appendTo( commonParent ); |
| | 285 | } |
| | 286 | |
| | 287 | function removeList( editor, groupObj, database ) |
| | 288 | { |
| | 289 | // This is very much like the change list type operation. |
| | 290 | // Except that we're changing the selected items' indent to -1 in the list array. |
| | 291 | var listArray = CKEDITOR.plugins.list.listToArray( groupObj.root, database ), |
| | 292 | selectedListItems = []; |
| | 293 | |
| | 294 | for ( var i = 0 ; i < groupObj.contents.length ; i++ ) |
| | 295 | { |
| | 296 | var itemNode = groupObj.contents[i]; |
| | 297 | itemNode = itemNode.getAscendant( 'li', true ); |
| | 298 | if ( !itemNode || itemNode.getCustomData( 'list_item_processed' ) ) |
| | 299 | continue; |
| | 300 | selectedListItems.push( itemNode ); |
| | 301 | CKEDITOR.dom.element.setMarker( database, itemNode, 'list_item_processed', true ); |
| | 302 | } |
| | 303 | |
| | 304 | var lastListIndex = null; |
| | 305 | for ( var i = 0 ; i < selectedListItems.length ; i++ ) |
| | 306 | { |
| | 307 | var listIndex = selectedListItems[i].getCustomData( 'listarray_index' ); |
| | 308 | listArray[listIndex].indent = -1; |
| | 309 | lastListIndex = listIndex; |
| | 310 | } |
| | 311 | |
| | 312 | // After cutting parts of the list out with indent=-1, we still have to maintain the array list |
| | 313 | // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the |
| | 314 | // list cannot be converted back to a real DOM list. |
| | 315 | for ( var i = lastListIndex + 1 ; i < listArray.length ; i++ ) |
| | 316 | { |
| | 317 | if ( listArray[i].indent > listArray[i-1].indent + 1 ) |
| | 318 | { |
| | 319 | var indentOffset = listArray[i-1].indent + 1 - listArray[i].indent; |
| | 320 | var oldIndent = listArray[i].indent; |
| | 321 | while ( listArray[i] && listArray[i].indent >= oldIndent ) |
| | 322 | { |
| | 323 | listArray[i].indent += indentOffset; |
| | 324 | i++; |
| | 325 | } |
| | 326 | i--; |
| | 327 | } |
| | 328 | } |
| | 329 | |
| | 330 | var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode ); |
| | 331 | // If groupObj.root is the last element in its parent, or its nextSibling is a <br>, then we should |
| | 332 | // not add a <br> after the final item. So, check for the cases and trim the <br>. |
| | 333 | if ( groupObj.root.getNext() == null || groupObj.root.getNext().$.nodeName.toLowerCase() == 'br' ) |
| | 334 | { |
| | 335 | if ( newList.listNode.getLast().$.nodeName.toLowerCase() == 'br' ) |
| | 336 | newList.listNode.getLast().remove(); |
| | 337 | } |
| | 338 | newList.listNode.replace( groupObj.root ); |
| | 339 | } |
| | 340 | |
| | 341 | function listCommand( name, type ) |
| | 342 | { |
| | 343 | this.name = name; |
| | 344 | this.type = type; |
| | 345 | } |
| | 346 | |
| | 347 | listCommand.prototype = { |
| | 348 | exec : function( editor ) |
| | 349 | { |
| | 350 | editor.focus(); |
| | 351 | |
| | 352 | var doc = editor.document, |
| | 353 | selection = editor.getSelection(), |
| | 354 | ranges = selection && selection.getRanges(); |
| | 355 | |
| | 356 | // There should be at least one selected range. |
| | 357 | if ( !ranges || ranges.length < 1 ) |
| | 358 | return; |
| | 359 | |
| | 360 | // Midas lists rule #1 says we can create a list even in an empty document. |
| | 361 | // But DOM iterator wouldn't run if the document is really empty. |
| | 362 | // So create a paragraph if the document is empty and we're going to create a list. |
| | 363 | if ( this.state == CKEDITOR.TRISTATE_OFF ) |
| | 364 | { |
| | 365 | var body = doc.getBody(); |
| | 366 | body.trim(); |
| | 367 | if ( !body.getFirst() ) |
| | 368 | { |
| | 369 | var paragraph = doc.createElement( editor.config.enterMode ); |
| | 370 | paragraph.appendTo( body ); |
| | 371 | ranges = [ new CKEDITOR.dom.range( doc ) ]; |
| | 372 | ranges[0].selectNodeContents( paragraph ); |
| | 373 | selection.selectRanges( ranges ); |
| | 374 | } |
| | 375 | } |
| | 376 | |
| | 377 | var bookmarks = selection.createBookmarks(); |
| | 378 | |
| | 379 | // Group the blocks up because there are many cases where multiple lists have to be created, |
| | 380 | // or multiple lists have to be cancelled. |
| | 381 | var listGroups = [], |
| | 382 | database = {}; |
| | 383 | |
| | 384 | while ( ranges.length > 0 ) |
| | 385 | { |
| | 386 | var range = ranges.shift(), |
| | 387 | boundaryNodes = range.getBoundaryNodes(), |
| | 388 | startNode = boundaryNodes.startNode, |
| | 389 | endNode = boundaryNodes.endNode; |
| | 390 | if ( startNode.type == CKEDITOR.NODE_ELEMENT && startNode.getName() == 'td' ) |
| | 391 | range.setStartAt( boundaryNodes.startNode, CKEDITOR.POSITION_AFTER_START ); |
| | 392 | if ( endNode.type == CKEDITOR.NODE_ELEMENT && endNode.getName() == 'td' ) |
| | 393 | range.setEndAt( boundaryNodes.endNode, CKEDITOR.POSITION_BEFORE_END ); |
| | 394 | |
| | 395 | var iterator = range.createIterator(), |
| | 396 | block; |
| | 397 | iterator.forceBrBreak = ( this.state == CKEDITOR.TRISTATE_OFF ); |
| | 398 | |
| | 399 | while ( ( block = iterator.getNextParagraph() ) ) |
| | 400 | { |
| | 401 | var path = new CKEDITOR.dom.elementPath( block ), |
| | 402 | listNode = null, |
| | 403 | processedFlag = false, |
| | 404 | blockLimit = path.blockLimit; |
| | 405 | |
| | 406 | // First, try to group by a list ancestor. |
| | 407 | for ( var i = 0 ; i < path.elements.length ; i++ ) |
| | 408 | { |
| | 409 | var element = path.elements[i]; |
| | 410 | if ( listNodeNames[ element.getName() ] ) |
| | 411 | { |
| | 412 | // If we've encountered a list inside a block limit |
| | 413 | // The last group object of the block limit element should |
| | 414 | // no longer be valid. Since paragraphs after the list |
| | 415 | // should belong to a different group of paragraphs before |
| | 416 | // the list. (Bug #1309) |
| | 417 | blockLimit.removeCustomData( 'list_group_object' ); |
| | 418 | |
| | 419 | var groupObj = element.getCustomData( 'list_group_object' ); |
| | 420 | if ( groupObj ) |
| | 421 | groupObj.contents.push( block ); |
| | 422 | else |
| | 423 | { |
| | 424 | groupObj = { root : element, contents : [ block ] }; |
| | 425 | listGroups.push( groupObj ); |
| | 426 | CKEDITOR.dom.element.setMarker( database, element, 'list_group_object', groupObj ); |
| | 427 | } |
| | 428 | processedFlag = true; |
| | 429 | break; |
| | 430 | } |
| | 431 | } |
| | 432 | |
| | 433 | if ( processedFlag ) |
| | 434 | continue; |
| | 435 | |
| | 436 | // No list ancestor? Group by block limit. |
| | 437 | var root = blockLimit; |
| | 438 | if ( root.getCustomData( 'list_group_object' ) ) |
| | 439 | root.getCustomData( 'list_group_object' ).contents.push( block ); |
| | 440 | else |
| | 441 | { |
| | 442 | var groupObj = { root : root, contents : [ block ] }; |
| | 443 | CKEDITOR.dom.element.setMarker( database, root, 'list_group_object', groupObj ); |
| | 444 | listGroups.push( groupObj ); |
| | 445 | } |
| | 446 | } |
| | 447 | } |
| | 448 | |
| | 449 | // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element. |
| | 450 | // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking |
| | 451 | // at the group that's not rooted at lists. So we have three cases to handle. |
| | 452 | var listsCreated = []; |
| | 453 | while ( listGroups.length > 0 ) |
| | 454 | { |
| | 455 | var groupObj = listGroups.shift(); |
| | 456 | if ( this.state == CKEDITOR.TRISTATE_OFF ) |
| | 457 | { |
| | 458 | if ( listNodeNames[ groupObj.root.getName() ] ) |
| | 459 | changeListType.call( this, editor, groupObj, database, listsCreated ); |
| | 460 | else |
| | 461 | createList.call( this, editor, groupObj, listsCreated ); |
| | 462 | } |
| | 463 | else if ( this.state == CKEDITOR.TRISTATE_ON && listNodeNames[ groupObj.root.getName() ] ) |
| | 464 | removeList.call( this, editor, groupObj, database ); |
| | 465 | } |
| | 466 | |
| | 467 | // For all new lists created, merge adjacent, same type lists. |
| | 468 | for ( var i = 0 ; i < listsCreated.length ; i++ ) |
| | 469 | { |
| | 470 | var listNode = listsCreated[i], |
| | 471 | stopFlag = false, |
| | 472 | currentNode = listNode; |
| | 473 | |
| | 474 | while ( !stopFlag ) |
| | 475 | { |
| | 476 | currentNode = currentNode.getNext(); |
| | 477 | if ( currentNode && currentNode.type == CKEDITOR.NODE_TEXT && emptyTextRegex.test( currentNode.getText() ) ) |
| | 478 | continue; |
| | 479 | stopFlag = true; |
| | 480 | } |
| | 481 | |
| | 482 | if ( currentNode && currentNode.getName() == this.type ) |
| | 483 | { |
| | 484 | currentNode.remove(); |
| | 485 | currentNode.moveChildren( listNode ); |
| | 486 | } |
| | 487 | |
| | 488 | stopFlag = false; |
| | 489 | currentNode = listNode; |
| | 490 | while ( !stopFlag ) |
| | 491 | { |
| | 492 | currentNode = currentNode.getNext(); |
| | 493 | if ( currentNode && currentNode.type == CKEDITOR.NODE_TEXT && emptyTextRegex.test( currentNode.getText() ) ) |
| | 494 | continue; |
| | 495 | stopFlag = true; |
| | 496 | } |
| | 497 | if ( currentNode && currentNode.getName() == this.type ) |
| | 498 | { |
| | 499 | currentNode.remove(); |
| | 500 | currentNode.moveChildren( listNode, true ); |
| | 501 | } |
| | 502 | } |
| | 503 | |
| | 504 | // Clean up, restore selection and update toolbar button states. |
| | 505 | CKEDITOR.dom.element.clearAllMarkers( database ); |
| | 506 | selection.selectBookmarks( bookmarks ); |
| | 507 | editor.focus(); |
| | 508 | } |
| | 509 | }; |
| | 510 | |
| | 511 | CKEDITOR.plugins.add( 'list', |
| | 512 | { |
| | 513 | init : function( editor ) |
| | 514 | { |
| | 515 | // Register commands. |
| | 516 | var numberedListCommand = new listCommand( 'numberedlist', 'ol' ), |
| | 517 | bulletedListCommand = new listCommand( 'bulletedlist', 'ul' ); |
| | 518 | editor.addCommand( 'numberedlist', numberedListCommand ); |
| | 519 | editor.addCommand( 'bulletedlist', bulletedListCommand ); |
| | 520 | |
| | 521 | // Register the toolbar button. |
| | 522 | editor.ui.addButton( 'NumberedList', |
| | 523 | { |
| | 524 | label : editor.lang.numberedlist, |
| | 525 | command : 'numberedlist' |
| | 526 | } ); |
| | 527 | editor.ui.addButton( 'BulletedList', |
| | 528 | { |
| | 529 | label : editor.lang.bulletedlist, |
| | 530 | command : 'bulletedlist' |
| | 531 | } ); |
| | 532 | |
| | 533 | // Register the state changing handlers. |
| | 534 | editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, numberedListCommand ) ); |
| | 535 | editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, bulletedListCommand ) ); |
| | 536 | }, |
| | 537 | |
| | 538 | requires : [ 'domiterator' ] |
| | 539 | } ); |
| | 540 | })(); |