diff --git a/blocks/block-alignment-toolbar/index.js b/blocks/block-alignment-toolbar/index.js index 60215df615549..577d2fb03ba83 100644 --- a/blocks/block-alignment-toolbar/index.js +++ b/blocks/block-alignment-toolbar/index.js @@ -2,12 +2,7 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Toolbar } from '@wordpress/components'; - -/** - * Internal dependencies - */ -import withEditorSettings from '../with-editor-settings'; +import { Toolbar, withContext } from '@wordpress/components'; const BLOCK_ALIGNMENTS_CONTROLS = { left: { @@ -59,7 +54,7 @@ function BlockAlignmentToolbar( { value, onChange, controls = DEFAULT_CONTROLS, ); } -export default withEditorSettings( +export default withContext( 'editor' )( ( settings ) => ( { wideControlsEnabled: settings.wideImages, } ) diff --git a/blocks/color-palette/index.js b/blocks/color-palette/index.js index bedee1740cca5..922ab3eb655d2 100644 --- a/blocks/color-palette/index.js +++ b/blocks/color-palette/index.js @@ -7,14 +7,13 @@ import { ChromePicker } from 'react-color'; /** * WordPress dependencies */ -import { Dropdown } from '@wordpress/components'; +import { Dropdown, withContext } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ import './style.scss'; -import withEditorSettings from '../with-editor-settings'; function ColorPalette( { colors, value, onChange } ) { return ( @@ -72,7 +71,7 @@ function ColorPalette( { colors, value, onChange } ) { ); } -export default withEditorSettings( +export default withContext( 'editor' )( ( settings ) => ( { colors: settings.colors, } ) diff --git a/blocks/library/image/block.js b/blocks/library/image/block.js index 4d0c769e74d8b..9dba76fef3cd4 100644 --- a/blocks/library/image/block.js +++ b/blocks/library/image/block.js @@ -24,12 +24,12 @@ import { DropZone, FormFileUpload, withAPIData, + withContext, } from '@wordpress/components'; /** * Internal dependencies */ -import withEditorSettings from '../../with-editor-settings'; import Editable from '../../editable'; import MediaUploadButton from '../../media-upload-button'; import InspectorControls from '../../inspector-controls'; @@ -282,7 +282,7 @@ class ImageBlock extends Component { } export default flowRight( [ - withEditorSettings(), + withContext( 'editor' ), withAPIData( ( props ) => { const { id } = props.attributes; if ( ! id ) { diff --git a/blocks/with-editor-settings/index.js b/components/higher-order/with-context/index.js similarity index 50% rename from blocks/with-editor-settings/index.js rename to components/higher-order/with-context/index.js index 8761bc108fc33..10c50bad0f53c 100644 --- a/blocks/with-editor-settings/index.js +++ b/components/higher-order/with-context/index.js @@ -8,12 +8,17 @@ import { noop } from 'lodash'; */ import { Component } from '@wordpress/element'; -const withEditorSettings = ( mapSettingsToProps ) => ( OriginalComponent ) => { +const withContext = ( contextName ) => ( mapSettingsToProps ) => ( OriginalComponent ) => { + // Allow call without explicit `mapSettingsToProps` + if ( mapSettingsToProps instanceof Component ) { + return withContext( contextName )()( mapSettingsToProps ); + } + class WrappedComponent extends Component { render() { const extraProps = mapSettingsToProps ? - mapSettingsToProps( this.context.editor, this.props ) : - { settings: this.context.editor }; + mapSettingsToProps( this.context[ contextName ], this.props ) : + this.context[ contextName ]; return ( ( OriginalComponent ) => { } WrappedComponent.contextTypes = { - editor: noop, + [ contextName ]: noop, }; return WrappedComponent; }; -export default withEditorSettings; +export default withContext; diff --git a/components/index.js b/components/index.js index 0e558bf9464e9..c68fbdbdec70f 100644 --- a/components/index.js +++ b/components/index.js @@ -37,6 +37,7 @@ export { Slot, Fill, Provider as SlotFillProvider } from './slot-fill'; // Higher-Order Components export { default as navigateRegions } from './higher-order/navigate-regions'; export { default as withAPIData } from './higher-order/with-api-data'; +export { default as withContext } from './higher-order/with-context'; export { default as withFocusOutside } from './higher-order/with-focus-outside'; export { default as withFocusReturn } from './higher-order/with-focus-return'; export { default as withInstanceId } from './higher-order/with-instance-id'; diff --git a/editor/components/inserter/index.js b/editor/components/inserter/index.js index 16282bce808cd..c13f15f35e760 100644 --- a/editor/components/inserter/index.js +++ b/editor/components/inserter/index.js @@ -3,12 +3,13 @@ */ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; +import { flowRight, isEmpty } from 'lodash'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Dropdown, IconButton } from '@wordpress/components'; +import { Dropdown, IconButton, withContext } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; import { Component } from '@wordpress/element'; @@ -61,8 +62,13 @@ class Inserter extends Component { children, onInsertBlock, insertionPoint, + hasSupportedBlocks, } = this.props; + if ( ! hasSupportedBlocks ) { + return null; + } + return ( { +export default flowRight( [ + connect( + ( state ) => { + return { + insertionPoint: getBlockInsertionPoint( state ), + mode: getEditorMode( state ), + }; + }, + ( dispatch ) => ( { + onInsertBlock( name, position ) { + dispatch( hideInsertionPoint() ); + dispatch( insertBlock( + createBlock( name ), + position + ) ); + }, + ...bindActionCreators( { + setInsertionPoint: setBlockInsertionPoint, + clearInsertionPoint: clearBlockInsertionPoint, + }, dispatch ), + } ) + ), + withContext( 'editor' )( ( settings ) => { + const { blockTypes } = settings; + return { - insertionPoint: getBlockInsertionPoint( state ), - mode: getEditorMode( state ), + hasSupportedBlocks: true === blockTypes || ! isEmpty( blockTypes ), }; - }, - ( dispatch ) => ( { - onInsertBlock( name, position ) { - dispatch( hideInsertionPoint() ); - dispatch( insertBlock( - createBlock( name ), - position - ) ); - }, - ...bindActionCreators( { - setInsertionPoint: setBlockInsertionPoint, - clearInsertionPoint: clearBlockInsertionPoint, - }, dispatch ), - } ) -)( Inserter ); + } ), +] )( Inserter ); diff --git a/editor/components/inserter/menu.js b/editor/components/inserter/menu.js index 005944dde38b6..4d6b9df6e0b14 100644 --- a/editor/components/inserter/menu.js +++ b/editor/components/inserter/menu.js @@ -24,6 +24,7 @@ import { TabbableContainer, withInstanceId, withSpokenMessages, + withContext, } from '@wordpress/components'; import { getCategories, getBlockTypes } from '@wordpress/blocks'; import { keycodes } from '@wordpress/utils'; @@ -112,8 +113,27 @@ export class InserterMenu extends Component { } getBlockTypes() { + const { blockTypes } = this.props; + + // If all block types disabled, return empty set + if ( ! blockTypes ) { + return []; + } + // Block types that are marked as private should not appear in the inserter - return getBlockTypes().filter( ( block ) => ! block.isPrivate ); + return getBlockTypes().filter( ( block ) => { + if ( block.isPrivate ) { + return false; + } + + // Block types defined as either `true` or array: + // - True: Allow + // - Array: Check block name within whitelist + return ( + ! Array.isArray( blockTypes ) || + includes( blockTypes, block.name ) + ); + } ); } searchBlocks( blockTypes ) { @@ -121,18 +141,28 @@ export class InserterMenu extends Component { } getBlocksForTab( tab ) { + const blockTypes = this.getBlockTypes(); // if we're searching, use everything, otherwise just get the blocks visible in this tab if ( this.state.filterValue ) { - return this.getBlockTypes(); + return blockTypes; } + + let predicate; switch ( tab ) { case 'recent': - return this.props.recentlyUsedBlocks; + predicate = ( block ) => find( this.props.recentlyUsedBlocks, { name: block.name } ); + break; + case 'blocks': - return filter( this.getBlockTypes(), ( block ) => block.category !== 'embed' ); + predicate = ( block ) => block.category !== 'embed'; + break; + case 'embeds': - return filter( this.getBlockTypes(), ( block ) => block.category === 'embed' ); + predicate = ( block ) => block.category === 'embed'; + break; } + + return filter( blockTypes, predicate ); } sortBlocks( blockTypes ) { @@ -201,17 +231,18 @@ export class InserterMenu extends Component { this.setState( { tab } ); } - renderTabView( tab, visibleBlocks ) { - switch ( tab ) { - case 'recent': - return this.renderBlocks( this.props.recentlyUsedBlocks, undefined ); - - case 'embed': - return this.renderBlocks( visibleBlocks.embed, undefined ); + renderTabView( tab ) { + const blocksForTab = this.getBlocksForTab( tab ); + if ( 'recent' === tab ) { + return this.renderBlocks( blocksForTab ); + } - default: - return this.renderCategories( visibleBlocks, undefined ); + const visibleBlocks = this.getVisibleBlocksByCategory( blocksForTab ); + if ( 'embed' === tab ) { + return this.renderBlocks( visibleBlocks.embed ); } + + return this.renderCategories( visibleBlocks ); } interceptArrows( event ) { @@ -266,19 +297,12 @@ export class InserterMenu extends Component { }, ] } > - { - ( tabKey ) => { - const blocksForTab = this.getBlocksForTab( tabKey ); - const visibleBlocks = this.getVisibleBlocksByCategory( blocksForTab ); - - return ( -
this.tabContainer = ref } - className="editor-inserter__content"> - { this.renderTabView( tabKey, visibleBlocks ) } -
- ); - } - } + { ( tabKey ) => ( +
this.tabContainer = ref } + className="editor-inserter__content"> + { this.renderTabView( tabKey ) } +
+ ) } } { isSearching && @@ -304,5 +328,6 @@ const connectComponent = connect( export default flow( withInstanceId, withSpokenMessages, + withContext( 'editor' )( ( settings ) => pick( settings, 'blockTypes' ) ), connectComponent )( InserterMenu ); diff --git a/editor/components/inserter/test/menu.js b/editor/components/inserter/test/menu.js index 856e0ba4f6495..5a01a2abc3013 100644 --- a/editor/components/inserter/test/menu.js +++ b/editor/components/inserter/test/menu.js @@ -97,6 +97,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + blockTypes /> ); @@ -107,6 +108,39 @@ describe( 'InserterMenu', () => { expect( visibleBlocks.length ).toBe( 0 ); } ); + it( 'should show no blocks if all block types disabled', () => { + const wrapper = mount( + + ); + + const visibleBlocks = wrapper.find( '.editor-inserter__block' ); + expect( visibleBlocks.length ).toBe( 0 ); + } ); + + it( 'should show filtered block types', () => { + const wrapper = mount( + + ); + + const visibleBlocks = wrapper.find( '.editor-inserter__block' ); + expect( visibleBlocks.length ).toBe( 1 ); + expect( visibleBlocks.at( 0 ).text() ).toBe( 'Text' ); + } ); + it( 'should show the recently used blocks in the recent tab', () => { const wrapper = mount( { blocks={ [] } recentlyUsedBlocks={ [ advancedTextBlock ] } debouncedSpeak={ noop } + blockTypes /> ); @@ -132,6 +167,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + blockTypes /> ); const embedTab = wrapper.find( '.editor-inserter__tab' ) @@ -155,6 +191,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + blockTypes /> ); const blocksTab = wrapper.find( '.editor-inserter__tab' ) @@ -180,6 +217,7 @@ describe( 'InserterMenu', () => { blocks={ [ { name: moreBlock.name } ] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + blockTypes /> ); const blocksTab = wrapper.find( '.editor-inserter__tab' ) @@ -200,6 +238,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + blockTypes /> ); wrapper.setState( { filterValue: 'text' } ); @@ -222,6 +261,7 @@ describe( 'InserterMenu', () => { blocks={ [] } recentlyUsedBlocks={ [] } debouncedSpeak={ noop } + blockTypes /> ); wrapper.setState( { filterValue: ' text' } ); diff --git a/lib/client-assets.php b/lib/client-assets.php index d5bd43b12aab3..fee8be2f5ab04 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -767,9 +767,19 @@ function gutenberg_editor_scripts_and_styles( $hook ) { $color_palette = $gutenberg_theme_support[0]['colors']; } + /** + * Filters the allowed block types for the editor, defaulting to true (all + * block types supported). + * + * @param bool|array $allowed_block_types Array of block type slugs, or + * boolean to enable/disable all. + */ + $allowed_block_types = apply_filters( 'allowed_block_types', true ); + $editor_settings = array( 'wideImages' => ! empty( $gutenberg_theme_support[0]['wide-images'] ), 'colors' => $color_palette, + 'blockTypes' => $allowed_block_types, ); wp_add_inline_script( 'wp-editor', 'wp.api.init().done( function() {'