diff --git a/editor/block-mover/index.js b/editor/block-mover/index.js index f040d2394a67f2..5a6ae7c9148431 100644 --- a/editor/block-mover/index.js +++ b/editor/block-mover/index.js @@ -8,31 +8,48 @@ import { first, last } from 'lodash'; * WordPress dependencies */ import { IconButton } from 'components'; +import { getBlockType } from 'blocks'; /** * Internal dependencies */ import './style.scss'; -import { isFirstBlock, isLastBlock } from '../selectors'; +import { isFirstBlock, isLastBlock, getBlockOrder, getBlock } from '../selectors'; +import { getBlockMoverLabel } from './mover-label'; -function BlockMover( { onMoveUp, onMoveDown, isFirst, isLast } ) { +function BlockMover( { onMoveUp, onMoveDown, isFirst, isLast, uids, blockType, firstIndex } ) { // We emulate a disabled state because forcefully applying the `disabled` // attribute on the button while it has focus causes the screen to change // to an unfocused state (body as active element) without firing blur on, // the rendering parent, leaving it unable to react to focus out. - return (
@@ -43,6 +60,8 @@ export default connect( ( state, ownProps ) => ( { isFirst: isFirstBlock( state, first( ownProps.uids ) ), isLast: isLastBlock( state, last( ownProps.uids ) ), + firstIndex: getBlockOrder( state, first( ownProps.uids ) ), + blockType: getBlockType( getBlock( state, first( ownProps.uids ) ).name ), } ), ( dispatch, ownProps ) => ( { onMoveDown() { diff --git a/editor/block-mover/mover-label.js b/editor/block-mover/mover-label.js new file mode 100644 index 00000000000000..65db9b512213a3 --- /dev/null +++ b/editor/block-mover/mover-label.js @@ -0,0 +1,109 @@ +/** + * Wordpress dependencies + */ +import { __, sprintf } from 'i18n'; + +/** + * Return a label for the block movement controls depending on block position. + * + * @param {number} selectedCount Number of blocks selected. + * @param {string} type Block type - in the case of a single block, should + * define its 'type'. I.e. 'Text', 'Heading', 'Image' etc. + * @param {number} firstIndex The index (position - 1) of the first block selected. + * @param {boolean} isFirst This is the first block. + * @param {boolean} isLast This is the last block. + * @param {number} dir Direction of movement (> 0 is considered to be going + * down, < 0 is up). + * @return {string} Label for the block movement controls. + */ +export function getBlockMoverLabel( selectedCount, type, firstIndex, isFirst, isLast, dir ) { + const position = ( firstIndex + 1 ); + + if ( selectedCount > 1 ) { + return getMultiBlockMoverLabel( selectedCount, firstIndex, isFirst, isLast, dir ); + } + + if ( isFirst && isLast ) { + // translators: %s: Type of block (i.e. Text, Image etc) + return sprintf( __( 'Block "%s" is the only block, and cannot be moved' ), type ); + } + + if ( dir > 0 && ! isLast ) { + // moving down + return sprintf( + __( 'Move "%(type)s" block from position %(position)d down to position %(newPosition)d' ), + { + type, + position, + newPosition: ( position + 1 ), + } + ); + } + + if ( dir > 0 && isLast ) { + // moving down, and is the last item + // translators: %s: Type of block (i.e. Text, Image etc) + return sprintf( __( 'Block "%s" is at the end of the content and can’t be moved down' ), type ); + } + + if ( dir < 0 && ! isFirst ) { + // moving up + return sprintf( + __( 'Move "%(type)s" block from position %(position)d up to position %(newPosition)d' ), + { + type, + position, + newPosition: ( position - 1 ), + } + ); + } + + if ( dir < 0 && isFirst ) { + // moving up, and is the first item + // translators: %s: Type of block (i.e. Text, Image etc) + return sprintf( __( 'Block "%s" is at the beginning of the content and can’t be moved up' ), type ); + } +} + +/** + * Return a label for the block movement controls depending on block position. + * + * @param {number} selectedCount Number of blocks selected. + * @param {number} firstIndex The index (position - 1) of the first block selected. + * @param {boolean} isFirst This is the first block. + * @param {boolean} isLast This is the last block. + * @param {number} dir Direction of movement (> 0 is considered to be going + * down, < 0 is up). + * @return {string} Label for the block movement controls. + */ +export function getMultiBlockMoverLabel( selectedCount, firstIndex, isFirst, isLast, dir ) { + const position = ( firstIndex + 1 ); + + if ( dir < 0 && isFirst ) { + return __( 'Blocks cannot be moved up as they are already at the top' ); + } + + if ( dir > 0 && isLast ) { + return __( 'Blocks cannot be moved down as they are already at the bottom' ); + } + + if ( dir < 0 && ! isFirst ) { + return sprintf( + __( 'Move %(selectedCount)d blocks from position %(position)d up by one place' ), + { + selectedCount, + position, + } + ); + } + + if ( dir > 0 && ! isLast ) { + return sprintf( + __( 'Move %(selectedCount)d blocks from position %(position)s down by one place' ), + { + selectedCount, + position, + } + ); + } +} diff --git a/editor/block-mover/test/mover-label.js b/editor/block-mover/test/mover-label.js new file mode 100644 index 00000000000000..354ab70dfb3413 --- /dev/null +++ b/editor/block-mover/test/mover-label.js @@ -0,0 +1,133 @@ +/** + * External dependencies + */ +import { expect } from 'chai'; + +/** + * Internal dependencies + */ +import { getBlockMoverLabel, getMultiBlockMoverLabel } from '../mover-label'; + +describe( 'block mover', () => { + const dirUp = -1, + dirDown = 1; + + describe( 'getBlockMoverLabel', () => { + const type = 'TestType'; + + it( 'Should generate a title for the first item moving up', () => { + expect( getBlockMoverLabel( + 1, + type, + 0, + true, + false, + dirUp, + ) ).to.equal( + `Block "${ type }" is at the beginning of the content and can’t be moved up` + ); + } ); + + it( 'Should generate a title for the last item moving down', () => { + expect( getBlockMoverLabel( + 1, + type, + 3, + false, + true, + dirDown, + ) ).to.equal( + `Block "${ type }" is at the end of the content and can’t be moved down` + ); + } ); + + it( 'Should generate a title for the second item moving up', () => { + expect( getBlockMoverLabel( + 1, + type, + 1, + false, + false, + dirUp, + ) ).to.equal( + `Move "${ type }" block from position 2 up to position 1` + ); + } ); + + it( 'Should generate a title for the second item moving down', () => { + expect( getBlockMoverLabel( + 1, + type, + 1, + false, + false, + dirDown, + ) ).to.equal( + `Move "${ type }" block from position 2 down to position 3` + ); + } ); + + it( 'Should generate a title for the only item in the list', () => { + expect( getBlockMoverLabel( + 1, + type, + 0, + true, + true, + dirDown, + ) ).to.equal( + `Block "${ type }" is the only block, and cannot be moved` + ); + } ); + } ); + + describe( 'getMultiBlockMoverLabel', () => { + it( 'Should generate a title moving multiple blocks up', () => { + expect( getMultiBlockMoverLabel( + 4, + 1, + false, + true, + dirUp, + ) ).to.equal( + 'Move 4 blocks from position 2 up by one place' + ); + } ); + + it( 'Should generate a title moving multiple blocks down', () => { + expect( getMultiBlockMoverLabel( + 4, + 0, + true, + false, + dirDown, + ) ).to.equal( + 'Move 4 blocks from position 1 down by one place' + ); + } ); + + it( 'Should generate a title for a selection of blocks at the top', () => { + expect( getMultiBlockMoverLabel( + 4, + 1, + true, + true, + dirUp, + ) ).to.equal( + 'Blocks cannot be moved up as they are already at the top' + ); + } ); + + it( 'Should generate a title for a selection of blocks at the bottom', () => { + expect( getMultiBlockMoverLabel( + 4, + 2, + false, + true, + dirDown, + ) ).to.equal( + 'Blocks cannot be moved down as they are already at the bottom' + ); + } ); + } ); +} );