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

Render block preview on the server #55850

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php
/**
* File description goes here.
*
* @package Gutenberg_Render_Blocks_Controller
*/

if ( ! class_exists( 'Gutenberg_Render_Blocks_Controller' ) ) {

/**
* Class Gutenberg_Render_Blocks_Controller
*
* Renders blocks from a REST API request.
*
* @package Gutenberg_Render_Blocks_Controller
*/
class Gutenberg_Render_Blocks_Controller extends WP_REST_Controller {

/**
* Constructor.
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'render_blocks';
}

/**
* Registers the routes for the objects of the controller.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'render_blocks_from_request' ),
'permission_callback' => array( $this, 'get_permissions_check' ),
'args' => array(
'blocks' => array(
'required' => true,
'validate_callback' => array( $this, 'validate_blocks' ),
'sanitize_callback' => array( $this, 'sanitize_blocks' ),
),
),
'schema' => array( $this, 'get_item_schema' ),
),
)
);
}

/**
* Checks if a given request has access to create items.
*/
public function get_permissions_check() {
return true;
}

/**
* Checks if the blocks string is valid.
*
* @param string $blocks Full data about the request.
* @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
*/
public function validate_blocks( $blocks ) {
$blocks = parse_blocks( $blocks );
if ( ! is_array( $blocks ) ) {
// If parse_blocks does not return an array, it's not a valid block string.
return new WP_Error( 'rest_invalid_blocks', __( 'The blocks parameter is invalid.', 'gutenberg' ), array( 'status' => 400 ) );
}

return true;
}

/**
* Sanitizes the 'blocks' parameter.
*
* @param string $blocks The blocks string.
*/
public function sanitize_blocks( $blocks ) {
// Sanitize the blocks string to ensure it's a clean string.
return wp_kses_post( $blocks );
}

/**
* Renders blocks from a REST API request.
*
* @param WP_REST_Request $request Full data about the request.
*/
public function render_blocks_from_request( $request ) {
global $wp_query, $post;

$data = $request->get_json_params();

// We need to fake a global $wp_query and $post.
// This is because some blocks (e.g. Query block) rely on them,
// and we don't have them in the REST API context.
// Without them, the preview will be empty.
$fake_query = new WP_Query(
array(
'post_type' => 'post',
'posts_per_page' => get_option( 'posts_per_page' ),
'post_status' => 'publish',
)
);
$wp_query = $fake_query;
$post = $wp_query->posts[0];

$rendered_blocks = do_blocks( $data['blocks'] );

return rest_ensure_response( $rendered_blocks );
}

/**
* Retrieves the block renderer's schema, conforming to JSON Schema.
*/
public function get_item_schema() {
return array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'block-render',
'type' => 'object',
'properties' => array(
'blocks' => array(
'description' => __( 'Serialized blocks to render', 'gutenberg' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
),
);
}
}
}
9 changes: 9 additions & 0 deletions lib/compat/wordpress-6.5/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,12 @@ function _gutenberg_register_wp_templates_additional_fields() {
}

add_action( 'rest_api_init', '_gutenberg_register_wp_templates_additional_fields' );
/**
* Registers the Block Rederer REST API routes.
*/
function gutenberg_register_block_rederer_routes() {
$block_renderer_controller = new Gutenberg_Render_Blocks_Controller();
$block_renderer_controller->register_routes();
}

add_action( 'rest_api_init', 'gutenberg_register_block_rederer_routes' );
28 changes: 28 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ function gutenberg_is_experiment_enabled( $name ) {

// WordPress 6.5 compat.
require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-rest-global-styles-revisions-controller-6-5.php';
require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-render-blocks-controller.php';
require_once __DIR__ . '/compat/wordpress-6.5/rest-api.php';

// Plugin specific code.
Expand Down Expand Up @@ -258,3 +259,30 @@ function () {

// Data views.
require_once __DIR__ . '/experimental/data-views.php';

// Updates all blocks to use their example data, if they have it.
function modify_block_attributes_before_render( $block ) {
Comment on lines +263 to +264
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we'll need to explain why this is being done 🙏

$block_type_registry = WP_Block_Type_Registry::get_instance();
$block_type = $block_type_registry->get_registered( $block['blockName'] );
if ( isset( $block_type->example ) && isset( $block_type->example[ 'attributes' ] ) ) {
foreach( $block_type->example[ 'attributes' ] as $attribute_name => $attribute_value ) {
// Only replace attributes that are already set.
if ( isset( $block['attrs'][ $attribute_name ] ) ) {
$block['attrs'][ $attribute_name ] = $attribute_value;
}
$attribute_definition = $block_type->attributes[ $attribute_name ];
// Is this attribute sourced from the block markup istead of the block json comment.
if ( isset( $attribute_definition['source'] ) && $attribute_definition['source'] === 'attribute' ) {
$processor = new WP_HTML_Tag_Processor( $block['innerHTML'] ); //Should this be innerContent?
if ( $processor->next_tag( $attribute_definition['selector'] ) ) {
$processor->set_attribute( $attribute_definition['attribute'], $attribute_value );
$block['innerHTML'] = $processor->get_updated_html();
$block['innerContent'] = array( $processor->get_updated_html() );
}
}
}
}
return $block;
}

add_filter( 'render_block_data', 'modify_block_attributes_before_render', 10, 2 );
34 changes: 34 additions & 0 deletions packages/block-editor/src/components/block-preview/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,40 @@ export function BlockPreview( {
// Deprecated props:
__experimentalMinHeight,
__experimentalPadding,
} ) {
const settings = useSelect(
( select ) => select( blockEditorStore ).getSettings(),
[]
);

if ( settings.blockPreview ) {
return settings.blockPreview( {
blocks,
viewportWidth,
minHeight,
additionalStyles,
} );
}
return (
<DefaultBlockPreview
blocks={ blocks }
viewportWidth={ viewportWidth }
minHeight={ minHeight }
additionalStyles={ additionalStyles }
__experimentalMinHeight={ __experimentalMinHeight }
__experimentalPadding={ __experimentalPadding }
/>
);
}

function DefaultBlockPreview( {
blocks,
viewportWidth = 1200,
minHeight,
additionalStyles = [],
// Deprecated props:
__experimentalMinHeight,
__experimentalPadding,
} ) {
if ( __experimentalMinHeight ) {
minHeight = __experimentalMinHeight;
Expand Down
7 changes: 7 additions & 0 deletions packages/block-library/src/image/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@
"attribute": "target"
}
},
"example": {
"attributes": {
"sizeSlug": "large",
"url": "https://s.w.org/images/core/5.3/MtBlanc1.jpg",
"caption": "Mont Blanc appears—still, snowy, and serene."
}
},
"supports": {
"interactivity": true,
"align": [ "left", "center", "right", "wide", "full" ],
Expand Down
8 changes: 0 additions & 8 deletions packages/block-library/src/image/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@ export { metadata, name };

export const settings = {
icon,
example: {
attributes: {
sizeSlug: 'large',
url: 'https://s.w.org/images/core/5.3/MtBlanc1.jpg',
// translators: Caption accompanying an image of the Mont Blanc, which serves as an example for the Image block.
caption: __( 'Mont Blanc appears—still, snowy, and serene.' ),
},
},
__experimentalLabel( attributes, { context } ) {
if ( context === 'accessibility' ) {
const { caption, alt, url } = attributes;
Expand Down
2 changes: 1 addition & 1 deletion packages/block-library/src/site-logo/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"viewportWidth": 500,
"attributes": {
"width": 350,
"className": "block-editor-block-types-list__site-logo-example"
"className": "block-editor-block-types-list__site-logo-example-blah"
scruffian marked this conversation as resolved.
Show resolved Hide resolved
}
},
"supports": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
NAVIGATION_POST_TYPE,
} from '../../utils/constants';
import { unlock } from '../../lock-unlock';
import BlockPreview from '../block-preview';

export default function SiteEditorCanvas() {
const { templateType, isFocusMode, isViewMode } = useSelect( ( select ) => {
Expand Down Expand Up @@ -78,7 +79,10 @@ export default function SiteEditorCanvas() {
>
<EditorCanvas
enableResizing={ enableResizing }
settings={ settings }
settings={ {
...settings,
blockPreview: BlockPreview,
} }
>
{ resizeObserver }
</EditorCanvas>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { store as preferencesStore } from '@wordpress/preferences';
*/
import { store as editSiteStore } from '../../store';
import { unlock } from '../../lock-unlock';
import BlockPreview from '../block-preview';

const { useBlockEditorSettings } = unlock( editorPrivateApis );

Expand Down Expand Up @@ -159,6 +160,8 @@ export function useSpecificEditorSettings() {
keepCaretInsideBlock,
defaultRenderingMode,

blockPreview: BlockPreview,

// I wonder if they should be set in the post editor too
__experimentalArchiveTitleTypeLabel: archiveLabels.archiveTypeLabel,
__experimentalArchiveTitleNameLabel: archiveLabels.archiveNameLabel,
Expand Down
Loading
Loading