-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Inserter: take into account children to disable blocks #8075
Conversation
editor/store/selectors.js
Outdated
@@ -1539,7 +1575,7 @@ export const getInserterItems = createSelector( | |||
|
|||
let isDisabled = false; | |||
if ( ! hasBlockSupport( blockType.name, 'multiple', true ) ) { | |||
isDisabled = some( getBlocks( state ), { name: blockType.name } ); | |||
isDisabled = some( getBlocksUnfolded( state ), { name: blockType.name } ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @nosolosw, the data structures we use on state are flat maybe we can take advantage of that. In state.editor.present.blocksByClientId we have an object with all the blocks including the nested ones. So we can just search for a block with that name on the list. Unfortunately, I think we have blocks that are previews so to make sure the block exists on the document if we find something we need to check if the UID is present in state.editor.present.blockOrder. This approach would take advantage of our already flat structures and avoid the new to create new arrays just to make a validation. Do you think this would work?
If we are storing blocks that are not part of the document in state.editor.present.blocksByClientId we should at least flag them with something like is preview blocks so we could make this check with just a search in an array. And plugin developers would be able to distinguish between blocks part of the document and blocks that are not part of the document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish we could do that, but in my testing, I've found that editor.present.blockOrder
and editor.present.blocksByUID
end up containing blocks that are not present in the document, so we can't operate solely on those. The only reliable mechanism I've found is to build the block tree from the blockOrder['']
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In ae57038 I've modified the external API to expose the getBlockIdsUnfolded
selectors instead of getBlocksUnfolded
.
That way the API has only two ways to query blocks:
getBlocks
for top-level blocksgetBlocksByUID
for retrieving any kind of blocks by IDs
which is arguably simpler to understand, but still exposes a helper selector (getBlockIdsUnfolded
) to query all the blocks present, including referenced by shared and children of nested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we find a block in blocksByUID, can we check if the block is, in fact, present in the document by checking if the block is referenced by some block or the root block in state.editor.present.blockOrder?
If that's not the case I feel plugin developers will also not be able to know what blocks are present in the document. So probably we should add some flags to specify if a block is a preview block or not (in a separate PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we find a block in blocksByUID, can we check if the block is, in fact, present in the document by checking if the block is referenced by some block or the root block in state.editor.present.blockOrder?
Yes, that works. This is what I mean by "building the block tree from the blockOrder['']
".
Note that if you want to check if a block is referenced by a shared block that wouldn't work because the relationship is encoded through the ref
attribute in the block contents.
If that's not the case I feel plugin developers will also not be able to know what blocks are present in the document. So probably we should add some flags to specify if a block is a preview block or not (in a separate PR).
What's a preview block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My idea would be to a check if a block is part of the document, by checking if the block is referenced by any other block in blockOrder or by blockOrder[''] without needing to iterate on the tree just checking the array.
forEach( blockOrder as blockUids) {
if( blockUIs contains blockUid ) {
//block uid is part of the document.
}
}
Do you think that this would work?
Edited: this would not work. We may have nested shared blocks not inserterd (as previews), and blocks inside the nested shared block would pass this validation.
What's a preview block?
I think the blocks present in blocksByUID that are not part of the document are used to display block previews. Maybe we can flag this blocks so just by iterating on blocksByUID we can know what blocks are part of the document or not.
I just discovered a bug related with this problem if we have shared blocks, we create an empty post we preview shared blocks on the inserter without previewing them our block counts are wrong because the counter thinks all blocks in blocksByUID are part of the document.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summing up here some bits that Jorge and I discussed privately:
- preview blocks are the ones displayed in the inserter when you hover over the shared blocks. See:
-
shared blocks (although not used) are appended to
blocksByClientId
. -
blocksByClientId
is updated every time any block attribute changes (like in any key press for paragraphs). The cache of any selector that is dependant on it will be invalidated every time it changes, rendering the cache unuseful. -
can the
getBlockIdsUnfolded
selector only depend onblockOrder
? No. Some plugin may update theblocksByClientId
by changing theref
attribute of a shared block present in the document. That change wouldn't be reflected inblockOrder
, and the selector uses theref
to compute the referenced blocks. -
the outstanding blocker for merging this would be performance. We don't know the impact of this for real, but we are operating on this assumption: avoid array concatenation (prefer flatMap).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The selector has been refactored to use flatMap
.
052827c
to
ae57038
Compare
editor/store/selectors.js
Outdated
const unfoldBlockIds = ( state, block ) => { | ||
const clientIds = []; | ||
if ( block.name === 'core/block' ) { | ||
clientIds.push( get( getSharedBlock( state, block.attributes.ref ), [ 'clientId' ], null ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we may need to apply an unfoldOperation to the result of getSharedBlock because that block may have other blocks nested inside.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Case and test added in 090209cb442966f30823a74c9c7416a9a004081a
Good catch!
editor/store/selectors.js
Outdated
* | ||
* @return {Array} All ids of top-level, referenced, and inner blocks. | ||
*/ | ||
export const getBlockIdsUnfolded = createSelector( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can call this select getAllBlockClientIdsReferenced and specify it returns the array of all block uids referenced/used in the document even the ones inside shared blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to getClientIdsUnfolded
which is in line with the other selector names (get + thing + modifier).
ae57038
to
bbee93a
Compare
@jorgefilipecosta I've addressed your feedback to date. Would you consider this mergeable? |
47a6d28
to
72b305b
Compare
@noisysocks some e2e test related to SharedBlocks are failing for this PR https://travis-ci.org/WordPress/gutenberg/jobs/408598718 It's a bit obscure to me as to why they fail, would you be available to help? |
@nosolosw: It looks like there's an exception being thrown when a new shared block is created.
|
72b305b
to
90ff826
Compare
Thanks Robert! Fixed at 90ff826. |
editor/store/selectors.js
Outdated
...unfoldClientIds( globalState, ...getBlocksByClientId( globalState, clientId ) ), | ||
]; | ||
} | ||
return [ null ]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we just return [] here we avoid the need for the filter by id right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so: in https://github.com/WordPress/gutenberg/pull/8075/files/90ff8269cf5999dd605cb71a51251846e3354efc#diff-4e1dd4776f75eb7a66b28280a56d8e0aR594 and https://github.com/WordPress/gutenberg/pull/8075/files/90ff8269cf5999dd605cb71a51251846e3354efc#diff-4e1dd4776f75eb7a66b28280a56d8e0aR585 we return null
if the shared or inner block doesn't have a clientId
. That's certainly unlikely, but I'd rather be cautious.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for clarigying, I missed that the "block" it self may be null because of the return of a selector.
I'm noticing that if for example, we create a shared block for more. We can insert that shared block multiple times. This verification for shared blocks is really hard to do as shared blocks can contain nested blocks inside and if one of the many nested blocks inside does not support multiple blocks and is present in the document (maybe via another shared block) the insertion cannot happen. Maybe given this complexity it may make sense to not unfold shared bocks and ignore them from this verifications. |
90ff826
to
707fc7a
Compare
Now that I've been more exposed to Gutenberg, I think we should implement this in a coherent way: either take SharedBlocks into account or not take them (I was initially wary of taking the use case @jorgefilipecosta mentioned into account). I lean towards adding them to this computation (and I can add more code to this PR to fix that), but I'm also open to remove them from this. Being a guten-newbie, I'm unsure about the best path here, so I'll invoke @noisysocks as I understand he's the most knowledgeable person about how SharedBlocks interact with the rest of Gutenberg - what are your thoughts, Robert? |
This is a tough one... With how reusable blocks are currently implemented, I'd lean towards taking them into account when computing whether or not a block is insertable because of a BUT, in #7453, we're considering changing our implementation of reusable blocks to use a nested instance of the Gutenberg editor. This would make iterating through "all of the blocks on the page" very difficult as it would involve iterating through different I think that, for now, let's not take reusable blocks into account. We can revaluate this later when #7453 is resolved. 1 Note, however, that not everything in |
When deriving whether a block should be disabled or not, the inserter should take into account the blocks referenced by the existing top-level shared blocks.
By doing this, we reduce the number of API methods to retrieve blocks to: - getBlocks: top level blocks - getBlocksByClientId: blocks by id And we expose getBlockIdsUnfolded as an utility to retrieve the ids of top-level, referenced, and nested blocks.
A shared block can contain nested blocks, so we need to unfold them.
Thanks for the feedback, Jorge & Robert. I'll shrink down the scope of this to Nested blocks, leaving Shared for a future iteration. |
707fc7a
to
da45c30
Compare
@jorgefilipecosta @noisysocks da45c30 has got rid of the logic that was specific to Shared blocks. Updated testing instructions and PR description as well. |
@jorgefilipecosta @noisysocks does this need anything else to be ready to merge? Or can I go ahead and land this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM—thanks @nosolosw 👍
Refs #7957
When evaluating whether a block should be disabled or not, the inserter should take into account children of nested blocks
as well as the blocks referenced by the existing shared blocks, as well as the- see #8075 (comment).Testing
After this PR, it shouldn't be possible to add a
More
block if there is one nestedMore
block: