diff --git a/docs/how-to-guides/themes/block-based-themes.md b/docs/how-to-guides/themes/block-based-themes.md index 3368d80fcfff33..6eb8608b0e02fe 100644 --- a/docs/how-to-guides/themes/block-based-themes.md +++ b/docs/how-to-guides/themes/block-based-themes.md @@ -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. diff --git a/lib/client-assets.php b/lib/client-assets.php index 6ffb733fc6a9b2..ed6ee3336f2a02 100644 --- a/lib/client-assets.php +++ b/lib/client-assets.php @@ -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 ] ); @@ -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(); diff --git a/lib/compat.php b/lib/compat.php index e391b66aa1fedf..6c748f02ed76f3 100644 --- a/lib/compat.php +++ b/lib/compat.php @@ -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. * diff --git a/lib/editor-settings.php b/lib/editor-settings.php index 3aff40945abf69..c154c3cac37ae3 100644 --- a/lib/editor-settings.php +++ b/lib/editor-settings.php @@ -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' ), @@ -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; } diff --git a/lib/full-site-editing/full-site-editing.php b/lib/full-site-editing/full-site-editing.php index 68e6d42d2f6bf5..3737f4bb693a25 100644 --- a/lib/full-site-editing/full-site-editing.php +++ b/lib/full-site-editing/full-site-editing.php @@ -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. */ diff --git a/lib/full-site-editing/page-templates.php b/lib/full-site-editing/page-templates.php index 6c5a11c8a40e23..e868d963e5aaf4 100644 --- a/lib/full-site-editing/page-templates.php +++ b/lib/full-site-editing/page-templates.php @@ -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 ); diff --git a/lib/full-site-editing/template-loader.php b/lib/full-site-editing/template-loader.php index c828a27c99a7bd..c02ebf78b96a19 100644 --- a/lib/full-site-editing/template-loader.php +++ b/lib/full-site-editing/template-loader.php @@ -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; } @@ -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; } } diff --git a/lib/full-site-editing/template-parts.php b/lib/full-site-editing/template-parts.php index a64416cfc39f9c..e264a8149ba204 100644 --- a/lib/full-site-editing/template-parts.php +++ b/lib/full-site-editing/template-parts.php @@ -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; } @@ -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; } @@ -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; } diff --git a/lib/full-site-editing/templates.php b/lib/full-site-editing/templates.php index 6362d7716c3c1c..0ecfcc9a68d8e6 100644 --- a/lib/full-site-editing/templates.php +++ b/lib/full-site-editing/templates.php @@ -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; } @@ -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; } @@ -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; diff --git a/lib/global-styles.php b/lib/global-styles.php index 9a935de29145eb..b563863330dc48 100644 --- a/lib/global-styles.php +++ b/lib/global-styles.php @@ -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'; @@ -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(); diff --git a/lib/init.php b/lib/init.php index d532a620e0de29..d320d3bffaf242 100644 --- a/lib/init.php +++ b/lib/init.php @@ -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' ); diff --git a/package-lock.json b/package-lock.json index 81b7f2e4bb11da..e4f25d93b2d6b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12783,7 +12783,8 @@ "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "rememo": "^3.0.0" + "rememo": "^3.0.0", + "uuid": "8.3.0" } }, "@wordpress/edit-site": { diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index dcbf37d93b2b8a..df7228676b05e9 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -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; } @@ -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' + ); +}; diff --git a/packages/e2e-test-utils/src/preview.js b/packages/e2e-test-utils/src/preview.js index f1f5a8747b46bd..789ca761b6ca20 100644 --- a/packages/e2e-test-utils/src/preview.js +++ b/packages/e2e-test-utils/src/preview.js @@ -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' diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/post-editor-template-mode.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/post-editor-template-mode.test.js.snap new file mode 100644 index 00000000000000..641ad5cc979217 --- /dev/null +++ b/packages/e2e-tests/specs/experiments/__snapshots__/post-editor-template-mode.test.js.snap @@ -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`] = ` +"

gutenberg

+ +

