diff --git a/CHANGES.md b/CHANGES.md index d006187d735..05148a87f5b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ CKEditor 4 Changelog * [#10370](http://dev.ckeditor.com/ticket/10370): Inconsistency in data events between framed and inline editors. * [#9794](http://dev.ckeditor.com/ticket/9794): OnChange event. * [#9923](http://dev.ckeditor.com/ticket/9923): HiDPI support in editor UI. HiDPI icons for Moono skin. +* [#10027](http://dev.ckeditor.com/ticket/10027): Separated list and block indentation. ## CKEditor 4.1.2 diff --git a/config.js b/config.js index e7d54256c9a..ca700080cf9 100644 --- a/config.js +++ b/config.js @@ -34,7 +34,8 @@ CKEDITOR.editorConfig = function( config ) { 'htmlwriter,' + 'image,' + 'iframe,' + - 'indent,' + + 'indentlist,' + + 'indentblock,' + 'justify,' + 'link,' + 'list,' + diff --git a/plugins/enterkey/plugin.js b/plugins/enterkey/plugin.js index eef41747691..cb533b1f30f 100644 --- a/plugins/enterkey/plugin.js +++ b/plugins/enterkey/plugin.js @@ -5,9 +5,6 @@ (function() { CKEDITOR.plugins.add( 'enterkey', { - // TODO: should not depend on a particular format plugin. - requires: 'indent', - init: function( editor ) { editor.addCommand( 'enter', { modes:{wysiwyg:1 }, editorFocus: false, @@ -48,13 +45,188 @@ var atBlockStart = range.checkStartOfBlock(), atBlockEnd = range.checkEndOfBlock(), path = editor.elementPath( range.startContainer ), - block = path.block; + block = path.block, + + // Determine the block element to be used. + blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ), + + newBlock; // Exit the list when we're inside an empty list item block. (#5376) if ( atBlockStart && atBlockEnd ) { // Exit the list when we're inside an empty list item block. (#5376) if ( block && ( block.is( 'li' ) || block.getParent().is( 'li' ) ) ) { - editor.execCommand( 'outdent' ); + var blockParent = block.getParent(), + blockGrandParent = blockParent.getParent(), + + firstChild = !block.hasPrevious(), + lastChild = !block.hasNext(), + + selection = editor.getSelection(), + bookmarks = selection.createBookmarks(), + + orgDir = block.getDirection( 1 ), + className = block.getAttribute( 'class' ), + style = block.getAttribute( 'style' ), + dirLoose = blockGrandParent.getDirection( 1 ) != orgDir, + + enterMode = editor.config.enterMode, + needsBlock = enterMode != CKEDITOR.ENTER_BR || dirLoose || style || className, + + child; + + if ( blockGrandParent.is( 'li' ) ) { + + // If block is the first or the last child of the parent + // list, degrade it and move to the outer list: + // before the parent list if block is first child and after + // the parent list if block is the last child, respectively. + // + // + // + // AND + // + // + + if ( firstChild || lastChild ) + block[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockGrandParent ); + + // If the empty block is neither first nor last child + // then split the list and the block as an element + // of outer list. + // + // => + + else + block.breakParent( blockGrandParent ); + } + + else if ( !needsBlock ) { + block.appendBogus(); + + // If block is the first or last child of the parent + // list, move all block's children out of the list: + // before the list if block is first child and after the list + // if block is the last child, respectively. + // + // => ^ + // + // AND + // + // + + if ( firstChild || lastChild ) { + while ( ( child = block[ firstChild ? 'getFirst' : 'getLast' ]() ) ) + child[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockParent ); + } + + // If the empty block is neither first nor last child + // then split the list and put all the block contents + // between two lists. + // + // => + + else { + block.breakParent( blockParent ); + + while ( ( child = block.getLast() ) ) + child.insertAfter( blockParent ); + } + + block.remove(); + } else { + // Use
block for ENTER_BR and ENTER_DIV. + newBlock = doc.createElement( mode == CKEDITOR.ENTER_P ? 'p' : 'div' ); + + if ( dirLoose ) + newBlock.setAttribute( 'dir', orgDir ); + + style && newBlock.setAttribute( 'style', style ); + className && newBlock.setAttribute( 'class', className ); + + // Move all the child nodes to the new block. + block.moveChildren( newBlock ); + + // If block is the first or last child of the parent + // list, move it out of the list: + // before the list if block is first child and after the list + // if block is the last child, respectively. + // + // =>

^

+ // + // AND + // + // + + if ( firstChild || lastChild ) + newBlock[ firstChild ? 'insertBefore' : 'insertAfter' ]( blockParent ); + + // If the empty block is neither first nor last child + // then split the list and put the new block between + // two lists. + // + // => + + else { + block.breakParent( blockParent ); + newBlock.insertAfter( blockParent ); + } + + block.remove(); + } + + selection.selectBookmarks( bookmarks ); + return; } @@ -82,9 +254,6 @@ } } - // Determine the block element to be used. - var blockTag = ( mode == CKEDITOR.ENTER_DIV ? 'div' : 'p' ); - // Split the range. var splitInfo = range.splitBlock( blockTag ); @@ -139,7 +308,7 @@ if ( nextBlock ) range.moveToElementEditStart( nextBlock ); } else { - var newBlock, newBlockDir; + var newBlockDir; if ( previousBlock ) { // Do not enter this block if it's a header tag, or we are in diff --git a/plugins/indent/dev/indent.html b/plugins/indent/dev/indent.html new file mode 100644 index 00000000000..62b56c05726 --- /dev/null +++ b/plugins/indent/dev/indent.html @@ -0,0 +1,290 @@ + + + + + Indent DEV sample + + + + + + + +

Indent DEV sample

+

List & Block

+ + +

Indent classes

+ + +

List only

+ + +

Block only

+ + +

CKEDITOR.ENTER_BR

+ + + + + diff --git a/plugins/indent/plugin.js b/plugins/indent/plugin.js index 8e182a2d93d..4167fc0252c 100755 --- a/plugins/indent/plugin.js +++ b/plugins/indent/plugin.js @@ -4,370 +4,50 @@ */ /** - * @fileOverview Increse and decrease indent commands. + * @fileOverview Increase and decrease indent commands. */ (function() { - var listNodeNames = { ol:1,ul:1 }, - isNotWhitespaces = CKEDITOR.dom.walker.whitespaces( true ), - isNotBookmark = CKEDITOR.dom.walker.bookmark( false, true ); - - function indentCommand( editor, name ) { - this.name = name; - var useClasses = this.useIndentClasses = editor.config.indentClasses && editor.config.indentClasses.length > 0; - if ( useClasses ) { - this.classNameRegex = new RegExp( '(?:^|\\s+)(' + editor.config.indentClasses.join( '|' ) + ')(?=$|\\s)' ); - this.indentClassMap = {}; - for ( var i = 0; i < editor.config.indentClasses.length; i++ ) - this.indentClassMap[ editor.config.indentClasses[ i ] ] = i + 1; - } - - this.startDisabled = name == 'outdent'; - - this.allowedContent = { - 'div h1 h2 h3 h4 h5 h6 ol p pre ul': { - // Do not add elements, but only text-align style if element is validated by other rule. - propertiesOnly: true, - styles: !useClasses ? 'margin-left,margin-right' : null, - classes: useClasses ? editor.config.indentClasses : null - } - }; - - // #10192: Either blocks intendation or lists are required - acitvate - // indent commands in both situations. Lists are sufficient, because - // indent is needed for leaving list with enter key. - this.requiredContent = [ - 'p' + ( useClasses ? '(' + editor.config.indentClasses[ 0 ] + ')' : '{margin-left}' ), - 'li' - ]; - } - - // Returns the CSS property to be used for identing a given element. - function getIndentCssProperty( element, dir ) { - return ( dir || element.getComputedStyle( 'direction' ) ) == 'ltr' ? 'margin-left' : 'margin-right'; - } - - function isListItem( node ) { - return node.type == CKEDITOR.NODE_ELEMENT && node.is( 'li' ); - } - - indentCommand.prototype = { - // It applies to a "block-like" context. - context: 'p', - - refresh: function( editor, path ) { - var list = path && path.contains( listNodeNames ), - firstBlock = path.block || path.blockLimit; - - if ( list ) - this.setState( CKEDITOR.TRISTATE_OFF ); - - else if ( !this.useIndentClasses && this.name == 'indent' ) - this.setState( CKEDITOR.TRISTATE_OFF ); - - else if ( !firstBlock ) - this.setState( CKEDITOR.TRISTATE_DISABLED ); - - else if ( this.useIndentClasses ) { - var indentClass = firstBlock.$.className.match( this.classNameRegex ), - indentStep = 0; - - if ( indentClass ) { - indentClass = indentClass[ 1 ]; - indentStep = this.indentClassMap[ indentClass ]; - } - - if ( ( this.name == 'outdent' && !indentStep ) || ( this.name == 'indent' && indentStep == editor.config.indentClasses.length ) ) - this.setState( CKEDITOR.TRISTATE_DISABLED ); - else - this.setState( CKEDITOR.TRISTATE_OFF ); - } else { - var indent = parseInt( firstBlock.getStyle( getIndentCssProperty( firstBlock ) ), 10 ); - if ( isNaN( indent ) ) - indent = 0; - if ( indent <= 0 ) - this.setState( CKEDITOR.TRISTATE_DISABLED ); - else - this.setState( CKEDITOR.TRISTATE_OFF ); - } - }, - exec: function( editor ) { - var self = this, - database = {}; - - function indentList( listNode ) { - // Our starting and ending points of the range might be inside some blocks under a list item... - // So before playing with the iterator, we need to expand the block to include the list items. - var startContainer = range.startContainer, - endContainer = range.endContainer; - while ( startContainer && !startContainer.getParent().equals( listNode ) ) - startContainer = startContainer.getParent(); - while ( endContainer && !endContainer.getParent().equals( listNode ) ) - endContainer = endContainer.getParent(); - - if ( !startContainer || !endContainer ) - return; - - // Now we can iterate over the individual items on the same tree depth. - var block = startContainer, - itemsToMove = [], - stopFlag = false; - while ( !stopFlag ) { - if ( block.equals( endContainer ) ) - stopFlag = true; - itemsToMove.push( block ); - block = block.getNext(); - } - if ( itemsToMove.length < 1 ) - return; - - // Do indent or outdent operations on the array model of the list, not the - // list's DOM tree itself. The array model demands that it knows as much as - // possible about the surrounding lists, we need to feed it the further - // ancestor node that is still a list. - var listParents = listNode.getParents( true ); - for ( var i = 0; i < listParents.length; i++ ) { - if ( listParents[ i ].getName && listNodeNames[ listParents[ i ].getName() ] ) { - listNode = listParents[ i ]; - break; - } - } - var indentOffset = self.name == 'indent' ? 1 : -1, - startItem = itemsToMove[ 0 ], - lastItem = itemsToMove[ itemsToMove.length - 1 ]; - - // Convert the list DOM tree into a one dimensional array. - var listArray = CKEDITOR.plugins.list.listToArray( listNode, database ); - - // Apply indenting or outdenting on the array. - var baseIndent = listArray[ lastItem.getCustomData( 'listarray_index' ) ].indent; - for ( i = startItem.getCustomData( 'listarray_index' ); i <= lastItem.getCustomData( 'listarray_index' ); i++ ) { - listArray[ i ].indent += indentOffset; - // Make sure the newly created sublist get a brand-new element of the same type. (#5372) - if ( indentOffset > 0 ) { - var listRoot = listArray[ i ].parent; - listArray[ i ].parent = new CKEDITOR.dom.element( listRoot.getName(), listRoot.getDocument() ); - } - } - - for ( i = lastItem.getCustomData( 'listarray_index' ) + 1; - i < listArray.length && listArray[ i ].indent > baseIndent; i++ ) - listArray[ i ].indent += indentOffset; - - // Convert the array back to a DOM forest (yes we might have a few subtrees now). - // And replace the old list with the new forest. - var newList = CKEDITOR.plugins.list.arrayToList( listArray, database, null, editor.config.enterMode, listNode.getDirection() ); - - // Avoid nested
  • after outdent even they're visually same, - // recording them for later refactoring.(#3982) - if ( self.name == 'outdent' ) { - var parentLiElement; - if ( ( parentLiElement = listNode.getParent() ) && parentLiElement.is( 'li' ) ) { - var children = newList.listNode.getChildren(), - pendingLis = [], - count = children.count(), - child; - - for ( i = count - 1; i >= 0; i-- ) { - if ( ( child = children.getItem( i ) ) && child.is && child.is( 'li' ) ) - pendingLis.push( child ); - } - } - } - - if ( newList ) - newList.listNode.replace( listNode ); - - // Move the nested
  • to be appeared after the parent. - if ( pendingLis && pendingLis.length ) { - for ( i = 0; i < pendingLis.length; i++ ) { - var li = pendingLis[ i ], - followingList = li; - - // Nest preceding