Gutenberg Wordpress: Extending Core Block - wordpress

I am trying to add a padding Inspector Control to all core block of the new Gutenberg Wordpress Editor. I have created the Control on the Editor and now I am trying to apply this style to the block element its self.
But I keep getting This block contains unexpected or invalid content. Error on the blocks. Can anyone help me out here on what exactly I am not doing?
var paddingEditor = wp.compose.createHigherOrderComponent(function(
BlockEdit
) {
return function(props) {
var padding = props.attributes.padding || 0;
handleChange = name => newValue => {
if (props) {
props.setAttributes({ [name]: newValue });
}
};
return el(
Fragment,
{},
el(BlockEdit, props),
el(
editor.InspectorControls,
{},
el(
components.PanelBody,
{
title: "Padding",
className: "",
initialOpen: false
},
el("p", {}, "Padding"),
el(components.TextControl, {
value: padding,
onChange: this.handleChange("padding")
})
)
)
);
};
},
"paddingStyle");
wp.hooks.addFilter(
"editor.BlockEdit",
"my-plugin/padding-style",
paddingStyle
);
function AddPaddingAttribute(element, blockType, attributes) {
Object.assign(blockType.attributes, {
padding: {
type: "string"
}
});
return element;
}
wp.hooks.addFilter(
"blocks.getSaveElement",
"my-plugin/add-padding-attr",
AddPaddingAttribute
);
function AddPaddingStyle(props, blockType, attributes) {
if (attributes.padding) {
props.style = lodash.assign(props.style, { padding: attributes.padding });
}
return props;
}
wp.hooks.addFilter(
"blocks.getSaveContent.extraProps",
"my-plugin/add-background-color-style",
AddPaddingStyle
);
PHP
function register_block_extensions(){
wp_register_script(
'extend-blocks', // Handle.
get_template_directory_uri() . '/js/extend-blocks.js',
array( 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-editor' )
);
wp_enqueue_script( 'extend-blocks' );
}
add_action('enqueue_block_editor_assets', 'register_block_extensions');

Your editor.BlockEdit looks right, but it's hard for me to parse the older syntax. Assuming it is correct, you'll need to replace blocks.getSaveElement with this:
function AddPaddingAttribute(props) {
if (props.attributes) { // Some modules don't have attributes
props.attributes = Object.assign(
props.attributes,
{
padding: {}
}
);
}
return props;
}
wp.hooks.addFilter(
'blocks.registerBlockType',
'my-plugin/add-padding-attr',
AddPaddingAttribute
);
And modify blocks.getSaveContent.extraProps to this:
function AddPaddingStyle(props, blockType, attributes) {
return Object.assign(
props,
{
style: {
padding: attributes.padding
}
}
);
}
wp.hooks.addFilter(
"blocks.getSaveContent.extraProps",
"my-plugin/add-background-color-style",
AddPaddingStyle
);

Related

How to create a custom DropdownMenu Button in Gutenberg Paragraph

I have some issues developing a Dropdownmenu for Core/Paragraph. I want to add a dropdownmenu to insert text (like Personalized Tags on Email Services).
I'm tried making this code in Vanilla to implement DropdownMenu for Core/Paragraph without success.
My code is:
( function( blocks, element, components, editor ) {
const el = element.createElement;
const { __ } = wp.i18n;
const withSelect = wp.data.withSelect;
const ifCondition = wp.compose.ifCondition;
const compose = wp.compose.compose;
console.log(components);
const EmailTagsMenu = el(
components.Toolbar,
null,
el(
components.DropDownMenu,
{
icon: 'admin-customizer',
isActive: false,
label: __('Insert Tag', 'amauta'),
controls: function(){
return [
{
title: __('Firstname', 'amauta'),
icon: 'admin-customizer',
onClick: function(){
}
},
{
title: __('Lastname', 'amauta'),
icon: 'admin-customizer',
onClick: function(){
}
}
];
}
}
)
);
var EmailTagsButton = compose(
withSelect( function( select ) {
return {
selectedBlock: select( 'core/block-editor' ).getSelectedBlock()
}
} ),
ifCondition( function( props ) {
return (
props.selectedBlock &&
props.selectedBlock.name === 'core/paragraph'
);
} )
)( EmailTagsMenu );
wp.richText.registerFormatType(
'amauta/email_tags', {
title: __('Email Tags', 'amauta' ),
tagName: null,
className: null,
edit: EmailTagsButton,
}
);
}(
window.wp.blocks,
window.wp.element,
window.wp.components,
window.wp.blockEditor
));
Please, can you help me? Thanks!

Block preview not visible when not in edit state

I have started using Gutenberg custom blocks, and I have built custom blocks successfully!
But I have an issue, I need to see the preview of the block when I am not editing it on the Admin side, I don't think Gutenberg provides it, because I searched the documentation, searched the internet but nothing found regarding preview of the block when not in edit state.
I have used Block Lab Plugin and it do have a preview, if Gutenberg don't provide it, how can I have a preview? Any Trick or Hack?
This is my block.js:
(function(blocks, components, element) {
var el = element.createElement;
var registerBlockType = blocks.registerBlockType;
var TextControl = components.TextControl;
registerBlockType("blocks/test-block", {
title: "Test Block",
description: "A custom block.",
icon: "businessman",
category: "common",
attributes: {
headline: {
type: "string"
}
},
edit: function(props) {
var headline = props.attributes.headline;
var onHeadlineChange = function(val) {
props.setAttributes({
headline: val
});
};
return el(TextControl, {
value: headline,
label: "Headline",
onChange: onHeadlineChange
});
},
save: function(props) {
return el(
"h3",
{
className: "headline"
},
props.attributes.headline
);
}
});
})(window.wp.blocks, window.wp.components, window.wp.element);
To display your custom block with a preview, just add example: () => {}, to section, in the same way as save and edit.
https://prnt.sc/rov8qo
//Custom Gutenberg block
(function(components, element) {
//Default
const { registerBlockType } = wp.blocks;
//Block registration
registerBlockType('', {
title: '',
description: '',
icon: '',
category: '',
keywords: [''],
attributes: {},
//Example function
example: () => {}, //Add this to get block preview works
//Edit function
edit: props => {},
//Save function
save: props => {}
});
})(window.wp.components, window.wp.element);
UPDATE 1
In order to make a preview of the block (how it will appear in the front), you need to add styles to this block, the same as on the front, and track props.isSelected this block in editor.
Depending on whether the block is selected or not, show different content.
//Custom Gutenberg block
(function(blocks, element, blockEditor) {
//Default variable
const { registerBlockType } = blocks;
const { createElement: el } = element;
const { RichText } = blockEditor;
//Block registration
registerBlockType("blocks/test-block", {
title: 'Test Block',
description: 'A custom block.',
icon: 'businessman',
category: 'common',
attributes: {
title: {
type: 'string',
source: 'html',
selector: '.headline'
}
},
//Example function
example: () => {},
//Edit function
edit: props => {
const attributes = props.attributes;
return (
el( 'div', { className: props.className },
//View input field
props.isSelected && el(RichText, {
tagName: 'h3',
placeholder: 'Headline...',
keepPlaceholderOnFocus: true,
value: attributes.title,
allowedFormats: [],
onChange: title => props.setAttributes({ title })
}),
//View frontend preview
!props.isSelected && el( 'div', { className: 'block__headline' },
el( 'div', { className: 'block__headline-title' }, attributes.title ? attributes.title : 'Entry headline...')
)
)
);
},
//Save function
save: props => {
return el( RichText.Content, {
tagName: 'h3',
className: 'headline',
value: props.attributes.title
});
}
});
})(window.wp.blocks, window.wp.element, window.wp.blockEditor);
Css the same as on your frontend.
.block__headline {
padding: 20px 15px 30px;
background: #fafafa;
text-align: center;
}
.block__headline-title {
font-family: 'Montserrat';
font-size: 30px;
font-weight: bold;
position: relative;
}
.block__headline-title:after {
content: '';
display: block;
width: 40px;
height: 2px;
background: #333;
margin: 0 auto;
}

Prevent wp.hooks.addFilter() from Running on Certain Custom Post Types in Gutenberg

I have been tasked with preventing addFilter() from running on certain custom post types using the new Gutenberg API and not any WP PHP. It's currently fed into the editor.PostFeaturedImage hook, meaning it fires every time the Gutenberg editor loads the Featured Image box.
The Filter calls a function that adds a dropdown menu underneath the featured image box to allow users to pick a contributor (which are themselves custom post types) to credit the image to.
The filter should not run on the contributor custom post type, but should run on other custom post types. There should still be a featured image box for the contributor custom post type, but no dropdown.
Letting the hook fire and then canceling within the function works but the function itself is resource heavy and the directive is to prevent the function from firing at all. The idea being that the built-in hook/function would fire instead.
Borrowing from this ticket, I attempted to place the main function setFeaturedImageArtist within an anonymous function that also printed the post type to console in addFilter(). I was able to get the post type to print, but calling setFeaturedImageArtist function didn't work as expected.
wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', function() {
console.log(wp.data.select("core/editor").getCurrentPostType())
return setFeaturedImageArtist()
});
Placing setFeaturedImageArtistin a wrapper function like so didn't work either. I'm assuming it's because it's the same thing.
function checkPostType() {
console.log(wp.data.select("core/editor").getCurrentPostType())
return setFeaturedImageArtist()
}
wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', checkPostType);
Here is the component the filter is triggering:
function setFeaturedImageArtist( OriginalComponent ) {
return ( props ) => {
const artistSelect = compose.compose(
withDispatch( function( dispatch, props ) {
return {
setMetaValue: function( metaValue ) {
dispatch( 'core/editor' ).editPost(
{ meta: { 'featured-image-artist': metaValue } }
);
}
}
} ),
withSelect( function( select, props ) {
let query = {
per_page : 20,
metaKey : '_author_type',
metaValue : 'artist'
};
let postType = select("core/editor").getCurrentPostType();
if ( postType === 'contributor' ) {
return null
}
// Please see below
// if ( postType === 'contributor' ) {
// return {
// postType
// }
// }
return {
posts: select( 'core' ).getEntityRecords( 'postType', 'contributor', query ),
metaValue: select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ 'featured-image-artist' ],
}
} ) )( function( props ) {
var options = [];
// This works in removing the dropdown for authors/artists
// if (props.postType === 'contributor'){
// return null
// }
if( props.posts ) {
options.push( { value: 0, label: __( 'Select an artist', 'blocks' ) } );
props.posts.forEach((post) => {
options.push({value:post.id, label:post.title.rendered});
});
} else {
options.push( { value: 0, label: __( 'Loading artists...', 'blocks' ) } )
}
return el( SelectControl,
{
label: __( 'Art Credit:', 'blocks' ),
options : options,
onChange: ( content ) => {
props.setMetaValue( content );
},
value: props.metaValue,
}
);
}
);
return (
el( 'div', { }, [
el( OriginalComponent, props ),
el( artistSelect )
] )
);
}
}
wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', setFeaturedImageArtist );
Here is the redacted component the filter is triggering:
function setFeaturedImageArtist( OriginalComponent ) {
return ( props ) => {
const artistSelect = compose.compose(
...
)( function( props ) {
... // Cancelling out here works, but resources are loaded by this point.
});
return (
el( 'div', { }, [
el( OriginalComponent, props ),
el( artistSelect )
])
);
}
}
wp.hooks.addFilter( 'editor.PostFeaturedImage', 'blocks/featured-image-artist', setFeaturedImageArtist );
This is the React error being received:
Element type is invalid: expected a string (for built-in components) or
a class/function (for composite components) but got: undefined.
I'm not sure what the best approach to this would be, and the documentation is scant/barely applicable. Is creating a custom hook that mimics editor.PostFeaturedImage but only fires on certain custom post types a possibility? Or is there some way to call a function like setFeaturedImageArtist within a wrapper that checks the post type?
Wordpress Hooks on Github
Hooks in Wordpress Handbook
I tried to recreate the script and fixed some issues:
const { createElement: el } = wp.element;
const { compose } = wp.compose;
const { withSelect, withDispatch } = wp.data;
const { SelectControl } = wp.components;
const { __ } = wp.i18n;
const ArtistSelect = compose(
withDispatch(function(dispatch, props) {
return {
setMetaValue: function(metaValue) {
dispatch("core/editor").editPost({
meta: { "featured-image-artist": metaValue }
});
}
};
}),
withSelect(function(select, props) {
let query = {
per_page: 20,
metaKey: "_author_type",
metaValue: "artist"
};
return {
postType: select("core/editor").getCurrentPostType(),
posts: select("core").getEntityRecords("postType", "contributor", query),
metaValue: select("core/editor").getEditedPostAttribute("meta")[
"featured-image-artist"
]
};
})
)(function(props) {
var options = [];
// This works in removing the dropdown for authors/artists
if (props.postType === "contributor") {
return null;
}
if (props.posts) {
options.push({ value: 0, label: __("Select an artist", "blocks") });
props.posts.forEach(post => {
options.push({ value: post.id, label: post.title.rendered });
});
} else {
options.push({ value: 0, label: __("Loading artists...", "blocks") });
}
return el(SelectControl, {
label: __("Art Credit:", "blocks"),
options: options,
onChange: content => {
props.setMetaValue(content);
},
value: props.metaValue
});
});
function setFeaturedImageArtist(OriginalComponent) {
return props => {
return el("div", {}, [el(OriginalComponent, props), el(ArtistSelect)]);
};
}
wp.hooks.addFilter(
"editor.PostFeaturedImage",
"blocks/featured-image-artist",
setFeaturedImageArtist
);
ArtistSelect is a component so we take it outside of setFeaturedImageArtist function. withSelect had a check for the postType that made it return null. Instead of that we pass that variable and then return null in the components render. An alternative would be to check inside setFeaturedImageArtist. This is a fixed version using JSX. Hope its clear:
const { compose } = wp.compose;
const { withSelect, withDispatch, select } = wp.data;
const { SelectControl } = wp.components;
const { __ } = wp.i18n;
const { addFilter } = wp.hooks;
const ArtistSelect = compose(
withDispatch(dispatch => {
return {
setMetaValue: metaValue => {
dispatch("core/editor").editPost({
meta: { "featured-image-artist": metaValue }
});
}
};
}),
withSelect(select => {
const query = {
per_page: 20,
metaKey: "_author_type",
metaValue: "artist"
};
return {
posts: select("core").getEntityRecords("postType", "contributor", query),
metaValue: select("core/editor").getEditedPostAttribute("meta")[
"featured-image-artist"
]
};
})
)(props => {
const { posts, setMetaValue, metaValue } = props;
const options = [];
if (posts) {
options.push({ value: 0, label: __("Select an artist", "blocks") });
posts.forEach(post => {
options.push({ value: post.id, label: post.title.rendered });
});
} else {
options.push({ value: 0, label: __("Loading artists...", "blocks") });
}
return (
<SelectControl
label={__("Art Credit:", "blocks")}
options={options}
onChange={content => setMetaValue(content)}
value={metaValue}
/>
);
});
const setFeaturedImageArtist = OriginalComponent => {
return props => {
const post_type = select("core/editor").getCurrentPostType();
if (post_type === "contributor") {
return <OriginalComponent {...props} />;
}
return (
<div>
<OriginalComponent {...props} />
<ArtistSelect />
</div>
);
};
};
wp.hooks.addFilter(
"editor.PostFeaturedImage",
"blocks/featured-image-artist",
setFeaturedImageArtist
);