Just another WordPress site

+ + +
+ + +

Another FSE Post

+ +
+

Hello World

+
+ + +

Just a random paragraph added to the template

+" +`; diff --git a/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js b/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js index 140f567e4d74fe..858ef67ac807ea 100644 --- a/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js +++ b/packages/e2e-tests/specs/experiments/post-editor-template-mode.test.js @@ -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' ); } ); @@ -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' ); @@ -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. @@ -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(); + } ); } ); diff --git a/packages/edit-post/package.json b/packages/edit-post/package.json index 2d5f7af3c145d9..7a4b71fc309dc0 100644 --- a/packages/edit-post/package.json +++ b/packages/edit-post/package.json @@ -52,7 +52,8 @@ "classnames": "^2.2.5", "lodash": "^4.17.19", "memize": "^1.1.0", - "rememo": "^3.0.0" + "rememo": "^3.0.0", + "uuid": "8.3.0" }, "publishConfig": { "access": "public" diff --git a/packages/edit-post/src/components/header/template-title/index.js b/packages/edit-post/src/components/header/template-title/index.js index deb2b905ee9e34..877c48ccf50da0 100644 --- a/packages/edit-post/src/components/header/template-title/index.js +++ b/packages/edit-post/src/components/header/template-title/index.js @@ -3,8 +3,6 @@ */ import { __, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; -import { store as editorStore } from '@wordpress/editor'; -import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -13,15 +11,12 @@ import { store as editPostStore } from '../../../store'; function TemplateTitle() { const { template, isEditing } = useSelect( ( select ) => { - const { getEditedPostAttribute } = select( editorStore ); - const { __experimentalGetTemplateForLink } = select( coreStore ); - const { isEditingTemplate } = select( editPostStore ); - const link = getEditedPostAttribute( 'link' ); + const { isEditingTemplate, getEditedPostTemplate } = select( + editPostStore + ); const _isEditing = isEditingTemplate(); return { - template: _isEditing - ? __experimentalGetTemplateForLink( link ) - : null, + template: _isEditing ? getEditedPostTemplate() : null, isEditing: _isEditing, }; }, [] ); diff --git a/packages/edit-post/src/components/sidebar/post-template/index.js b/packages/edit-post/src/components/sidebar/post-template/index.js index 806f49fa04edb3..05eef4febd9b8f 100644 --- a/packages/edit-post/src/components/sidebar/post-template/index.js +++ b/packages/edit-post/src/components/sidebar/post-template/index.js @@ -1,51 +1,61 @@ +/** + * External dependencies + */ +import { kebabCase } from 'lodash'; + /** * WordPress dependencies */ -import { __, sprintf } from '@wordpress/i18n'; -import { PanelRow, Button } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { + PanelRow, + Button, + Modal, + TextControl, + Flex, + FlexItem, +} from '@wordpress/components'; import { useSelect, useDispatch } from '@wordpress/data'; -import { createInterpolateElement } from '@wordpress/element'; +import { useState } from '@wordpress/element'; import { store as editorStore } from '@wordpress/editor'; import { store as coreStore } from '@wordpress/core-data'; -import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies */ import { store as editPostStore } from '../../../store'; +import { createBlock, serialize } from '@wordpress/blocks'; function PostTemplate() { - const { template, isEditing, isFSETheme } = useSelect( ( select ) => { - const { - getEditedPostAttribute, - getCurrentPostType, - getCurrentPost, - } = select( editorStore ); - const { __experimentalGetTemplateForLink, getPostType } = select( - coreStore - ); - const { isEditingTemplate } = select( editPostStore ); - const link = getEditedPostAttribute( 'link' ); - const isFSEEnabled = select( editorStore ).getEditorSettings() - .isFSETheme; - const isViewable = - getPostType( getCurrentPostType() )?.viewable ?? false; - return { - template: - isFSEEnabled && - isViewable && - link && - getCurrentPost().status !== 'auto-draft' - ? __experimentalGetTemplateForLink( link ) - : null, - isEditing: isEditingTemplate(), - isFSETheme: isFSEEnabled, - }; - }, [] ); - const { setIsEditingTemplate } = useDispatch( editPostStore ); - const { createSuccessNotice } = useDispatch( noticesStore ); + const [ isModalOpen, setIsModalOpen ] = useState( false ); + const [ title, setTitle ] = useState( '' ); + const { template, isEditing, supportsTemplateMode } = useSelect( + ( select ) => { + const { getCurrentPostType } = select( editorStore ); + const { getPostType } = select( coreStore ); + const { isEditingTemplate, getEditedPostTemplate } = select( + editPostStore + ); + const _supportsTemplateMode = select( + editorStore + ).getEditorSettings().supportsTemplateMode; + const isViewable = + getPostType( getCurrentPostType() )?.viewable ?? false; + + return { + template: + supportsTemplateMode && + isViewable && + getEditedPostTemplate(), + isEditing: isEditingTemplate(), + supportsTemplateMode: _supportsTemplateMode, + }; + }, + [] + ); + const { __unstableSwitchToTemplateMode } = useDispatch( editPostStore ); - if ( ! isFSETheme || ! template ) { + if ( ! supportsTemplateMode ) { return null; } @@ -53,40 +63,95 @@ function PostTemplate() { { __( 'Template' ) } { ! isEditing && ( +
+
+ { !! template && template?.title?.raw } + { !! template && + ! template?.title?.raw && + template.slug } + { ! template && __( 'Default' ) } +
+
+ { !! template && ( + + ) } + +
+
+ ) } + { isEditing && ( - { createInterpolateElement( - sprintf( - /* translators: 1: Template name. */ - __( '%s (Edit)' ), - template.slug - ), - { - a: ( + { template?.slug } + + ) } + { isModalOpen && ( + { + setIsModalOpen( false ); + setTitle( '' ); + } } + overlayClassName="edit-post-post-template__modal" + > +
{ + event.preventDefault(); + const defaultTitle = __( 'Custom Template' ); + const templateContent = [ + createBlock( 'core/site-title' ), + createBlock( 'core/site-tagline' ), + createBlock( 'core/separator' ), + createBlock( 'core/post-title' ), + createBlock( 'core/post-content' ), + ]; + __unstableSwitchToTemplateMode( { + slug: + 'wp-custom-template-' + + kebabCase( title ?? defaultTitle ), + content: serialize( templateContent ), + title: title ?? defaultTitle, + } ); + setIsModalOpen( false ); + } } + > + + + - ), - } - ) } - - ) } - { isEditing && ( - - { template.slug } - + + + + + + +
) }
); diff --git a/packages/edit-post/src/components/sidebar/post-template/style.scss b/packages/edit-post/src/components/sidebar/post-template/style.scss index d625e897224368..ed36bf1ea4d941 100644 --- a/packages/edit-post/src/components/sidebar/post-template/style.scss +++ b/packages/edit-post/src/components/sidebar/post-template/style.scss @@ -1,6 +1,7 @@ .edit-post-post-template { width: 100%; justify-content: left; + align-items: baseline; span { display: block; @@ -11,3 +12,15 @@ .edit-post-post-template__value { padding-left: 6px; } + +.edit-post-post-template__modal-actions { + padding-top: $grid-unit-15; +} + +.edit-post-post-template__actions { + margin-top: $grid-unit-10; + + button:not(:last-child) { + margin-right: $grid-unit-10; + } +} diff --git a/packages/edit-post/src/editor.js b/packages/edit-post/src/editor.js index 3f0fa4d1105c7a..12aabd865e5dc4 100644 --- a/packages/edit-post/src/editor.js +++ b/packages/edit-post/src/editor.js @@ -56,14 +56,12 @@ function Editor( { getPreference, __experimentalGetPreviewDeviceType, isEditingTemplate, + getEditedPostTemplate, } = select( editPostStore ); - const { - getEntityRecord, - __experimentalGetTemplateForLink, - getPostType, - getEntityRecords, - } = select( 'core' ); - const { getEditorSettings, getCurrentPost } = select( 'core/editor' ); + const { getEntityRecord, getPostType, getEntityRecords } = select( + 'core' + ); + const { getEditorSettings } = select( 'core/editor' ); const { getBlockTypes } = select( blocksStore ); const isTemplate = [ 'wp_template', 'wp_template_part' ].includes( postType @@ -79,7 +77,7 @@ function Editor( { } else { postObject = getEntityRecord( 'postType', postType, postId ); } - const isFSETheme = getEditorSettings().isFSETheme; + const supportsTemplateMode = getEditorSettings().supportsTemplateMode; const isViewable = getPostType( postType )?.viewable ?? false; return { @@ -100,11 +98,8 @@ function Editor( { keepCaretInsideBlock: isFeatureActive( 'keepCaretInsideBlock' ), isTemplateMode: isEditingTemplate(), template: - isFSETheme && - isViewable && - postObject && - getCurrentPost().status !== 'auto-draft' - ? __experimentalGetTemplateForLink( postObject.link ) + supportsTemplateMode && isViewable + ? getEditedPostTemplate() : null, post: postObject, }; diff --git a/packages/edit-post/src/store/actions.js b/packages/edit-post/src/store/actions.js index 61d481143e095c..e8f4d1ab12d515 100644 --- a/packages/edit-post/src/store/actions.js +++ b/packages/edit-post/src/store/actions.js @@ -11,6 +11,8 @@ import { apiFetch } from '@wordpress/data-controls'; import { store as interfaceStore } from '@wordpress/interface'; import { controls, dispatch, select, subscribe } from '@wordpress/data'; import { speak } from '@wordpress/a11y'; +import { store as noticesStore } from '@wordpress/notices'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -436,3 +438,43 @@ export function setIsEditingTemplate( value ) { value, }; } + +/** + * Potentially create a block based template and switches to the template mode. + * + * @param {Object?} template template to create and assign before switching. + */ +export function* __unstableSwitchToTemplateMode( template ) { + if ( !! template ) { + const savedTemplate = yield controls.dispatch( + coreStore, + 'saveEntityRecord', + 'postType', + 'wp_template', + template + ); + const post = yield controls.select( 'core/editor', 'getCurrentPost' ); + + yield controls.dispatch( + coreStore, + 'editEntityRecord', + 'postType', + post.type, + post.id, + { + template: savedTemplate.slug, + } + ); + } + + yield setIsEditingTemplate( true ); + + const message = !! template + ? __( "Custom template created. You're in template mode now." ) + : __( + 'Editing template. Changes made here affect all posts and pages that use the template.' + ); + yield controls.dispatch( noticesStore, 'createSuccessNotice', message, { + type: 'snackbar', + } ); +} diff --git a/packages/edit-post/src/store/selectors.js b/packages/edit-post/src/store/selectors.js index 2fb6c95a8276de..940ad34517ac82 100644 --- a/packages/edit-post/src/store/selectors.js +++ b/packages/edit-post/src/store/selectors.js @@ -9,6 +9,8 @@ import { get, includes, some, flatten, values } from 'lodash'; */ import { createRegistrySelector } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as editorStore } from '@wordpress/editor'; /** * Returns the current editing mode. * @@ -335,3 +337,30 @@ export function isInserterOpened( state ) { export function isEditingTemplate( state ) { return state.isEditingTemplate; } + +/** + * Retrieves the template of the currently edited post. + * + * @return {Object?} Post Template. + */ +export const getEditedPostTemplate = createRegistrySelector( + ( select ) => () => { + const currentTemplate = select( editorStore ).getEditedPostAttribute( + 'template' + ); + if ( currentTemplate ) { + return select( coreStore ) + .getEntityRecords( 'postType', 'wp_template' ) + ?.find( ( template ) => template.slug === currentTemplate ); + } + + const post = select( editorStore ).getCurrentPost(); + if ( post.link && post.status !== 'auto-draft' ) { + return select( coreStore ).__experimentalGetTemplateForLink( + post.link + ); + } + + return null; + } +);