From aa05bd47df3247730065b197a170ab84e4c6761f Mon Sep 17 00:00:00 2001 From: Jacopo Tomasone Date: Thu, 1 Apr 2021 11:52:46 +0100 Subject: [PATCH] Site Editor: Revert custom templates to their original theme-provided files (#28141) --- lib/full-site-editing/block-templates.php | 69 ++++-- .../class-wp-block-template.php | 13 +- .../class-wp-rest-templates-controller.php | 74 +++--- packages/e2e-test-utils/README.md | 14 ++ packages/e2e-test-utils/src/index.js | 1 + packages/e2e-tests/experimental-features.js | 34 ++- .../specs/experiments/template-revert.test.js | 220 ++++++++++++++++++ .../edit-site/src/components/header/index.js | 8 +- .../src/components/template-details/index.js | 27 ++- .../components/template-details/style.scss | 16 +- packages/edit-site/src/store/actions.js | 143 +++++++++++- .../src/utils/is-template-revertable.js | 14 ++ phpunit/class-block-templates-test.php | 16 +- ...class-wp-rest-template-controller-test.php | 124 +++++----- 14 files changed, 641 insertions(+), 132 deletions(-) create mode 100644 packages/e2e-tests/specs/experiments/template-revert.test.js create mode 100644 packages/edit-site/src/utils/is-template-revertable.js diff --git a/lib/full-site-editing/block-templates.php b/lib/full-site-editing/block-templates.php index 265d0bda0f4a7..d0bcab00c9152 100644 --- a/lib/full-site-editing/block-templates.php +++ b/lib/full-site-editing/block-templates.php @@ -184,15 +184,16 @@ function _gutenberg_build_template_result_from_file( $template_file, $template_t $template_content = file_get_contents( $template_file['path'] ); $theme = wp_get_theme()->get_stylesheet(); - $template = new WP_Block_Template(); - $template->id = $theme . '//' . $template_file['slug']; - $template->theme = $theme; - $template->content = _inject_theme_attribute_in_content( $template_content ); - $template->slug = $template_file['slug']; - $template->is_custom = false; - $template->type = $template_type; - $template->title = $template_file['slug']; - $template->status = 'publish'; + $template = new WP_Block_Template(); + $template->id = $theme . '//' . $template_file['slug']; + $template->theme = $theme; + $template->content = _inject_theme_attribute_in_content( $template_content ); + $template->slug = $template_file['slug']; + $template->source = 'theme'; + $template->type = $template_type; + $template->title = $template_file['slug']; + $template->status = 'publish'; + $template->has_theme_file = true; if ( 'wp_template' === $template_type && isset( $default_template_types[ $template_file['slug'] ] ) ) { $template->description = $default_template_types[ $template_file['slug'] ]['description']; @@ -224,19 +225,22 @@ function _gutenberg_build_template_result_from_post( $post ) { return new WP_Error( 'template_missing_theme', __( 'No theme is defined for this template.', 'gutenberg' ) ); } - $theme = $terms[0]->name; - - $template = new WP_Block_Template(); - $template->wp_id = $post->ID; - $template->id = $theme . '//' . $post->post_name; - $template->theme = $theme; - $template->content = $post->post_content; - $template->slug = $post->post_name; - $template->is_custom = true; - $template->type = $post->post_type; - $template->description = $post->post_excerpt; - $template->title = $post->post_title; - $template->status = $post->post_status; + $theme = $terms[0]->name; + $has_theme_file = wp_get_theme()->get_stylesheet() === $theme && + null !== _gutenberg_get_template_file( $post->post_type, $post->post_name ); + + $template = new WP_Block_Template(); + $template->wp_id = $post->ID; + $template->id = $theme . '//' . $post->post_name; + $template->theme = $theme; + $template->content = $post->post_content; + $template->slug = $post->post_name; + $template->source = 'custom'; + $template->type = $post->post_type; + $template->description = $post->post_excerpt; + $template->title = $post->post_title; + $template->status = $post->post_status; + $template->has_theme_file = $has_theme_file; if ( 'wp_template_part' === $post->post_type ) { $type_terms = get_the_terms( $post, 'wp_template_part_area' ); @@ -331,7 +335,7 @@ function gutenberg_get_block_templates( $query = array(), $template_type = 'wp_t /** * Retrieves a single unified template object using its id. * - * @param string $id Template unique identifier (example: theme|slug). + * @param string $id Template unique identifier (example: theme_slug//template_slug). * @param array $template_type wp_template or wp_template_part. * * @return WP_Block_Template|null Template. @@ -367,6 +371,25 @@ function gutenberg_get_block_template( $id, $template_type = 'wp_template' ) { } } + return gutenberg_get_block_file_template( $id, $template_type ); +} + +/** + * Retrieves a single unified template object using its id. + * Retrieves the file template. + * + * @param string $id Template unique identifier (example: theme_slug//template_slug). + * @param array $template_type wp_template or wp_template_part. + * + * @return WP_Block_Template|null File template. + */ +function gutenberg_get_block_file_template( $id, $template_type = 'wp_template' ) { + $parts = explode( '//', $id, 2 ); + if ( count( $parts ) < 2 ) { + return null; + } + list( $theme, $slug ) = $parts; + if ( wp_get_theme()->get_stylesheet() === $theme ) { $template_file = _gutenberg_get_template_file( $template_type, $slug ); if ( null !== $template_file ) { diff --git a/lib/full-site-editing/class-wp-block-template.php b/lib/full-site-editing/class-wp-block-template.php index b5e051241bc39..d8c9d5c54a2b9 100644 --- a/lib/full-site-editing/class-wp-block-template.php +++ b/lib/full-site-editing/class-wp-block-template.php @@ -61,11 +61,11 @@ class WP_Block_Template { public $description = ''; /** - * Whether it's a theme file template or a custom one. + * Source of the content. `theme` and `custom` is used for now. * - * @var boolean + * @var string */ - public $is_custom = false; + public $source = 'theme'; /** * Post Id. @@ -80,4 +80,11 @@ class WP_Block_Template { * @var string */ public $status; + + /** + * Whether a template is, or is based upon, an existing template file. + * + * @var boolean + */ + public $has_theme_file; } diff --git a/lib/full-site-editing/class-wp-rest-templates-controller.php b/lib/full-site-editing/class-wp-rest-templates-controller.php index a4d818fca256a..e6b3a90efd3d3 100644 --- a/lib/full-site-editing/class-wp-rest-templates-controller.php +++ b/lib/full-site-editing/class-wp-rest-templates-controller.php @@ -168,7 +168,11 @@ public function get_item_permissions_check( $request ) { * @return WP_REST_Response|WP_Error */ public function get_item( $request ) { - $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + $template = gutenberg_get_block_file_template( $request['id'], $this->post_type ); + } else { + $template = gutenberg_get_block_template( $request['id'], $this->post_type ); + } if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); @@ -199,9 +203,14 @@ public function update_item( $request ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } + if ( isset( $request['source'] ) && 'theme' === $request['source'] ) { + wp_delete_post( $template->wp_id, true ); + return $this->prepare_item_for_response( gutenberg_get_block_file_template( $request['id'], $this->post_type ), $request ); + } + $changes = $this->prepare_item_for_database( $request ); - if ( $template->is_custom ) { + if ( 'custom' === $template->source ) { $result = wp_update_post( wp_slash( (array) $changes ), true ); } else { $result = wp_insert_post( wp_slash( (array) $changes ), true ); @@ -283,7 +292,7 @@ public function delete_item( $request ) { if ( ! $template ) { return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.', 'gutenberg' ), array( 'status' => 404 ) ); } - if ( ! $template->is_custom ) { + if ( 'custom' !== $template->source ) { return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.', 'gutenberg' ), array( 'status' => 400 ) ); } @@ -335,7 +344,7 @@ protected function prepare_item_for_database( $request ) { $changes->tax_input = array( 'wp_theme' => isset( $request['theme'] ) ? $request['content'] : wp_get_theme()->get_stylesheet(), ); - } elseif ( ! $template->is_custom ) { + } elseif ( 'custom' !== $template->source ) { $changes->post_type = $this->post_type; $changes->post_status = 'publish'; $changes->tax_input = array( @@ -347,24 +356,24 @@ protected function prepare_item_for_database( $request ) { } if ( isset( $request['content'] ) ) { $changes->post_content = $request['content']; - } elseif ( null !== $template && ! $template->is_custom ) { + } elseif ( null !== $template && 'custom' !== $template->source ) { $changes->post_content = $template->content; } if ( isset( $request['title'] ) ) { $changes->post_title = $request['title']; - } elseif ( null !== $template && ! $template->is_custom ) { + } elseif ( null !== $template && 'custom' !== $template->source ) { $changes->post_title = $template->title; } if ( isset( $request['description'] ) ) { $changes->post_excerpt = $request['description']; - } elseif ( null !== $template && ! $template->is_custom ) { + } elseif ( null !== $template && 'custom' !== $template->source ) { $changes->post_excerpt = $template->description; } if ( 'wp_template_part' === $this->post_type ) { if ( isset( $request['area'] ) ) { $changes->tax_input['wp_template_part_area'] = gutenberg_filter_template_part_area( $request['area'] ); - } elseif ( null !== $template && ! $template->is_custom && $template->area ) { + } elseif ( null !== $template && 'custom' !== $template->source && $template->area ) { $changes->tax_input['wp_template_part_area'] = gutenberg_filter_template_part_area( $template->area ); } elseif ( ! $template->area ) { $changes->tax_input['wp_template_part_area'] = WP_TEMPLATE_PART_AREA_UNCATEGORIZED; @@ -384,19 +393,20 @@ protected function prepare_item_for_database( $request ) { */ public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable $result = array( - 'id' => $template->id, - 'theme' => $template->theme, - 'content' => array( 'raw' => $template->content ), - 'slug' => $template->slug, - 'is_custom' => $template->is_custom, - 'type' => $template->type, - 'description' => $template->description, - 'title' => array( + 'id' => $template->id, + 'theme' => $template->theme, + 'content' => array( 'raw' => $template->content ), + 'slug' => $template->slug, + 'source' => $template->source, + 'type' => $template->type, + 'description' => $template->description, + 'title' => array( 'raw' => $template->title, 'rendered' => $template->title, ), - 'status' => $template->status, - 'wp_id' => $template->wp_id, + 'status' => $template->status, + 'wp_id' => $template->wp_id, + 'has_theme_file' => $template->has_theme_file, ); if ( 'wp_template_part' === $template->type ) { @@ -495,13 +505,13 @@ public function get_item_schema() { 'title' => $this->post_type, 'type' => 'object', 'properties' => array( - 'id' => array( + 'id' => array( 'description' => __( 'ID of template.', 'gutenberg' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'slug' => array( + 'slug' => array( 'description' => __( 'Unique slug identifying the template.', 'gutenberg' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), @@ -509,47 +519,53 @@ public function get_item_schema() { 'minLength' => 1, 'pattern' => '[a-zA-Z_\-]+', ), - 'theme' => array( + 'theme' => array( 'description' => __( 'Theme identifier for the template.', 'gutenberg' ), 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), ), - 'is_custom' => array( - 'description' => __( 'Whether the template is customized.', 'gutenberg' ), - 'type' => 'bool', + 'source' => array( + 'description' => __( 'Source of template', 'gutenberg' ), + 'type' => 'string', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), - 'content' => array( + 'content' => array( 'description' => __( 'Content of template.', 'gutenberg' ), 'type' => array( 'object', 'string' ), 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), ), - 'title' => array( + 'title' => array( 'description' => __( 'Title of template.', 'gutenberg' ), 'type' => array( 'object', 'string' ), 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), ), - 'description' => array( + 'description' => array( 'description' => __( 'Description of template.', 'gutenberg' ), 'type' => 'string', 'default' => '', 'context' => array( 'embed', 'view', 'edit' ), ), - 'status' => array( + 'status' => array( 'description' => __( 'Status of template.', 'gutenberg' ), 'type' => 'string', 'default' => 'publish', 'context' => array( 'embed', 'view', 'edit' ), ), - 'wp_id' => array( + 'wp_id' => array( 'description' => __( 'Post ID.', 'gutenberg' ), 'type' => 'integer', 'context' => array( 'embed', 'view', 'edit' ), 'readonly' => true, ), + 'has_theme_file' => array( + 'description' => __( 'Theme file exists.', 'gutenberg' ), + 'type' => 'bool', + 'context' => array( 'embed', 'view', 'edit' ), + 'readonly' => true, + ), ), ); diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index 55e162ac6bec3..d7e3889ec9dd5 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -713,6 +713,20 @@ _Parameters_ - _width_ `number`: Width of the window. - _height_ `number`: Height of the window. +# **wpDataSelect** + +Queries the WordPress data module. + +_Parameters_ + +- _store_ `string`: Store to query e.g: core/editor, core/blocks... +- _selector_ `string`: Selector to exectute e.g: getBlocks. +- _parameters_ `...Object`: Parameters to pass to the selector. + +_Returns_ + +- `Promise`: Result of querying. + diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 20fd36167a5b7..f71d2151ecd36 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -77,5 +77,6 @@ export { visitAdminPage } from './visit-admin-page'; export { waitForWindowDimensions } from './wait-for-window-dimensions'; export { showBlockToolbar } from './show-block-toolbar'; export { openPreviewPage } from './preview'; +export { wpDataSelect } from './wp-data-select'; export * from './mocks'; diff --git a/packages/e2e-tests/experimental-features.js b/packages/e2e-tests/experimental-features.js index a8185b5f36a72..115b69d9e45e2 100644 --- a/packages/e2e-tests/experimental-features.js +++ b/packages/e2e-tests/experimental-features.js @@ -2,7 +2,7 @@ * WordPress dependencies */ import { addQueryArgs } from '@wordpress/url'; -import { visitAdminPage } from '@wordpress/e2e-test-utils'; +import { visitAdminPage, wpDataSelect } from '@wordpress/e2e-test-utils'; async function setExperimentalFeaturesState( features, enable ) { const query = addQueryArgs( '', { @@ -129,4 +129,36 @@ export const siteEditor = { await elementToClick.click(); }, + + async getEditedPostContent() { + const postId = await wpDataSelect( + 'core/edit-site', + 'getEditedPostId' + ); + const postType = await wpDataSelect( + 'core/edit-site', + 'getEditedPostType' + ); + const record = await wpDataSelect( + 'core', + 'getEditedEntityRecord', + 'postType', + postType, + postId + ); + if ( record ) { + if ( typeof record.content === 'function' ) { + return record.content( record ); + } else if ( record.blocks ) { + return await page.evaluate( + ( blocks ) => + window.wp.blocks.__unstableSerializeAndClean( blocks ), + record.blocks + ); + } else if ( record.content ) { + return record.content; + } + } + return ''; + }, }; diff --git a/packages/e2e-tests/specs/experiments/template-revert.test.js b/packages/e2e-tests/specs/experiments/template-revert.test.js new file mode 100644 index 0000000000000..cd0dcaa7ab3d5 --- /dev/null +++ b/packages/e2e-tests/specs/experiments/template-revert.test.js @@ -0,0 +1,220 @@ +/** + * WordPress dependencies + */ +import { + insertBlock, + trashAllPosts, + activateTheme, + switchUserToAdmin, + switchUserToTest, + visitAdminPage, +} from '@wordpress/e2e-test-utils'; +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { siteEditor } from '../../experimental-features'; + +const { visit: visitSiteEditor, getEditedPostContent } = siteEditor; + +const assertSaveButtonIsDisabled = () => + page.waitForSelector( + '.edit-site-save-button__button[aria-disabled="true"]' + ); + +const assertSaveButtonIsEnabled = () => + page.waitForSelector( + '.edit-site-save-button__button[aria-disabled="false"]' + ); + +const waitForNotice = () => page.waitForSelector( '.components-snackbar' ); + +const clickButtonInNotice = async () => { + const selector = '.components-snackbar button'; + await page.waitForSelector( selector ); + await page.click( selector ); +}; + +const clickUndoInHeaderToolbar = () => + page.click( '.edit-site-header__toolbar button[aria-label="Undo"]' ); + +const clickRedoInHeaderToolbar = () => + page.click( '.edit-site-header__toolbar button[aria-label="Redo"]' ); + +const undoRevertInHeaderToolbar = async () => { + await clickUndoInHeaderToolbar(); + await assertSaveButtonIsEnabled(); +}; + +const undoRevertInNotice = async () => { + await clickButtonInNotice(); + await assertSaveButtonIsEnabled(); +}; + +const addDummyText = async () => { + await insertBlock( 'Paragraph' ); + await page.keyboard.type( 'Test' ); +}; + +const save = async () => { + await page.click( '.edit-site-save-button__button' ); + await page.click( '.editor-entities-saved-states__save-button' ); + await page.waitForSelector( + '.edit-site-save-button__button:not(.is-busy)' + ); +}; + +const revertTemplate = async () => { + await page.click( '.edit-site-document-actions__get-info' ); + await page.click( '.edit-site-template-details__revert button' ); + await waitForNotice(); + await assertSaveButtonIsEnabled(); +}; + +const assertTemplatesAreDeleted = async () => { + await switchUserToAdmin(); + const query = addQueryArgs( '', { + post_type: 'wp_template', + } ).slice( 1 ); + await visitAdminPage( 'edit.php', query ); + const element = await page.waitForSelector( '#the-list .no-items' ); + expect( element ).toBeTruthy(); + await switchUserToTest(); +}; + +describe( 'Template Revert', () => { + beforeAll( async () => { + await activateTheme( 'tt1-blocks' ); + await trashAllPosts( 'wp_template' ); + await trashAllPosts( 'wp_template_part' ); + } ); + afterAll( async () => { + await trashAllPosts( 'wp_template' ); + await trashAllPosts( 'wp_template_part' ); + await activateTheme( 'twentytwentyone' ); + } ); + beforeEach( async () => { + await trashAllPosts( 'wp_template' ); + await visitSiteEditor(); + } ); + + it( 'should delete the template after saving the reverted template', async () => { + await addDummyText(); + await save(); + await revertTemplate(); + await save(); + + await assertTemplatesAreDeleted(); + } ); + + it( 'should show the original content after revert', async () => { + const contentBefore = await getEditedPostContent(); + + await addDummyText(); + await save(); + await revertTemplate(); + await save(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the original content after revert and page reload', async () => { + const contentBefore = await getEditedPostContent(); + + await addDummyText(); + await save(); + await revertTemplate(); + await save(); + await visitSiteEditor(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the edited content after revert and clicking undo in the header toolbar', async () => { + await addDummyText(); + await save(); + const contentBefore = await getEditedPostContent(); + + await revertTemplate(); + await save(); + await undoRevertInHeaderToolbar(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the edited content after revert and clicking undo in the notice', async () => { + await addDummyText(); + await save(); + const contentBefore = await getEditedPostContent(); + + await revertTemplate(); + await save(); + await undoRevertInNotice(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the original content after revert, clicking undo then redo in the header toolbar', async () => { + const contentBefore = await getEditedPostContent(); + + await addDummyText(); + await save(); + await revertTemplate(); + await save(); + await undoRevertInHeaderToolbar(); + await clickRedoInHeaderToolbar(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the original content after revert, clicking undo in the notice then undo in the header toolbar', async () => { + const contentBefore = await getEditedPostContent(); + + await addDummyText(); + await save(); + await revertTemplate(); + await save(); + await undoRevertInNotice(); + await undoRevertInHeaderToolbar(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the edited content after revert, clicking undo in the header toolbar, save and reload', async () => { + await addDummyText(); + await save(); + const contentBefore = await getEditedPostContent(); + + await revertTemplate(); + await save(); + await clickUndoInHeaderToolbar(); + await save(); + await assertSaveButtonIsDisabled(); + await visitSiteEditor(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); + + it( 'should show the edited content after revert, clicking undo in the notice and reload', async () => { + await addDummyText(); + await save(); + const contentBefore = await getEditedPostContent(); + + await revertTemplate(); + await save(); + await undoRevertInNotice(); + await save(); + await visitSiteEditor(); + + const contentAfter = await getEditedPostContent(); + expect( contentBefore ).toBe( contentAfter ); + } ); +} ); diff --git a/packages/edit-site/src/components/header/index.js b/packages/edit-site/src/components/header/index.js index 9e6a6fbf31a18..da94aecc5ae5f 100644 --- a/packages/edit-site/src/components/header/index.js +++ b/packages/edit-site/src/components/header/index.js @@ -14,6 +14,8 @@ import { _x, __ } from '@wordpress/i18n'; import { listView, plus } from '@wordpress/icons'; import { Button } from '@wordpress/components'; import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies @@ -46,15 +48,15 @@ export default function Header( { openEntitiesSavedStates } ) { isInserterOpened, isListViewOpened, } = select( editSiteStore ); - const { getEntityRecord } = select( 'core' ); + const { getEditedEntityRecord } = select( coreStore ); const { __experimentalGetTemplateInfo: getTemplateInfo } = select( - 'core/editor' + editorStore ); const { getShortcutRepresentation } = select( keyboardShortcutsStore ); const postType = getEditedPostType(); const postId = getEditedPostId(); - const record = getEntityRecord( 'postType', postType, postId ); + const record = getEditedEntityRecord( 'postType', postType, postId ); const _entityTitle = 'wp_template' === postType ? getTemplateInfo( record ).title diff --git a/packages/edit-site/src/components/template-details/index.js b/packages/edit-site/src/components/template-details/index.js index a8896c24ace79..103c4f0e4107b 100644 --- a/packages/edit-site/src/components/template-details/index.js +++ b/packages/edit-site/src/components/template-details/index.js @@ -2,12 +2,17 @@ * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Button, __experimentalText as Text } from '@wordpress/components'; +import { + Button, + MenuItem, + __experimentalText as Text, +} from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; /** * Internal dependencies */ +import isTemplateRevertable from '../../utils/is-template-revertable'; import { MENU_TEMPLATES } from '../navigation-sidebar/navigation-panel/constants'; import { store as editSiteStore } from '../../store'; @@ -17,7 +22,9 @@ export default function TemplateDetails( { template, onClose } ) { select( 'core/editor' ).__experimentalGetTemplateInfo( template ), [] ); - const { openNavigationPanelToMenu } = useDispatch( editSiteStore ); + const { openNavigationPanelToMenu, revertTemplate } = useDispatch( + editSiteStore + ); if ( ! template ) { return null; @@ -28,6 +35,11 @@ export default function TemplateDetails( { template, onClose } ) { openNavigationPanelToMenu( MENU_TEMPLATES ); }; + const revert = () => { + revertTemplate( template ); + onClose(); + }; + return ( <>
@@ -43,6 +55,17 @@ export default function TemplateDetails( { template, onClose } ) { ) }
+ { isTemplateRevertable( template ) && ( +
+ + { __( 'Clear customizations' ) } + +
+ ) } +