2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
7 * @file Insert and remove numbered and bulleted lists.
12 var listNodeNames
= { ol
: 1, ul
: 1 },
13 emptyTextRegex
= /^[\n\r\t ]*$/;
15 var whitespaces
= CKEDITOR
.dom
.walker
.whitespaces(),
16 bookmarks
= CKEDITOR
.dom
.walker
.bookmark(),
17 nonEmpty = function( node
){ return !( whitespaces( node
) || bookmarks( node
) ); };
19 CKEDITOR
.plugins
.list
= {
21 * Convert a DOM list tree into a data structure that is easier to
22 * manipulate. This operation should be non-intrusive in the sense that it
23 * does not change the DOM tree, with the exception that it may add some
24 * markers to the list item nodes when database is specified.
26 listToArray : function( listNode
, database
, baseArray
, baseIndentLevel
, grandparentNode
)
28 if ( !listNodeNames
[ listNode
.getName() ] )
31 if ( !baseIndentLevel
)
36 // Iterate over all list items to and look for inner lists.
37 for ( var i
= 0, count
= listNode
.getChildCount() ; i
< count
; i
++ )
39 var listItem
= listNode
.getChild( i
);
41 // Fixing malformed nested lists by moving it into a previous list item. (#6236)
42 if( listItem
.type
== CKEDITOR
.NODE_ELEMENT
&& listItem
.getName() in CKEDITOR
.dtd
.$list
)
43 CKEDITOR
.plugins
.list
.listToArray( listItem
, database
, baseArray
, baseIndentLevel
+ 1 );
45 // It may be a text node or some funny stuff.
46 if ( listItem
.$.nodeName
.toLowerCase() != 'li' )
49 var itemObj
= { 'parent' : listNode
, indent
: baseIndentLevel
, element
: listItem
, contents
: [] };
50 if ( !grandparentNode
)
52 itemObj
.grandparent
= listNode
.getParent();
53 if ( itemObj
.grandparent
&& itemObj
.grandparent
.$.nodeName
.toLowerCase() == 'li' )
54 itemObj
.grandparent
= itemObj
.grandparent
.getParent();
57 itemObj
.grandparent
= grandparentNode
;
60 CKEDITOR
.dom
.element
.setMarker( database
, listItem
, 'listarray_index', baseArray
.length
);
61 baseArray
.push( itemObj
);
63 for ( var j
= 0, itemChildCount
= listItem
.getChildCount(), child
; j
< itemChildCount
; j
++ )
65 child
= listItem
.getChild( j
);
66 if ( child
.type
== CKEDITOR
.NODE_ELEMENT
&& listNodeNames
[ child
.getName() ] )
67 // Note the recursion here, it pushes inner list items with
68 // +1 indentation in the correct order.
69 CKEDITOR
.plugins
.list
.listToArray( child
, database
, baseArray
, baseIndentLevel
+ 1, itemObj
.grandparent
);
71 itemObj
.contents
.push( child
);
77 // Convert our internal representation of a list back to a DOM forest.
78 arrayToList : function( listArray
, database
, baseIndex
, paragraphMode
, dir
)
82 if ( !listArray
|| listArray
.length
< baseIndex
+ 1 )
84 var doc
= listArray
[ baseIndex
].parent
.getDocument(),
85 retval
= new CKEDITOR
.dom
.documentFragment( doc
),
87 currentIndex
= baseIndex
,
88 indentLevel
= Math
.max( listArray
[ baseIndex
].indent
, 0 ),
89 currentListItem
= null,
91 paragraphName
= ( paragraphMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' );
94 var item
= listArray
[ currentIndex
];
96 orgDir
= item
.element
.getDirection( 1 );
98 if ( item
.indent
== indentLevel
)
100 if ( !rootNode
|| listArray
[ currentIndex
].parent
.getName() != rootNode
.getName() )
102 rootNode
= listArray
[ currentIndex
].parent
.clone( false, 1 );
103 dir
&& rootNode
.setAttribute( 'dir', dir
);
104 retval
.append( rootNode
);
106 currentListItem
= rootNode
.append( item
.element
.clone( 0, 1 ) );
108 if ( orgDir
!= rootNode
.getDirection( 1 ) )
109 currentListItem
.setAttribute( 'dir', orgDir
);
111 currentListItem
.removeAttribute( 'dir' );
113 for ( var i
= 0 ; i
< item
.contents
.length
; i
++ )
114 currentListItem
.append( item
.contents
[i
].clone( 1, 1 ) );
117 else if ( item
.indent
== Math
.max( indentLevel
, 0 ) + 1 )
119 // Maintain original direction (#6861).
120 var currDir
= listArray
[ currentIndex
- 1 ].element
.getDirection( 1 ),
121 listData
= CKEDITOR
.plugins
.list
.arrayToList( listArray
, null, currentIndex
, paragraphMode
,
122 currDir
!= orgDir
? orgDir
: null );
124 // If the next block is an <li> with another list tree as the first
125 // child, we'll need to append a filler (<br>/NBSP) or the list item
126 // wouldn't be editable. (#6724)
127 if ( !currentListItem
.getChildCount() && CKEDITOR
.env
.ie
&& !( doc
.$.documentMode
> 7 ))
128 currentListItem
.append( doc
.createText( '\xa0' ) );
129 currentListItem
.append( listData
.listNode
);
130 currentIndex
= listData
.nextIndex
;
132 else if ( item
.indent
== -1 && !baseIndex
&& item
.grandparent
)
134 if ( listNodeNames
[ item
.grandparent
.getName() ] )
135 currentListItem
= item
.element
.clone( false, true );
138 // Create completely new blocks here.
139 if ( dir
|| item
.element
.hasAttributes() || paragraphMode
!= CKEDITOR
.ENTER_BR
)
141 currentListItem
= doc
.createElement( paragraphName
);
142 item
.element
.copyAttributes( currentListItem
, { type
:1, value
:1 } );
144 // There might be a case where there are no attributes in the element after all
145 // (i.e. when "type" or "value" are the only attributes set). In this case, if enterMode = BR,
146 // the current item should be a fragment.
147 if ( !dir
&& paragraphMode
== CKEDITOR
.ENTER_BR
&& !currentListItem
.hasAttributes() )
148 currentListItem
= new CKEDITOR
.dom
.documentFragment( doc
);
151 currentListItem
= new CKEDITOR
.dom
.documentFragment( doc
);
154 if ( currentListItem
.type
== CKEDITOR
.NODE_ELEMENT
)
156 if ( item
.grandparent
.getDirection( 1 ) != orgDir
)
157 currentListItem
.setAttribute( 'dir', orgDir
);
159 currentListItem
.removeAttribute( 'dir' );
162 for ( i
= 0 ; i
< item
.contents
.length
; i
++ )
163 currentListItem
.append( item
.contents
[i
].clone( 1, 1 ) );
165 if ( currentListItem
.type
== CKEDITOR
.NODE_DOCUMENT_FRAGMENT
166 && currentIndex
!= listArray
.length
- 1 )
168 var last
= currentListItem
.getLast();
169 if ( last
&& last
.type
== CKEDITOR
.NODE_ELEMENT
170 && last
.getAttribute( 'type' ) == '_moz' )
175 if ( !( last
= currentListItem
.getLast( nonEmpty
)
176 && last
.type
== CKEDITOR
.NODE_ELEMENT
177 && last
.getName() in CKEDITOR
.dtd
.$block
) )
179 currentListItem
.append( doc
.createElement( 'br' ) );
183 if ( currentListItem
.type
== CKEDITOR
.NODE_ELEMENT
&&
184 currentListItem
.getName() == paragraphName
&&
185 currentListItem
.$.firstChild
)
187 currentListItem
.trim();
188 var firstChild
= currentListItem
.getFirst();
189 if ( firstChild
.type
== CKEDITOR
.NODE_ELEMENT
&& firstChild
.isBlockBoundary() )
191 var tmp
= new CKEDITOR
.dom
.documentFragment( doc
);
192 currentListItem
.moveChildren( tmp
);
193 currentListItem
= tmp
;
197 var currentListItemName
= currentListItem
.$.nodeName
.toLowerCase();
198 if ( !CKEDITOR
.env
.ie
&& ( currentListItemName
== 'div' || currentListItemName
== 'p' ) )
199 currentListItem
.appendBogus();
200 retval
.append( currentListItem
);
207 if ( listArray
.length
<= currentIndex
|| Math
.max( listArray
[ currentIndex
].indent
, 0 ) < indentLevel
)
211 // Clear marker attributes for the new list tree made of cloned nodes, if any.
214 var currentNode
= retval
.getFirst();
215 while ( currentNode
)
217 if ( currentNode
.type
== CKEDITOR
.NODE_ELEMENT
)
218 CKEDITOR
.dom
.element
.clearMarkers( database
, currentNode
);
219 currentNode
= currentNode
.getNextSourceNode();
223 return { listNode
: retval
, nextIndex
: currentIndex
};
227 function onSelectionChange( evt
)
229 if ( evt
.editor
.readOnly
)
232 var path
= evt
.data
.path
,
233 blockLimit
= path
.blockLimit
,
234 elements
= path
.elements
,
238 // Grouping should only happen under blockLimit.(#3940).
239 for ( i
= 0 ; i
< elements
.length
&& ( element
= elements
[ i
] )
240 && !element
.equals( blockLimit
); i
++ )
242 if ( listNodeNames
[ elements
[ i
].getName() ] )
243 return this.setState( this.type
== elements
[ i
].getName() ? CKEDITOR
.TRISTATE_ON
: CKEDITOR
.TRISTATE_OFF
);
246 return this.setState( CKEDITOR
.TRISTATE_OFF
);
249 function changeListType( editor
, groupObj
, database
, listsCreated
)
251 // This case is easy...
252 // 1. Convert the whole list into a one-dimensional array.
253 // 2. Change the list type by modifying the array.
254 // 3. Recreate the whole list by converting the array to a list.
255 // 4. Replace the original list with the recreated list.
256 var listArray
= CKEDITOR
.plugins
.list
.listToArray( groupObj
.root
, database
),
257 selectedListItems
= [];
259 for ( var i
= 0 ; i
< groupObj
.contents
.length
; i
++ )
261 var itemNode
= groupObj
.contents
[i
];
262 itemNode
= itemNode
.getAscendant( 'li', true );
263 if ( !itemNode
|| itemNode
.getCustomData( 'list_item_processed' ) )
265 selectedListItems
.push( itemNode
);
266 CKEDITOR
.dom
.element
.setMarker( database
, itemNode
, 'list_item_processed', true );
269 var root
= groupObj
.root
,
270 fakeParent
= root
.getDocument().createElement( this.type
);
271 // Copy all attributes, except from 'start' and 'type'.
272 root
.copyAttributes( fakeParent
, { start
: 1, type
: 1 } );
273 // The list-style-type property should be ignored.
274 fakeParent
.removeStyle( 'list-style-type' );
276 for ( i
= 0 ; i
< selectedListItems
.length
; i
++ )
278 var listIndex
= selectedListItems
[i
].getCustomData( 'listarray_index' );
279 listArray
[listIndex
].parent
= fakeParent
;
281 var newList
= CKEDITOR
.plugins
.list
.arrayToList( listArray
, database
, null, editor
.config
.enterMode
);
282 var child
, length
= newList
.listNode
.getChildCount();
283 for ( i
= 0 ; i
< length
&& ( child
= newList
.listNode
.getChild( i
) ) ; i
++ )
285 if ( child
.getName() == this.type
)
286 listsCreated
.push( child
);
288 newList
.listNode
.replace( groupObj
.root
);
291 var headerTagRegex
= /^h[1-6]$/;
293 function createList( editor
, groupObj
, listsCreated
)
295 var contents
= groupObj
.contents
,
296 doc
= groupObj
.root
.getDocument(),
299 // It is possible to have the contents returned by DomRangeIterator to be the same as the root.
300 // e.g. when we're running into table cells.
301 // In such a case, enclose the childNodes of contents[0] into a <div>.
302 if ( contents
.length
== 1 && contents
[0].equals( groupObj
.root
) )
304 var divBlock
= doc
.createElement( 'div' );
305 contents
[0].moveChildren
&& contents
[0].moveChildren( divBlock
);
306 contents
[0].append( divBlock
);
307 contents
[0] = divBlock
;
310 // Calculate the common parent node of all content blocks.
311 var commonParent
= groupObj
.contents
[0].getParent();
312 for ( var i
= 0 ; i
< contents
.length
; i
++ )
313 commonParent
= commonParent
.getCommonAncestor( contents
[i
].getParent() );
315 var useComputedState
= editor
.config
.useComputedState
,
316 listDir
, explicitDirection
;
318 useComputedState
= useComputedState
=== undefined || useComputedState
;
320 // We want to insert things that are in the same tree level only, so calculate the contents again
321 // by expanding the selected blocks to the same tree level.
322 for ( i
= 0 ; i
< contents
.length
; i
++ )
324 var contentNode
= contents
[i
],
326 while ( ( parentNode
= contentNode
.getParent() ) )
328 if ( parentNode
.equals( commonParent
) )
330 listContents
.push( contentNode
);
332 // Determine the lists's direction.
333 if ( !explicitDirection
&& contentNode
.getDirection() )
334 explicitDirection
= 1;
336 var itemDir
= contentNode
.getDirection( useComputedState
);
338 if ( listDir
!== null )
340 // If at least one LI have a different direction than current listDir, we can't have listDir.
341 if ( listDir
&& listDir
!= itemDir
)
349 contentNode
= parentNode
;
353 if ( listContents
.length
< 1 )
356 // Insert the list to the DOM tree.
357 var insertAnchor
= listContents
[ listContents
.length
- 1 ].getNext(),
358 listNode
= doc
.createElement( this.type
);
360 listsCreated
.push( listNode
);
362 var contentBlock
, listItem
;
364 while ( listContents
.length
)
366 contentBlock
= listContents
.shift();
367 listItem
= doc
.createElement( 'li' );
369 // Preserve preformat block and heading structure when converting to list item. (#5335) (#5271)
370 if ( contentBlock
.is( 'pre' ) || headerTagRegex
.test( contentBlock
.getName() ) )
371 contentBlock
.appendTo( listItem
);
374 contentBlock
.copyAttributes( listItem
);
375 // Remove direction attribute after it was merged into list root. (#7657)
376 if ( listDir
&& contentBlock
.getDirection() )
378 listItem
.removeStyle( 'direction' );
379 listItem
.removeAttribute( 'dir' );
381 contentBlock
.moveChildren( listItem
);
382 contentBlock
.remove();
385 listItem
.appendTo( listNode
);
388 // Apply list root dir only if it has been explicitly declared.
389 if ( listDir
&& explicitDirection
)
390 listNode
.setAttribute( 'dir', listDir
);
393 listNode
.insertBefore( insertAnchor
);
395 listNode
.appendTo( commonParent
);
398 function removeList( editor
, groupObj
, database
)
400 // This is very much like the change list type operation.
401 // Except that we're changing the selected items' indent to -1 in the list array.
402 var listArray
= CKEDITOR
.plugins
.list
.listToArray( groupObj
.root
, database
),
403 selectedListItems
= [];
405 for ( var i
= 0 ; i
< groupObj
.contents
.length
; i
++ )
407 var itemNode
= groupObj
.contents
[i
];
408 itemNode
= itemNode
.getAscendant( 'li', true );
409 if ( !itemNode
|| itemNode
.getCustomData( 'list_item_processed' ) )
411 selectedListItems
.push( itemNode
);
412 CKEDITOR
.dom
.element
.setMarker( database
, itemNode
, 'list_item_processed', true );
415 var lastListIndex
= null;
416 for ( i
= 0 ; i
< selectedListItems
.length
; i
++ )
418 var listIndex
= selectedListItems
[i
].getCustomData( 'listarray_index' );
419 listArray
[listIndex
].indent
= -1;
420 lastListIndex
= listIndex
;
423 // After cutting parts of the list out with indent=-1, we still have to maintain the array list
424 // model's nextItem.indent <= currentItem.indent + 1 invariant. Otherwise the array model of the
425 // list cannot be converted back to a real DOM list.
426 for ( i
= lastListIndex
+ 1 ; i
< listArray
.length
; i
++ )
428 if ( listArray
[i
].indent
> listArray
[i
-1].indent
+ 1 )
430 var indentOffset
= listArray
[i
-1].indent
+ 1 - listArray
[i
].indent
;
431 var oldIndent
= listArray
[i
].indent
;
432 while ( listArray
[i
] && listArray
[i
].indent
>= oldIndent
)
434 listArray
[i
].indent
+= indentOffset
;
441 var newList
= CKEDITOR
.plugins
.list
.arrayToList( listArray
, database
, null, editor
.config
.enterMode
,
442 groupObj
.root
.getAttribute( 'dir' ) );
444 // Compensate <br> before/after the list node if the surrounds are non-blocks.(#3836)
445 var docFragment
= newList
.listNode
, boundaryNode
, siblingNode
;
446 function compensateBrs( isStart
)
448 if ( ( boundaryNode
= docFragment
[ isStart
? 'getFirst' : 'getLast' ]() )
449 && !( boundaryNode
.is
&& boundaryNode
.isBlockBoundary() )
450 && ( siblingNode
= groupObj
.root
[ isStart
? 'getPrevious' : 'getNext' ]
451 ( CKEDITOR
.dom
.walker
.whitespaces( true ) ) )
452 && !( siblingNode
.is
&& siblingNode
.isBlockBoundary( { br
: 1 } ) ) )
453 editor
.document
.createElement( 'br' )[ isStart
? 'insertBefore' : 'insertAfter' ]( boundaryNode
);
455 compensateBrs( true );
458 docFragment
.replace( groupObj
.root
);
461 function listCommand( name
, type
)
467 listCommand
.prototype = {
468 exec : function( editor
)
470 var doc
= editor
.document
,
471 config
= editor
.config
,
472 selection
= editor
.getSelection(),
473 ranges
= selection
&& selection
.getRanges( true );
475 // There should be at least one selected range.
476 if ( !ranges
|| ranges
.length
< 1 )
479 // Midas lists rule #1 says we can create a list even in an empty document.
480 // But DOM iterator wouldn't run if the document is really empty.
481 // So create a paragraph if the document is empty and we're going to create a list.
482 if ( this.state
== CKEDITOR
.TRISTATE_OFF
)
484 var body
= doc
.getBody();
485 if ( !body
.getFirst( nonEmpty
) )
487 config
.enterMode
== CKEDITOR
.ENTER_BR
?
489 ranges
[ 0 ].fixBlock( 1, config
.enterMode
== CKEDITOR
.ENTER_P
? 'p' : 'div' );
491 selection
.selectRanges( ranges
);
493 // Maybe a single range there enclosing the whole list,
494 // turn on the list state manually(#4129).
497 var range
= ranges
.length
== 1 && ranges
[ 0 ],
498 enclosedNode
= range
&& range
.getEnclosedNode();
499 if ( enclosedNode
&& enclosedNode
.is
500 && this.type
== enclosedNode
.getName() )
501 this.setState( CKEDITOR
.TRISTATE_ON
);
505 var bookmarks
= selection
.createBookmarks( true );
507 // Group the blocks up because there are many cases where multiple lists have to be created,
508 // or multiple lists have to be cancelled.
511 rangeIterator
= ranges
.createIterator(),
514 while ( ( range
= rangeIterator
.getNextRange() ) && ++index
)
516 var boundaryNodes
= range
.getBoundaryNodes(),
517 startNode
= boundaryNodes
.startNode
,
518 endNode
= boundaryNodes
.endNode
;
520 if ( startNode
.type
== CKEDITOR
.NODE_ELEMENT
&& startNode
.getName() == 'td' )
521 range
.setStartAt( boundaryNodes
.startNode
, CKEDITOR
.POSITION_AFTER_START
);
523 if ( endNode
.type
== CKEDITOR
.NODE_ELEMENT
&& endNode
.getName() == 'td' )
524 range
.setEndAt( boundaryNodes
.endNode
, CKEDITOR
.POSITION_BEFORE_END
);
526 var iterator
= range
.createIterator(),
529 iterator
.forceBrBreak
= ( this.state
== CKEDITOR
.TRISTATE_OFF
);
531 while ( ( block
= iterator
.getNextParagraph() ) )
533 // Avoid duplicate blocks get processed across ranges.
534 if( block
.getCustomData( 'list_block' ) )
537 CKEDITOR
.dom
.element
.setMarker( database
, block
, 'list_block', 1 );
539 var path
= new CKEDITOR
.dom
.elementPath( block
),
540 pathElements
= path
.elements
,
541 pathElementsCount
= pathElements
.length
,
544 blockLimit
= path
.blockLimit
,
547 // First, try to group by a list ancestor.
548 for ( var i
= pathElementsCount
- 1; i
>= 0 && ( element
= pathElements
[ i
] ); i
-- )
550 if ( listNodeNames
[ element
.getName() ]
551 && blockLimit
.contains( element
) ) // Don't leak outside block limit (#3940).
553 // If we've encountered a list inside a block limit
554 // The last group object of the block limit element should
555 // no longer be valid. Since paragraphs after the list
556 // should belong to a different group of paragraphs before
557 // the list. (Bug #1309)
558 blockLimit
.removeCustomData( 'list_group_object_' + index
);
560 var groupObj
= element
.getCustomData( 'list_group_object' );
562 groupObj
.contents
.push( block
);
565 groupObj
= { root
: element
, contents
: [ block
] };
566 listGroups
.push( groupObj
);
567 CKEDITOR
.dom
.element
.setMarker( database
, element
, 'list_group_object', groupObj
);
577 // No list ancestor? Group by block limit, but don't mix contents from different ranges.
578 var root
= blockLimit
;
579 if ( root
.getCustomData( 'list_group_object_' + index
) )
580 root
.getCustomData( 'list_group_object_' + index
).contents
.push( block
);
583 groupObj
= { root
: root
, contents
: [ block
] };
584 CKEDITOR
.dom
.element
.setMarker( database
, root
, 'list_group_object_' + index
, groupObj
);
585 listGroups
.push( groupObj
);
590 // Now we have two kinds of list groups, groups rooted at a list, and groups rooted at a block limit element.
591 // We either have to build lists or remove lists, for removing a list does not makes sense when we are looking
592 // at the group that's not rooted at lists. So we have three cases to handle.
593 var listsCreated
= [];
594 while ( listGroups
.length
> 0 )
596 groupObj
= listGroups
.shift();
597 if ( this.state
== CKEDITOR
.TRISTATE_OFF
)
599 if ( listNodeNames
[ groupObj
.root
.getName() ] )
600 changeListType
.call( this, editor
, groupObj
, database
, listsCreated
);
602 createList
.call( this, editor
, groupObj
, listsCreated
);
604 else if ( this.state
== CKEDITOR
.TRISTATE_ON
&& listNodeNames
[ groupObj
.root
.getName() ] )
605 removeList
.call( this, editor
, groupObj
, database
);
608 // For all new lists created, merge adjacent, same type lists.
609 for ( i
= 0 ; i
< listsCreated
.length
; i
++ )
611 listNode
= listsCreated
[i
];
612 var mergeSibling
, listCommand
= this;
613 ( mergeSibling = function( rtl
){
615 var sibling
= listNode
[ rtl
?
616 'getPrevious' : 'getNext' ]( CKEDITOR
.dom
.walker
.whitespaces( true ) );
617 if ( sibling
&& sibling
.getName
&&
618 sibling
.getName() == listCommand
.type
)
621 // Move children order by merge direction.(#3820)
622 sibling
.moveChildren( listNode
, rtl
);
628 // Clean up, restore selection and update toolbar button states.
629 CKEDITOR
.dom
.element
.clearAllMarkers( database
);
630 selection
.selectBookmarks( bookmarks
);
635 var dtd
= CKEDITOR
.dtd
;
636 var tailNbspRegex
= /[\t\r\n ]*(?: |\xa0)$/;
638 function indexOfFirstChildElement( element
, tagNameList
)
641 children
= element
.children
,
642 length
= children
.length
;
644 for ( var i
= 0 ; i
< length
; i
++ )
646 child
= children
[ i
];
647 if ( child
.name
&& ( child
.name
in tagNameList
) )
654 function getExtendNestedListFilter( isHtmlFilter
)
656 // An element filter function that corrects nested list start in an empty
657 // list item for better displaying/outputting. (#3165)
658 return function( listItem
)
660 var children
= listItem
.children
,
661 firstNestedListIndex
= indexOfFirstChildElement( listItem
, dtd
.$list
),
662 firstNestedList
= children
[ firstNestedListIndex
],
663 nodeBefore
= firstNestedList
&& firstNestedList
.previous
,
667 && ( nodeBefore
.name
&& nodeBefore
.name
== 'br'
668 || nodeBefore
.value
&& ( tailNbspmatch
= nodeBefore
.value
.match( tailNbspRegex
) ) ) )
670 var fillerNode
= nodeBefore
;
672 // Always use 'nbsp' as filler node if we found a nested list appear
673 // in front of a list item.
674 if ( !( tailNbspmatch
&& tailNbspmatch
.index
) && fillerNode
== children
[ 0 ] )
675 children
[ 0 ] = ( isHtmlFilter
|| CKEDITOR
.env
.ie
) ?
676 new CKEDITOR
.htmlParser
.text( '\xa0' ) :
677 new CKEDITOR
.htmlParser
.element( 'br', {} );
679 // Otherwise the filler is not needed anymore.
680 else if ( fillerNode
.name
== 'br' )
681 children
.splice( firstNestedListIndex
- 1, 1 );
683 fillerNode
.value
= fillerNode
.value
.replace( tailNbspRegex
, '' );
689 var defaultListDataFilterRules
= { elements
: {} };
690 for ( var i
in dtd
.$listItem
)
691 defaultListDataFilterRules
.elements
[ i
] = getExtendNestedListFilter();
693 var defaultListHtmlFilterRules
= { elements
: {} };
694 for ( i
in dtd
.$listItem
)
695 defaultListHtmlFilterRules
.elements
[ i
] = getExtendNestedListFilter( true );
697 CKEDITOR
.plugins
.add( 'list',
699 init : function( editor
)
701 // Register commands.
702 var numberedListCommand
= editor
.addCommand( 'numberedlist', new listCommand( 'numberedlist', 'ol' ) ),
703 bulletedListCommand
= editor
.addCommand( 'bulletedlist', new listCommand( 'bulletedlist', 'ul' ) );
705 // Register the toolbar button.
706 editor
.ui
.addButton( 'NumberedList',
708 label
: editor
.lang
.numberedlist
,
709 command
: 'numberedlist'
711 editor
.ui
.addButton( 'BulletedList',
713 label
: editor
.lang
.bulletedlist
,
714 command
: 'bulletedlist'
717 // Register the state changing handlers.
718 editor
.on( 'selectionChange', CKEDITOR
.tools
.bind( onSelectionChange
, numberedListCommand
) );
719 editor
.on( 'selectionChange', CKEDITOR
.tools
.bind( onSelectionChange
, bulletedListCommand
) );
722 afterInit : function ( editor
)
724 var dataProcessor
= editor
.dataProcessor
;
727 dataProcessor
.dataFilter
.addRules( defaultListDataFilterRules
);
728 dataProcessor
.htmlFilter
.addRules( defaultListHtmlFilterRules
);
732 requires
: [ 'domiterator' ]