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 15 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' );
1 change: 1 addition & 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
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
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
128 changes: 128 additions & 0 deletions packages/edit-site/src/components/block-preview/editor-styles.js
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is duplicated from the block editor package,

Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* External dependencies
*/
import { colord, extend } from 'colord';
import namesPlugin from 'colord/plugins/names';
import a11yPlugin from 'colord/plugins/a11y';

/**
* WordPress dependencies
*/
import { SVG } from '@wordpress/components';
import { useCallback, useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import {
transformStyles,
store as blockEditorStore,
} from '@wordpress/block-editor';

/**
* Internal dependencies
*/
import { unlock } from '../../lock-unlock';

extend( [ namesPlugin, a11yPlugin ] );

function useDarkThemeBodyClassName( styles, scope ) {
return useCallback(
( node ) => {
if ( ! node ) {
return;
}

const { ownerDocument } = node;
const { defaultView, body } = ownerDocument;
const canvas = scope ? ownerDocument.querySelector( scope ) : body;

let backgroundColor;

if ( ! canvas ) {
// The real .editor-styles-wrapper element might not exist in the
// DOM, so calculate the background color by creating a fake
// wrapper.
const tempCanvas = ownerDocument.createElement( 'div' );
tempCanvas.classList.add( 'editor-styles-wrapper' );
body.appendChild( tempCanvas );

backgroundColor = defaultView
?.getComputedStyle( tempCanvas, null )
.getPropertyValue( 'background-color' );

body.removeChild( tempCanvas );
} else {
backgroundColor = defaultView
?.getComputedStyle( canvas, null )
.getPropertyValue( 'background-color' );
}
const colordBackgroundColor = colord( backgroundColor );
// If background is transparent, it should be treated as light color.
if (
colordBackgroundColor.luminance() > 0.5 ||
colordBackgroundColor.alpha() === 0
) {
body.classList.remove( 'is-dark-theme' );
} else {
body.classList.add( 'is-dark-theme' );
}
},
[ styles, scope ]
);
}

export default function EditorStyles( { styles, scope } ) {
const overrides = useSelect(
( select ) => unlock( select( blockEditorStore ) ).getStyleOverrides(),
[]
);
const [ transformedStyles, transformedSvgs ] = useMemo( () => {
const _styles = Object.values( styles ?? [] );

for ( const [ id, override ] of overrides ) {
const index = _styles.findIndex( ( { id: _id } ) => id === _id );
const overrideWithId = { ...override, id };
if ( index === -1 ) {
_styles.push( overrideWithId );
} else {
_styles[ index ] = overrideWithId;
}
}

return [
transformStyles(
_styles.filter( ( style ) => style?.css ),
scope
),
_styles
.filter( ( style ) => style.__unstableType === 'svgs' )
.map( ( style ) => style.assets )
.join( '' ),
];
}, [ styles, overrides, scope ] );

return (
<>
{ /* Use an empty style element to have a document reference,
but this could be any element. */ }
<style
ref={ useDarkThemeBodyClassName( transformedStyles, scope ) }
/>
{ transformedStyles.map( ( css, index ) => (
<style key={ index }>{ css }</style>
) ) }
<SVG
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 0 0"
width="0"
height="0"
role="none"
style={ {
visibility: 'hidden',
position: 'absolute',
left: '-9999px',
overflow: 'hidden',
} }
dangerouslySetInnerHTML={ { __html: transformedSvgs } }
/>
</>
);
}
Loading
Loading