Skip to content

Commit

Permalink
Smart block appender (#16708)
Browse files Browse the repository at this point in the history
* if thre is only one there is only one

* made a new insertion point selector, some code review refactoring

* better handling of inserter

* refactoring and named block insertion

* updates to the appender

* update snapshots

* update docs

* default inserter label is used in so many tests

* fixed allowed blocks test

* snapshot updated

* better naming and removed the need for es-lint disabling

* improved the inserter label construction

* improved the doc of getTheOnlyAllowedItem selector

* reverting test patches becasue patching without understanding is bad, bad, bad - don't do it

* moved getInsertionIndex out of selectos and back into each component that used it

* docs generated

* added experimental labels to new selectors, added es-lint comment back

* updated docs

* Update packages/block-editor/src/store/selectors.js

Co-Authored-By: Miguel Fonseca <miguelcsf@gmail.com>

* Update packages/block-editor/src/store/selectors.js

Co-Authored-By: Miguel Fonseca <miguelcsf@gmail.com>

* refactored and fixed some coding errors

* small code move

* small code move

* removes aria attrs for autoinserted items

* fixes typo, adds translators comment

* simplifies the intserter logic

* fix for the simplification

* simplifies by using one selector and passing props in compose

* small code updates

* lint

* renamed insertedBlock

* small doc update

* adds tooltip to the default button appender

* refactores for more self documenting varnames
  • Loading branch information
draganescu authored and hypest committed Nov 4, 2019
1 parent d420534 commit 38a1227
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 34 deletions.
40 changes: 27 additions & 13 deletions packages/block-editor/src/components/button-block-appender/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { Button, Icon } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { Button, Icon, Tooltip } from '@wordpress/components';
import { _x, sprintf } from '@wordpress/i18n';

/**
* Internal dependencies
Expand All @@ -21,17 +21,31 @@ function ButtonBlockAppender( { rootClientId, className } ) {
<BlockDropZone rootClientId={ rootClientId } />
<Inserter
rootClientId={ rootClientId }
renderToggle={ ( { onToggle, disabled, isOpen } ) => (
<Button
className={ classnames( className, 'block-editor-button-block-appender' ) }
onClick={ onToggle }
aria-expanded={ isOpen }
disabled={ disabled }
>
<span className="screen-reader-text">{ __( 'Add Block' ) }</span>
<Icon icon="insert" />
</Button>
) }
renderToggle={ ( { onToggle, disabled, isOpen, blockTitle, hasSingleBlockType } ) => {
let label;
if ( hasSingleBlockType ) {
// translators: %s: the name of the block when there is only one
label = sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle );
} else {
label = _x( 'Add block', 'Generic label for block inserter button' );
}
const isToggleButton = ! hasSingleBlockType;
return (
<Tooltip text={ label }>
<Button
className={ classnames( className, 'block-editor-button-block-appender' ) }
onClick={ onToggle }
aria-haspopup={ isToggleButton ? 'true' : undefined }
aria-expanded={ isToggleButton ? isOpen : undefined }
disabled={ disabled }
label={ label }
>
<span className="screen-reader-text">{ label }</span>
<Icon icon="insert" />
</Button>
</Tooltip>
);
} }
isAppender
/>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ exports[`DefaultBlockAppender should append a default block when input focused 1
rows={1}
value="Start writing or type / to choose a block"
/>
<WithSelect(IfCondition(Inserter))
<WithSelect(WithDispatch(IfCondition(Inserter)))
isAppender={true}
position="top right"
/>
Expand All @@ -53,7 +53,7 @@ exports[`DefaultBlockAppender should match snapshot 1`] = `
rows={1}
value="Start writing or type / to choose a block"
/>
<WithSelect(IfCondition(Inserter))
<WithSelect(WithDispatch(IfCondition(Inserter)))
isAppender={true}
position="top right"
/>
Expand All @@ -77,7 +77,7 @@ exports[`DefaultBlockAppender should optionally show without prompt 1`] = `
rows={1}
value=""
/>
<WithSelect(IfCondition(Inserter))
<WithSelect(WithDispatch(IfCondition(Inserter)))
isAppender={true}
position="top right"
/>
Expand Down
118 changes: 100 additions & 18 deletions packages/block-editor/src/components/inserter/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
/**
* External dependencies
*/
import { get } from 'lodash';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { __, _x, sprintf } from '@wordpress/i18n';
import { Dropdown, IconButton } from '@wordpress/components';
import { Component } from '@wordpress/element';
import { withSelect } from '@wordpress/data';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose, ifCondition } from '@wordpress/compose';
import {
createBlock,
getBlockType,
} from '@wordpress/blocks';

/**
* Internal dependencies
*/
import InserterMenu from './menu';

