-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Fix DisableNonPageContentBlocks
#59297
Conversation
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.
To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Size Change: -13 B (0%) Total Size: 1.71 MB
ℹ️ View Unchanged
|
export const getPageContentBlocks = createRegistrySelector( | ||
( select ) => | ||
( state, rootClientId = '' ) => { | ||
const { getBlockOrder, getBlockName } = select( blockEditorStore ); | ||
const contentIds = []; | ||
for ( const clientId of getBlockOrder( rootClientId ) ) { | ||
const blockName = getBlockName( clientId ); | ||
if ( | ||
blockName === 'core/post-title' || | ||
blockName === 'core/post-featured-image' || | ||
blockName === 'core/post-content' | ||
) { | ||
contentIds.push( clientId ); | ||
} else if ( blockName !== 'core/query' ) { | ||
contentIds.push( | ||
...getPageContentBlocks( state, clientId ) | ||
); | ||
} | ||
} | ||
return contentIds; | ||
} | ||
); |
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.
This looks like a block-editor
store selector, which should be memoized; otherwise, clientIds
will be a new array on each call.
Sidenote: Recursively calling memoized selectors could impact performance - #58356.
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 like putting this in @wordpress/block-editor
because I feel block-editor
shouldn't know about "post title", "post featured image", "post content", the concept of page content, etc.
Unfortunately we can't combine createSelector
and createRegistrySelector
.
I'll go back to a solution that doesn't involve a new selector 👍 0af14addd13093038b7ca9e9f18a56eb3ae0a63f
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.
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.
Yes, they can be combined now, since #57888:
createRegistrySelector( ( select ) => createSelector( ( state, rootClientId = '' ) => {
...
} );
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 useSelect
solution still lacks memoization, returning a new .filter
result on each call, even if the underlying block editor state didn't change at all. It will probably trigger the "returns different values when called with the same state and parameters" warning.
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 useSelect
solution should be okay in this case. A mapSelect
returns an array of clientId
strings, which will pass shallow equality. There are few places in the core where we use similar "hacks"
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 can confirm that this does indeed fix the issue described :) Good find!
CleanShot.2024-02-23.at.08.09.58.mp4
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.
This is testing nicely for me, and the approach of checking the block parents for a match looks good to me 👍
One of the e2e tests was failing, but I've kicked it off again. It didn't look related to me, so let's see if it passes with another attempt.
Thanks for the follow-up! 🎉
]; | ||
|
||
function useDisableNonPageContentBlocks() { | ||
const contentIds = useSelect( ( select ) => { |
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.
Just a question for my own understanding: it looks like this is safely not creating a new reference too frequently (i.e. it's used in the useEffect
's dependencies and doesn't cause any issues). I'm used to the idea of trying to avoid .filter()
calls within a useSelect
because we then wind up with a new reference. In this case, is the reason that it's safe that contentIds
is the value returned by the callback here, rather than destructuring a value within an object?
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.
Correct, this is a "hack"; mapSelect
returns an array of clientId
strings, which can correctly pass shallow equality check.
I'm used to the idea of trying to avoid .filter() calls within a useSelect because we then wind up with a new reference.
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.
Ah, gotcha. Thanks for confirming!
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.
Huh, I didn't know it was a "hack" 😀 I just prefer not returning multiple things from useSelect
where possible as I think it's easier to read.
There's a warning if you do it wrong so I just let the computer do the thinking about this stuff.
gutenberg/packages/data/src/components/use-select/index.js
Lines 163 to 165 in 8f61b9b
console.warn( | |
`The 'useSelect' hook returns different values when called with the same state and parameters. This can lead to unnecessary rerenders.` | |
); |
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 mean “hack” because it’s not obvious why it’s okay to use filter
in current situation if you’re not familiar with useSelect
internals.
Hrm, it looks like |
It's failing on all my recent PRs too 😄 So maybe not related? |
Here's the fix for e2e test failure - #59356. |
- Updates DisableNonPageContentBlocks to only affect top level content. For example, a Featured Image within a Content block should remain as is. - Refactors DisableNonPageContentBlocks to use a selector so that we can write unit tests for the logic.
0af14ad
to
379190d
Compare
Nice work folks 👏 |
* Fix DisableNonPageContentBlocks - Updates DisableNonPageContentBlocks to only affect top level content. For example, a Featured Image within a Content block should remain as is. - Refactors DisableNonPageContentBlocks to use a selector so that we can write unit tests for the logic. * Use useSelect() approach instead of private selector Co-authored-by: noisysocks <noisysocks@git.wordpress.org> Co-authored-by: Mamaduka <mamaduka@git.wordpress.org> Co-authored-by: fabiankaegy <fabiankaegy@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: kmanijak <karolmanijak@git.wordpress.org>
I just cherry-picked this PR to the cherry-pick-wp-6-5-beta-3 branch to get it included in the next release: b9020fa |
* Fix DisableNonPageContentBlocks - Updates DisableNonPageContentBlocks to only affect top level content. For example, a Featured Image within a Content block should remain as is. - Refactors DisableNonPageContentBlocks to use a selector so that we can write unit tests for the logic. * Use useSelect() approach instead of private selector Co-authored-by: noisysocks <noisysocks@git.wordpress.org> Co-authored-by: Mamaduka <mamaduka@git.wordpress.org> Co-authored-by: fabiankaegy <fabiankaegy@git.wordpress.org> Co-authored-by: andrewserong <andrewserong@git.wordpress.org> Co-authored-by: jsnajdr <jsnajdr@git.wordpress.org> Co-authored-by: ramonjd <ramonopoly@git.wordpress.org> Co-authored-by: kmanijak <karolmanijak@git.wordpress.org>
Thank you, folks! |
What?
Fixes a bug noticed by @andrewserong in #59295 (review).
When editing a page we lock all blocks except for Title, Featured Image, and Content which are put into "content only" mode meaning that you can't interact with its non-content controls, e.g. you can't set a background or an alignment. This doesn't apply to blocks within the Content block which are fully editable.
Currently, Title or Featured Image blocks that are nested within a Content block are erroneously in "content only" mode. This is because of a flaw in the
DisableNonPageContentBlocks
component.DisableNonPageContentBlocks
will:setBlockEditingMode( '', 'disabled' )
.setBlockEditingMode( clientId, 'contentOnly' )
.The bug is in how
DisableNonPageContentBlocks
selects which content blocks to re-enable. It's naive and selects all blocks matching a given name. This includes blocks nested within Content, which isn't right.How?
This PR fixes the issue by rewriting
DisableNonPageContentBlocks
to use a more explicituseSelect
function to select content blocks. I think the extra explicitness will avoid confusion and future bugs of this nature. I also added unit tests.Testing Instructions