@gechiui/block-editor
Version:
313 lines (276 loc) • 7.54 kB
JavaScript
/**
* External dependencies
*/
import { size } from 'lodash';
import classnames from 'classnames';
/**
* GeChiUI dependencies
*/
import { speak } from '@gechiui/a11y';
import { __, _x, sprintf } from '@gechiui/i18n';
import { Dropdown, Button } from '@gechiui/components';
import { Component } from '@gechiui/element';
import { withDispatch, withSelect } from '@gechiui/data';
import { compose, ifCondition } from '@gechiui/compose';
import { createBlock, store as blocksStore } from '@gechiui/blocks';
import { plus } from '@gechiui/icons';
/**
* Internal dependencies
*/
import InserterMenu from './menu';
import QuickInserter from './quick-inserter';
import { store as blockEditorStore } from '../../store';
const defaultRenderToggle = ( {
onToggle,
disabled,
isOpen,
blockTitle,
hasSingleBlockType,
toggleProps = {},
} ) => {
let label;
if ( hasSingleBlockType ) {
label = sprintf(
// translators: %s: the name of the block when there is only one
_x( 'Add %s', 'directly add the only allowed block' ),
blockTitle
);
} else {
label = _x( 'Add block', 'Generic label for block inserter button' );
}
const { onClick, ...rest } = toggleProps;
// Handle both onClick functions from the toggle and the parent component
function handleClick( event ) {
if ( onToggle ) {
onToggle( event );
}
if ( onClick ) {
onClick( event );
}
}
return (
<Button
icon={ plus }
label={ label }
tooltipPosition="bottom"
onClick={ handleClick }
className="block-editor-inserter__toggle"
aria-haspopup={ ! hasSingleBlockType ? 'true' : false }
aria-expanded={ ! hasSingleBlockType ? isOpen : false }
disabled={ disabled }
{ ...rest }
/>
);
};
class Inserter extends Component {
constructor() {
super( ...arguments );
this.onToggle = this.onToggle.bind( this );
this.renderToggle = this.renderToggle.bind( this );
this.renderContent = this.renderContent.bind( this );
}
onToggle( isOpen ) {
const { onToggle } = this.props;
// Surface toggle callback to parent component
if ( onToggle ) {
onToggle( isOpen );
}
}
/**
* Render callback to display Dropdown toggle element.
*
* @param {Object} options
* @param {Function} options.onToggle Callback to invoke when toggle is
* pressed.
* @param {boolean} options.isOpen Whether dropdown is currently open.
*
* @return {GCElement} Dropdown toggle element.
*/
renderToggle( { onToggle, isOpen } ) {
const {
disabled,
blockTitle,
hasSingleBlockType,
directInsertBlock,
toggleProps,
hasItems,
renderToggle = defaultRenderToggle,
} = this.props;
return renderToggle( {
onToggle,
isOpen,
disabled: disabled || ! hasItems,
blockTitle,
hasSingleBlockType,
directInsertBlock,
toggleProps,
} );
}
/**
* Render callback to display Dropdown content element.
*
* @param {Object} options
* @param {Function} options.onClose Callback to invoke when dropdown is
* closed.
*
* @return {GCElement} Dropdown content element.
*/
renderContent( { onClose } ) {
const {
rootClientId,
clientId,
isAppender,
showInserterHelpPanel,
// This prop is experimental to give some time for the quick inserter to mature
// Feel free to make them stable after a few releases.
__experimentalIsQuick: isQuick,
} = this.props;
if ( isQuick ) {
return (
<QuickInserter
onSelect={ () => {
onClose();
} }
rootClientId={ rootClientId }
clientId={ clientId }
isAppender={ isAppender }
/>
);
}
return (
<InserterMenu
onSelect={ () => {
onClose();
} }
rootClientId={ rootClientId }
clientId={ clientId }
isAppender={ isAppender }
showInserterHelpPanel={ showInserterHelpPanel }
/>
);
}
render() {
const {
position,
hasSingleBlockType,
directInsertBlock,
insertOnlyAllowedBlock,
__experimentalIsQuick: isQuick,
onSelectOrClose,
} = this.props;
if ( hasSingleBlockType || directInsertBlock?.length ) {
return this.renderToggle( { onToggle: insertOnlyAllowedBlock } );
}
return (
<Dropdown
className="block-editor-inserter"
contentClassName={ classnames(
'block-editor-inserter__popover',
{ 'is-quick': isQuick }
) }
position={ position }
onToggle={ this.onToggle }
expandOnMobile
headerTitle={ __( '添加区块' ) }
renderToggle={ this.renderToggle }
renderContent={ this.renderContent }
onClose={ onSelectOrClose }
/>
);
}
}
export default compose( [
withSelect( ( select, { clientId, rootClientId } ) => {
const {
getBlockRootClientId,
hasInserterItems,
__experimentalGetAllowedBlocks,
__experimentalGetDirectInsertBlock,
} = select( blockEditorStore );
const { getBlockVariations } = select( blocksStore );
rootClientId =
rootClientId || getBlockRootClientId( clientId ) || undefined;
const allowedBlocks = __experimentalGetAllowedBlocks( rootClientId );
const directInsertBlock = __experimentalGetDirectInsertBlock(
rootClientId
);
const hasSingleBlockType =
size( allowedBlocks ) === 1 &&
size(
getBlockVariations( allowedBlocks[ 0 ].name, 'inserter' )
) === 0;
let allowedBlockType = false;
if ( hasSingleBlockType ) {
allowedBlockType = allowedBlocks[ 0 ];
}
return {
hasItems: hasInserterItems( rootClientId ),
hasSingleBlockType,
blockTitle: allowedBlockType ? allowedBlockType.title : '',
allowedBlockType,
directInsertBlock,
rootClientId,
};
} ),
withDispatch( ( dispatch, ownProps, { select } ) => {
return {
insertOnlyAllowedBlock() {
const {
rootClientId,
clientId,
isAppender,
hasSingleBlockType,
allowedBlockType,
directInsertBlock,
onSelectOrClose,
} = ownProps;
if ( ! hasSingleBlockType && ! directInsertBlock?.length ) {
return;
}
function getInsertionIndex() {
const {
getBlockIndex,
getBlockSelectionEnd,
getBlockOrder,
getBlockRootClientId,
} = select( blockEditorStore );
// If the clientId is defined, we insert at the position of the block.
if ( clientId ) {
return getBlockIndex( clientId );
}
// If there a selected block, we insert after the selected block.
const end = getBlockSelectionEnd();
if (
! isAppender &&
end &&
getBlockRootClientId( end ) === rootClientId
) {
return getBlockIndex( end ) + 1;
}
// Otherwise, we insert at the end of the current rootClientId
return getBlockOrder( rootClientId ).length;
}
const { insertBlock } = dispatch( blockEditorStore );
const blockToInsert = directInsertBlock?.length
? createBlock( ...directInsertBlock )
: createBlock( allowedBlockType.name );
insertBlock( blockToInsert, getInsertionIndex(), rootClientId );
if ( onSelectOrClose ) {
onSelectOrClose();
}
const message = sprintf(
// translators: %s: the name of the block that has been added
__( '%s区块已添加' ),
allowedBlockType.title
);
speak( message );
},
};
} ),
// The global inserter should always be visible, we are using ( ! isAppender && ! rootClientId && ! clientId ) as
// a way to detect the global Inserter.
ifCondition(
( { hasItems, isAppender, rootClientId, clientId } ) =>
hasItems || ( ! isAppender && ! rootClientId && ! clientId )
),
] )( Inserter );