Skip to content

Commit

Permalink
Add custom attributes sources block support
Browse files Browse the repository at this point in the history
  • Loading branch information
youknowriad committed Jun 9, 2023
1 parent b52d61e commit dd59572
Show file tree
Hide file tree
Showing 6 changed files with 277 additions and 2 deletions.
2 changes: 1 addition & 1 deletion docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ Start with the basic building block of all narrative. ([Source](https://github.c

- **Name:** core/paragraph
- **Category:** text
- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~
- **Supports:** __unstablePasteTextInline, anchor, color (background, gradients, link, text), customSources (content), spacing (margin, padding), typography (fontSize, lineHeight), ~~className~~
- **Attributes:** align, content, direction, dropCap, placeholder

## Pattern
Expand Down
74 changes: 74 additions & 0 deletions gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,77 @@ function gutenberg_pre_init() {

require_once __DIR__ . '/lib/load.php';
}

/**
* Renders the block meta attributes.
*
* @param string $block_content Block Content.
* @param array $block Block attributes.
* @param string $block_instance The block instance.
*/
function render_custom_sources( $block_content, $block, $block_instance ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
if ( null === $block_type ) {
return $block_content;
}


$custom_sources = _wp_array_get( $block_type->supports, 'customSources', false );
if ( ! $custom_sources ) {
// TODO: for some reason the "customSources" support is not being registered as it should.
// return $block_content;
}

$attribute_sources = _wp_array_get( $block['attrs'], array( 'source' ), array() );
foreach ( $attribute_sources as $attribute_name => $attribute_source ) {
$attribute_config = _wp_array_get( $block_type->attributes, array( $attribute_name ), false );
if ( ! $attribute_config || ! $attribute_source || 'meta' !== $attribute_source['type'] ) {
continue;
}
$meta_field = $attribute_source['name'];
$meta_value = get_post_meta( $block_instance->context['postId'], $meta_field, true );
$p = new WP_HTML_Tag_Processor( $block_content );
$found = $p->next_tag(
array(
// TODO: build the query from CSS selector.
'tag_name' => $attribute_config['selector'],
)
);
if ( ! $found ) {
continue;
}
$tag_name = $p->get_tag();
$markup = "<$tag_name>$meta_value</$tag_name>";
$p2 = new WP_HTML_Tag_Processor( $markup );
$p2->next_tag();
$names = $p->get_attribute_names_with_prefix( '' );
foreach ( $names as $name ) {
$p2->set_attribute( $name, $p->get_attribute( $name ) );
}

$block_content = $p2 . '';
}

return $block_content;
}

add_filter( 'render_block', 'render_custom_sources', 10, 3 );

// ----- what follows is just random test code.

/**
* Registers a custom meta for use by the test paragraph variation.
*/
function init_test_summary_meta_field() {
register_meta(
'post',
'summary',
array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
)
);
}

add_action( 'init', 'init_test_summary_meta_field' );
6 changes: 5 additions & 1 deletion packages/block-library/src/paragraph/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"description": "Start with the basic building block of all narrative.",
"keywords": [ "text" ],
"textdomain": "default",
"usesContext": [ "postType", "postId" ],
"attributes": {
"align": {
"type": "string"
Expand Down Expand Up @@ -63,7 +64,10 @@
}
},
"__experimentalSelector": "p",
"__unstablePasteTextInline": true
"__unstablePasteTextInline": true,
"customSources": {
"content": true
}
},
"editorStyle": "wp-block-paragraph-editor",
"style": "wp-block-paragraph"
Expand Down
16 changes: 16 additions & 0 deletions packages/block-library/src/paragraph/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,22 @@ export const settings = {
},
edit,
save,
variations: [
{
name: 'core/post-custom-field',
title: __( 'Post Summary' ),
description: __(
'Just a block to edit a custom field named "summary".'
),
attributes: {
source: { content: { type: 'meta', name: 'summary' } },
},
isActive: ( blockAttributes ) =>
blockAttributes.source?.content?.type === 'meta' &&
blockAttributes.source?.content?.name === 'summary',
scope: [ 'block', 'inserter', 'transform' ],
},
],
};

