Skip to content
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

Allow creating custom block templates in classic themes #30438

Merged
merged 12 commits into from
Apr 8, 2021
10 changes: 10 additions & 0 deletions docs/how-to-guides/themes/block-based-themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ As we're still early in the process, the number of blocks specifically dedicated

One of the most important aspects of themes (if not the most important) is the styling. While initially you'll be able to provide styles and enqueue them using the same hooks themes have always used, the [Global Styles](/docs/how-to-guides/themes/theme-json.md) effort will provide a scaffolding for adding many theme styles in the future.

## Classic Themes

Users of classic themes can also build custom block templates and use theme in their Pages and Custom Post Types that supports Page Templates.

Theme authors can opt-out of this feature by removing the `block-templates` theme support in their `functions.php` file.

```php
remove_theme_support( 'block-templates' );
```

## Resources

- [Full Site Editing](https://github.com/WordPress/gutenberg/labels/%5BFeature%5D%20Full%20Site%20Editing) label.
Expand Down
4 changes: 2 additions & 2 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ function gutenberg_extend_block_editor_styles( $settings ) {
}

// Remove the default font editor styles for FSE themes.
if ( gutenberg_is_fse_theme() ) {
if ( gutenberg_supports_block_templates() ) {
foreach ( $settings['styles'] as $j => $style ) {
if ( 0 === strpos( $style['css'], 'body { font-family:' ) ) {
unset( $settings['styles'][ $j ] );
Expand All @@ -666,7 +666,7 @@ function gutenberg_extend_block_editor_styles( $settings ) {
* @return array Filtered editor settings.
*/
function gutenberg_extend_block_editor_settings_with_fse_theme_flag( $settings ) {
$settings['isFSETheme'] = gutenberg_is_fse_theme();
$settings['supportsTemplateMode'] = gutenberg_supports_block_templates();

// Enable the new layout options for themes with a theme.json file.
$settings['supportsLayout'] = WP_Theme_JSON_Resolver::theme_has_support();
Expand Down
2 changes: 1 addition & 1 deletion lib/compat.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* @return bool
*/
function gutenberg_should_load_separate_block_assets() {
$load_separate_styles = gutenberg_is_fse_theme();
$load_separate_styles = gutenberg_supports_block_templates();
/**
* Determine if separate styles will be loaded for blocks on-render or not.
*
Expand Down
4 changes: 2 additions & 2 deletions lib/editor-settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function gutenberg_get_common_block_editor_settings() {
};

$settings = array(
'__unstableEnableFullSiteEditingBlocks' => gutenberg_is_fse_theme(),
'__unstableEnableFullSiteEditingBlocks' => gutenberg_supports_block_templates(),
'disableCustomColors' => get_theme_support( 'disable-custom-colors' ),
'disableCustomFontSizes' => get_theme_support( 'disable-custom-font-sizes' ),
'disableCustomGradients' => get_theme_support( 'disable-custom-gradients' ),
Expand Down Expand Up @@ -81,7 +81,7 @@ function gutenberg_extend_post_editor_settings( $settings ) {
$image_sizes = wp_list_pluck( $settings['imageSizes'], 'slug' );

$settings['imageDefaultSize'] = in_array( $image_default_size, $image_sizes, true ) ? $image_default_size : 'large';
$settings['__unstableEnableFullSiteEditingBlocks'] = gutenberg_is_fse_theme();
$settings['__unstableEnableFullSiteEditingBlocks'] = gutenberg_supports_block_templates();

return $settings;
}
Expand Down
9 changes: 9 additions & 0 deletions lib/full-site-editing/full-site-editing.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ function gutenberg_is_fse_theme() {
return is_readable( get_stylesheet_directory() . '/block-templates/index.html' );
}

/**
* Returns whether the current theme is FSE-enabled or not.
*
* @return boolean Whether the current theme is FSE-enabled or not.
*/
function gutenberg_supports_block_templates() {
return gutenberg_is_fse_theme() || current_theme_supports( 'block-templates' );
}

/**
* Show a notice when a Full Site Editing theme is used.
*/
Expand Down
18 changes: 6 additions & 12 deletions lib/full-site-editing/page-templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,16 @@
* @return array (Maybe) modified page templates array.
*/
function gutenberg_load_block_page_templates( $templates, $theme, $post, $post_type ) {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return $templates;
}

$data = WP_Theme_JSON_Resolver::get_theme_data()->get_custom_templates();
$custom_templates = array();
if ( isset( $data ) ) {
foreach ( $data as $key => $template ) {
if ( ( ! isset( $template['postTypes'] ) && 'page' === $post_type ) ||
( isset( $template['postTypes'] ) && in_array( $post_type, $template['postTypes'], true ) )
) {
$custom_templates[ $key ] = $template['title'];
}
}
$block_templates = gutenberg_get_block_templates( array(), 'wp_template' );
foreach ( $block_templates as $template ) {
// TODO: exclude templates that are not concerned by the current post type.
$templates[ $template->slug ] = $template->title;
}

return $custom_templates;
return $templates;
}
add_filter( 'theme_templates', 'gutenberg_load_block_page_templates', 10, 4 );
13 changes: 11 additions & 2 deletions lib/full-site-editing/template-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Adds necessary filters to use 'wp_template' posts instead of theme template files.
*/
function gutenberg_add_template_loader_filters() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}

Expand Down Expand Up @@ -71,9 +71,18 @@ function gutenberg_override_query_template( $template, $type, array $templates =
foreach ( $templates as $template_item ) {
$template_item_slug = gutenberg_strip_php_suffix( $template_item );

// Is this a custom template?
// This check should be removed when merged in core.
// Instead, wp_templates should be considered valid in locate_template.
$is_custom_template = 0 === strpos( $current_block_template_slug, 'wp-custom-template-' );

// Don't override the template if we find a template matching the slug we look for
// and which does not match a block template slug.
if ( $current_template_slug !== $current_block_template_slug && $current_template_slug === $template_item_slug ) {
if (
! $is_custom_template &&
$current_template_slug !== $current_block_template_slug &&
$current_template_slug === $template_item_slug
) {
return $template;
}
}
Expand Down
6 changes: 3 additions & 3 deletions lib/full-site-editing/template-parts.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* Registers block editor 'wp_template_part' post type.
*/
function gutenberg_register_template_part_post_type() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}

Expand Down Expand Up @@ -64,7 +64,7 @@ function gutenberg_register_template_part_post_type() {
* Registers the 'wp_template_part_area' taxonomy.
*/
function gutenberg_register_wp_template_part_area_taxonomy() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}

Expand Down Expand Up @@ -107,7 +107,7 @@ function gutenberg_register_wp_template_part_area_taxonomy() {
* Fixes the label of the 'wp_template_part' admin menu entry.
*/
function gutenberg_fix_template_part_admin_menu_entry() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}

Expand Down
6 changes: 3 additions & 3 deletions lib/full-site-editing/templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function gutenberg_get_template_paths() {
* Registers block editor 'wp_template' post type.
*/
function gutenberg_register_template_post_type() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}

Expand Down Expand Up @@ -84,7 +84,7 @@ function gutenberg_register_template_post_type() {
* Registers block editor 'wp_theme' taxonomy.
*/
function gutenberg_register_wp_theme_taxonomy() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}

Expand Down Expand Up @@ -139,7 +139,7 @@ function gutenberg_grant_template_caps( array $allcaps ) {
* Fixes the label of the 'wp_template' admin menu entry.
*/
function gutenberg_fix_template_admin_menu_entry() {
if ( ! gutenberg_is_fse_theme() ) {
if ( ! gutenberg_supports_block_templates() ) {
return;
}
global $submenu;
Expand Down
4 changes: 2 additions & 2 deletions lib/global-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function gutenberg_experimental_global_styles_settings( $settings ) {
$origin = 'theme';
if (
WP_Theme_JSON_Resolver::theme_has_support() &&
gutenberg_is_fse_theme()
gutenberg_supports_block_templates()
) {
// Only lookup for the user data if we need it.
$origin = 'user';
Expand All @@ -224,7 +224,7 @@ function gutenberg_experimental_global_styles_settings( $settings ) {
function_exists( 'gutenberg_is_edit_site_page' ) &&
gutenberg_is_edit_site_page( $screen->id ) &&
WP_Theme_JSON_Resolver::theme_has_support() &&
gutenberg_is_fse_theme()
gutenberg_supports_block_templates()
) {
$user_cpt_id = WP_Theme_JSON_Resolver::get_user_custom_post_type_id();
$base_styles = WP_Theme_JSON_Resolver::get_merged_data( $theme_support_data, 'theme' )->get_raw_data();
Expand Down
1 change: 1 addition & 0 deletions lib/init.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,4 @@ function register_site_icon_url( $response ) {
add_filter( 'rest_index', 'register_site_icon_url' );

add_theme_support( 'widgets-block-editor' );
add_theme_support( 'block-templates' );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@youknowriad I find this part a bit unexpected. Why are we unconditionally declaring theme support in the plugin/core? 🤔

If we want to have this active by default, we could remove the theme support part altogether, and modify the activation checks to use the filter for opt-out, instead of:

Theme authors can opt-out of this feature by removing the block-templates theme support in their functions.php file.

Copy link
Contributor Author

@youknowriad youknowriad May 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something I explain here #30465 (comment)

Instead of having a "disable-something" theme support, Core prefer to have theme supports enabled by default and plugins/themes can do remove_theme_support as it reads better. Adding a theme support calls "disable-something" doesn't read very well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying! While reading the activation conditions I assumed that this will be only enabled for themes that explicitly declare support, so I was a bit surprised afterward when I found out in testing that it actually works for all themes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I the moment, this is opt-out because we want people to experience this, depending how the betas of WP go though, we might reconsider.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To close the loop, #32858 makes it opt-in on classic themes and opt-out for themes using theme.json.

3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 20 additions & 6 deletions packages/core-data/src/resolvers.js
Original file line number Diff line number Diff line change
Expand Up @@ -385,13 +385,18 @@ export function* __experimentalGetTemplateForLink( link ) {
// Ideally this should be using an apiFetch call
// We could potentially do so by adding a "filter" to the `wp_template` end point.
// Also it seems the returned object is not a regular REST API post type.
const template = yield regularFetch(
addQueryArgs( link, {
'_wp-find-template': true,
} )
);
let template;
try {
template = yield regularFetch(
addQueryArgs( link, {
'_wp-find-template': true,
} )
);
} catch ( e ) {
// For non-FSE themes, it is possible that this request returns an error.
}

if ( template === null ) {
if ( ! template ) {
return;
}

Expand All @@ -410,3 +415,12 @@ export function* __experimentalGetTemplateForLink( link ) {
} );
}
}

__experimentalGetTemplateForLink.shouldInvalidate = ( action ) => {
return (
( action.type === 'RECEIVE_ITEMS' || action.type === 'REMOVE_ITEMS' ) &&
action.invalidateCache &&
action.kind === 'postType' &&
action.name === 'wp_template'
);
};
3 changes: 3 additions & 0 deletions packages/e2e-test-utils/src/preview.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { last } from 'lodash';
export async function openPreviewPage( editorPage = page ) {
let openTabs = await browser.pages();
const expectedTabsCount = openTabs.length + 1;
await page.waitForSelector(
'.block-editor-post-preview__button-toggle:not([disabled])'
);
await editorPage.click( '.block-editor-post-preview__button-toggle' );
await editorPage.waitForSelector(
'.edit-post-header-preview__button-external'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Post Editor Template mode Allow creating custom block templates in classic themes 1`] = `
"<h1 class=\\"wp-block-site-title\\"><a href=\\"http://localhost:8889\\" rel=\\"home\\">gutenberg</a></h1>

<p class=\\"wp-block-site-tagline\\">Just another WordPress site</p>


<hr class=\\"wp-block-separator\\">


<h2 class=\\"wp-block-post-title\\">Another FSE Post</h2>

<div class=\\"entry-content wp-block-post-content\\">
<p>Hello World</p>
</div>


<p>Just a random paragraph added to the template</p>
"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {

describe( 'Post Editor Template mode', () => {
beforeAll( async () => {
await activateTheme( 'tt1-blocks' );
await trashAllPosts( 'wp_template' );
await trashAllPosts( 'wp_template_part' );
} );
Expand All @@ -22,11 +21,9 @@ describe( 'Post Editor Template mode', () => {
await activateTheme( 'twentytwentyone' );
} );

beforeEach( async () => {
await createNewPost();
} );

it( 'Allow to switch to template mode, edit the template and check the result', async () => {
await activateTheme( 'tt1-blocks' );
await createNewPost();
// Create a random post.
await page.type( '.editor-post-title__input', 'Just an FSE Post' );
await page.keyboard.press( 'Enter' );
Expand All @@ -45,9 +42,9 @@ describe( 'Post Editor Template mode', () => {

// Switch to template mode.
await openDocumentSettingsSidebar();
const switchLink = await page.waitForSelector(
'.edit-post-post-template button'
);
const editTemplateXPath =
"//*[contains(@class, 'edit-post-post-template__actions')]//button[contains(text(), 'Edit')]";
const switchLink = await page.waitForXPath( editTemplateXPath );
await switchLink.click();

// Check that we switched properly to edit mode.
Expand Down Expand Up @@ -82,4 +79,68 @@ describe( 'Post Editor Template mode', () => {
'//p[contains(text(), "Just a random paragraph added to the template")]'
);
} );

it( 'Allow creating custom block templates in classic themes', async () => {
await activateTheme( 'twentytwentyone' );
await createNewPost();
// Create a random post.
await page.type( '.editor-post-title__input', 'Another FSE Post' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( 'Hello World' );

// Unselect the blocks.
await page.evaluate( () => {
wp.data.dispatch( 'core/block-editor' ).clearSelectedBlock();
} );

// Save the post
// Saving shouldn't be necessary but unfortunately,
// there's a template resolution bug forcing us to do so.
await saveDraft();
await page.reload();

// Create a new custom template.
await openDocumentSettingsSidebar();
const newTemplateXPath =
"//*[contains(@class, 'edit-post-post-template__actions')]//button[contains(text(), 'New')]";
const newButton = await page.waitForXPath( newTemplateXPath );
await newButton.click();

// Fill the template title and submit.
const templateNameInputSelector =
'.edit-post-post-template__modal .components-text-control__input';
await page.click( templateNameInputSelector );
await page.keyboard.type( 'Blank Template' );
await page.keyboard.press( 'Enter' );

// Check that we switched properly to edit mode.
await page.waitForXPath(
'//*[contains(@class, "components-snackbar")]/*[text()="Custom template created. You\'re in template mode now."]'
);

// Edit the template
await insertBlock( 'Paragraph' );
await page.keyboard.type(
'Just a random paragraph added to the template'
);

// Save changes
const doneButton = await page.waitForXPath(
`//button[contains(text(), 'Apply')]`
);
await doneButton.click();
const saveButton = await page.waitForXPath(
`//div[contains(@class, "entities-saved-states__panel-header")]/button[contains(text(), 'Save')]`
);
await saveButton.click();

// Preview changes
const previewPage = await openPreviewPage();
await previewPage.waitForSelector( '.wp-site-blocks' );
const content = await previewPage.evaluate(
() => document.querySelector( '.wp-site-blocks' ).innerHTML
);

expect( content ).toMatchSnapshot();
} );
} );
Loading