From 8fdf2265c45a27ce1a413bc1e652c4d8de139874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 7 Aug 2018 12:09:35 +0200 Subject: [PATCH] Allow plugins to extend the block settings menu (#7895) --- edit-post/README.md | 82 +++++++++++++++++++ .../plugin-block-settings-menu-group.js | 33 ++++++++ .../plugin-block-settings-menu-item.js | 52 ++++++++++++ edit-post/components/visual-editor/index.js | 5 ++ edit-post/index.js | 1 + .../block-settings-menu-plugins-extension.js | 10 +++ .../components/block-settings-menu/index.js | 2 + packages/editor/src/components/index.js | 1 + 8 files changed, 186 insertions(+) create mode 100644 edit-post/components/block-settings-menu/plugin-block-settings-menu-group.js create mode 100644 edit-post/components/block-settings-menu/plugin-block-settings-menu-item.js create mode 100644 packages/editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js diff --git a/edit-post/README.md b/edit-post/README.md index af78b310c4fd76..f03ae16000e0c6 100644 --- a/edit-post/README.md +++ b/edit-post/README.md @@ -9,6 +9,88 @@ Refer to [the plugins module documentation](../plugins/) for more information. The following components can be used with the `registerPlugin` ([see documentation](../packages/plugins)) API. They can be found in the global variable `wp.editPost` when defining `wp-edit-post` as a script dependency. +### `PluginBlockSettingsMenuItem` + +Renders a new item in the block settings menu. + +Example: + +{% codetabs %} + +{% ES5 %} +```js +var __ = wp.i18n.__; +var PluginBlockSettingsMenuItem = wp.editPost.PluginBlockSettingsMenuItem; + +function doOnClick(){ + // To be called when the user clicks the menu item. +} + +function MyPluginBlockSettingsMenuItem() { + return el( + PluginBlockSettingsMenuItem, + { + allowedBlockNames: [ 'core/paragraph' ], + icon: 'dashicon-name', + label: __( 'Menu item text' ), + onClick: doOnClick, + } + ); +} +``` + +{% ESNext %} +```jsx +import { __ } from wp.i18n; +import { PluginBlockSettingsMenuItem } from wp.editPost; + +const doOnClick = ( ) => { + // To be called when the user clicks the menu item. +}; + +const MyPluginBlockSettingsMenuItem = () => ( + +); +``` + +{% end %} + +#### Props + +##### allowedBlockNames + +An array containing a whitelist of block names for which the item should be shown. If this prop is not present the item will be rendered for any block. If multiple blocks are selected, it'll be shown if and only if all of them are in the whitelist. + +- Type: `Array` +- Required: No +- Default: Menu item is shown for any block + +##### icon + +The [Dashicon](https://developer.wordpress.org/resource/dashicons/) icon slug string, or an SVG WP element, to be rendered to the left of the menu item label. + +- Type: `String` | `Element` +- Required: No +- Default: Menu item wil be rendered without icon + +##### label + +A string containing the menu item text. + +- Type: `String` +- Required: Yes + +##### onClick + +The callback function to be executed when the user clicks the menu item. + +- Type: `function` +- Required: Yes + ### `PluginSidebar` Renders a sidebar when activated. The contents within the `PluginSidebar` will appear as content within the sidebar. diff --git a/edit-post/components/block-settings-menu/plugin-block-settings-menu-group.js b/edit-post/components/block-settings-menu/plugin-block-settings-menu-group.js new file mode 100644 index 00000000000000..2018035e92ff6b --- /dev/null +++ b/edit-post/components/block-settings-menu/plugin-block-settings-menu-group.js @@ -0,0 +1,33 @@ +/** + * External dependencies + */ +import { isEmpty, map } from 'lodash'; + +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; +import { Fragment } from '@wordpress/element'; +import { withSelect } from '@wordpress/data'; + +const { Fill: PluginBlockSettingsMenuGroup, Slot } = createSlotFill( 'PluginBlockSettingsMenuGroup' ); + +const PluginBlockSettingsMenuGroupSlot = ( { fillProps, selectedBlocks } ) => { + selectedBlocks = map( selectedBlocks, ( block ) => block.name ); + return ( + + { ( fills ) => ! isEmpty( fills ) && ( + +
+ { fills } + + ) } + + ); +}; + +PluginBlockSettingsMenuGroup.Slot = withSelect( ( select, { fillProps: { clientIds } } ) => ( { + selectedBlocks: select( 'core/editor' ).getBlocksByUID( clientIds ), +} ) )( PluginBlockSettingsMenuGroupSlot ); + +export default PluginBlockSettingsMenuGroup; diff --git a/edit-post/components/block-settings-menu/plugin-block-settings-menu-item.js b/edit-post/components/block-settings-menu/plugin-block-settings-menu-item.js new file mode 100644 index 00000000000000..9fd8b376c1478c --- /dev/null +++ b/edit-post/components/block-settings-menu/plugin-block-settings-menu-item.js @@ -0,0 +1,52 @@ +/** + * External dependencies + */ +import { difference } from 'lodash'; + +/** + * WordPress dependencies + */ +import { IconButton } from '@wordpress/components'; +import { compose } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import PluginBlockSettingsMenuGroup from './plugin-block-settings-menu-group'; + +const isEverySelectedBlockAllowed = ( selected, allowed ) => difference( selected, allowed ).length === 0; + +/** + * Plugins may want to add an item to the menu either for every block + * or only for the specific ones provided in the `allowedBlocks` component property. + * + * If there are multiple blocks selected the item will be rendered if every block + * is of one allowed type (not necesarily the same). + * + * @param {string[]} selectedBlockNames Array containing the names of the blocks selected + * @param {string[]} allowedBlockNames Array containing the names of the blocks allowed + * @return {boolean} Whether the item will be rendered or not. + */ +const shouldRenderItem = ( selectedBlockNames, allowedBlockNames ) => ! Array.isArray( allowedBlockNames ) || + isEverySelectedBlockAllowed( selectedBlockNames, allowedBlockNames ); + +const PluginBlockSettingsMenuItem = ( { allowedBlocks, icon, label, onClick, small, role } ) => ( + + { ( { selectedBlocks, onClose } ) => { + if ( ! shouldRenderItem( selectedBlocks, allowedBlocks ) ) { + return null; + } + return ( + { ! small && label } + ); + } } + +); + +export default PluginBlockSettingsMenuItem; diff --git a/edit-post/components/visual-editor/index.js b/edit-post/components/visual-editor/index.js index c166c75484a211..df1852492117e3 100644 --- a/edit-post/components/visual-editor/index.js +++ b/edit-post/components/visual-editor/index.js @@ -11,6 +11,7 @@ import { BlockSelectionClearer, MultiSelectScrollIntoView, _BlockSettingsMenuFirstItem, + _BlockSettingsMenuPluginsExtension, } from '@wordpress/editor'; /** @@ -18,6 +19,7 @@ import { */ import './style.scss'; import BlockInspectorButton from './block-inspector-button'; +import PluginBlockSettingsMenuGroup from '../block-settings-menu/plugin-block-settings-menu-group'; function VisualEditor() { return ( @@ -34,6 +36,9 @@ function VisualEditor() { <_BlockSettingsMenuFirstItem> { ( { onClose } ) => } + <_BlockSettingsMenuPluginsExtension> + { ( { clientIds, onClose } ) => } + ); } diff --git a/edit-post/index.js b/edit-post/index.js index 88c7580e16643d..8b0ae01d53ac22 100644 --- a/edit-post/index.js +++ b/edit-post/index.js @@ -83,6 +83,7 @@ export function initializeEditor( id, postType, postId, settings, overridePost ) }; } +export { default as PluginBlockSettingsMenuItem } from './components/block-settings-menu/plugin-block-settings-menu-item'; export { default as PluginPostPublishPanel } from './components/sidebar/plugin-post-publish-panel'; export { default as PluginPostStatusInfo } from './components/sidebar/plugin-post-status-info'; export { default as PluginPrePublishPanel } from './components/sidebar/plugin-pre-publish-panel'; diff --git a/packages/editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js b/packages/editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js new file mode 100644 index 00000000000000..3357a6592fe0f4 --- /dev/null +++ b/packages/editor/src/components/block-settings-menu/block-settings-menu-plugins-extension.js @@ -0,0 +1,10 @@ +/** + * WordPress dependencies + */ +import { createSlotFill } from '@wordpress/components'; + +const { Fill: _BlockSettingsMenuPluginsExtension, Slot } = createSlotFill( '_BlockSettingsMenuPluginsExtension' ); + +_BlockSettingsMenuPluginsExtension.Slot = Slot; + +export default _BlockSettingsMenuPluginsExtension; diff --git a/packages/editor/src/components/block-settings-menu/index.js b/packages/editor/src/components/block-settings-menu/index.js index 06c439ccc524d6..0b5c4d6cdb4338 100644 --- a/packages/editor/src/components/block-settings-menu/index.js +++ b/packages/editor/src/components/block-settings-menu/index.js @@ -24,6 +24,7 @@ import ReusableBlockDeleteButton from './reusable-block-delete-button'; import BlockHTMLConvertButton from './block-html-convert-button'; import BlockUnknownConvertButton from './block-unknown-convert-button'; import _BlockSettingsMenuFirstItem from './block-settings-menu-first-item'; +import _BlockSettingsMenuPluginsExtension from './block-settings-menu-plugins-extension'; import withDeprecatedUniqueId from '../with-deprecated-unique-id'; export class BlockSettingsMenu extends Component { @@ -126,6 +127,7 @@ export class BlockSettingsMenu extends Component { itemsRole="menuitem" /> ) } + <_BlockSettingsMenuPluginsExtension.Slot fillProps={ { clientIds, onClose } } />
{ count === 1 && (