export const init = () => initBlock( { name, metadata, settings } );
180 changes: 180 additions & 0 deletions packages/editor/src/hooks/custom-sources-v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* WordPress dependencies
*/
import { getBlockType, hasBlockSupport } from '@wordpress/blocks';
import { useRegistry } from '@wordpress/data';
import { useEntityProp } from '@wordpress/core-data';
import { useMemo } from '@wordpress/element';
import { createHigherOrderComponent } from '@wordpress/compose';
import { addFilter } from '@wordpress/hooks';

/**
* Filters registered block settings, extending attributes to include `style` attribute.
*
* @param {Object} settings Original block settings.
*
* @return {Object} Filtered block settings.
*/
function addAttribute( settings ) {
if ( ! hasBlockSupport( settings, 'customSources' ) ) {
return settings;
}

// Allow blocks to specify their own attribute definition with default values if needed.
if ( ! settings.attributes.source ) {
Object.assign( settings.attributes, {
source: {
type: 'object',
},
} );
}

return settings;
}

/** @typedef {import('@wordpress/compose').WPHigherOrderComponent} WPHigherOrderComponent */
/** @typedef {import('@wordpress/blocks').WPBlockSettings} WPBlockSettings */

/**
* Given a mapping of attribute names (meta source attributes) to their
* associated meta key, returns a higher order component that overrides its
* `attributes` and `setAttributes` props to sync any changes with the edited
* post's meta keys.
*
* @return {WPHigherOrderComponent} Higher-order component.
*/
const createEditFunctionWithCustomSources = () =>
createHigherOrderComponent(
( BlockEdit ) =>
( {
name,
attributes,
setAttributes,
context: { postType, postId },
...props
} ) => {
const registry = useRegistry();
const [ meta, setMeta ] = useEntityProp(
'postType',
postType,
'meta',
postId
);

const blockType = getBlockType( name );

const mergedAttributes = useMemo( () => {
if ( ! blockType.supports?.customSources ) {
return attributes;
}
return {
...attributes,
...Object.fromEntries(
Object.keys( blockType.supports.customSources ).map(
( attributeName ) => {
if (
attributes.source?.[ attributeName ]
?.type === 'meta'
) {
return [
attributeName,
meta?.[
attributes.source?.[
attributeName
]?.name
],
];
}
return [
attributeName,
attributes[ attributeName ],
];
}
)
),
};
}, [ blockType.supports?.customSources, attributes, meta ] );

return (
<BlockEdit
attributes={ mergedAttributes }
setAttributes={ ( nextAttributes ) => {
const nextMeta = Object.fromEntries(
Object.entries( nextAttributes ?? {} )
.filter(
// Filter to intersection of keys between the updated
// attributes and those with an associated meta key.
( [ key ] ) =>
blockType.supports?.customSources &&
key in
blockType.supports
?.customSources &&
attributes.source?.[ key ]?.type ===
'meta'
)
.map( ( [ attributeKey, value ] ) => [
// Rename the keys to the expected meta key name.
attributes.source?.[ attributeKey ]
?.name,
value,
] )
);

const updatedAttributes = Object.entries( nextMeta )
.length
? Object.fromEntries(
Object.entries( nextAttributes ).filter(
( [ key ] ) =>
! (
blockType.supports
?.customSources &&
key in
blockType.supports
?.customSources &&
attributes.source?.[ key ]
?.type === 'meta'
)
)
)
: nextAttributes;

registry.batch( () => {
if ( Object.entries( nextMeta ).length ) {
setMeta( nextMeta );
}

setAttributes( updatedAttributes );
} );
} }
{ ...props }
/>
);
},
'withCustomSources'
);

/**
* Filters a registered block's settings to enhance a block's `edit` component
* to upgrade meta-sourced attributes to use the post's meta entity property.
*
* @param {WPBlockSettings} settings Registered block settings.
*
* @return {WPBlockSettings} Filtered block settings.
*/
function shimAttributeSource( settings ) {
settings.edit = createEditFunctionWithCustomSources()( settings.edit );

return settings;
}

addFilter(
'blocks.registerBlockType',
'core/editor/custom-sources-backwards-compatibility/shim-attribute-source',
shimAttributeSource
);

addFilter(
'blocks.registerBlockType',
'core/custom-sources-v2/addAttribute',
addAttribute
);
1 change: 1 addition & 0 deletions packages/editor/src/hooks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
* Internal dependencies
*/
import './custom-sources-backwards-compatibility';
import './custom-sources-v2';
import './default-autocompleters';

0 comments on commit dd59572

Please sign in to comment.