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

Move the block manager to block editor package #39672

Closed
wants to merge 11 commits into from
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/base-styles/_z-index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,9 @@ $z-layers: (
".components-resizable-box__corner-handle": 2,

// Make sure block manager sticky category titles appear above the options
".edit-post-block-manager__category-title": 1,
".block-editor-block-manager__category-title": 1,
// And block manager sticky disabled block count is higher still
".edit-post-block-manager__disabled-blocks-count": 2,
".block-editor-block-manager__disabled-blocks-count": 2,

// Needs to be higher than any other element as this is an overlay to catch all events
".components-tooltip .event-catcher": 100002,
Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ _Related_

Undocumented declaration.

### BlockManager

Undocumented declaration.

### BlockMover

_Related_
Expand Down
1 change: 1 addition & 0 deletions packages/block-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts",
"@wordpress/keycodes": "file:../keycodes",
"@wordpress/notices": "file:../notices",
"@wordpress/preferences": "file:../preferences",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure that I like that personally, I feel the block-editor package shouldn't be depending on the "preferences" package. The reason is that the "preferences" package is something that is generic now but I see it as more tied to WP especially if we implement later API based preferences (persist in the user profile)

I guess we can implement this WP relationship in a non-intrusive way, using adapters or something but maybe we should cross that bridge at that moment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is something I was unsure about.

There's also #39632 where I've migrated some of the block-editor's persisted data to the preferences package, that would also add the dependency.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #39632 it seems it's even harder to remove that dependency. So maybe we can just accept it as a dependency but make sure that any "WP" dependency should be added in an optional "adapter" layer

Copy link
Member

@gziolo gziolo Mar 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also look at the Block Directory integration:
https://github.com/WordPress/gutenberg/tree/trunk/packages/block-directory

At least outside of the WordPress core, the block manager UI isn't so important as you can have full control over the blocks used.

It's shaped as an optional plugin that integrates with the block editor. Although, I'm not sure how that would work with the preferences store, to be honest.

Copy link
Contributor Author

@talldan talldan Mar 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The block directory renders into slots IIRC, so it seems a bit easier to make optional.

As Riad mentions, #39632 (block insert usage, which is used to power the quick inserter) is a more difficult thing to make optional. That does seem more important to a standalone editor, though perhaps a fallback to in-memory persistence could be an option.

The only way I can see to keep it working would be to extract the whole feature from the block-editor package. There would need to be a way to listen for block insertion events so that insertUsage could be updated. The block manager could also potentially live in a different package and that would solve the dependency issue for this one.

But there's no hurry to merge this or #39632, so it could be worth exploring how a flexible persistence layer in preferences might be integrated first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the block directory integration was easier to make optional. It's worth also pursuing the same approach because it makes the editor easier to extend assuming you would find a decent way to orchestrate the preferences store. It would benefit also plugin authors. I'm happy to pair with you to discuss the challenges and possible ideas.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an experiment here to try making the persistence aspect of preferences optional/configurable - #39795.

"@wordpress/rich-text": "file:../rich-text",
"@wordpress/shortcode": "file:../shortcode",
"@wordpress/style-engine": "file:../style-engine",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,46 @@ import { includes, map, without } from 'lodash';
* WordPress dependencies
*/
import { useMemo, useCallback } from '@wordpress/element';
import { useDispatch, useSelect } from '@wordpress/data';
import { useSelect } from '@wordpress/data';
import { useInstanceId } from '@wordpress/compose';
import { CheckboxControl } from '@wordpress/components';
import { store as editorStore } from '@wordpress/editor';

/**
* Internal dependencies
*/
import { store as blockEditorStore } from '../../store';
import BlockTypesChecklist from './checklist';
import { store as editPostStore } from '../../store';
import useHiddenBlockTypes from './use-hidden-block-types';

function BlockManagerCategory( { title, blockTypes } ) {
function BlockManagerCategory( { scope, title, blockTypes } ) {
const {
hiddenBlockTypes,
showBlockTypes,
hideBlockTypes,
} = useHiddenBlockTypes( scope );
const instanceId = useInstanceId( BlockManagerCategory );
const { defaultAllowedBlockTypes, hiddenBlockTypes } = useSelect(
( select ) => {
const { getEditorSettings } = select( editorStore );
const { getHiddenBlockTypes } = select( editPostStore );
return {
defaultAllowedBlockTypes: getEditorSettings()
.defaultAllowedBlockTypes,
hiddenBlockTypes: getHiddenBlockTypes(),
};
},
[]
);
const allowedBlockTypes = useSelect( ( select ) => {
const { getSettings } = select( blockEditorStore );
return getSettings()?.allowedBlockTypes ?? [];
}, [] );
const filteredBlockTypes = useMemo( () => {
if ( defaultAllowedBlockTypes === true ) {
if ( allowedBlockTypes === true ) {
return blockTypes;
}
return blockTypes.filter( ( { name } ) => {
return includes( defaultAllowedBlockTypes || [], name );
return includes( allowedBlockTypes || [], name );
} );
}, [ defaultAllowedBlockTypes, blockTypes ] );
const { showBlockTypes, hideBlockTypes } = useDispatch( editPostStore );
const toggleVisible = useCallback( ( blockName, nextIsChecked ) => {
if ( nextIsChecked ) {
showBlockTypes( blockName );
} else {
hideBlockTypes( blockName );
}
}, [] );
}, [ allowedBlockTypes, blockTypes ] );
const toggleVisible = useCallback(
( blockName, nextIsChecked ) => {
if ( nextIsChecked ) {
showBlockTypes( blockName );
} else {
hideBlockTypes( blockName );
}
},
[ showBlockTypes, hideBlockTypes ]
);
const toggleAllVisible = useCallback(
( nextIsChecked ) => {
const blockNames = map( blockTypes, 'name' );
Expand All @@ -57,7 +56,7 @@ function BlockManagerCategory( { title, blockTypes } ) {
hideBlockTypes( blockNames );
}
},
[ blockTypes ]
[ blockTypes, showBlockTypes, hideBlockTypes ]
);

if ( ! filteredBlockTypes.length ) {
Expand All @@ -69,7 +68,7 @@ function BlockManagerCategory( { title, blockTypes } ) {
...hiddenBlockTypes
);

const titleId = 'edit-post-block-manager__category-title-' + instanceId;
const titleId = 'block-editor-block-manager__category-title-' + instanceId;

const isAllChecked = checkedBlockNames.length === filteredBlockTypes.length;

Expand All @@ -86,12 +85,12 @@ function BlockManagerCategory( { title, blockTypes } ) {
<div
role="group"
aria-labelledby={ titleId }
className="edit-post-block-manager__category"
className="block-editor-block-manager__category"
>
<CheckboxControl
checked={ isAllChecked }
onChange={ toggleAllVisible }
className="edit-post-block-manager__category-title"
className="block-editor-block-manager__category-title"
aria-checked={ ariaChecked }
label={ <span id={ titleId }>{ title }</span> }
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ import { partial } from 'lodash';
/**
* WordPress dependencies
*/
import { BlockIcon } from '@wordpress/block-editor';
import { CheckboxControl } from '@wordpress/components';

/**
* Internal dependencies
*/
import BlockIcon from '../block-icon';

function BlockTypesChecklist( { blockTypes, value, onItemChange } ) {
return (
<ul className="edit-post-block-manager__checklist">
<ul className="block-editor-block-manager__checklist">
{ blockTypes.map( ( blockType ) => (
<li
key={ blockType.name }
className="edit-post-block-manager__checklist-item"
className="block-editor-block-manager__checklist-item"
>
<CheckboxControl
label={
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* External dependencies
*/
import { filter, includes, isArray } from 'lodash';
import { filter, includes } from 'lodash';

/**
* WordPress dependencies
*/
import { store as blocksStore } from '@wordpress/blocks';
import { withSelect } from '@wordpress/data';
import { useSelect } from '@wordpress/data';
import { SearchControl } from '@wordpress/components';
import { __, _n, sprintf } from '@wordpress/i18n';
import { useEffect, useState } from '@wordpress/element';
Expand All @@ -18,22 +18,40 @@ import { speak } from '@wordpress/a11y';
* Internal dependencies
*/
import BlockManagerCategory from './category';
import { store as editPostStore } from '../../store';
import useHiddenBlockTypes from './use-hidden-block-types';

export default function BlockManager( { scope } ) {
const {
blockTypes,
categories,
hasBlockSupport,
isMatchingSearchTerm,
} = useSelect( ( select ) => {
const {
getBlockTypes,
getCategories,
hasBlockSupport: _hasBlockSupport,
isMatchingSearchTerm: _isMatchingSearchTerm,
} = select( blocksStore );

return {
blockTypes: getBlockTypes(),
categories: getCategories(),
hasBlockSupport: _hasBlockSupport,
isMatchingSearchTerm: _isMatchingSearchTerm,
};
} );

const { hiddenBlockTypes } = useHiddenBlockTypes( scope );
const numberOfHiddenBlocks = hiddenBlockTypes?.length ?? 0;

function BlockManager( {
blockTypes,
categories,
hasBlockSupport,
isMatchingSearchTerm,
numberOfHiddenBlocks,
} ) {
const debouncedSpeak = useDebounce( speak, 500 );
const [ search, setSearch ] = useState( '' );

// Filtering occurs here (as opposed to `withSelect`) to avoid
// wasted renders by consequence of `Array#filter` producing
// a new value reference on each call.
blockTypes = blockTypes.filter(
const filteredBlockTypes = blockTypes.filter(
( blockType ) =>
hasBlockSupport( blockType, 'inserter', true ) &&
( ! search || isMatchingSearchTerm( blockType, search ) ) &&
Expand All @@ -46,19 +64,19 @@ function BlockManager( {
if ( ! search ) {
return;
}
const count = blockTypes.length;
const count = filteredBlockTypes.length;
const resultsFoundMessage = sprintf(
/* translators: %d: number of results. */
_n( '%d result found.', '%d results found.', count ),
count
);
debouncedSpeak( resultsFoundMessage );
}, [ blockTypes.length, search, debouncedSpeak ] );
}, [ filteredBlockTypes.length, search, debouncedSpeak ] );

return (
<div className="edit-post-block-manager__content">
<div className="block-editor-block-manager__content">
{ !! numberOfHiddenBlocks && (
<div className="edit-post-block-manager__disabled-blocks-count">
<div className="block-editor-block-manager__disabled-blocks-count">
{ sprintf(
/* translators: %d: number of blocks. */
_n(
Expand All @@ -75,57 +93,38 @@ function BlockManager( {
placeholder={ __( 'Search for a block' ) }
value={ search }
onChange={ ( nextSearch ) => setSearch( nextSearch ) }
className="edit-post-block-manager__search"
className="block-editor-block-manager__search"
/>
<div
tabIndex="0"
role="region"
aria-label={ __( 'Available block types' ) }
className="edit-post-block-manager__results"
className="block-editor-block-manager__results"
>
{ blockTypes.length === 0 && (
<p className="edit-post-block-manager__no-results">
{ filteredBlockTypes.length === 0 && (
<p className="block-editor-block-manager__no-results">
{ __( 'No blocks found.' ) }
</p>
) }
{ categories.map( ( category ) => (
<BlockManagerCategory
key={ category.slug }
scope={ scope }
title={ category.title }
blockTypes={ filter( blockTypes, {
blockTypes={ filter( filteredBlockTypes, {
category: category.slug,
} ) }
/>
) ) }
<BlockManagerCategory
scope={ scope }
title={ __( 'Uncategorized' ) }
blockTypes={ filter(
blockTypes,
filteredBlockTypes,
( { category } ) => ! category
) }
/>
</div>
</div>
);
}

export default withSelect( ( select ) => {
const {
getBlockTypes,
getCategories,
hasBlockSupport,
isMatchingSearchTerm,
} = select( blocksStore );
const { getHiddenBlockTypes } = select( editPostStore );
const hiddenBlockTypes = getHiddenBlockTypes();
const numberOfHiddenBlocks =
isArray( hiddenBlockTypes ) && hiddenBlockTypes.length;

return {
blockTypes: getBlockTypes(),
categories: getCategories(),
hasBlockSupport,
isMatchingSearchTerm,
numberOfHiddenBlocks,
};
} )( BlockManager );
Loading