Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart block appender #16708

Merged
merged 34 commits into from
Oct 29, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a1d158b
if thre is only one there is only one
draganescu Jul 22, 2019
1f5081b
made a new insertion point selector, some code review refactoring
draganescu Aug 3, 2019
06d44c1
better handling of inserter
draganescu Aug 3, 2019
05c1ebf
refactoring and named block insertion
draganescu Aug 3, 2019
525385c
updates to the appender
draganescu Sep 24, 2019
2e904e1
update snapshots
draganescu Sep 30, 2019
4d89b0b
update docs
draganescu Sep 30, 2019
d80fc7f
default inserter label is used in so many tests
draganescu Oct 1, 2019
55f3f95
fixed allowed blocks test
draganescu Oct 1, 2019
ec1eecc
snapshot updated
draganescu Oct 2, 2019
07b652c
better naming and removed the need for es-lint disabling
draganescu Oct 17, 2019
ce0ec9d
improved the inserter label construction
draganescu Oct 17, 2019
3bf7909
improved the doc of getTheOnlyAllowedItem selector
draganescu Oct 17, 2019
c977baf
reverting test patches becasue patching without understanding is bad,…
draganescu Oct 17, 2019
d2fda6e
moved getInsertionIndex out of selectos and back into each component …
draganescu Oct 18, 2019
157097b
docs generated
draganescu Oct 18, 2019
02edd01
added experimental labels to new selectors, added es-lint comment back
draganescu Oct 18, 2019
e451afd
updated docs
draganescu Oct 18, 2019
bb2bd63
Update packages/block-editor/src/store/selectors.js
draganescu Oct 24, 2019
97dac6e
Update packages/block-editor/src/store/selectors.js
draganescu Oct 24, 2019
134d182
refactored and fixed some coding errors
draganescu Oct 24, 2019
b2f709a
small code move
draganescu Oct 24, 2019
1a40731
small code move
draganescu Oct 24, 2019
7049dc4
removes aria attrs for autoinserted items
draganescu Oct 25, 2019
9910348
fixes typo, adds translators comment
draganescu Oct 25, 2019
4832f45
simplifies the intserter logic
draganescu Oct 25, 2019
36de754
fix for the simplification
draganescu Oct 25, 2019
3f0d68e
simplifies by using one selector and passing props in compose
draganescu Oct 25, 2019
e82ba8f
small code updates
draganescu Oct 27, 2019
65a4b58
lint
draganescu Oct 27, 2019
a3d9ad6
renamed insertedBlock
draganescu Oct 27, 2019
50b2637
small doc update
draganescu Oct 27, 2019
488aa5c
adds tooltip to the default button appender
draganescu Oct 28, 2019
b2c27f6
refactores for more self documenting varnames
draganescu Oct 28, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
108 changes: 90 additions & 18 deletions packages/block-editor/src/components/inserter/index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
/**
* 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 } ) => {
const label = blockTitle === '' ? _x( 'Add block', 'Generic label for block inseter button' ) : sprintf( _x( 'Add %s', 'directly add the only allowed block' ), blockTitle );
draganescu marked this conversation as resolved.
Show resolved Hide resolved

return (
<IconButton
icon="insert"
label={ label }
labelPosition="bottom"
onClick={ onToggle }
className="editor-inserter__toggle block-editor-inserter__toggle"
aria-haspopup="true"
aria-expanded={ isOpen }
draganescu marked this conversation as resolved.
Show resolved Hide resolved
disabled={ disabled }
/>
);
};

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

return renderToggle( { onToggle, isOpen, disabled } );
if ( hasOnlyOneAllowedInserterItem ) {
draganescu marked this conversation as resolved.
Show resolved Hide resolved
onToggle = insertTheOnlyAllowedItem;
}

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

/**
Expand Down Expand Up @@ -87,7 +108,6 @@ class Inserter extends Component {

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

return (
<Dropdown
className="editor-inserter block-editor-inserter"
Expand All @@ -105,10 +125,62 @@ class Inserter extends Component {

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

const { hasInserterItems, __experimentalHasOnlyOneAllowedInserterItem, __experimentalGetTheOnlyAllowedItem } = select( 'core/block-editor' );
draganescu marked this conversation as resolved.
Show resolved Hide resolved
const allowedBlock = getBlockType( __experimentalGetTheOnlyAllowedItem( rootClientId ) );
return {
hasItems: hasInserterItems( rootClientId ),
hasOnlyOneAllowedInserterItem: __experimentalHasOnlyOneAllowedInserterItem( rootClientId ),
blockTitle: allowedBlock ? allowedBlock.title : '',
};
} ),
withDispatch( ( dispatch, ownProps, { select } ) => {
return {
insertTheOnlyAllowedItem: () => {
draganescu marked this conversation as resolved.
Show resolved Hide resolved
const { rootClientId, clientId, destinationRootClientId, isAppender } = ownProps;
const {
getBlockListSettings,
} = select( 'core/block-editor' );
const parentBlockListSettings = getBlockListSettings( rootClientId );
const isOnlyOneAllowedBlock = get( parentBlockListSettings, [ 'allowedBlocks', 'length' ], 0 ) === 1;
draganescu marked this conversation as resolved.
Show resolved Hide resolved

if ( ! isOnlyOneAllowedBlock ) {
return false;
draganescu marked this conversation as resolved.
Show resolved Hide resolved
}

const parentAllowedBlocks = get( parentBlockListSettings, [ 'allowedBlocks' ] );
draganescu marked this conversation as resolved.
Show resolved Hide resolved

function getInsertionIndex() {
draganescu marked this conversation as resolved.
Show resolved Hide resolved
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 insertedBlock = createBlock( parentAllowedBlocks[ 0 ] );
insertBlock(
insertedBlock,
getInsertionIndex( clientId, destinationRootClientId, isAppender ),
rootClientId
);
},
};
} ),
ifCondition( ( { hasItems } ) => hasItems ),
Expand Down
24 changes: 12 additions & 12 deletions packages/block-editor/src/components/inserter/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,15 +478,7 @@ export default compose(
};
} ),
withDispatch( ( dispatch, ownProps, { select } ) => {
const {
showInsertionPoint,
hideInsertionPoint,
} = dispatch( 'core/block-editor' );

// This should be an external action provided in the editor settings.
const {
__experimentalFetchReusableBlocks: fetchReusableBlocks,
} = dispatch( 'core/editor' );
draganescu marked this conversation as resolved.
Show resolved Hide resolved
const { clientId, destinationRootClientId, isAppender } = ownProps;

// To avoid duplication, getInsertionIndex is extracted and used in two event handlers
// This breaks the withDispatch not containing any logic rule.
Expand All @@ -499,7 +491,6 @@ export default compose(
getBlockSelectionEnd,
getBlockOrder,
} = select( 'core/block-editor' );
const { clientId, destinationRootClientId, isAppender } = ownProps;
draganescu marked this conversation as resolved.
Show resolved Hide resolved

// If the clientId is defined, we insert at the position of the block.
if ( clientId ) {
Expand All @@ -516,10 +507,20 @@ export default compose(
return getBlockOrder( destinationRootClientId ).length;
}

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

// This should be an external action provided in the editor settings.
const {
__experimentalFetchReusableBlocks: fetchReusableBlocks,
} = dispatch( 'core/editor' );

return {
fetchReusableBlocks,
showInsertionPoint() {
const index = getInsertionIndex();
const index = getInsertionIndex( clientId, destinationRootClientId, isAppender );
draganescu marked this conversation as resolved.
Show resolved Hide resolved
showInsertionPoint( ownProps.destinationRootClientId, index );
},
hideInsertionPoint,
Expand All @@ -531,7 +532,6 @@ export default compose(
const {
getSelectedBlock,
} = select( 'core/block-editor' );
const { isAppender } = ownProps;
const { name, initialAttributes } = item;
const selectedBlock = getSelectedBlock();
const insertedBlock = createBlock( name, initialAttributes );
Expand Down
39 changes: 39 additions & 0 deletions packages/block-editor/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,45 @@ export const hasInserterItems = createSelector(
],
);

/**
* Determines whether there is only one item that may be inserted.
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
*
* @return {boolean} True if there is one item available, false if zero or more than one.
*/
export const __experimentalHasOnlyOneAllowedInserterItem = ( state, rootClientId = null ) => {
if ( rootClientId ) {
const parentBlockListSettings = getBlockListSettings( state, rootClientId );
return get( parentBlockListSettings, [ 'allowedBlocks', 'length' ], 0 ) === 1;
draganescu marked this conversation as resolved.
Show resolved Hide resolved
}

return false;
};

/**
* Gets the name of the only item that may be inserted.
draganescu marked this conversation as resolved.
Show resolved Hide resolved
*
* @param {Object} state Editor state.
* @param {?string} rootClientId Optional root client ID of block list.
*
* @return {string?} The name of the allowed block or null.
draganescu marked this conversation as resolved.
Show resolved Hide resolved
*/
export const __experimentalGetTheOnlyAllowedItem = ( state, rootClientId = null ) => {
draganescu marked this conversation as resolved.
Show resolved Hide resolved
if ( ! rootClientId ) {
return null;
}
if ( ! __experimentalHasOnlyOneAllowedInserterItem( state, rootClientId ) ) {
return null;
}

const parentBlockListSettings = getBlockListSettings( state, rootClientId );
const name = get( parentBlockListSettings, [ 'allowedBlocks', 0 ], 0 );

return name;
};
draganescu marked this conversation as resolved.
Show resolved Hide resolved
draganescu marked this conversation as resolved.
Show resolved Hide resolved

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