diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index 6e66df88bbac8..39e4fc4f6c550 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -42,7 +42,7 @@ Create and save content to reuse across your site. Update the pattern, and the c - **Name:** core/block - **Category:** reusable -- **Supports:** ~~customClassName~~, ~~html~~, ~~inserter~~ +- **Supports:** layout (~~allowEditing~~), ~~customClassName~~, ~~html~~, ~~inserter~~ - **Attributes:** ref ## Button diff --git a/packages/block-library/src/block/block.json b/packages/block-library/src/block/block.json index 4cb53960725d2..2c36efd94c07c 100644 --- a/packages/block-library/src/block/block.json +++ b/packages/block-library/src/block/block.json @@ -15,6 +15,9 @@ "supports": { "customClassName": false, "html": false, - "inserter": false + "inserter": false, + "layout": { + "allowEditing": false + } } } diff --git a/packages/block-library/src/block/deprecated.js b/packages/block-library/src/block/deprecated.js new file mode 100644 index 0000000000000..649551c76da78 --- /dev/null +++ b/packages/block-library/src/block/deprecated.js @@ -0,0 +1,19 @@ +/** + * Version prior to the addition of a wrapping element for the frontend. No + * attributes changed only the saved markup. + * + * See https://github.com/WordPress/gutenberg/issues/8288 + */ +const v1 = { + attributes: { ref: { type: 'number' } }, + supports: { + customClassName: false, + html: false, + inserter: false, + }, + save: () => null, +}; + +const deprecated = [ v1 ]; + +export default deprecated; diff --git a/packages/block-library/src/block/edit.js b/packages/block-library/src/block/edit.js index 13745ae0fd6de..15069a998e1f5 100644 --- a/packages/block-library/src/block/edit.js +++ b/packages/block-library/src/block/edit.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ @@ -12,6 +17,7 @@ import { TextControl, PanelBody, } from '@wordpress/components'; +import { useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; import { useInnerBlocksProps, @@ -21,9 +27,22 @@ import { InspectorControls, useBlockProps, Warning, + store as blockEditorStore, } from '@wordpress/block-editor'; +import { useEffect, useState } from '@wordpress/element'; + +const fullAlignments = [ 'full', 'wide', 'left', 'right' ]; + +export default function ReusableBlockEdit( { + attributes, + setAttributes, + __unstableParentLayout, +} ) { + const { ref } = attributes; + const [ inferredAlignment, setInferredAlignment ] = useState(); -export default function ReusableBlockEdit( { attributes: { ref } } ) { + const { __unstableMarkNextChangeAsNotPersistent } = + useDispatch( blockEditorStore ); const hasAlreadyRendered = useHasRecursion( ref ); const { record, hasResolved } = useEntityRecord( 'postType', @@ -45,8 +64,39 @@ export default function ReusableBlockEdit( { attributes: { ref } } ) { ref ); + useEffect( () => { + // We only track the initial alignment so that if the user could + // initially apply wide or full alignments to inner blocks, that ability + // is maintained even if they temporarily remove the last inner block + // with that wide/full alignment. + if ( ! blocks?.length || inferredAlignment !== undefined ) { + return; + } + + const isConstrained = __unstableParentLayout?.type === 'constrained'; + const hasFullAlignment = blocks.some( ( block ) => + fullAlignments.includes( block.attributes.align ) + ); + const alignment = isConstrained && hasFullAlignment ? 'full' : null; + + setInferredAlignment( alignment ); + __unstableMarkNextChangeAsNotPersistent(); + setAttributes( { + layout: alignment ? __unstableParentLayout : undefined, + } ); + }, [ + blocks, + inferredAlignment, + setAttributes, + __unstableMarkNextChangeAsNotPersistent, + __unstableParentLayout, + ] ); + const blockProps = useBlockProps( { - className: 'block-library-block__reusable-block-container', + className: classnames( + 'block-library-block__reusable-block-container', + { [ `align${ inferredAlignment }` ]: !! inferredAlignment } + ), } ); const innerBlocksProps = useInnerBlocksProps( blockProps, { diff --git a/packages/block-library/src/block/index.js b/packages/block-library/src/block/index.js index 95e090f0afd6a..0f612f9f9028a 100644 --- a/packages/block-library/src/block/index.js +++ b/packages/block-library/src/block/index.js @@ -6,17 +6,21 @@ import { symbol as icon } from '@wordpress/icons'; /** * Internal dependencies */ +import deprecated from './deprecated'; +import edit from './edit'; import initBlock from '../utils/init-block'; import metadata from './block.json'; -import edit from './edit'; +import save from './save.js'; const { name } = metadata; export { metadata, name }; export const settings = { + deprecated, edit, icon, + save, }; export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/block/index.php b/packages/block-library/src/block/index.php index d51b35d68b23d..b3003469f3603 100644 --- a/packages/block-library/src/block/index.php +++ b/packages/block-library/src/block/index.php @@ -8,11 +8,12 @@ /** * Renders the `core/block` block on server. * - * @param array $attributes The block attributes. + * @param array $attributes The block attributes. + * @param string $block_content The block content. * * @return string Rendered HTML of the referenced block. */ -function render_block_core_block( $attributes ) { +function render_block_core_block( $attributes, $block_content ) { static $seen_refs = array(); if ( empty( $attributes['ref'] ) ) { @@ -41,14 +42,78 @@ function render_block_core_block( $attributes ) { $seen_refs[ $attributes['ref'] ] = true; + // We need to determine the widest alignment of inner blocks so it can be + // applied to the pattern's wrapper. + $widest_alignment = null; + $filter_alignments = static function( $parsed_block, $source_block, $parent_block ) use ( &$widest_alignment ) { + // If this isn't a top level block in the pattern or we have already + // determined that we have a full aligned block, skip it. + if ( isset( $parent_block ) || 'full' === $widest_alignment ) { + return $parsed_block; + } + + $alignment = _wp_array_get( $parsed_block, array( 'attrs', 'align' ), null ); + $full_alignments = array( 'full', 'left', 'right' ); + + if ( in_array( $alignment, $full_alignments, true ) ) { + $widest_alignment = 'full'; + return $parsed_block; + } + + if ( 'wide' === $alignment ) { + $widest_alignment = $alignment; + } + + return $parsed_block; + }; + + // Only need to add the render_block_data filter when the pattern has saved + // a wrapper element in its block content. + if ( $block_content ) { + add_filter( 'render_block_data', $filter_alignments, 10, 3 ); + } + // Handle embeds for reusable blocks. global $wp_embed; + $content = $wp_embed->run_shortcode( $reusable_block->post_content ); $content = $wp_embed->autoembed( $content ); - $content = do_blocks( $content ); + unset( $seen_refs[ $attributes['ref'] ] ); - return $content; + + // Older block versions used only the post's content without incorporating + // the editor's wrapper. Newer versions added a wrapper through saved + // markup, so utilize it to return only the post's content when appropriate. + if ( ! $block_content ) { + return $content; + } + + remove_filter( 'render_block_data', $filter_alignments, 10 ); + + $processor = new WP_HTML_Tag_Processor( $block_content ); + $processor->next_tag(); + + // Apply the alignment class to the original block content so its all + // copied across to the result with other classes etc. + if ( null !== $widest_alignment ) { + $processor->add_class( 'align' . $widest_alignment ); + } + + $tag_name = $processor->get_tag(); + $markup = "<$tag_name>$content$tag_name>"; + + $merged_content = new WP_HTML_Tag_Processor( $markup ); + $merged_content->next_tag(); + + // Get all the attributes from the original block content and add them to + // the new markup. + $names = $processor->get_attribute_names_with_prefix( '' ); + foreach ( $names as $name ) { + $merged_content->set_attribute( $name, $processor->get_attribute( $name ) ); + } + + return $merged_content->get_updated_html(); } /** @@ -63,3 +128,31 @@ function register_block_core_block() { ); } add_action( 'init', 'register_block_core_block' ); + +/** + * Filter to short circuit rendering of old deprecated pattern blocks which + * haven't any saved content and need to skip any application of block supports. + * + * @param string|null $pre_render The pre-rendered content. Default null. + * @param array $block The block being rendered. + * + * @return string|null; + */ +function block_core_block_pre_render( $pre_render, $block ) { + if ( 'core/block' !== $block['blockName'] ) { + return null; + } + + $attributes = _wp_array_get( $block, array( 'attrs' ), array() ); + $inner_html = _wp_array_get( $block, array( 'innerHTML' ), null ); + + // For patterns that contain wrappers, skip short-circuiting, and allow + // block supports to be applied. + if ( $inner_html ) { + return null; + } + + return render_block_core_block( $attributes, $inner_html ); +} + +add_filter( 'pre_render_block', 'block_core_block_pre_render', 10, 2 ); diff --git a/packages/block-library/src/block/save.js b/packages/block-library/src/block/save.js new file mode 100644 index 0000000000000..effc97a5db77a --- /dev/null +++ b/packages/block-library/src/block/save.js @@ -0,0 +1,8 @@ +/** + * WordPress dependencies + */ +import { useBlockProps } from '@wordpress/block-editor'; + +export default function save() { + return
; +} diff --git a/packages/block-library/src/block/test/__snapshots__/transforms.native.js.snap b/packages/block-library/src/block/test/__snapshots__/transforms.native.js.snap index 3c4d791eb9f75..9392cf2f0cd79 100644 --- a/packages/block-library/src/block/test/__snapshots__/transforms.native.js.snap +++ b/packages/block-library/src/block/test/__snapshots__/transforms.native.js.snap @@ -3,13 +3,17 @@ exports[`Pattern block transforms to Columns block 1`] = ` "