-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create Block Template from copied block content #33379
Comments
On the other hand, I can use the copied block content and paste it into a paragraph block. The result is as expected, all the blocks from my copied block content are very well made. But when I use the way from my description above it doesn't work. |
To work directly with the block content (block html) currently works as expected for:
... or works as expected for:
But why it dosen't work with
|
Hi @CreativeDive. You are correct, the format for the template is expected to be this array structure rather than raw block grammar. Here is the code which parses the gutenberg/packages/editor/src/store/actions.js Lines 54 to 56 in 5c5ff4d
As you can see it expects the array. To work around this you can use the To build on your example: function myplugin_register_template() {
$post_type_object = get_post_type_object( 'post' );
// Parse the blocks
$blocks = parse_blocks( '<!-- wp:separator {"className":"is-style-default"} --><hr class="wp-block-separator is-style-default"/><!-- /wp:separator -->' );
// Convert to required format
$block_template = array_map(
function( $block ) {
return array(
$block['blockName'],
array(
'content' => $block['innerContent'],
),
$block['innerBlocks'],
);
},
$blocks
);
$post_type_object->template = $block_template;
}
add_action( 'init', 'myplugin_register_template' ); You might need to tweak that a little bit and test with more complex templates but it should work. Alternatively we could look to update if ( isNewPost && template ) {
try {
// Attempt to parse raw block grammar if provided
blocks = parse( template );
} catch ( error ) {
// Fallback to current behaviour
blocks = synchronizeBlocksWithTemplate( blocks, template );
}
} I wonder what @youknowriad thinks about that and the idea of aligning the API for block templates across all use cases. |
@getdave thanks for helping. I've tested your code, but unfortunately it's not working. After adding your code to convert the block content the the expected array, the block editor get a white screen. In the browser console I get this JS error:
|
@creativecoder No problem. Shame it didn't work for you as it works well for me with the content you provided. Can you advise on exactly what steps you are taking and what content you are using in the template? I would also recommend doing some digging and looking into whether the properties you're trying to access in PHP on the You could also add some breakpoints around the below in JS to see if you can determine the source of the error: gutenberg/packages/editor/src/store/actions.js Lines 54 to 56 in 5c5ff4d
|
@getdave basically it works if I use this content instead:
and changing But with more complex block content it fails. It seems there are some more investigations necessary to get it working fine, like you say to work with Is this planned to add this possibility as a WordPress build in functionality or to do the same like in I wonder, if I copy very complex block content from the "Copy all content" functionality and paste it directly inside an empty paragraph block ... everything works really fine. But I think this is managed with javascript, right? For the PHP solution we have to use an other way? And there is no build in PHP function from WordPress which is doing the same? |
Hi @CreativeDive. I've solved the problem and will post a solution shortly. 🤞 this will work for you. |
@CreativeDive As this was become complex to fix in a GH issue, I've written up a quick blog post on the solution that I found. TL;DR - we needed to recurse into the With this solution you shoudl be able to copy/paste directly from the editor and it will work perfectly. I also tested against patterns from the Pattern Directory. Hopefully this will work for you? I'd also encourage you to explore the Template Editor and Block Templates that's just landed in WP 5.8 as that might present an alternative solution for you. |
@getdave really nice and thank you for creating this block post. Nice content for your website :-D I will investigate this soon and will test different block contents with your solution. I will leave my feedback here. To work with Template Editor and Block Templates are an other possibility. But the work with the
|
@getdave I've tested your solution from your great blog post and see basically it works also with more complex block content. But it seems there is something wrong. Here is my example. I use a simple heading block code like this:
After using your awesome convert to block array function, I get this wrong result if I create a new post:
The h1 tag existing multiple times. The same thing happens with other blocks. Maybe there is something wrong with the " |
@getdave It's possible that |
@CreativeDive As it looks like the array format expects the content to be a plain string, how about trying to strip all the tags from the content? function convertBlockToTemplate( $block ) {
$template = array(
$block['blockName'],
array_merge(
$block['attrs'],
array(
'content' => wp_strip_all_tags( $block['innerHTML']),
),
),
);
$template[] = ( ! empty( $block['innerBlocks'] ) ) ? array_map( 'convertBlockToTemplate', $block['innerBlocks'] ) : array();
return $template;
} |
@getdave very good idea. It seems this is working great and the block content is recreated like expected. I have found one additional issue. Linked images, like in the "Media/text" block are missing after converting the content to the array. I'll take a look at this soon. |
@CreativeDive Any luck here or can we now close this one out? |
@getdave sorry for the missing feedback. Basically your method to convert the block content to an array is great. But at the end it's not a fully working solution. If I use I don't know how we can solve this topic. I think it is better to leave this issue open to use as a feature request. I hope this will come to WordPress as a default part, like |
@CreativeDive Thanks for the feedback. Given this then, it looks like it's not possible (or at least simple) to convert copied block content to the array format required by the Therefore as suggested the best option would be to simply update the template parsing to accept copied block content as well as the current array format. Something along these lines could be added here: if ( isNewPost && template ) {
try {
// Attempt to parse raw block grammar if provided
blocks = parse( template );
} catch ( error ) {
// Fallback to current behaviour
blocks = synchronizeBlocksWithTemplate( blocks, template );
}
} @youknowriad could we explore this approach? Any other routes you'd suggest? |
I'd love more opinions on this cc @mcsf @mtias but I'm hesitant a bit:
I guess I could be in favor of soft-replacing the |
I think that's a killer feature. I'm not necessarily proposing we drop the existing array-based format in PHP, but if we could also allow the user to provide the copied block content this would make it a tonne easier for folks to create complex templates rather than have to manually author them by hand. If not then we at least need to provide a mechanism in our PHP API that allows us to reliably and consistently convert copied block content into the array format required by the I tried this in the thread above and in this blog post, but we kept hitting issues with HTML tags within the content of certain blocks. Perhaps I missed something? |
Adding onto this - as a theme developer, the divergent APIs between creating block patterns and creating default templates is jarring. Seems like one method should be supported and recommended, rather than one using PHP arrays and the other using an HTML string. |
Hi. I struggled with this issue for a while until I started using " wp.data.select( 'core/block-editor' ).getBlocks() " as a source for block editor data rather than the html comment block syntax.
Then I convert that into a working block template: This seems to work very well : Core tables, lists, columns work. 🤞 Here's the code for my class. The methods are very similar to functions in this issue.
|
Wow, my lucky day that I came here trying to figure out some kind of solution for getting a JS array for use in template code out of the block editor, and @Jaska was here just 6 hours ago with a one-liner! I'd just like to add, at least for me in Firefox, that it was better for me to drop the JSON.stringify() and go straight for This is also useful for block variations, as they are defined via this template array notation too. Note that there are some extra properties that come along for the ride than need to be removed per block, such as |
Thanks @cr0ybot! Are you registering the block template with javascript rather than php? If so, I'd like to see how you do that 😀 |
@Jaska generally speaking, yes, mostly for assigning a block template in a custom block with InnerBlocks or, as I was looking for here, registering a block variation. Here's an example of creating a block variation that I was experimenting with, though I can't say this will get used in production. I was mostly testing different ways of inserting block templates into the editor that were template-locked. import { registerBlockVariation } from '@wordpress/blocks';
import domReady from '@wordpress/dom-ready';
import { __ } from '@wordpress/i18n';
domReady( () => {
registerBlockVariation( 'core/group', {
name: 'latest-case-studies',
title: __( 'Latest Case Studies', 'blackbird' ),
attributes: {
templateLock: 'all',
},
innerBlocks: [
{
name: 'core/group',
attributes: {
tagName: 'div',
className: 'v-latest-case-studies',
layout: {
inherit: true,
},
},
innerBlocks: [
{
name: 'core/heading',
isValid: true,
attributes: {
textAlign: 'center',
content: 'Case Studies',
level: 2,
fontSize: 'h3',
anchor: 'case-studies',
},
innerBlocks: [],
},
{
name: 'core/query',
isValid: true,
attributes: {
queryId: 0,
query: {
perPage: 4,
pages: 0,
offset: 0,
postType: 'case_study',
order: 'desc',
orderBy: 'date',
author: '',
search: '',
exclude: [],
sticky: '',
inherit: false,
},
tagName: 'div',
displayLayout: {
type: 'flex',
columns: 2,
},
layout: {
inherit: true,
},
},
innerBlocks: [
{
name: 'core/post-template',
attributes: {},
innerBlocks: [
{
name: 'core/post-featured-image',
attributes: {
isLink: true,
scale: 'cover',
className: 'ar-4-3',
},
innerBlocks: [],
},
],
},
],
},
],
},
],
} );
} ); |
This plugin might help: https://wordpress.org/plugins/block-xray-attributes/ |
@getdave , @CreativeDive i've made a little improvement on convertBlockToTemplate() function convertBlockToTemplate($blocks) {
$tpl = [];
foreach ($blocks as $block) {
if (!empty($block['blockName'])) {
if( "core/image" === $block['blockName']) {
if ( !empty($block['attrs']['id']) ) {
$block['attrs']['url'] = wp_get_attachment_url($block['attrs']['id']);
}
}
$template = array(
$block['blockName'],
array_merge(
$block['attrs'],
array(
'content' => $block['innerHTML'],
),
),
);
$template[] = (!empty($block['innerBlocks'])) ? convertBlockToTemplate($block['innerBlocks']) : array();
$tpl[] = $template;
}
}
return $tpl;
}
// ....
$template = parse_blocks(file_get_contents( '/my-dir-to/block-pattern/my-pattern-or-template.html'));
$template = convertBlockToTemplate($template);
register_post_type(
//....,
[
'template' => $template,
// 'template_lock' => true,
]
); This can load the "html template block" (from a file) directly to a post_type default template. |
@mkdgs thanks for the input. But things like ...
... is one case of a lot. Hopefully there comes an out of the box solution. @getdave All what we need is a core implementation like:
It would be very helpful for register block templates on post/page creation. |
@CreativeDive for sure a native solution would be great. I'm not using acf, can you confirm this solution not works or not in this case ? |
@mkdgs it's not only an ACF block issue, it's an issue with all kind of blocks. I have wrote in a comment above, this solution dosen't work because, inline tags will removed. At the end there is a ready to use method in WordPress core using the |
@CreativeDive i've taking some time to see what is under the hood.
There is a lack in parse_blocks, but we can try to fill all missing attribute without knowing them. function convertBlockToTemplate($blocks)
{
$tpl = [];
// get specs of all registered blocks
$registry = WP_Block_Type_Registry::get_instance();
$blocks_reg = $registry->get_all_registered();
foreach ($blocks as $block) {
if (!empty($block['blockName'])) {
// foreach blocks, set the attribute accordling to the source
if (array_key_exists($block['blockName'], $blocks_reg)) {
$block_reg = $blocks_reg[$block['blockName']];
$dom = null;
foreach ($block_reg->attributes as $attr => $vattr) {
// no html content
if (empty($block['innerHTML'])) continue;
// no source attribute
if (empty($vattr['source'])) continue;
if ( in_array($vattr['source'], ['html', 'attribute']) ) {
if (empty($vattr['selector'])) {
$block['attrs'][$attr] = $block['innerHTML'];
} else {
// create a dom fragment, only one time per block
if (!is_object($dom)) {
libxml_use_internal_errors(true);
$dom = new \DomDocument('1.0', 'UTF-8');
$dom->recover = true;
$var = '<div>' . $block['innerHTML'] . '</div>';
try {
$dom->loadHTML('<?xml encoding="UTF-8">' . $var);
$dom->encoding = 'UTF-8';
} catch (\Exception $e) {
}
}
// create an expression to explore the dom
$xpath = new DOMXpath($dom);
// maybe interesting to convert css selector to xpath
// or find a better way to use css selector with php
// https://github.com/ThomasWeinert/PhpCss
$xpath_query = "//" . $vattr['selector'];
if (!empty($vattr['multiline'])) {
$xpath_query = "//" . $vattr['selector'] . '/' . $vattr['multiline'];
}
// get the content with xpath corresponding to the selector and set the attribute
$el = $xpath->query($xpath_query);
if (is_object($el) && !empty($el[0])) {
if ( !empty($vattr['attribute']) ) {
$block['attrs'][$attr] = $el[0]->getAttribute($vattr['attribute']);
}
else {
$block['attrs'][$attr] = simplexml_import_dom($el[0])->asXML();
}
}
}
}
}
}
$innerBlocks = (!empty($block['innerBlocks'])) ? convertBlockToTemplate($block['innerBlocks']) : array();
$tpl[] = array(
$block['blockName'],
$block['attrs'],
$innerBlocks
);
}
}
return $tpl;
} I'ts a little bit crazy, but it's works with your initial exemple. |
@jorgefilipecosta I light of the fact it's going to be possible to register block patterns for any post type in WordPress 6.1 is there any need to pursue the option I suggested here to allow for block grammar to be used as the I think the patterns solution you've implemented is very similar to what @youknowriad theorised about in his response to my comment when he said:
|
I guess the fact we can have patterns for any post type does not remove the usefulness of having a template CPT as an HTML string in the same format as patterns. So I think the option you suggested is still useful. |
I just thought I would share the solution I have found, and will use from now on. And this is to use the /** Add Default content for post types. */
function cpt_content_template( $content, $post ) {
if ( $post->post_type == 'organisation' ) {
$content = file_get_contents(get_theme_file_path('templates/single-organisation.html'));
}
return $content;
}
add_filter('default_content', 'cpt_content_template', 10, 2); |
@ngearing Thanks, a really good solution. It works in the same use case as using Since a practical solution With that I close the issue. Should there still be a need to expand the functionality for |
Hello,
currently there is the
$post_type_object->template
way to provide a default block template in specific cases. For example for a specific post type.We can use this way to do that:
This is working with a template array like:
But in the real world, we creating block templates in the editor way. And if we have finalized a really nice template with the combination of different blocks, we can use the "Copy all Content" functionality.
The result of this functionality is the block content like this:
So I want to use this block content code from my created layout to create a default template in a specific case when adding a new page.
So I thought I can use the same method as the block template array like the following (this is just an example).
Also this is not working:
Is there a possibility at this time to create a default block template on creating a post by using the block content instead of a block template array?
Or is there a way to automatically convert block content to a block template array?
The text was updated successfully, but these errors were encountered: