+ // Merge list adjacent, of same type lists.\r
+ function mergeListSiblings( listNode )\r
+ {\r
+ var mergeSibling;\r
+ ( mergeSibling = function( rtl )\r
+ {\r
+ var sibling = listNode[ rtl ? 'getPrevious' : 'getNext' ]( nonEmpty );\r
+ if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT &&\r
+ sibling.is( listNode.getName() ) )\r
+ {\r
+ // Move children order by merge direction.(#3820)\r
+ mergeChildren( listNode, sibling, null, !rtl );\r
+\r
+ listNode.remove();\r
+ listNode = sibling;\r
+ }\r
+ } )();\r
+ mergeSibling( 1 );\r
+ }\r
+\r
+ var dtd = CKEDITOR.dtd;\r
+ var tailNbspRegex = /[\t\r\n ]*(?: |\xa0)$/;\r
+\r
+ function indexOfFirstChildElement( element, tagNameList )\r
+ {\r
+ var child,\r
+ children = element.children,\r
+ length = children.length;\r
+\r
+ for ( var i = 0 ; i < length ; i++ )\r
+ {\r
+ child = children[ i ];\r
+ if ( child.name && ( child.name in tagNameList ) )\r
+ return i;\r
+ }\r
+\r
+ return length;\r
+ }\r
+\r
+ function getExtendNestedListFilter( isHtmlFilter )\r
+ {\r
+ // An element filter function that corrects nested list start in an empty\r
+ // list item for better displaying/outputting. (#3165)\r
+ return function( listItem )\r
+ {\r
+ var children = listItem.children,\r
+ firstNestedListIndex = indexOfFirstChildElement( listItem, dtd.$list ),\r
+ firstNestedList = children[ firstNestedListIndex ],\r
+ nodeBefore = firstNestedList && firstNestedList.previous,\r
+ tailNbspmatch;\r
+\r
+ if ( nodeBefore\r
+ && ( nodeBefore.name && nodeBefore.name == 'br'\r
+ || nodeBefore.value && ( tailNbspmatch = nodeBefore.value.match( tailNbspRegex ) ) ) )\r
+ {\r
+ var fillerNode = nodeBefore;\r
+\r
+ // Always use 'nbsp' as filler node if we found a nested list appear\r
+ // in front of a list item.\r
+ if ( !( tailNbspmatch && tailNbspmatch.index ) && fillerNode == children[ 0 ] )\r
+ children[ 0 ] = ( isHtmlFilter || CKEDITOR.env.ie ) ?\r
+ new CKEDITOR.htmlParser.text( '\xa0' ) :\r
+ new CKEDITOR.htmlParser.element( 'br', {} );\r
+\r
+ // Otherwise the filler is not needed anymore.\r
+ else if ( fillerNode.name == 'br' )\r
+ children.splice( firstNestedListIndex - 1, 1 );\r
+ else\r
+ fillerNode.value = fillerNode.value.replace( tailNbspRegex, '' );\r
+ }\r
+\r
+ };\r
+ }\r
+\r
+ var defaultListDataFilterRules = { elements : {} };\r
+ for ( var i in dtd.$listItem )\r
+ defaultListDataFilterRules.elements[ i ] = getExtendNestedListFilter();\r
+\r
+ var defaultListHtmlFilterRules = { elements : {} };\r
+ for ( i in dtd.$listItem )\r
+ defaultListHtmlFilterRules.elements[ i ] = getExtendNestedListFilter( true );\r
+\r
+ // Check if node is block element that recieves text.\r
+ function isTextBlock( node )\r
+ {\r
+ return node.type == CKEDITOR.NODE_ELEMENT &&\r
+ ( node.getName() in CKEDITOR.dtd.$block ||\r
+ node.getName() in CKEDITOR.dtd.$listItem ) &&\r
+ CKEDITOR.dtd[ node.getName() ][ '#' ];\r
+ }\r
+\r
+ // Join visually two block lines.\r
+ function joinNextLineToCursor( editor, cursor, nextCursor )\r
+ {\r
+ editor.fire( 'saveSnapshot' );\r
+\r
+ // Merge with previous block's content.\r
+ nextCursor.enlarge( CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS );\r
+ var frag = nextCursor.extractContents();\r
+\r
+ cursor.trim( false, true );\r
+ var bm = cursor.createBookmark();\r
+\r
+ // Kill original bogus;\r
+ var currentPath = new CKEDITOR.dom.elementPath( cursor.startContainer ),\r
+ pathBlock = currentPath.block,\r
+ currentBlock = currentPath.lastElement.getAscendant( 'li', 1 ) || pathBlock,\r
+ nextPath = new CKEDITOR.dom.elementPath( nextCursor.startContainer ),\r
+ nextLi = nextPath.contains( CKEDITOR.dtd.$listItem ),\r
+ nextList = nextPath.contains( CKEDITOR.dtd.$list ),\r
+ last;\r
+\r
+ // Remove bogus node the current block/pseudo block.\r
+ if ( pathBlock )\r
+ {\r
+ var bogus = pathBlock.getBogus();\r
+ bogus && bogus.remove();\r
+ }\r
+ else if ( nextList )\r
+ {\r
+ last = nextList.getPrevious( nonEmpty );\r
+ if ( last && blockBogus( last ) )\r
+ last.remove();\r
+ }\r
+\r
+ // Kill the tail br in extracted.\r
+ last = frag.getLast();\r
+ if ( last && last.type == CKEDITOR.NODE_ELEMENT && last.is( 'br' ) )\r
+ last.remove();\r
+\r
+ // Insert fragment at the range position.\r
+ var nextNode = cursor.startContainer.getChild( cursor.startOffset );\r
+ if ( nextNode )\r
+ frag.insertBefore( nextNode );\r
+ else\r
+ cursor.startContainer.append( frag );\r
+\r
+ // Move the sub list nested in the next list item.\r
+ if ( nextLi )\r
+ {\r
+ var sublist = getSubList( nextLi );\r
+ if ( sublist )\r
+ {\r
+ // If next line is in the sub list of the current list item.\r
+ if ( currentBlock.contains( nextLi ) )\r
+ {\r
+ mergeChildren( sublist, nextLi.getParent(), nextLi );\r
+ sublist.remove();\r
+ }\r
+ // Migrate the sub list to current list item.\r
+ else\r
+ currentBlock.append( sublist );\r
+ }\r
+ }\r
+\r
+ // Remove any remaining empty path blocks at next line after merging.\r
+ while ( nextCursor.checkStartOfBlock() &&\r
+ nextCursor.checkEndOfBlock() )\r
+ {\r
+ nextPath = new CKEDITOR.dom.elementPath( nextCursor.startContainer );\r
+ var nextBlock = nextPath.block, parent;\r
+\r
+ // Check if also to remove empty list.\r
+ if ( nextBlock.is( 'li' ) )\r
+ {\r
+ parent = nextBlock.getParent();\r
+ if ( nextBlock.equals( parent.getLast( nonEmpty ) )\r
+ && nextBlock.equals( parent.getFirst( nonEmpty ) ) )\r
+ nextBlock = parent;\r
+ }\r
+\r
+ nextCursor.moveToPosition( nextBlock, CKEDITOR.POSITION_BEFORE_START );\r
+ nextBlock.remove();\r
+ }\r
+\r
+ // Check if need to further merge with the list resides after the merged block. (#9080)\r
+ var walkerRng = nextCursor.clone(), body = editor.document.getBody();\r
+ walkerRng.setEndAt( body, CKEDITOR.POSITION_BEFORE_END );\r
+ var walker = new CKEDITOR.dom.walker( walkerRng );\r
+ walker.evaluator = function( node ) { return nonEmpty( node ) && !blockBogus( node ); };\r
+ var next = walker.next();\r
+ if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.getName() in CKEDITOR.dtd.$list )\r
+ mergeListSiblings( next );\r
+\r
+ cursor.moveToBookmark( bm );\r
+\r
+ // Make fresh selection.\r
+ cursor.select();\r
+\r
+ editor.selectionChange( 1 );\r
+ editor.fire( 'saveSnapshot' );\r
+ }\r
+\r
+ function getSubList( li )\r
+ {\r
+ var last = li.getLast( nonEmpty );\r
+ return last && last.type == CKEDITOR.NODE_ELEMENT && last.getName() in listNodeNames ? last : null;\r
+ }\r
+\r