-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Block.* in save component #20721
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
Block.* in save component #20721
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,11 @@ import classnames from 'classnames'; | |
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { InnerBlocks, getColorClassName } from '@wordpress/block-editor'; | ||
| import { | ||
| InnerBlocks, | ||
| getColorClassName, | ||
| __experimentalBlock as Block, | ||
| } from '@wordpress/block-editor'; | ||
|
|
||
| export default function save( { attributes } ) { | ||
| const { | ||
|
|
@@ -38,8 +42,11 @@ export default function save( { attributes } ) { | |
| }; | ||
|
|
||
| return ( | ||
| <div className={ className ? className : undefined } style={ style }> | ||
| <Block.Save.div | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reiterating a concern I raised in previous pull requests: I can't help but foresee this becoming a never-ending maintenance burden. For what reason do we need to whitelist these tag names? Could it be something where we use instead some prop, like...
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The main idea of That said, I think it has limits, for example, it's impossible to support custom tags (which I think we should support). So we might have to find a new approach to solve both problems. |
||
| className={ className ? className : undefined } | ||
| style={ style } | ||
| > | ||
| <InnerBlocks.Content /> | ||
| </div> | ||
| </Block.Save.div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,12 @@ import { isEmpty, reduce, isObject, castArray, startsWith } from 'lodash'; | |
| /** | ||
| * WordPress dependencies | ||
| */ | ||
| import { Component, cloneElement, renderToString } from '@wordpress/element'; | ||
| import { | ||
| Component, | ||
| cloneElement, | ||
| renderToString, | ||
| createContext, | ||
| } from '@wordpress/element'; | ||
| import { hasFilter, applyFilters } from '@wordpress/hooks'; | ||
| import isShallowEqual from '@wordpress/is-shallow-equal'; | ||
|
|
||
|
|
@@ -17,6 +22,7 @@ import { | |
| getBlockType, | ||
| getFreeformContentHandlerName, | ||
| getUnregisteredTypeHandlerName, | ||
| hasBlockSupport, | ||
| } from './registration'; | ||
| import { normalizeBlockType } from './utils'; | ||
| import BlockContentProvider from '../block-content-provider'; | ||
|
|
@@ -68,6 +74,8 @@ export function getBlockMenuDefaultClassName( blockName ) { | |
| ); | ||
| } | ||
|
|
||
| export const BlockPropsFilterContext = createContext(); | ||
|
|
||
| /** | ||
| * Given a block type containing a save render implementation and attributes, returns the | ||
| * enhanced element to be saved or string when raw HTML expected. | ||
|
|
@@ -95,27 +103,43 @@ export function getSaveElement( | |
| } | ||
|
|
||
| let element = save( { attributes, innerBlocks } ); | ||
| let blockPropsFilter = ( props ) => props; | ||
|
|
||
| if ( hasFilter( 'blocks.getSaveContent.extraProps' ) ) { | ||
| if ( hasBlockSupport( blockType, 'lightBlockWrapper', false ) ) { | ||
| blockPropsFilter = ( props ) => { | ||
| /** | ||
| * Filters the props applied to the block save result element. | ||
| * | ||
| * @param {Object} props Props applied to save element. | ||
| * @param {WPBlock} blockType Block type definition. | ||
| * @param {Object} attributes Block attributes. | ||
| */ | ||
| return applyFilters( | ||
| 'blocks.getSaveContent.extraProps', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing I was wondering is whether we should just apply this hook to the Edit version of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could, but we'd need more context and then the hook namespace also doesn't make sense anymore. |
||
| { ...props }, | ||
| blockType, | ||
| attributes | ||
| ); | ||
| }; | ||
| } else if ( isObject( element ) ) { | ||
| /** | ||
| * Filters the props applied to the block save result element. | ||
| * | ||
| * @param {Object} props Props applied to save element. | ||
| * @param {WPBlock} blockType Block type definition. | ||
| * @param {Object} attributes Block attributes. | ||
| */ | ||
| const props = applyFilters( | ||
| 'blocks.getSaveContent.extraProps', | ||
| { ...element.props }, | ||
| blockType, | ||
| attributes | ||
| ); | ||
|
|
||
| if ( | ||
| isObject( element ) && | ||
| hasFilter( 'blocks.getSaveContent.extraProps' ) | ||
| ) { | ||
| /** | ||
| * Filters the props applied to the block save result element. | ||
| * | ||
| * @param {Object} props Props applied to save element. | ||
| * @param {WPBlock} blockType Block type definition. | ||
| * @param {Object} attributes Block attributes. | ||
| */ | ||
| const props = applyFilters( | ||
| 'blocks.getSaveContent.extraProps', | ||
| { ...element.props }, | ||
| blockType, | ||
| attributes | ||
| ); | ||
|
|
||
| if ( ! isShallowEqual( props, element.props ) ) { | ||
| element = cloneElement( element, props ); | ||
| if ( ! isShallowEqual( props, element.props ) ) { | ||
| element = cloneElement( element, props ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -134,9 +158,11 @@ export function getSaveElement( | |
| ); | ||
|
|
||
| return ( | ||
| <BlockContentProvider innerBlocks={ innerBlocks }> | ||
| { element } | ||
| </BlockContentProvider> | ||
| <BlockPropsFilterContext.Provider value={ blockPropsFilter }> | ||
| <BlockContentProvider innerBlocks={ innerBlocks }> | ||
| { element } | ||
| </BlockContentProvider> | ||
| </BlockPropsFilterContext.Provider> | ||
| ); | ||
| } | ||
|
|
||
|
|
||
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'm running into a really strange problem here.
filterreturns theBlockContentfunction from theBlockContentProvider(forInnerBlocks)... even though I'm subscribing toBlockPropsFilterContext. If I swap the two provider components, then it works, but thenInnerBlocks.Contentstops working. @youknowriad Any idea why context is behaving strangely during serialisation? Been staring at this for a while. 🤔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 know that we have a custom serializer implementation and we don't rely on React. That might be the reason. cc @aduth
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.
Yeah, it could very well be an issue in the implementation of the custom serializer.
In particular, looking at this code, I can see how it might be problematic for two separate context values to coexist in the same render tree, since the second argument would replace the current context:
gutenberg/packages/element/src/serialize.js
Lines 413 to 414 in b7125c0
It's likely not been discovered since our use of context in serialization is pretty minimal to date.
The solution may be a bit tricky, since we context still needs to be associated 1:1 for a given provider/context pair, and we can't assume anything about the shape of that value (i.e. can't be doing any
Object.assign).I'd guess we might want to implement context here as some [Weak]Map which can be looked up by Provider class at the time if/when the Consumer is rendered.
Failing test case:
On the general point of the custom serializer, there's other issues here as well (notably, hooks, see #15873). Even if we fixed the above implementation of serializer provider rendering, I'd guess
useContextlikely still wouldn't work? I'm open to reevaluating this implementation, depending how much a burden it continues to be. For context on why it exists in the first place, see #5897 (#3353).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.
See: #21156