Blocks: Support reusable nested blocks (reusable blocks refactor)#5228
Blocks: Support reusable nested blocks (reusable blocks refactor)#5228
Conversation
8a970d1 to
5c6cd14
Compare
|
Rebased to resolve conflicts. Also made an update in 5c6cd14 which avoids referencing the For the editable controls removed in 7d0bdde, I think there might be a solution involving Still need to reinstate effects tests. |
There was a problem hiding this comment.
This is looking great. Really nice work 😍
Two issues I noticed in my testing:
- Reusable blocks are not displaying at all in the inserter. They display correctly on
master. I can see that theFETCHhappens, so my guess is that it's a regression in thegetInserterItemsselector. - If you have a reusable block in the editor and you reload the page, there is a moment where the Block has been deleted or is unavailable message appears for about a single frame, right after the loading spinner hides. My guess is that this message appears in between when
FETCH_REUSABLE_BLOCKS_SUCCESSis dispatched and whenRECEIVE_REUSABLE_BLOCKSis dispatched.
editor/store/selectors.js
Outdated
| return { | ||
| ...block, | ||
| id: ref, | ||
| isTemporary: ! Number.isInteger( ref ), |
There was a problem hiding this comment.
Should we split this out into a seperate isReusableBlockTemporary selector? This would match isFetchingReusableBlock and isSavingReusableBlock.
blocks/library/block/index.js
Outdated
| { element } | ||
| { ( isSelected || isEditing ) && ( | ||
| <ReusableBlockEditPanel | ||
| key="panel" |
There was a problem hiding this comment.
We can omit this now that we're using <Fragment>.
blocks/block-edit/index.js
Outdated
|
|
||
| // `edit` and `save` are functions or components describing the markup | ||
| // with which a block is displayed. If `blockType` is valid, assign | ||
| // them preferencially as the render value for the block. |
There was a problem hiding this comment.
s/preferencially/preferentially/
| } ) ), | ||
| withAPIData( ( { postType } ) => ( { | ||
| user: `/wp/v2/users/me?post_type=${ postType }&context=edit`, | ||
| } ) ), |
There was a problem hiding this comment.
I've seen this pattern in a few places now. I wonder if it's worth encapsulating this in a new withUserCapabilities HOC?
There was a problem hiding this comment.
Probably not necessary for now.
There was a problem hiding this comment.
With #5219, I might imagine this could be a simple selector on a core data module.
| isFetchingReusableBlock, | ||
| isSavingReusableBlock, | ||
| getBlock, | ||
| } = select( 'core/editor' ); |
editor/store/effects.js
Outdated
|
|
||
| const { id } = action; | ||
| const { getState, dispatch } = store; | ||
| const state = getState(); |
There was a problem hiding this comment.
nit: Could avoid assigning getState to its own variable now that it's only used once.
const { id } = action;
const { dispatch } = store;
const state = store.getState();| } ) | ||
| ) ); | ||
|
|
||
| dispatch( receiveBlocks( [ parsedBlock ] ) ); |
There was a problem hiding this comment.
This confused me for a minute. Let's add a comment explaining that it's necessary to put parsedBlock back into the store because replaceBlock will have removed it.
| const parsedBlock = getBlock( getState(), action.uid ); | ||
| const reusableBlock = { | ||
| id: uniqueId( 'reusable' ), | ||
| uid: parsedBlock.uid, |
There was a problem hiding this comment.
Hmm, this id vs uid distinction is extra confusing now they're in the same object 😬
I'm more and more convinced we should rename uid to id throughout the codebase. This can then be:
const reusableBlock = {
id: uniqueId( 'reusable' ),
blockId: parsedBlock.id,No need to do this right now though.
There was a problem hiding this comment.
Couldn't naming the property blockUID achieve the same effect as what you're describing?
editor/store/test/selectors.js
Outdated
| } ); | ||
|
|
||
| describe( 'isFetchingReusableBlock', () => { | ||
| it( 'should return false when the block is not being saved', () => { |
editor/store/test/selectors.js
Outdated
| expect( isFetching ).toBe( false ); | ||
| } ); | ||
|
|
||
| it( 'should return true when the block is being saved', () => { |
|
🎶 Ain't no party like a recursive block party, because a recursive block party don't stop. 🎶 I ran into the same problem with the inserter as @noisysocks, so I did this by manually editing the shared block in the database.
It'd probably be a good idea to implement recursion limits on this. I'd probably start with not allowing a nested shared block to contain itself, then let's see if there's potential for abuse of nested block trees before we worry too much about putting limits on it. |
5c6cd14 to
e34e523
Compare
|
I rebased, addressed my own feedback, fixed those two issues that I described, and fixed an additional issue I discovered where deleting a reusable block would cause blocks that were created via 'Convert to Regular Block' to be removed. Still to come: update the effects tests that were removed. |
a33325c to
ba90cbc
Compare
|
This should be good to go. Care to give it a quick check, @aduth? |
aduth
left a comment
There was a problem hiding this comment.
I'm noticing that the default block appender no longer shows at all when editing nested blocks. Thinking this could perhaps be related to recent refactoring with it (#5478).
We should probably also wait to merge this until after the next release, since it's large enough to risk breakage.
| /** | ||
| * Internal dependencies | ||
| */ | ||
| import { createInnerBlockList } from '../block-list/utils'; |
There was a problem hiding this comment.
I don't love that we reach into the local utilities of a separate component. Seems like if we need it in both places, might be best to extract up a level to a common place.
There was a problem hiding this comment.
👌 moved in e99f333aaeb2626b40f4c03c6fb8d5996443d50c.
editor/store/selectors.js
Outdated
| */ | ||
| export function getReusableBlocks( state ) { | ||
| return Object.values( state.reusableBlocks.data ); | ||
| return Object.keys( state.reusableBlocks.data ).map( ( ref ) => getReusableBlock( state, ref ) ); |
There was a problem hiding this comment.
Minor: Lodash's _.map works well on objects for this purpose (also trivially more performant by avoiding a second iteration):
return map(
state.reusableBlocks.data,
( data, ref ) => getReusableBlock( state, ref )
);There was a problem hiding this comment.
👌 changed in 7d87636ec2d95a9e678423fa66390ee2649af4ad.
It looks like this is broken in
👍 |
ba90cbc to
e99f333
Compare
This reverts commit 8872058.
In nested context, block selection changes from reusable block to the inner block being edited, but we want to keep the UI shown
e.g. within Disabled component, e.g. within reusable nested block
Generate BlockList from block's own utils, which has advantage of: - Not having circular dependency from blocks to editor - Respecting block menu and contextual toolbar props
Makes `getInserterItems` and `getFrecentInserterItems` respect that reusable blocks now point to a block that is elsewhere in the editor state. This makes reusable blocks again appear in the inserter.
Allow reusable nested blocks to be previewed in the inserter by having BlockPreview inject a createInnerBlockList function via context.
Re-order the FETCH_REUSABLE_BLOCKS effect so that the reusable block is added to the store before it is marked as no longer being fetched. This prevents core/block from briefly flashing the 'Not found' error message in between dispatches.
Making a copy of the referenced block prevents the regular block from being removed should the reuasble block be later deleted.
Use _.map to avoid iterating through the reusable blocks twice in `getReusableBlocks`.
e99f333 to
3f0180c
Compare
|
This is looking good, and I'd like it to have a few days to sit in master ahead of the next release 👍 The regression with columns default appender should be addressed separately, since it was not introduced here. |
|
Thanks for your help with this pull request @noisysocks |
Supersedes #5010
Cherry-picks 840fbec and ee61e18 from #5010
Cherry-picks dde1787 and 80e03de from #5223
Cherry-picks 66fdda5 from #5220
This pull request seeks to support reusable nested blocks. Unlike #5010, the changes here do not expand the reusable blocks API to support nested blocks, but rather refactor reusable blocks to serve as pointers to the original block references, therefore having better natural support for nested blocks.
A few of the changes have been brought along from #5010:
BlockListBlocktoBlockEdit, as the latter is the component used in rendering fromcore/block'seditblockstoeditor, asBlockEditrequires access to theBlockListcomponentA complication of reusable blocks as pointers it that we need to add support for the blocks state to receive arbitrary blocks after initial load without dirtying state (the loaded result of a reusable block should be merged into but not dirtying to blocks state). New options of
ignoreTypeshave been added to both thewithChangeDetectionandwithHistoryhigher-order reducers to accommodate this.Testing instructions:
Verify there are no regressions in the behavior of reusable blocks.
Ensure that you can save a Columns block, including content, as a reusable block.
Ensure unit tests pass:
Remaining tasks: