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

Block Supports: Refactor styles rendering to utility function and add to REST API responses #35450

32 changes: 10 additions & 22 deletions lib/block-supports/duotone.php
Original file line number Diff line number Diff line change
Expand Up @@ -251,17 +251,16 @@ function gutenberg_register_duotone_support( $block_type ) {
}

/**
* Renders the duotone filter SVG and returns the CSS filter property to
* reference the rendered SVG.
* Renders the Duotone filter SVG
*
* @param array $preset Duotone preset value as seen in theme.json.
*
* @return string Duotone CSS filter property.
* @return string The Duotone filter SVG markup.
*/
function gutenberg_render_duotone_filter_preset( $preset ) {
$duotone_id = $preset['slug'];
$filter_id = $preset['slug'];
$duotone_colors = $preset['colors'];
$filter_id = 'wp-duotone-' . $duotone_id;

$duotone_values = array(
'r' => array(),
'g' => array(),
Expand Down Expand Up @@ -319,17 +318,7 @@ function gutenberg_render_duotone_filter_preset( $preset ) {
$svg = trim( $svg );
}

add_action(
// Safari doesn't render SVG filters defined in data URIs,
// and SVG filters won't render in the head of a document,
// so the next best place to put the SVG is in the footer.
is_admin() ? 'admin_footer' : 'wp_footer',
function () use ( $svg ) {
echo $svg;
}
);

return "url('#" . $filter_id . "')";
return $svg;
}

/**
Expand Down Expand Up @@ -357,11 +346,12 @@ function gutenberg_render_duotone_support( $block_content, $block ) {
}

$filter_preset = array(
'slug' => uniqid(),
'slug' => 'wp-duotone-' . uniqid(),
'colors' => $block['attrs']['style']['color']['duotone'],
);
$filter_property = gutenberg_render_duotone_filter_preset( $filter_preset );
$filter_id = 'wp-duotone-' . $filter_preset['slug'];
$svg = gutenberg_render_duotone_filter_preset( $filter_preset );
$filter_id = $filter_preset['slug'];
$filter_property = "url('#" . $filter_id . "')";

$scope = '.' . $filter_id;
$selectors = explode( ',', $duotone_support );
Expand All @@ -377,9 +367,7 @@ function gutenberg_render_duotone_support( $block_content, $block ) {
? $selector . " {\n\tfilter: " . $filter_property . " !important;\n}\n"
: $selector . '{filter:' . $filter_property . ' !important;}';

wp_register_style( $filter_id, false, array(), true, true );
wp_add_inline_style( $filter_id, $filter_style );
wp_enqueue_style( $filter_id );
gutenberg_render_block_supports_style( $svg . '<style>' . $filter_style . '</style>' );

// Like the layout hook, this assumes the hook only applies to blocks with a single wrapper.
return preg_replace(
Expand Down
10 changes: 1 addition & 9 deletions lib/block-supports/elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,7 @@ function gutenberg_render_elements_support( $block_content, $block ) {
$content = substr_replace( $block_content, ' class="' . $class_name . '"', $first_element_offset + strlen( $first_element ) - 1, 0 );
}

// Ideally styles should be loaded in the head, but blocks may be parsed
// after that, so loading in the footer for now.
// See https://core.trac.wordpress.org/ticket/53494.
add_action(
'wp_footer',
function () use ( $style ) {
echo $style;
}
);
gutenberg_render_block_supports_style( $style );

return $content;
}
Expand Down
10 changes: 1 addition & 9 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,7 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
1
);

// Ideally styles should be loaded in the head, but blocks may be parsed
// after that, so loading in the footer for now.
// See https://core.trac.wordpress.org/ticket/53494.
add_action(
'wp_footer',
function () use ( $style ) {
echo '<style>' . $style . '</style>';
}
);
gutenberg_render_block_supports_style( '<style>' . $style . '</style>' );

return $content;
}
Expand Down
67 changes: 67 additions & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -620,3 +620,70 @@ function gutenberg_multiple_block_styles( $metadata ) {
return $metadata;
}
add_filter( 'block_type_metadata', 'gutenberg_multiple_block_styles' );

/**
* Renders block support styles by registering against an action to output
* to the footer for server rendered views of the site, and via a filter
* to append to API responses via an additional field.
*
* @see gutenberg_register_block_supports_styles_api_field
*
* @param string $content The content to be rendered.
* @param string $action The type of action to use, e.g. `wp_footer` or `admin_footer`.
*/
function gutenberg_render_block_supports_style( $content, $action = 'wp_footer' ) {
// Ideally styles should be loaded in the head, but blocks may be parsed
// after that, so loading in the footer for now.
// See https://core.trac.wordpress.org/ticket/53494.
add_action(
$action,
function () use ( $content ) {
echo $content;
}
);

// Register styles to be rendered in REST API responses.
add_filter(
'wp_render_block_supports_styles',
function ( $block_supports_styles ) use ( $content ) {
return $block_supports_styles . $content;
},
10,
1
);
}

/**
* Apply the block supports styles filters, rendering out all registered style
* markup that will be used in API responses.
*
* @see gutenberg_render_block_supports_style
*
* @return string The concatenated block supports styles to be output in an API response.
*/
function gutenberg_render_block_supports_styles_to_api_response() {
return apply_filters( 'wp_render_block_supports_styles', '' );
}

/**
* Register a separate `block_support_styles` field on public post type API endpoints.
*
* This field contains styles generated by block supports that are typically rendered
* in `wp_footer` or `admin_footer` in server-rendered views of the front end of a site
* or the block editor.
*/
function gutenberg_register_block_supports_styles_api_field() {
$post_types = get_post_types( array( 'public' => true ) );

foreach ( $post_types as $post_type ) {
register_rest_field(
$post_type,
'block_supports_styles',
array(
'get_callback' => 'gutenberg_render_block_supports_styles_to_api_response',
'schema' => array( 'type' => 'string' ),
)
);
}
}
add_action( 'rest_api_init', 'gutenberg_register_block_supports_styles_api_field' );
26 changes: 16 additions & 10 deletions packages/block-library/src/post-content/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@ import {
store as blockEditorStore,
Warning,
} from '@wordpress/block-editor';
import { useEntityProp, useEntityBlockEditor } from '@wordpress/core-data';
import { useEntityBlockEditor, store as coreStore } from '@wordpress/core-data';

/**
* Internal dependencies
*/
import { useCanEditEntity } from '../utils/hooks';

function ReadOnlyContent( { userCanEdit, postType, postId } ) {
const [ , , content ] = useEntityProp(
'postType',
postType,
'content',
postId
);
const { content, blockSupportsStyles } = useSelect( ( select ) => {
const { getEntityRecord } = select( coreStore );
const currentPost = getEntityRecord( 'postType', postType, postId );

return {
content: currentPost?.content,
blockSupportsStyles: currentPost?.block_supports_styles,
};
} );

const blockProps = useBlockProps();
return content?.protected && ! userCanEdit ? (
<div { ...blockProps }>
<Warning>{ __( 'This content is password protected.' ) }</Warning>
</div>
) : (
<div { ...blockProps }>
<RawHTML key="html">{ content?.rendered }</RawHTML>
</div>
<RawHTML { ...blockProps } key="html">
{ blockSupportsStyles && content?.rendered
? content.rendered + blockSupportsStyles
: content?.rendered }
</RawHTML>
Copy link
Member

Choose a reason for hiding this comment

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

What if layout, duotone etc. is rendered outside of the post content?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question, thanks for taking a look @ellatrix. This particular line just deals with the Post Content block, so grabbing blockSupportsStyles as a field from the posts endpoint currently works, but this PR does assume we're dealing with post content. Outside of the Post Content block within the site editor, the block supports still appear to be rendering okay in my testing so far, but I haven't tested with other REST API based server-rendered blocks, so there are likely still many gaps.

We could also add the field to other resources that need it, e.g. comments, possibly the block renderer, too?

I've struggled a bit with coming up with a clean solution to ensure that the rendered block supports styles are always available and will be used in each potential case. In this PR, we mostly solve for the problem of rendering within the Post Content block in the editor, however I can imagine there will be a number of edge cases where we'll need to be intentional about how we render (and make available) the block supports styles. If this approach looks viable, we can always follow up with each of the edge cases as we encounter them, but I'm aware that approach could be brittle (and not necessarily great for other consumers of the REST API).

Let me know if you can think of a better way to expose the styles that we're currently rendering out to wp_footer, I'm very happy to rework this PR if we can come up with better ways to handle it! 🙂

);
}

Expand Down