diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md index df16ea29219d54..5199ce7c7b8cc9 100644 --- a/docs/reference-guides/core-blocks.md +++ b/docs/reference-guides/core-blocks.md @@ -330,8 +330,8 @@ Insert an image to make a visual statement. ([Source](https://github.com/WordPre - **Name:** core/image - **Category:** media -- **Supports:** anchor, color (~~background~~, ~~text~~), filter (duotone) -- **Attributes:** align, alt, behaviors, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, sizeSlug, title, url, width +- **Supports:** anchor, behaviors (lightbox), color (~~background~~, ~~text~~), filter (duotone) +- **Attributes:** align, alt, caption, height, href, id, linkClass, linkDestination, linkTarget, rel, sizeSlug, title, url, width ## Latest Comments diff --git a/lib/block-supports/behaviors.php b/lib/block-supports/behaviors.php new file mode 100644 index 00000000000000..ea753b2e6f2612 --- /dev/null +++ b/lib/block-supports/behaviors.php @@ -0,0 +1,138 @@ +attributes ) { + $block_type->attributes = array(); + } + + $block_type->attributes['behaviors'] = array( + 'type' => 'object', + ); + + // If it supports the lightbox behavior, add the hook to that block. + // In the future, this should be a loop with all the behaviors. + $has_lightbox_support = block_has_support( $block_type, array( 'behaviors', 'lightbox' ), false ); + if ( $has_lightbox_support ) { + // Use priority 15 to run this hook after other hooks/plugins. + // They could use the `render_block_{$this->name}` filter to modify the markup. + add_filter( 'render_block_' . $block_type->name, 'gutenberg_render_behaviors_support_lightbox', 15, 2 ); + } +} + +/** + * Add the directives and layout needed for the lightbox behavior. + * This functions shouldn't be in this file. It should be moved to a package (or somewhere else), where all the behaviors logic is defined. + * + * @param string $block_content Rendered block content. + * @param array $block Block object. + * @return string Filtered block content. + */ +function gutenberg_render_behaviors_support_lightbox( $block_content, $block ) { + $experiments = get_option( 'gutenberg-experiments' ); + $link_destination = isset( $block['attrs']['linkDestination'] ) ? $block['attrs']['linkDestination'] : 'none'; + // Get the lightbox setting from the block attributes. + if ( isset( $block['attrs']['behaviors']['lightbox'] ) ) { + $lightbox = $block['attrs']['behaviors']['lightbox']; + // If the lightbox setting is not set in the block attributes, get it from the theme.json file. + } else { + $theme_data = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->get_data(); + if ( isset( $theme_data['behaviors']['blocks'][ $block['blockName'] ]['lightbox'] ) ) { + $lightbox = $theme_data['behaviors']['blocks'][ $block['blockName'] ]['lightbox']; + } else { + $lightbox = false; + } + } + + if ( ! $lightbox || 'none' !== $link_destination || empty( $experiments['gutenberg-interactivity-api-core-blocks'] ) ) { + return $block_content; + } + + $processor = new WP_HTML_Tag_Processor( $block_content ); + + $aria_label = __( 'Enlarge image', 'gutenberg' ); + + $alt_attribute = trim( $processor->get_attribute( 'alt' ) ); + + if ( $alt_attribute ) { + /* translators: %s: Image alt text. */ + $aria_label = sprintf( __( 'Enlarge image: %s', 'gutenberg' ), $alt_attribute ); + } + $content = $processor->get_updated_html(); + + $w = new WP_HTML_Tag_Processor( $content ); + $w->next_tag( 'figure' ); + $w->add_class( 'wp-lightbox-container' ); + $w->set_attribute( 'data-wp-interactive', true ); + $w->set_attribute( 'data-wp-context', '{ "core": { "image": { "initialized": false, "lightboxEnabled": false } } }' ); + $body_content = $w->get_updated_html(); + + // Wrap the image in the body content with a button. + $img = null; + preg_match( '/]+>/', $content, $img ); + $button = '
+ ' + . $img[0] . + '
'; + $body_content = preg_replace( '/]+>/', $button, $body_content ); + + // Add directive to expand modal image if appropriate. + $m = new WP_HTML_Tag_Processor( $content ); + $m->next_tag( 'img' ); + $m->set_attribute( 'data-wp-context', '{ "core": { "image": { "imageSrc": "' . wp_get_attachment_url( $block['attrs']['id'] ) . '"} } }' ); + $m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.imageSrc' ); + $modal_content = $m->get_updated_html(); + + $background_color = esc_attr( wp_get_global_styles( array( 'color', 'background' ) ) ); + + $close_button_icon = ''; + $close_button_color = esc_attr( wp_get_global_styles( array( 'color', 'text' ) ) ); + $dialog_label = $alt_attribute ? esc_attr( $alt_attribute ) : esc_attr__( 'Image', 'gutenberg' ); + $close_button_label = esc_attr__( 'Close', 'gutenberg' ); + + $lightbox_html = << + + $modal_content +
+ +HTML; + + return str_replace( '', $lightbox_html . '', $body_content ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'behaviors', + array( + 'register_attribute' => 'gutenberg_register_behaviors_support', + ) +); diff --git a/lib/load.php b/lib/load.php index 4f947263a572b1..b0ed333185ef6a 100644 --- a/lib/load.php +++ b/lib/load.php @@ -166,3 +166,4 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/anchor.php'; require __DIR__ . '/block-supports/shadow.php'; +require __DIR__ . '/block-supports/behaviors.php'; diff --git a/packages/block-editor/src/hooks/behaviors.js b/packages/block-editor/src/hooks/behaviors.js index 05c2792bb0419c..6676b51442579b 100644 --- a/packages/block-editor/src/hooks/behaviors.js +++ b/packages/block-editor/src/hooks/behaviors.js @@ -8,6 +8,7 @@ import { __experimentalHStack as HStack, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; +import { hasBlockSupport } from '@wordpress/blocks'; import { createHigherOrderComponent } from '@wordpress/compose'; import { useSelect } from '@wordpress/data'; @@ -42,24 +43,17 @@ function BehaviorsControl( { [ blockName ] ); - if ( - ! settings || - // If every behavior is disabled, do not show the behaviors inspector control. - Object.entries( settings ).every( ( [ , value ] ) => ! value ) - ) { - return null; - } - - // Block behaviors take precedence over theme behaviors. - const behaviors = merge( themeBehaviors, blockBehaviors || {} ); - const noBehaviorsOption = { value: '', label: __( 'No behaviors' ), }; const behaviorsOptions = Object.entries( settings ) - .filter( ( [ , behaviorValue ] ) => behaviorValue ) // Filter out behaviors that are disabled. + .filter( + ( [ behaviorName, behaviorValue ] ) => + hasBlockSupport( blockName, 'behaviors.' + behaviorName ) && + behaviorValue + ) // Filter out behaviors that are disabled. .map( ( [ behaviorName ] ) => ( { value: behaviorName, label: @@ -68,8 +62,14 @@ function BehaviorsControl( { behaviorName.slice( 1 ).toLowerCase(), } ) ); + // If every behavior is disabled, do not show the behaviors inspector control. + if ( behaviorsOptions.length === 0 ) return null; + const options = [ noBehaviorsOption, ...behaviorsOptions ]; + // Block behaviors take precedence over theme behaviors. + const behaviors = merge( themeBehaviors, blockBehaviors || {} ); + const helpText = disabled ? __( 'The lightbox behavior is disabled for linked images.' ) : __( 'Add behaviors.' ); @@ -116,8 +116,8 @@ function BehaviorsControl( { export const withBehaviors = createHigherOrderComponent( ( BlockEdit ) => { return ( props ) => { const blockEdit = ; - // Only add behaviors to the core/image block. - if ( props.name !== 'core/image' ) { + // Only add behaviors to blocks with support. + if ( ! hasBlockSupport( props.name, 'behaviors' ) ) { return blockEdit; } const blockHasLink = diff --git a/packages/block-library/src/image/block.json b/packages/block-library/src/image/block.json index d51d70dd711f0e..436331e37c3321 100644 --- a/packages/block-library/src/image/block.json +++ b/packages/block-library/src/image/block.json @@ -80,13 +80,13 @@ "source": "attribute", "selector": "figure > a", "attribute": "target" - }, - "behaviors": { - "type": "object" } }, "supports": { "anchor": true, + "behaviors": { + "lightbox": true + }, "color": { "text": false, "background": false diff --git a/packages/block-library/src/image/index.php b/packages/block-library/src/image/index.php index 70a40f4b59db9c..1e437d860aa097 100644 --- a/packages/block-library/src/image/index.php +++ b/packages/block-library/src/image/index.php @@ -30,90 +30,6 @@ function render_block_core_image( $attributes, $content ) { $processor->set_attribute( 'data-id', $attributes['data-id'] ); } - $link_destination = isset( $attributes['linkDestination'] ) ? $attributes['linkDestination'] : 'none'; - - // Get the lightbox setting from the block attributes. - if ( isset( $attributes['behaviors']['lightbox'] ) ) { - $lightbox = $attributes['behaviors']['lightbox']; - // If the lightbox setting is not set in the block attributes, get it from the theme.json file. - } else { - $theme_data = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data()->get_data(); - if ( isset( $theme_data['behaviors']['blocks']['core/image']['lightbox'] ) ) { - $lightbox = $theme_data['behaviors']['blocks']['core/image']['lightbox']; - } else { - $lightbox = false; - } - } - - $experiments = get_option( 'gutenberg-experiments' ); - - if ( ! empty( $experiments['gutenberg-interactivity-api-core-blocks'] ) && 'none' === $link_destination && $lightbox ) { - - $aria_label = __( 'Enlarge image' ); - - $alt_attribute = trim( $processor->get_attribute( 'alt' ) ); - - if ( $alt_attribute ) { - /* translators: %s: Image alt text. */ - $aria_label = sprintf( __( 'Enlarge image: %s' ), $alt_attribute ); - } - $content = $processor->get_updated_html(); - - $w = new WP_HTML_Tag_Processor( $content ); - $w->next_tag( 'figure' ); - $w->add_class( 'wp-lightbox-container' ); - $w->set_attribute( 'data-wp-interactive', true ); - $w->set_attribute( 'data-wp-context', '{ "core": { "image": { "initialized": false, "lightboxEnabled": false } } }' ); - $body_content = $w->get_updated_html(); - - // Wrap the image in the body content with a button. - $img = null; - preg_match( '/]+>/', $content, $img ); - $button = '
- ' - . $img[0] . - '
'; - $body_content = preg_replace( '/]+>/', $button, $body_content ); - - // Add directive to expand modal image if appropriate. - $m = new WP_HTML_Tag_Processor( $content ); - $m->next_tag( 'img' ); - $m->set_attribute( 'data-wp-context', '{ "core": { "image": { "imageSrc": "' . wp_get_attachment_url( $attributes['id'] ) . '"} } }' ); - $m->set_attribute( 'data-wp-bind--src', 'selectors.core.image.imageSrc' ); - $modal_content = $m->get_updated_html(); - - $background_color = esc_attr( wp_get_global_styles( array( 'color', 'background' ) ) ); - - $close_button_icon = ''; - $close_button_color = esc_attr( wp_get_global_styles( array( 'color', 'text' ) ) ); - - $dialog_label = $alt_attribute ? esc_attr( $alt_attribute ) : esc_attr__( 'Image' ); - $close_button_label = esc_attr__( 'Close' ); - - $lightbox_html = << - - $modal_content -
- -HTML; - - return str_replace( '', $lightbox_html . '', $body_content ); - } - return $processor->get_updated_html(); }