X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=_source%2Fplugins%2Flist%2Fplugin.js;h=3beb9fb5b2ff15464a3909bcfb20fc9b73bc405a;hb=3fe9cac293e090ea459a3ee10d78cbe9e1dd0e03;hp=0b61c2d7f06329e23ceab83aa824db17196b26d5;hpb=039a051ccf3901311661022a30afd60fc38130c9;p=ckeditor.git diff --git a/_source/plugins/list/plugin.js b/_source/plugins/list/plugin.js index 0b61c2d..3beb9fb 100644 --- a/_source/plugins/list/plugin.js +++ b/_source/plugins/list/plugin.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2003-2010, CKSource - Frederico Knabben. All rights reserved. +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.html or http://ckeditor.com/license */ @@ -12,6 +12,36 @@ For licensing, see LICENSE.html or http://ckeditor.com/license var listNodeNames = { ol : 1, ul : 1 }, emptyTextRegex = /^[\n\r\t ]*$/; + var whitespaces = CKEDITOR.dom.walker.whitespaces(), + bookmarks = CKEDITOR.dom.walker.bookmark(), + nonEmpty = function( node ){ return !( whitespaces( node ) || bookmarks( node ) );}, + blockBogus = CKEDITOR.dom.walker.bogus(); + + function cleanUpDirection( element ) + { + var dir, parent, parentDir; + if ( ( dir = element.getDirection() ) ) + { + parent = element.getParent(); + while ( parent && !( parentDir = parent.getDirection() ) ) + parent = parent.getParent(); + + if ( dir == parentDir ) + element.removeAttribute( 'dir' ); + } + } + + // Inheirt inline styles from another element. + function inheirtInlineStyles( parent, el ) + { + var style = parent.getAttribute( 'style' ); + + // Put parent styles before child styles. + style && el.setAttribute( 'style', + style.replace( /([^;])$/, '$1;' ) + + ( el.getAttribute( 'style' ) || '' ) ); + } + CKEDITOR.plugins.list = { /* * Convert a DOM list tree into a data structure that is easier to @@ -34,6 +64,10 @@ For licensing, see LICENSE.html or http://ckeditor.com/license { var listItem = listNode.getChild( i ); + // Fixing malformed nested lists by moving it into a previous list item. (#6236) + if( listItem.type == CKEDITOR.NODE_ELEMENT && listItem.getName() in CKEDITOR.dtd.$list ) + CKEDITOR.plugins.list.listToArray( listItem, database, baseArray, baseIndentLevel + 1 ); + // It may be a text node or some funny stuff. if ( listItem.$.nodeName.toLowerCase() != 'li' ) continue; @@ -73,83 +107,127 @@ For licensing, see LICENSE.html or http://ckeditor.com/license baseIndex = 0; if ( !listArray || listArray.length < baseIndex + 1 ) return null; - var doc = listArray[ baseIndex ].parent.getDocument(), + var i, + doc = listArray[ baseIndex ].parent.getDocument(), retval = new CKEDITOR.dom.documentFragment( doc ), rootNode = null, currentIndex = baseIndex, indentLevel = Math.max( listArray[ baseIndex ].indent, 0 ), currentListItem = null, + orgDir, + block, paragraphName = ( paragraphMode == CKEDITOR.ENTER_P ? 'p' : 'div' ); while ( 1 ) { - var item = listArray[ currentIndex ]; + var item = listArray[ currentIndex ], + itemGrandParent = item.grandparent; + + orgDir = item.element.getDirection( 1 ); + if ( item.indent == indentLevel ) { if ( !rootNode || listArray[ currentIndex ].parent.getName() != rootNode.getName() ) { rootNode = listArray[ currentIndex ].parent.clone( false, 1 ); + dir && rootNode.setAttribute( 'dir', dir ); retval.append( rootNode ); } currentListItem = rootNode.append( item.element.clone( 0, 1 ) ); - for ( var i = 0 ; i < item.contents.length ; i++ ) + + if ( orgDir != rootNode.getDirection( 1 ) ) + currentListItem.setAttribute( 'dir', orgDir ); + + for ( i = 0 ; i < item.contents.length ; i++ ) currentListItem.append( item.contents[i].clone( 1, 1 ) ); currentIndex++; } else if ( item.indent == Math.max( indentLevel, 0 ) + 1 ) { - var listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode ); + // Maintain original direction (#6861). + var currDir = listArray[ currentIndex - 1 ].element.getDirection( 1 ), + listData = CKEDITOR.plugins.list.arrayToList( listArray, null, currentIndex, paragraphMode, + currDir != orgDir ? orgDir: null ); + + // If the next block is an
  • with another list tree as the first + // child, we'll need to append a filler (
    /NBSP) or the list item + // wouldn't be editable. (#6724) + if ( !currentListItem.getChildCount() && CKEDITOR.env.ie && !( doc.$.documentMode > 7 )) + currentListItem.append( doc.createText( '\xa0' ) ); currentListItem.append( listData.listNode ); currentIndex = listData.nextIndex; } - else if ( item.indent == -1 && !baseIndex && item.grandparent ) + else if ( item.indent == -1 && !baseIndex && itemGrandParent ) { - currentListItem; - if ( listNodeNames[ item.grandparent.getName() ] ) + if ( listNodeNames[ itemGrandParent.getName() ] ) + { currentListItem = item.element.clone( false, true ); + if ( orgDir != itemGrandParent.getDirection( 1 ) ) + currentListItem.setAttribute( 'dir', orgDir ); + } else + currentListItem = new CKEDITOR.dom.documentFragment( doc ); + + // Migrate all children to the new container, + // apply the proper text direction. + var dirLoose = itemGrandParent.getDirection( 1 ) != orgDir, + li = item.element, + className = li.getAttribute( 'class' ), + style = li.getAttribute( 'style' ); + + var needsBlock = currentListItem.type == + CKEDITOR.NODE_DOCUMENT_FRAGMENT && + ( paragraphMode != CKEDITOR.ENTER_BR || dirLoose || + style || className ); + + var child, count = item.contents.length; + for ( i = 0 ; i < count; i++ ) { - // Create completely new blocks here. - if ( dir || item.element.hasAttributes() || - ( paragraphMode != CKEDITOR.ENTER_BR && item.grandparent.getName() != 'td' ) ) + child = item.contents[ i ]; + + if ( child.type == CKEDITOR.NODE_ELEMENT && child.isBlockBoundary() ) { - currentListItem = doc.createElement( paragraphName ); - item.element.copyAttributes( currentListItem, { type:1, value:1 } ); - dir && currentListItem.setAttribute( 'dir', dir ); - - // There might be a case where there are no attributes in the element after all - // (i.e. when "type" or "value" are the only attributes set). In this case, if enterMode = BR, - // the current item should be a fragment. - if ( !dir && paragraphMode == CKEDITOR.ENTER_BR && !currentListItem.hasAttributes() ) - currentListItem = new CKEDITOR.dom.documentFragment( doc ); + // Apply direction on content blocks. + if ( dirLoose && !child.getDirection() ) + child.setAttribute( 'dir', orgDir ); + + inheirtInlineStyles( li, child ); + + className && child.addClass( className ); } - else - currentListItem = new CKEDITOR.dom.documentFragment( doc ); - } + else if ( needsBlock ) + { + // Establish new block to hold text direction and styles. + if ( !block ) + { + block = doc.createElement( paragraphName ); + dirLoose && block.setAttribute( 'dir', orgDir ); + } - for ( i = 0 ; i < item.contents.length ; i++ ) - currentListItem.append( item.contents[i].clone( 1, 1 ) ); + // Copy over styles to new block; + style && block.setAttribute( 'style', style ); + className && block.setAttribute( 'class', className ); + + block.append( child.clone( 1, 1 ) ); + } + + currentListItem.append( block || child.clone( 1, 1 ) ); + } if ( currentListItem.type == CKEDITOR.NODE_DOCUMENT_FRAGMENT && currentIndex != listArray.length - 1 ) { - if ( currentListItem.getLast() - && currentListItem.getLast().type == CKEDITOR.NODE_ELEMENT - && currentListItem.getLast().getAttribute( 'type' ) == '_moz' ) - currentListItem.getLast().remove(); - currentListItem.appendBogus(); - } + var last = currentListItem.getLast(); + if ( last && last.type == CKEDITOR.NODE_ELEMENT + && last.getAttribute( 'type' ) == '_moz' ) + { + last.remove(); + } - if ( currentListItem.type == CKEDITOR.NODE_ELEMENT && - currentListItem.getName() == paragraphName && - currentListItem.$.firstChild ) - { - currentListItem.trim(); - var firstChild = currentListItem.getFirst(); - if ( firstChild.type == CKEDITOR.NODE_ELEMENT && firstChild.isBlockBoundary() ) + if ( !( last = currentListItem.getLast( nonEmpty ) + && last.type == CKEDITOR.NODE_ELEMENT + && last.getName() in CKEDITOR.dtd.$block ) ) { - var tmp = new CKEDITOR.dom.documentFragment( doc ); - currentListItem.moveChildren( tmp ); - currentListItem = tmp; + currentListItem.append( doc.createElement( 'br' ) ); } } @@ -163,18 +241,29 @@ For licensing, see LICENSE.html or http://ckeditor.com/license else return null; + block = null; + if ( listArray.length <= currentIndex || Math.max( listArray[ currentIndex ].indent, 0 ) < indentLevel ) break; } - // Clear marker attributes for the new list tree made of cloned nodes, if any. if ( database ) { - var currentNode = retval.getFirst(); + var currentNode = retval.getFirst(), + listRoot = listArray[ 0 ].parent; + while ( currentNode ) { if ( currentNode.type == CKEDITOR.NODE_ELEMENT ) + { + // Clear marker attributes for the new list tree made of cloned nodes, if any. CKEDITOR.dom.element.clearMarkers( database, currentNode ); + + // Clear redundant direction attribute specified on list items. + if ( currentNode.getName() in CKEDITOR.dtd.$listItem ) + cleanUpDirection( currentNode ); + } + currentNode = currentNode.getNextSourceNode(); } } @@ -183,30 +272,26 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }; - function setState( editor, state ) - { - editor.getCommand( this.name ).setState( state ); - } - function onSelectionChange( evt ) { + if ( evt.editor.readOnly ) + return null; + var path = evt.data.path, blockLimit = path.blockLimit, elements = path.elements, - element; + element, + i; // Grouping should only happen under blockLimit.(#3940). - for ( var i = 0 ; i < elements.length && ( element = elements[ i ] ) + for ( i = 0 ; i < elements.length && ( element = elements[ i ] ) && !element.equals( blockLimit ); i++ ) { - if ( listNodeNames[ elements[i].getName() ] ) - { - return setState.call( this, evt.editor, - this.type == elements[i].getName() ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); - } + if ( listNodeNames[ elements[ i ].getName() ] ) + return this.setState( this.type == elements[ i ].getName() ? CKEDITOR.TRISTATE_ON : CKEDITOR.TRISTATE_OFF ); } - return setState.call( this, evt.editor, CKEDITOR.TRISTATE_OFF ); + return this.setState( CKEDITOR.TRISTATE_OFF ); } function changeListType( editor, groupObj, database, listsCreated ) @@ -230,17 +315,27 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } var root = groupObj.root, - fakeParent = root.getDocument().createElement( this.type ); - // Copy all attributes, except from 'start' and 'type'. - root.copyAttributes( fakeParent, { start : 1, type : 1 } ); - // The list-style-type property should be ignored. - fakeParent.removeStyle( 'list-style-type' ); + doc = root.getDocument(), + listNode, + newListNode; for ( i = 0 ; i < selectedListItems.length ; i++ ) { var listIndex = selectedListItems[i].getCustomData( 'listarray_index' ); - listArray[listIndex].parent = fakeParent; + listNode = listArray[ listIndex ].parent; + + // Switch to new list node for this particular item. + if ( !listNode.is( this.type ) ) + { + newListNode = doc.createElement( this.type ); + // Copy all attributes, except from 'start' and 'type'. + listNode.copyAttributes( newListNode, { start : 1, type : 1 } ); + // The list-style-type property should be ignored. + newListNode.removeStyle( 'list-style-type' ); + listArray[ listIndex ].parent = newListNode; + } } + var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode ); var child, length = newList.listNode.getChildCount(); for ( i = 0 ; i < length && ( child = newList.listNode.getChild( i ) ) ; i++ ) @@ -334,14 +429,13 @@ For licensing, see LICENSE.html or http://ckeditor.com/license contentBlock.appendTo( listItem ); else { - // Remove DIR attribute if it was merged into list root. + contentBlock.copyAttributes( listItem ); + // Remove direction attribute after it was merged into list root. (#7657) if ( listDir && contentBlock.getDirection() ) { - contentBlock.removeStyle( 'direction' ); - contentBlock.removeAttribute( 'dir' ); + listItem.removeStyle( 'direction' ); + listItem.removeAttribute( 'dir' ); } - - contentBlock.copyAttributes( listItem ); contentBlock.moveChildren( listItem ); contentBlock.remove(); } @@ -428,12 +522,30 @@ For licensing, see LICENSE.html or http://ckeditor.com/license this.type = type; } + var elementType = CKEDITOR.dom.walker.nodeType( CKEDITOR.NODE_ELEMENT ); + + // Merge child nodes with direction preserved. (#7448) + function mergeChildren( from, into, refNode, forward ) + { + var child, itemDir; + while ( ( child = from[ forward ? 'getLast' : 'getFirst' ]( elementType ) ) ) + { + if ( ( itemDir = child.getDirection( 1 ) ) !== into.getDirection( 1 ) ) + child.setAttribute( 'dir', itemDir ); + + child.remove(); + + refNode ? + child[ forward ? 'insertBefore' : 'insertAfter' ]( refNode ) : + into.append( child, forward ); + } + } + listCommand.prototype = { exec : function( editor ) { - editor.focus(); - var doc = editor.document, + config = editor.config, selection = editor.getSelection(), ranges = selection && selection.getRanges( true ); @@ -447,21 +559,12 @@ For licensing, see LICENSE.html or http://ckeditor.com/license if ( this.state == CKEDITOR.TRISTATE_OFF ) { var body = doc.getBody(); - body.trim(); - if ( !body.getFirst() ) + if ( !body.getFirst( nonEmpty ) ) { - var paragraph = doc.createElement( editor.config.enterMode == CKEDITOR.ENTER_P ? 'p' : - ( editor.config.enterMode == CKEDITOR.ENTER_DIV ? 'div' : 'br' ) ); - paragraph.appendTo( body ); - ranges = new CKEDITOR.dom.rangeList( [ new CKEDITOR.dom.range( doc ) ] ); - // IE exception on inserting anything when anchor inside
    . - if ( paragraph.is( 'br' ) ) - { - ranges[ 0 ].setStartBefore( paragraph ); - ranges[ 0 ].setEndAfter( paragraph ); - } - else - ranges[ 0 ].selectNodeContents( paragraph ); + config.enterMode == CKEDITOR.ENTER_BR ? + body.appendBogus() : + ranges[ 0 ].fixBlock( 1, config.enterMode == CKEDITOR.ENTER_P ? 'p' : 'div' ); + selection.selectRanges( ranges ); } // Maybe a single range there enclosing the whole list, @@ -472,7 +575,7 @@ For licensing, see LICENSE.html or http://ckeditor.com/license enclosedNode = range && range.getEnclosedNode(); if ( enclosedNode && enclosedNode.is && this.type == enclosedNode.getName() ) - setState.call( this, editor, CKEDITOR.TRISTATE_ON ); + this.setState( CKEDITOR.TRISTATE_ON ); } } @@ -579,25 +682,9 @@ For licensing, see LICENSE.html or http://ckeditor.com/license removeList.call( this, editor, groupObj, database ); } - // For all new lists created, merge adjacent, same type lists. + // For all new lists created, merge into adjacent, same type lists. for ( i = 0 ; i < listsCreated.length ; i++ ) - { - listNode = listsCreated[i]; - var mergeSibling, listCommand = this; - ( mergeSibling = function( rtl ){ - - var sibling = listNode[ rtl ? - 'getPrevious' : 'getNext' ]( CKEDITOR.dom.walker.whitespaces( true ) ); - if ( sibling && sibling.getName && - sibling.getName() == listCommand.type ) - { - sibling.remove(); - // Move children order by merge direction.(#3820) - sibling.moveChildren( listNode, rtl ); - } - } )(); - mergeSibling( 1 ); - } + mergeListSiblings( listsCreated[ i ] ); // Clean up, restore selection and update toolbar button states. CKEDITOR.dom.element.clearAllMarkers( database ); @@ -606,6 +693,26 @@ For licensing, see LICENSE.html or http://ckeditor.com/license } }; + // Merge list adjacent, of same type lists. + function mergeListSiblings( listNode ) + { + var mergeSibling; + ( mergeSibling = function( rtl ) + { + var sibling = listNode[ rtl ? 'getPrevious' : 'getNext' ]( nonEmpty ); + if ( sibling && sibling.type == CKEDITOR.NODE_ELEMENT && + sibling.is( listNode.getName() ) ) + { + // Move children order by merge direction.(#3820) + mergeChildren( listNode, sibling, null, !rtl ); + + listNode.remove(); + listNode = sibling; + } + } )(); + mergeSibling( 1 ); + } + var dtd = CKEDITOR.dtd; var tailNbspRegex = /[\t\r\n ]*(?: |\xa0)$/; @@ -668,15 +775,130 @@ For licensing, see LICENSE.html or http://ckeditor.com/license for ( i in dtd.$listItem ) defaultListHtmlFilterRules.elements[ i ] = getExtendNestedListFilter( true ); + // Check if node is block element that recieves text. + function isTextBlock( node ) + { + return node.type == CKEDITOR.NODE_ELEMENT && + ( node.getName() in CKEDITOR.dtd.$block || + node.getName() in CKEDITOR.dtd.$listItem ) && + CKEDITOR.dtd[ node.getName() ][ '#' ]; + } + + // Join visually two block lines. + function joinNextLineToCursor( editor, cursor, nextCursor ) + { + editor.fire( 'saveSnapshot' ); + + // Merge with previous block's content. + nextCursor.enlarge( CKEDITOR.ENLARGE_LIST_ITEM_CONTENTS ); + var frag = nextCursor.extractContents(); + + cursor.trim( false, true ); + var bm = cursor.createBookmark(); + + // Kill original bogus; + var currentPath = new CKEDITOR.dom.elementPath( cursor.startContainer ), + pathBlock = currentPath.block, + currentBlock = currentPath.lastElement.getAscendant( 'li', 1 ) || pathBlock, + nextPath = new CKEDITOR.dom.elementPath( nextCursor.startContainer ), + nextLi = nextPath.contains( CKEDITOR.dtd.$listItem ), + nextList = nextPath.contains( CKEDITOR.dtd.$list ), + last; + + // Remove bogus node the current block/pseudo block. + if ( pathBlock ) + { + var bogus = pathBlock.getBogus(); + bogus && bogus.remove(); + } + else if ( nextList ) + { + last = nextList.getPrevious( nonEmpty ); + if ( last && blockBogus( last ) ) + last.remove(); + } + + // Kill the tail br in extracted. + last = frag.getLast(); + if ( last && last.type == CKEDITOR.NODE_ELEMENT && last.is( 'br' ) ) + last.remove(); + + // Insert fragment at the range position. + var nextNode = cursor.startContainer.getChild( cursor.startOffset ); + if ( nextNode ) + frag.insertBefore( nextNode ); + else + cursor.startContainer.append( frag ); + + // Move the sub list nested in the next list item. + if ( nextLi ) + { + var sublist = getSubList( nextLi ); + if ( sublist ) + { + // If next line is in the sub list of the current list item. + if ( currentBlock.contains( nextLi ) ) + { + mergeChildren( sublist, nextLi.getParent(), nextLi ); + sublist.remove(); + } + // Migrate the sub list to current list item. + else + currentBlock.append( sublist ); + } + } + + // Remove any remaining empty path blocks at next line after merging. + while ( nextCursor.checkStartOfBlock() && + nextCursor.checkEndOfBlock() ) + { + nextPath = new CKEDITOR.dom.elementPath( nextCursor.startContainer ); + var nextBlock = nextPath.block, parent; + + // Check if also to remove empty list. + if ( nextBlock.is( 'li' ) ) + { + parent = nextBlock.getParent(); + if ( nextBlock.equals( parent.getLast( nonEmpty ) ) + && nextBlock.equals( parent.getFirst( nonEmpty ) ) ) + nextBlock = parent; + } + + nextCursor.moveToPosition( nextBlock, CKEDITOR.POSITION_BEFORE_START ); + nextBlock.remove(); + } + + // Check if need to further merge with the list resides after the merged block. (#9080) + var walkerRng = nextCursor.clone(), body = editor.document.getBody(); + walkerRng.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + var walker = new CKEDITOR.dom.walker( walkerRng ); + walker.evaluator = function( node ) { return nonEmpty( node ) && !blockBogus( node ); }; + var next = walker.next(); + if ( next && next.type == CKEDITOR.NODE_ELEMENT && next.getName() in CKEDITOR.dtd.$list ) + mergeListSiblings( next ); + + cursor.moveToBookmark( bm ); + + // Make fresh selection. + cursor.select(); + + editor.selectionChange( 1 ); + editor.fire( 'saveSnapshot' ); + } + + function getSubList( li ) + { + var last = li.getLast( nonEmpty ); + return last && last.type == CKEDITOR.NODE_ELEMENT && last.getName() in listNodeNames ? last : null; + } + CKEDITOR.plugins.add( 'list', { init : function( editor ) { // Register commands. - var numberedListCommand = new listCommand( 'numberedlist', 'ol' ), - bulletedListCommand = new listCommand( 'bulletedlist', 'ul' ); - editor.addCommand( 'numberedlist', numberedListCommand ); - editor.addCommand( 'bulletedlist', bulletedListCommand ); + var numberedListCommand = editor.addCommand( 'numberedlist', new listCommand( 'numberedlist', 'ol' ) ), + bulletedListCommand = editor.addCommand( 'bulletedlist', new listCommand( 'bulletedlist', 'ul' ) ); // Register the toolbar button. editor.ui.addButton( 'NumberedList', @@ -693,6 +915,202 @@ For licensing, see LICENSE.html or http://ckeditor.com/license // Register the state changing handlers. editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, numberedListCommand ) ); editor.on( 'selectionChange', CKEDITOR.tools.bind( onSelectionChange, bulletedListCommand ) ); + + // Handled backspace/del key to join list items. (#8248,#9080) + editor.on( 'key', function( evt ) + { + var key = evt.data.keyCode; + + // DEl/BACKSPACE + if ( editor.mode == 'wysiwyg' && key in { 8 : 1, 46 : 1 } ) + { + var sel = editor.getSelection(), + range = sel.getRanges()[ 0 ]; + + if ( !range.collapsed ) + return; + + var path = new CKEDITOR.dom.elementPath( range.startContainer ); + var isBackspace = key == 8; + var body = editor.document.getBody(); + var walker = new CKEDITOR.dom.walker( range.clone() ); + walker.evaluator = function( node ) { return nonEmpty( node ) && !blockBogus( node ); }; + + // Backspace/Del behavior at the start/end of table is handled in core. + walker.guard = function( node, isOut ) { return !( isOut && node.type == CKEDITOR.NODE_ELEMENT && node.is( 'table' ) ); }; + + var cursor = range.clone(); + + if ( isBackspace ) + { + var previous, joinWith; + + // Join a sub list's first line, with the previous visual line in parent. + if ( ( previous = path.contains( listNodeNames ) ) && + range.checkBoundaryOfElement( previous, CKEDITOR.START ) && + ( previous = previous.getParent() ) && previous.is( 'li' ) && + ( previous = getSubList( previous ) ) ) + { + joinWith = previous; + previous = previous.getPrevious( nonEmpty ); + // Place cursor before the nested list. + cursor.moveToPosition( + previous && blockBogus( previous ) ? previous : joinWith, + CKEDITOR.POSITION_BEFORE_START ); + } + // Join any line following a list, with the last visual line of the list. + else + { + walker.range.setStartAt( body, CKEDITOR.POSITION_AFTER_START ); + walker.range.setEnd( range.startContainer, range.startOffset ); + previous = walker.previous(); + + if ( previous && previous.type == CKEDITOR.NODE_ELEMENT && + ( previous.getName() in listNodeNames || previous.is( 'li' ) ) ) + { + if ( !previous.is( 'li' ) ) + { + walker.range.selectNodeContents( previous ); + walker.reset(); + walker.evaluator = isTextBlock; + previous = walker.previous(); + } + + joinWith = previous; + // Place cursor at the end of previous block. + cursor.moveToElementEditEnd( joinWith ); + } + } + + if ( joinWith ) + { + joinNextLineToCursor( editor, cursor, range ); + evt.cancel(); + } + else + { + var list = path.contains( listNodeNames ), li; + // Backspace pressed at the start of list outdents the first list item. (#9129) + if ( list && range.checkBoundaryOfElement( list, CKEDITOR.START ) ) + { + li = list.getFirst( nonEmpty ); + + if ( range.checkBoundaryOfElement( li, CKEDITOR.START ) ) + { + previous = list.getPrevious( nonEmpty ); + + // Only if the list item contains a sub list, do nothing but + // simply move cursor backward one character. + if ( getSubList( li ) ) + { + if ( previous ) { + range.moveToElementEditEnd( previous ); + range.select(); + } + + evt.cancel(); + } + else + { + editor.execCommand( 'outdent' ); + evt.cancel(); + } + } + } + } + } + else + { + var next, nextLine; + li = range.startContainer.getAscendant( 'li', 1 ); + + if ( li ) + { + walker.range.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + + var last = li.getLast( nonEmpty ); + var block = last && isTextBlock( last ) ? last : li; + + // Indicate cursor at the visual end of an list item. + var isAtEnd = 0; + + next = walker.next(); + + // When list item contains a sub list. + if ( next && next.type == CKEDITOR.NODE_ELEMENT && + next.getName() in listNodeNames + && next.equals( last ) ) + { + isAtEnd = 1; + + // Move to the first item in sub list. + next = walker.next(); + } + // Right at the end of list item. + else if ( range.checkBoundaryOfElement( block, CKEDITOR.END ) ) + isAtEnd = 1; + + + if ( isAtEnd && next ) + { + // Put cursor range there. + nextLine = range.clone(); + nextLine.moveToElementEditStart( next ); + + joinNextLineToCursor( editor, cursor, nextLine ); + evt.cancel(); + } + } + else + { + // Handle Del key pressed before the list. + walker.range.setEndAt( body, CKEDITOR.POSITION_BEFORE_END ); + next = walker.next(); + + if ( next && next.type == CKEDITOR.NODE_ELEMENT && + next.getName() in listNodeNames ) + { + // The start
  • + next = next.getFirst( nonEmpty ); + + // Simply remove the current empty block, move cursor to the + // subsequent list. + if ( path.block && + range.checkStartOfBlock() && + range.checkEndOfBlock() ) + { + path.block.remove(); + range.moveToElementEditStart( next ); + range.select(); + evt.cancel(); + } + + // Preventing the default (merge behavior), but simply move + // the cursor one character forward if subsequent list item + // contains sub list. + else if ( getSubList( next ) ) + { + range.moveToElementEditStart( next ); + range.select(); + evt.cancel(); + } + // Merge the first list item with the current line. + else + { + nextLine = range.clone(); + nextLine.moveToElementEditStart( next ); + joinNextLineToCursor( editor, cursor, nextLine ); + evt.cancel(); + } + } + } + } + + // The backspace/del could potentially put cursor at a bad position, + // being it handled or not, check immediately the selection to have it fixed. + setTimeout( function() { editor.selectionChange( 1 ); } ); + } + } ); }, afterInit : function ( editor )