diff --git a/lib/experimental/editor-settings.php b/lib/experimental/editor-settings.php index cec1eafcf94fa0..3233624557a5cb 100644 --- a/lib/experimental/editor-settings.php +++ b/lib/experimental/editor-settings.php @@ -90,6 +90,10 @@ function gutenberg_enable_experiments() { wp_add_inline_script( 'wp-block-editor', 'window.__experimentalEnableGroupGridVariation = true', 'before' ); } + if ( $gutenberg_experiments && array_key_exists( 'gutenberg-connections', $gutenberg_experiments ) ) { + wp_add_inline_script( 'wp-block-editor', 'window.__experimentalConnections = true', 'before' ); + } + if ( gutenberg_is_experiment_enabled( 'gutenberg-no-tinymce' ) ) { wp_add_inline_script( 'wp-block-library', 'window.__experimentalDisableTinymce = true', 'before' ); } diff --git a/lib/experiments-page.php b/lib/experiments-page.php index d10be06feef19a..74b2ad01672f71 100644 --- a/lib/experiments-page.php +++ b/lib/experiments-page.php @@ -115,6 +115,18 @@ function gutenberg_initialize_experiments_settings() { ) ); + add_settings_field( + 'gutenberg-custom-fields', + __( 'Connections', 'gutenberg' ), + 'gutenberg_display_experiment_field', + 'gutenberg-experiments', + 'gutenberg_experiments_section', + array( + 'label' => __( 'Test Connections', 'gutenberg' ), + 'id' => 'gutenberg-connections', + ) + ); + register_setting( 'gutenberg-experiments', 'gutenberg-experiments' diff --git a/packages/block-editor/src/hooks/custom-fields.js b/packages/block-editor/src/hooks/custom-fields.js new file mode 100644 index 00000000000000..dbc8c3ec2c089f --- /dev/null +++ b/packages/block-editor/src/hooks/custom-fields.js @@ -0,0 +1,139 @@ +/** + * WordPress dependencies + */ +import { addFilter } from '@wordpress/hooks'; +import { PanelBody, TextControl } from '@wordpress/components'; +import { __, sprintf } from '@wordpress/i18n'; +import { hasBlockSupport } from '@wordpress/blocks'; +import { createHigherOrderComponent } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import { InspectorControls } from '../components'; +import { useBlockEditingMode } from '../components/block-editing-mode'; + +/** + * Filters registered block settings, extending attributes to include `connections`. + * + * @param {Object} settings Original block settings. + * + * @return {Object} Filtered block settings. + */ +function addAttribute( settings ) { + if ( hasBlockSupport( settings, '__experimentalConnections', true ) ) { + // Gracefully handle if settings.attributes.connections is undefined. + settings.attributes = { + ...settings.attributes, + connections: { + type: 'object', + }, + }; + } + + return settings; +} + +/** + * Override the default edit UI to include a new block inspector control for + * assigning a connection to blocks that has support for connections. + * Currently, only the `core/paragraph` block is supported and there is only a relation + * between paragraph content and a custom field. + * + * @param {WPComponent} BlockEdit Original component. + * + * @return {WPComponent} Wrapped component. + */ +const withInspectorControl = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + const blockEditingMode = useBlockEditingMode(); + const hasCustomFieldsSupport = hasBlockSupport( + props.name, + '__experimentalConnections', + false + ); + + // Check if the current block is a paragraph or image block. + // Currently, only these two blocks are supported. + if ( ! [ 'core/paragraph', 'core/image' ].includes( props.name ) ) { + return ; + } + + // If the block is a paragraph or image block, we need to know which + // attribute to use for the connection. Only the `content` attribute + // of the paragraph block and the `url` attribute of the image block are supported. + let attributeName; + if ( props.name === 'core/paragraph' ) attributeName = 'content'; + if ( props.name === 'core/image' ) attributeName = 'url'; + + if ( hasCustomFieldsSupport && props.isSelected ) { + return ( + <> + + { blockEditingMode === 'default' && ( + + + { + if ( nextValue === '' ) { + props.setAttributes( { + connections: undefined, + [ attributeName ]: undefined, + placeholder: undefined, + } ); + } else { + props.setAttributes( { + connections: { + attributes: { + // The attributeName will be either `content` or `url`. + [ attributeName ]: { + // Source will be variable, could be post_meta, user_meta, term_meta, etc. + // Could even be a custom source like a social media attribute. + source: 'meta_fields', + value: nextValue, + }, + }, + }, + [ attributeName ]: undefined, + placeholder: sprintf( + 'This content will be replaced on the frontend by the value of "%s" custom field.', + nextValue + ), + } ); + } + } } + /> + + + ) } + + ); + } + + return ; + }; +}, 'withInspectorControl' ); + +if ( window.__experimentalConnections ) { + addFilter( + 'blocks.registerBlockType', + 'core/connections/attribute', + addAttribute + ); + addFilter( + 'editor.BlockEdit', + 'core/connections/with-inspector-control', + withInspectorControl + ); +} diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index a66aa0a73ed411..6834d859d25453 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -21,6 +21,7 @@ import './content-lock-ui'; import './metadata'; import './metadata-name'; import './behaviors'; +import './custom-fields'; export { useCustomSides } from './dimensions'; export { useLayoutClasses, useLayoutStyles } from './layout'; diff --git a/packages/block-library/src/paragraph/block.json b/packages/block-library/src/paragraph/block.json index 7e13b13dc4feb9..db30d95db84756 100644 --- a/packages/block-library/src/paragraph/block.json +++ b/packages/block-library/src/paragraph/block.json @@ -41,6 +41,7 @@ "text": true } }, + "__experimentalConnections": true, "spacing": { "margin": true, "padding": true,