const defaultRenderToggle = ( { onToggle, disabled, isOpen } ) => (
<IconButton
icon="insert"
label={ __( 'Add block' ) }
labelPosition="bottom"
onClick={ onToggle }
className="editor-inserter__toggle block-editor-inserter__toggle"
aria-haspopup="true"
aria-expanded={ isOpen }
disabled={ disabled }
/>
);
const defaultRenderToggle = ( { onToggle, disabled, isOpen, blockTitle, hasSingleBlockType } ) => {
let label;
if ( hasSingleBlockType ) {
// translators: %s: the name of the block when there is only one
label = sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle );
} else {
label = _x( 'Add block', 'Generic label for block inserter button' );
}
return (
<IconButton
icon="insert"
label={ label }
labelPosition="bottom"
onClick={ onToggle }
className="editor-inserter__toggle block-editor-inserter__toggle"
aria-haspopup={ ! hasSingleBlockType ? 'true' : false }
aria-expanded={ ! hasSingleBlockType ? isOpen : false }
disabled={ disabled }
/>
);
};

class Inserter extends Component {
constructor() {
Expand Down Expand Up @@ -56,10 +73,12 @@ class Inserter extends Component {
renderToggle( { onToggle, isOpen } ) {
const {
disabled,
blockTitle,
hasSingleBlockType,
renderToggle = defaultRenderToggle,
} = this.props;

return renderToggle( { onToggle, isOpen, disabled } );
return renderToggle( { onToggle, isOpen, disabled, blockTitle, hasSingleBlockType } );
}

/**
Expand All @@ -86,8 +105,10 @@ class Inserter extends Component {
}

render() {
const { position } = this.props;

const { position, hasSingleBlockType, insertOnlyAllowedBlock } = this.props;
if ( hasSingleBlockType ) {
return this.renderToggle( { onToggle: insertOnlyAllowedBlock } );
}
return (
<Dropdown
className="editor-inserter block-editor-inserter"
Expand All @@ -105,10 +126,71 @@ class Inserter extends Component {

export default compose( [
withSelect( ( select, { rootClientId } ) => {
const { hasInserterItems } = select( 'core/block-editor' );
const {
hasInserterItems,
__experimentalGetAllowedBlocks,
} = select( 'core/block-editor' );

const allowedBlocks = __experimentalGetAllowedBlocks( rootClientId );

const hasSingleBlockType = allowedBlocks && ( get( allowedBlocks, [ 'length' ], 0 ) === 1 );
let allowedBlockType = false;
if ( hasSingleBlockType ) {
allowedBlockType = getBlockType( allowedBlocks );
}
return {
hasItems: hasInserterItems( rootClientId ),
hasSingleBlockType,
blockTitle: allowedBlockType ? allowedBlockType.title : '',
allowedBlockType,
};
} ),
withDispatch( ( dispatch, ownProps, { select } ) => {
return {
insertOnlyAllowedBlock() {
const { rootClientId, clientId, isAppender, destinationRootClientId } = ownProps;
const {
hasSingleBlockType,
allowedBlockType,
} = ownProps;

if ( ! hasSingleBlockType ) {
return;
}

function getInsertionIndex() {
const {
getBlockIndex,
getBlockSelectionEnd,
getBlockOrder,
} = select( 'core/block-editor' );

// If the clientId is defined, we insert at the position of the block.
if ( clientId ) {
return getBlockIndex( clientId, destinationRootClientId );
}

// If there a selected block, we insert after the selected block.
const end = getBlockSelectionEnd();
if ( ! isAppender && end ) {
return getBlockIndex( end, destinationRootClientId ) + 1;
}

// Otherwise, we insert at the end of the current rootClientId
return getBlockOrder( destinationRootClientId ).length;
}

const {
insertBlock,
} = dispatch( 'core/block-editor' );

const blockToInsert = createBlock( allowedBlockType.name );
insertBlock(
blockToInsert,
getInsertionIndex(),
rootClientId
);
},
};
} ),
ifCondition( ( { hasItems } ) => hasItems ),
Expand Down
16 changes: 16 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1310,6 +1310,22 @@ export const hasInserterItems = createSelector(
],
);

/**
* Returns the list of allowed inserter blocks for inner blocks children
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
*
* @return {Array?} The list of allowed block types or false.
*/
export const __experimentalGetAllowedBlocks = ( state, rootClientId = null ) => {
if ( ! rootClientId ) {
return false;
}
const { allowedBlocks } = getBlockListSettings( state, rootClientId );
return allowedBlocks;
};

/**
* Returns the Block List settings of a block, if any exist.
*
Expand Down

0 comments on commit 38a1227

Please sign in to comment.