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