How I can add a custom control to a page?

I need to add a checkbox to the page editing form in Gutenberg without third-party plugins like ACF. I did some tutorials, including the one on the official Wordpress page, but it does not behave as I need to.
I already have the addition to the sidebar (I replaced the checkbox with a toogle but it would be the same), the element does not work itself, if I click does not change its status, nor can I store the value when saving the page.
Formerly I would have solved it with metabox, but it is no longer compatible with this version of Wordpress.
What should I modify in the code for the component to change its status and then store it in database when saving a page?
I tried with this and works, but isn't what I need: https://developer.wordpress.org/block-editor/tutorials/plugin-sidebar-0/plugin-sidebar-1-up-and-running/
I tried whit this: https://www.codeinwp.com/blog/make-plugin-compatible-with-gutenberg-sidebar-api/
export class MyPluginSidebar{
constructor(wp){
const { __ } = wp.i18n;
const {
PluginSidebar,
PluginSidebarMoreMenuItem
} = wp.editPost;
const {
PanelBody,
TextControl,
ToggleControl
} = wp.components;
const {
Component,
Fragment
} = wp.element;
const { withSelect } = wp.data;
const { registerPlugin } = wp.plugins;
const { withState } = wp.compose;
class Hello_Gutenberg extends Component {
constructor() {
super( ...arguments );
this.state = {
key: '_hello_gutenberg_field',
value: '',
}
wp.apiFetch( { path: `/wp/v2/posts/${this.props.postId}`, method: 'GET' } ).then(
( data ) => {
console.log('apiFetch data', data.meta);
this.setState( {
value: data.meta._hello_gutenberg_field
} );
return data;
},
( err ) => {
console.log('wp api fetch error', err);
return err;
}
);
}
static getDerivedStateFromProps( nextProps, state ) {
if ( ( nextProps.isPublishing || nextProps.isSaving ) && !nextProps.isAutoSaving ) {
wp.apiRequest( { path: `/hello-gutenberg/v1/update-meta?id=${nextProps.postId}`, method: 'POST', data: state } ).then(
( data ) => {
return data;
},
( err ) => {
return err;
}
);
}
}
render() {
var hasFixedBackground = true;
return (
<Fragment>
<PluginSidebarMoreMenuItem
target="hello-gutenberg-sidebar"
>
{ __( 'Sidebar title' ) }
</PluginSidebarMoreMenuItem>
<PluginSidebar
name="hello-gutenberg-sidebar"
title={ __( 'Sidebar title' ) }
>
<PanelBody>
<ToggleControl
label="Control label"
//help={ hasFixedBackground ? 'Has fixed background.' : 'No fixed background.' }
checked={ hasFixedBackground }
//onChange={ () => this.setState( ( state ) => ( { hasFixedBackground: ! state.hasFixedBackground } ) ) }
/>
</PanelBody>
</PluginSidebar>
</Fragment>
)
}
}
// Pass post ID to plugin class
// Prevent to save on each state change, just save on post save
const HOC = withSelect( ( select, { forceIsSaving } ) => {
const {
getCurrentPostId,
isSavingPost,
isPublishingPost,
isAutosavingPost,
} = select( 'core/editor' );
return {
postId: getCurrentPostId(),
isSaving: forceIsSaving || isSavingPost(),
isAutoSaving: isAutosavingPost(),
isPublishing: isPublishingPost(),
};
} )( Hello_Gutenberg );
registerPlugin( 'hello-gutenberg', {
icon: 'admin-site',
render: HOC,
} );
}
}
This code register the sidebar, add the control but didn't change his state neither save in database.
Any help are welcome.
Regards!
If you want to save data that you have added later on you gutenberg block you need to use addFilter. I can show you an example i am using:
wp.hooks.addFilter('blocks.registerBlockType', 'custom/filter', function(x,y) {
if(x.hasOwnProperty('attributes')){
x.attributes.background_image = {
type: 'string',
default: ''
};
}
return x;
});

