diff --git a/editor/actions.js b/editor/actions.js
index 072232bc7d2f71..47b09d0f338988 100644
--- a/editor/actions.js
+++ b/editor/actions.js
@@ -108,6 +108,18 @@ export function selectBlock( uid ) {
};
}
+export function startMultiSelect() {
+ return {
+ type: 'START_MULTI_SELECT',
+ };
+}
+
+export function stopMultiSelect() {
+ return {
+ type: 'STOP_MULTI_SELECT',
+ };
+}
+
export function multiSelect( start, end ) {
return {
type: 'MULTI_SELECT',
diff --git a/editor/block-mover/index.js b/editor/block-mover/index.js
index d127eea6b03baa..f86da5bcc86ee4 100644
--- a/editor/block-mover/index.js
+++ b/editor/block-mover/index.js
@@ -17,6 +17,7 @@ import { getBlockType } from '@wordpress/blocks';
import './style.scss';
import { isFirstBlock, isLastBlock, getBlockIndex, getBlock } from '../selectors';
import { getBlockMoverLabel } from './mover-label';
+import { selectBlock } from '../actions';
function BlockMover( { onMoveUp, onMoveDown, isFirst, isLast, uids, blockType, firstIndex } ) {
// We emulate a disabled state because forcefully applying the `disabled`
@@ -68,12 +69,20 @@ export default connect(
} ),
( dispatch, ownProps ) => ( {
onMoveDown() {
+ if ( ownProps.uids.length === 1 ) {
+ dispatch( selectBlock( first( ownProps.uids ) ) );
+ }
+
dispatch( {
type: 'MOVE_BLOCKS_DOWN',
uids: ownProps.uids,
} );
},
onMoveUp() {
+ if ( ownProps.uids.length === 1 ) {
+ dispatch( selectBlock( first( ownProps.uids ) ) );
+ }
+
dispatch( {
type: 'MOVE_BLOCKS_UP',
uids: ownProps.uids,
diff --git a/editor/block-settings-menu/content.js b/editor/block-settings-menu/content.js
index e5d278a4447fd8..c6298e9d779f38 100644
--- a/editor/block-settings-menu/content.js
+++ b/editor/block-settings-menu/content.js
@@ -6,18 +6,18 @@ import { connect } from 'react-redux';
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
+import { __, sprintf, _n } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';
/**
* Internal dependencies
*/
import { isEditorSidebarOpened } from '../selectors';
-import { selectBlock, removeBlock, toggleSidebar, setActivePanel, toggleBlockMode } from '../actions';
+import { removeBlocks, toggleSidebar, setActivePanel, toggleBlockMode } from '../actions';
-function BlockSettingsMenuContent( { onDelete, onSelect, isSidebarOpened, onToggleSidebar, onShowInspector, onToggleMode } ) {
+function BlockSettingsMenuContent( { onDelete, isSidebarOpened, onToggleSidebar, onShowInspector, onToggleMode, uids } ) {
+ const count = uids.length;
const toggleInspector = () => {
- onSelect();
onShowInspector();
if ( ! isSidebarOpened ) {
onToggleSidebar();
@@ -36,14 +36,14 @@ function BlockSettingsMenuContent( { onDelete, onSelect, isSidebarOpened, onTogg
className="editor-block-settings-menu__control"
onClick={ onDelete }
icon="trash"
- label={ __( 'Delete the block' ) }
+ label={ sprintf( _n( 'Delete the block', 'Delete the %d blocks', count ), count ) }
/>
-
+ /> }
);
}
@@ -54,10 +54,7 @@ export default connect(
} ),
( dispatch, ownProps ) => ( {
onDelete() {
- dispatch( removeBlock( ownProps.uid ) );
- },
- onSelect() {
- dispatch( selectBlock( ownProps.uid ) );
+ dispatch( removeBlocks( ownProps.uids ) );
},
onShowInspector() {
dispatch( setActivePanel( 'block' ) );
@@ -66,7 +63,7 @@ export default connect(
dispatch( toggleSidebar() );
},
onToggleMode() {
- dispatch( toggleBlockMode( ownProps.uid ) );
+ dispatch( toggleBlockMode( ownProps.uids[ 0 ] ) );
},
} )
)( BlockSettingsMenuContent );
diff --git a/editor/block-settings-menu/index.js b/editor/block-settings-menu/index.js
index c4071727d8d404..e79f1d2599ac66 100644
--- a/editor/block-settings-menu/index.js
+++ b/editor/block-settings-menu/index.js
@@ -28,7 +28,11 @@ class BlockSettingsMenu extends Component {
}
toggleMenu() {
- this.props.onSelect();
+ // Block could be hovered, not selected.
+ if ( this.props.uids.length === 1 ) {
+ this.props.onSelect( this.props.uids[ 0 ] );
+ }
+
this.setState( ( state ) => ( {
opened: ! state.opened,
} ) );
@@ -36,7 +40,7 @@ class BlockSettingsMenu extends Component {
render() {
const { opened } = this.state;
- const { uid } = this.props;
+ const { uids, focus } = this.props;
const toggleClassname = classnames( 'editor-block-settings-menu__toggle', 'editor-block-settings-menu__control', {
'is-opened': opened,
} );
@@ -48,9 +52,10 @@ class BlockSettingsMenu extends Component {
onClick={ this.toggleMenu }
icon="ellipsis"
label={ opened ? __( 'Close Settings Menu' ) : __( 'Open Settings Menu' ) }
+ focus={ focus }
/>
- { opened && }
+ { opened && }
);
}
@@ -58,9 +63,9 @@ class BlockSettingsMenu extends Component {
export default connect(
undefined,
- ( dispatch, ownProps ) => ( {
- onSelect() {
- dispatch( selectBlock( ownProps.uid ) );
+ ( dispatch ) => ( {
+ onSelect( uid ) {
+ dispatch( selectBlock( uid ) );
},
} )
)( BlockSettingsMenu );
diff --git a/editor/header/index.js b/editor/header/index.js
index 0d487693196554..9c922f4f72599e 100644
--- a/editor/header/index.js
+++ b/editor/header/index.js
@@ -6,7 +6,7 @@ import { connect } from 'react-redux';
/**
* WordPress dependencies
*/
-import { sprintf, _n, __ } from '@wordpress/i18n';
+import { __ } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';
/**
@@ -18,13 +18,10 @@ import PublishButton from './publish-button';
import PreviewButton from './preview-button';
import ModeSwitcher from './mode-switcher';
import Inserter from '../inserter';
-import { getMultiSelectedBlockUids, hasEditorUndo, hasEditorRedo, isEditorSidebarOpened } from '../selectors';
-import { clearSelectedBlock, toggleSidebar, removeBlocks } from '../actions';
+import { hasEditorUndo, hasEditorRedo, isEditorSidebarOpened } from '../selectors';
+import { toggleSidebar } from '../actions';
function Header( {
- multiSelectedBlockUids,
- onRemove,
- onDeselect,
undo,
redo,
hasRedo,
@@ -32,39 +29,6 @@ function Header( {
onToggleSidebar,
isSidebarOpened,
} ) {
- const count = multiSelectedBlockUids.length;
-
- if ( count ) {
- return (
-
-
- { sprintf( _n( '%d block selected', '%d blocks selected', count ), count ) }
-
-
- onRemove( multiSelectedBlockUids ) }
- focus={ true }
- >
- { __( 'Delete' ) }
-
-
-
- onDeselect() }
- />
-
-
- );
- }
-
return (
( {
- multiSelectedBlockUids: getMultiSelectedBlockUids( state ),
hasUndo: hasEditorUndo( state ),
hasRedo: hasEditorRedo( state ),
isSidebarOpened: isEditorSidebarOpened( state ),
} ),
( dispatch ) => ( {
- onDeselect: () => dispatch( clearSelectedBlock() ),
- onRemove: ( uids ) => dispatch( removeBlocks( uids ) ),
undo: () => dispatch( { type: 'UNDO' } ),
redo: () => dispatch( { type: 'REDO' } ),
onToggleSidebar: () => dispatch( toggleSidebar() ),
diff --git a/editor/header/style.scss b/editor/header/style.scss
index 37f4da4e05cc5f..63237df326f551 100644
--- a/editor/header/style.scss
+++ b/editor/header/style.scss
@@ -73,21 +73,6 @@
right: $admin-sidebar-width-big;
}
-.editor-header-multi-select {
- background: $blue-medium-100;
- border-bottom: 1px solid $blue-medium-200;
-}
-
-.editor-selected-count {
- padding-right: $item-spacing;
- color: $dark-gray-500;
- border-right: 1px solid $light-gray-500;
-}
-
-.editor-selected-clear {
- margin: 0 0 0 auto;
-}
-
// hide all action buttons except the inserter on mobile
.editor-header__content-tools > .components-button {
display: none;
diff --git a/editor/modes/visual-editor/block-list.js b/editor/modes/visual-editor/block-list.js
index 8ae044dc085679..5b3e3f1b4f92e3 100644
--- a/editor/modes/visual-editor/block-list.js
+++ b/editor/modes/visual-editor/block-list.js
@@ -25,7 +25,7 @@ import {
getMultiSelectedBlocks,
getMultiSelectedBlockUids,
} from '../../selectors';
-import { insertBlock, multiSelect } from '../../actions';
+import { insertBlock, startMultiSelect, stopMultiSelect, multiSelect } from '../../actions';
const INSERTION_POINT_PLACEHOLDER = '[[insertion-point]]';
@@ -138,6 +138,8 @@ class VisualEditorBlockList extends Component {
// Capture scroll on all elements.
window.addEventListener( 'scroll', this.onScroll, true );
window.addEventListener( 'mouseup', this.onSelectionEnd );
+
+ this.props.onStartMultiSelect();
}
onSelectionChange( uid ) {
@@ -169,6 +171,8 @@ class VisualEditorBlockList extends Component {
window.removeEventListener( 'mousemove', this.onPointerMove );
window.removeEventListener( 'scroll', this.onScroll, true );
window.removeEventListener( 'mouseup', this.onSelectionEnd );
+
+ this.props.onStopMultiSelect();
}
appendDefaultBlock() {
@@ -244,6 +248,12 @@ export default connect(
onInsertBlock( block ) {
dispatch( insertBlock( block ) );
},
+ onStartMultiSelect() {
+ dispatch( startMultiSelect() );
+ },
+ onStopMultiSelect() {
+ dispatch( stopMultiSelect() );
+ },
onMultiSelect( start, end ) {
dispatch( multiSelect( start, end ) );
},
diff --git a/editor/modes/visual-editor/block.js b/editor/modes/visual-editor/block.js
index e8c41c96ae32ca..e6f6d98e2da7b7 100644
--- a/editor/modes/visual-editor/block.js
+++ b/editor/modes/visual-editor/block.js
@@ -40,6 +40,7 @@ import {
import {
getBlock,
getBlockFocus,
+ isMultiSelecting,
getBlockIndex,
getEditedPostAttribute,
getMultiSelectedBlockUids,
@@ -136,7 +137,6 @@ class VisualEditorBlock extends Component {
bindBlockNode( node ) {
this.node = node;
- this.props.blockRef( node );
}
setAttributes( attributes ) {
@@ -256,8 +256,9 @@ class VisualEditorBlock extends Component {
}
onPointerDown( event ) {
- // Not the main button (usually the left button on pointer device).
- if ( event.buttons !== 1 ) {
+ // Not the main button.
+ // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
+ if ( event.button !== 0 ) {
return;
}
@@ -306,12 +307,13 @@ class VisualEditorBlock extends Component {
// Generate the wrapper class names handling the different states of the block.
const { isHovered, isSelected, isMultiSelected, isFirstMultiSelected, focus } = this.props;
const showUI = isSelected && ( ! this.props.isTyping || focus.collapsed === false );
+ const isProperlyHovered = isHovered && ! this.props.isSelecting;
const { error } = this.state;
const wrapperClassname = classnames( 'editor-visual-editor__block', {
'has-warning': ! isValid || !! error,
'is-selected': showUI,
'is-multi-selected': isMultiSelected,
- 'is-hovered': isHovered,
+ 'is-hovered': isProperlyHovered,
} );
const { onMouseLeave, onFocus, onReplace } = this.props;
@@ -330,31 +332,37 @@ class VisualEditorBlock extends Component {
/* eslint-disable jsx-a11y/no-static-element-interactions, jsx-a11y/onclick-has-role, jsx-a11y/click-events-have-key-events */
return (
- { ( showUI || isHovered ) &&
}
- { ( showUI || isHovered ) &&
}
+ { ( showUI || isProperlyHovered ) &&
}
+ { ( showUI || isProperlyHovered ) &&
}
{ showUI && isValid && mode === 'visual' &&
}
-
- { isFirstMultiSelected && (
+ { isFirstMultiSelected && ! this.props.isSelecting &&
- ) }
+ }
+ { isFirstMultiSelected && ! this.props.isSelecting &&
+
+ }
event.preventDefault() }
onMouseDown={ this.onPointerDown }
+ onKeyDown={ this.onKeyDown }
+ onFocus={ this.onFocus }
className="editor-visual-editor__block-edit"
+ tabIndex="0"
+ aria-label={ blockLabel }
>
{ isValid && mode === 'visual' && (
@@ -404,6 +412,7 @@ export default connect(
isFirstMultiSelected: isFirstMultiSelectedBlock( state, ownProps.uid ),
isHovered: isBlockHovered( state, ownProps.uid ),
focus: getBlockFocus( state, ownProps.uid ),
+ isSelecting: isMultiSelecting( state ),
isTyping: isTyping( state ),
order: getBlockIndex( state, ownProps.uid ),
multiSelectedBlockUids: getMultiSelectedBlockUids( state ),
diff --git a/editor/reducer.js b/editor/reducer.js
index df8a45f1015ff4..c5b91422ce1464 100644
--- a/editor/reducer.js
+++ b/editor/reducer.js
@@ -314,11 +314,18 @@ export function blockSelection( state = { start: null, end: null, focus: null },
end: null,
focus: null,
};
+ case 'START_MULTI_SELECT':
+ return {
+ ...state,
+ isMultiSelecting: true,
+ };
+ case 'STOP_MULTI_SELECT':
+ return omit( state, 'isMultiSelecting' );
case 'MULTI_SELECT':
return {
start: action.start,
end: action.end,
- focus: null,
+ focus: state.focus,
};
case 'SELECT_BLOCK':
if ( action.uid === state.start && action.uid === state.end ) {
@@ -350,17 +357,6 @@ export function blockSelection( state = { start: null, end: null, focus: null },
end: action.blocks[ 0 ].uid,
focus: {},
};
- case 'MOVE_BLOCKS_UP':
- case 'MOVE_BLOCKS_DOWN': {
- const firstUid = first( action.uids );
- return firstUid === state.start
- ? state
- : {
- start: firstUid,
- end: firstUid,
- focus: {},
- };
- }
}
return state;
diff --git a/editor/selectors.js b/editor/selectors.js
index 09df4ed4185b84..c89b667ac0aae4 100644
--- a/editor/selectors.js
+++ b/editor/selectors.js
@@ -451,12 +451,28 @@ export const getBlocks = createSelector(
* Returns the number of blocks currently present in the post.
*
* @param {Object} state Global application state
- * @return {Object} Number of blocks in the post
+ * @return {Number} Number of blocks in the post
*/
export function getBlockCount( state ) {
return getBlockUids( state ).length;
}
+/**
+ * Returns the number of blocks currently selected in the post.
+ *
+ * @param {Object} state Global application state
+ * @return {Number} Number of blocks selected in the post
+ */
+export function getSelectedBlockCount( state ) {
+ const multiSelectedBlockCount = getMultiSelectedBlockUids( state ).length;
+
+ if ( multiSelectedBlockCount ) {
+ return multiSelectedBlockCount;
+ }
+
+ return state.blockSelection.start ? 1 : 0;
+}
+
/**
* Returns the currently selected block, or null if there is no selected block.
*
@@ -705,12 +721,24 @@ export function isBlockHovered( state, uid ) {
* @return {Object} Block focus state
*/
export function getBlockFocus( state, uid ) {
- if ( ! isBlockSelected( state, uid ) ) {
+ // If there is multi-selection, keep returning the focus object for the start block.
+ if ( ! isBlockSelected( state, uid ) && state.blockSelection.start !== uid ) {
return null;
}
return state.blockSelection.focus;
}
+
+/**
+ * Whether in the process of multi-selecting or not.
+ *
+ * @param {Object} state Global application state
+ * @return {Boolean} True if multi-selecting, false if not.
+ */
+export function isMultiSelecting( state ) {
+ return !! state.blockSelection.isMultiSelecting;
+}
+
/**
* Returns thee block's editing mode
*
diff --git a/editor/sidebar/block-inspector/index.js b/editor/sidebar/block-inspector/index.js
index 0024e89b430122..bbcfdac10a4587 100644
--- a/editor/sidebar/block-inspector/index.js
+++ b/editor/sidebar/block-inspector/index.js
@@ -15,9 +15,13 @@ import { Panel, PanelBody } from '@wordpress/components';
*/
import './style.scss';
import BlockInspectorAdvancedControls from './advanced-controls';
-import { getSelectedBlock } from '../../selectors';
+import { getSelectedBlock, getSelectedBlockCount } from '../../selectors';
+
+const BlockInspector = ( { selectedBlock, count } ) => {
+ if ( count > 1 ) {
+ return { __( 'Coming Soon' ) };
+ }
-const BlockInspector = ( { selectedBlock } ) => {
if ( ! selectedBlock ) {
return { __( 'No block selected.' ) };
}
@@ -36,6 +40,7 @@ export default connect(
( state ) => {
return {
selectedBlock: getSelectedBlock( state ),
+ count: getSelectedBlockCount( state ),
};
}
)( BlockInspector );
diff --git a/editor/sidebar/block-inspector/style.scss b/editor/sidebar/block-inspector/style.scss
index a0772bfe573d57..9b702667def7d6 100644
--- a/editor/sidebar/block-inspector/style.scss
+++ b/editor/sidebar/block-inspector/style.scss
@@ -8,7 +8,8 @@
}
}
-.editor-block-inspector__no-blocks {
+.editor-block-inspector__no-blocks,
+.editor-block-inspector__multi-blocks {
display: block;
font-size: $default-font-size;
background: $white;
diff --git a/editor/sidebar/header.js b/editor/sidebar/header.js
index 3970128ef4061b..d2788cf4a1f7db 100644
--- a/editor/sidebar/header.js
+++ b/editor/sidebar/header.js
@@ -6,16 +6,19 @@ import { connect } from 'react-redux';
/**
* WordPress dependencies
*/
-import { __ } from '@wordpress/i18n';
+import { __, _n, sprintf } from '@wordpress/i18n';
import { IconButton } from '@wordpress/components';
/**
* Internal Dependencies
*/
-import { getActivePanel } from '../selectors';
+import { getActivePanel, getSelectedBlockCount } from '../selectors';
import { toggleSidebar, setActivePanel } from '../actions';
-const SidebarHeader = ( { panel, onSetPanel, onToggleSidebar } ) => {
+const SidebarHeader = ( { panel, onSetPanel, onToggleSidebar, count } ) => {
+ // Do not display "0 Blocks".
+ count = count === 0 ? 1 : count;
+
return (
{
export default connect(
( state ) => ( {
panel: getActivePanel( state ),
+ count: getSelectedBlockCount( state ),
} ),
( dispatch ) => ( {
onSetPanel: ( panel ) => dispatch( setActivePanel( panel ) ),
diff --git a/editor/test/reducer.js b/editor/test/reducer.js
index 453b8b73af2372..b4da8e1f6ff21f 100644
--- a/editor/test/reducer.js
+++ b/editor/test/reducer.js
@@ -693,13 +693,14 @@ describe( 'state', () => {
} );
it( 'should set multi selection', () => {
- const state = blockSelection( undefined, {
+ const original = deepFreeze( { focus: { editable: 'citation' } } );
+ const state = blockSelection( original, {
type: 'MULTI_SELECT',
start: 'ribs',
end: 'chicken',
} );
- expect( state ).toEqual( { start: 'ribs', end: 'chicken', focus: null } );
+ expect( state ).toEqual( { start: 'ribs', end: 'chicken', focus: { editable: 'citation' } } );
} );
it( 'should not update the state if the block is already selected', () => {
@@ -733,24 +734,6 @@ describe( 'state', () => {
expect( state3 ).toEqual( { start: 'ribs', end: 'ribs', focus: {} } );
} );
- it( 'should return with block moved up', () => {
- const state = blockSelection( undefined, {
- type: 'MOVE_BLOCKS_UP',
- uids: [ 'ribs' ],
- } );
-
- expect( state ).toEqual( { start: 'ribs', end: 'ribs', focus: {} } );
- } );
-
- it( 'should return with block moved down', () => {
- const state = blockSelection( undefined, {
- type: 'MOVE_BLOCKS_DOWN',
- uids: [ 'chicken' ],
- } );
-
- expect( state ).toEqual( { start: 'chicken', end: 'chicken', focus: {} } );
- } );
-
it( 'should not update the state if the block moved is already selected', () => {
const original = deepFreeze( { start: 'ribs', end: 'ribs', focus: {} } );
const state = blockSelection( original, {
diff --git a/editor/test/selectors.js b/editor/test/selectors.js
index 5f8f3987a4be1c..89aa057145c99d 100644
--- a/editor/test/selectors.js
+++ b/editor/test/selectors.js
@@ -1493,6 +1493,30 @@ describe( 'selectors', () => {
expect( getBlockFocus( state, 123 ) ).toEqual( { editable: 'cite' } );
} );
+ it( 'should return the block focus for the start if the block is multi-selected', () => {
+ const state = {
+ blockSelection: {
+ start: 123,
+ end: 124,
+ focus: { editable: 'cite' },
+ },
+ };
+
+ expect( getBlockFocus( state, 123 ) ).toEqual( { editable: 'cite' } );
+ } );
+
+ it( 'should return null for the end if the block is multi-selected', () => {
+ const state = {
+ blockSelection: {
+ start: 123,
+ end: 124,
+ focus: { editable: 'cite' },
+ },
+ };
+
+ expect( getBlockFocus( state, 124 ) ).toEqual( null );
+ } );
+
it( 'should return null if the block is not selected', () => {
const state = {
blockSelection: {
diff --git a/editor/writing-flow/index.js b/editor/writing-flow/index.js
index fd9b5080760576..d3d2239d3f752f 100644
--- a/editor/writing-flow/index.js
+++ b/editor/writing-flow/index.js
@@ -36,7 +36,7 @@ class WritingFlow extends Component {
node.nodeName === 'INPUT' ||
node.nodeName === 'TEXTAREA' ||
node.contentEditable === 'true' ||
- node.classList.contains( 'editor-visual-editor__block' )
+ node.classList.contains( 'editor-visual-editor__block-edit' )
) );
}