WordPress Gutenberg RichText not displaying HTML

As shown in the code snippet below, I am trying to append HTML or JSX to RichText content in vain.
const { registerBlockType } = wp.blocks;
const { RichText } = wp.editor;
registerBlockType( /* ... */, {
// ...
attributes: {
content: {
type: 'array',
source: 'children',
selector: 'p',
default: [],
},
},
edit( { className, attributes, setAttributes } ) {
const updateContentWithString = ( content ) => {
setAttributes( { content: content.concat( 'test' ) } );
}
const updateContentWithHTML = ( content ) => {
setAttributes( { content: content.concat( <Button isDefault >Test Button</Button> ) } );
}
return (
<RichText
tagName="p"
className={ className }
value={ attributes.content }
onChange={ updateContentWithString } // updateContentWithString works fine BUT updateContentWithHTML doesn't work at all
/>
);
},
save( { attributes } ) {
return <RichText.Content tagName="p" value={ attributes.content } />;
}
} );
I am wondering why updateContentWithString() works fine BUT updateContentWithHTML() doesn't work at all.
Kindly guide me on this.
Thanks
Try
setAttributes( { content: content.concat( '<Button isDefault >Test Button</Button> )' } );
In JSX <Button/> will be preprocessed/conversed to React.createElement to create component.
Note: In JSX <Button/> differs from <button/> (component vs. html)
UPDATE:
Above was related to react/JSX only.
I don't know or used Gutenberg (yet) but according to this html elements should be passed as objects, not html source. Probably you should use wp.element.createElement or RichText or even define/use own custom block types.
Search for method of editing/updating/creating html (elements) in Gutenberg.

Resources