From 7f3133fc296eb497881f2121b6b5a2f5f23ad33c Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 7 Feb 2023 16:21:20 +0000 Subject: [PATCH] Add: Modal to choose a start pattern on new templates. --- lib/compat/wordpress-6.1/rest-api.php | 16 -- ...utenberg-rest-templates-controller-6-2.php | 68 +++++++ lib/compat/wordpress-6.2/rest-api.php | 15 ++ lib/load.php | 1 + .../add-new-template/new-template.js | 17 +- .../edit-site/src/components/editor/index.js | 2 + .../start-template-options/index.js | 171 ++++++++++++++++++ .../start-template-options/style.scss | 76 ++++++++ packages/edit-site/src/style.scss | 1 + ...tenberg-rest-templates-controller-test.php | 6 + test/emptytheme/templates/tag.html | 0 11 files changed, 341 insertions(+), 32 deletions(-) create mode 100644 lib/compat/wordpress-6.2/class-gutenberg-rest-templates-controller-6-2.php create mode 100644 packages/edit-site/src/components/start-template-options/index.js create mode 100644 packages/edit-site/src/components/start-template-options/style.scss create mode 100644 test/emptytheme/templates/tag.html diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index 5dd06d3aad855..f2293e5169a9c 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -5,22 +5,6 @@ * @package gutenberg */ -/** - * Update `wp_template` and `wp_template-part` post types to use - * Gutenberg's REST controller. - * - * @param array $args Array of arguments for registering a post type. - * @param string $post_type Post type key. - */ -function gutenberg_update_templates_template_parts_rest_controller( $args, $post_type ) { - if ( in_array( $post_type, array( 'wp_template', 'wp_template-part' ), true ) ) { - $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller'; - } - return $args; -} -add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); - - /** * Add the post type's `icon`(menu_icon) in the response. * When we backport this change we will need to add the diff --git a/lib/compat/wordpress-6.2/class-gutenberg-rest-templates-controller-6-2.php b/lib/compat/wordpress-6.2/class-gutenberg-rest-templates-controller-6-2.php new file mode 100644 index 0000000000000..16d38ac073e68 --- /dev/null +++ b/lib/compat/wordpress-6.2/class-gutenberg-rest-templates-controller-6-2.php @@ -0,0 +1,68 @@ +namespace, + '/' . $this->rest_base . '/lookup', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_template_fallback' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'slug' => array( + 'description' => __( 'The slug of the template to get the fallback for', 'gutenberg' ), + 'type' => 'string', + 'required' => true, + ), + 'is_custom' => array( + 'description' => __( 'Indicates if a template is custom or part of the template hierarchy', 'gutenberg' ), + 'type' => 'boolean', + ), + 'template_prefix' => array( + 'description' => __( 'The template prefix for the created template. This is used to extract the main template type ex. in `taxonomy-books` we extract the `taxonomy`', 'gutenberg' ), + 'type' => 'string', + ), + ), + ), + ) + ); + parent::register_routes(); + // Get fallback template content. + } + + /** + * Returns the fallback template for a given slug. + * + * @param WP_REST_Request $request The request instance. + * + * @return WP_REST_Response|WP_Error + */ + public function get_template_fallback( $request ) { + $hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] ); + $fallback_template = null; + do { + $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' ); + array_shift( $hierarchy ); + } while ( ! empty( $hierarchy ) && empty( $fallback_template->content ) ); + $response = $this->prepare_item_for_response( $fallback_template, $request ); + return rest_ensure_response( $response ); + } +} diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php index a504be4dca2a6..06780aceb245e 100644 --- a/lib/compat/wordpress-6.2/rest-api.php +++ b/lib/compat/wordpress-6.2/rest-api.php @@ -5,6 +5,21 @@ * @package gutenberg */ +/** + * Update `wp_template` and `wp_template-part` post types to use + * Gutenberg's REST controller. + * + * @param array $args Array of arguments for registering a post type. + * @param string $post_type Post type key. + */ +function gutenberg_update_templates_template_parts_rest_controller( $args, $post_type ) { + if ( in_array( $post_type, array( 'wp_template', 'wp_template-part' ), true ) ) { + $args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_2'; + } + return $args; +} +add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); + /** * Registers the block pattern categories REST API routes. */ diff --git a/lib/load.php b/lib/load.php index fd8a7b9057753..dc253408a9289 100644 --- a/lib/load.php +++ b/lib/load.php @@ -44,6 +44,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-patterns-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-block-pattern-categories-controller.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-pattern-directory-controller-6-2.php'; + require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-templates-controller-6-2.php'; require_once __DIR__ . '/compat/wordpress-6.2/rest-api.php'; require_once __DIR__ . '/compat/wordpress-6.2/block-patterns.php'; require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php'; diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 4b825de340e00..c2a05fbe49451 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -1,8 +1,6 @@ /** * WordPress dependencies */ -import apiFetch from '@wordpress/api-fetch'; -import { addQueryArgs } from '@wordpress/url'; import { DropdownMenu, MenuGroup, @@ -109,19 +107,7 @@ export default function NewTemplate( { } setIsCreatingTemplate( true ); try { - const { title, description, slug, templatePrefix } = template; - let templateContent = template.content; - // Try to find fallback content from existing templates. - if ( ! templateContent ) { - const fallbackTemplate = await apiFetch( { - path: addQueryArgs( '/wp/v2/templates/lookup', { - slug, - is_custom: ! isWPSuggestion, - template_prefix: templatePrefix, - } ), - } ); - templateContent = fallbackTemplate.content.raw; - } + const { title, description, slug } = template; const newTemplate = await saveEntityRecord( 'postType', 'wp_template', @@ -131,7 +117,6 @@ export default function NewTemplate( { slug: slug.toString(), status: 'publish', title, - content: templateContent, // This adds a post meta field in template that is part of `is_custom` value calculation. is_wp_suggestion: isWPSuggestion, }, diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index a18664f882265..6a5c036f40ae8 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -29,6 +29,7 @@ import KeyboardShortcuts from '../keyboard-shortcuts'; import InserterSidebar from '../secondary-sidebar/inserter-sidebar'; import ListViewSidebar from '../secondary-sidebar/list-view-sidebar'; import WelcomeGuide from '../welcome-guide'; +import StartTemplateOptions from '../start-template-options'; import { store as editSiteStore } from '../../store'; import { GlobalStylesRenderer } from '../global-styles-renderer'; import { GlobalStylesProvider } from '../global-styles/global-styles-provider'; @@ -170,6 +171,7 @@ export default function Editor() { + { isEditMode && } { + apiFetch( { + path: addQueryArgs( '/wp/v2/templates/lookup', { + slug, + is_custom: isCustom, + ignore_empty: true, + } ), + } ).then( ( { content } ) => setTemplateContent( content.raw ) ); + }, [ slug ] ); + return templateContent; +} + +const START_BLANK_TITLE = __( 'Start blank' ); + +function PatternSelection( { fallbackContent, onChoosePattern, postType } ) { + const [ , , onChange ] = useEntityBlockEditor( 'postType', postType ); + const blockPatterns = useMemo( + () => [ + { + name: 'fallback', + blocks: parse( fallbackContent ), + title: __( 'Fallback content' ), + }, + { + name: 'start-blank', + blocks: parse( + '

' + ), + title: START_BLANK_TITLE, + }, + ], + [ fallbackContent ] + ); + const shownBlockPatterns = useAsyncList( blockPatterns ); + + return ( +
+ { + onChange( 'start-blank' === pattern.name ? [] : blocks, { + selection: undefined, + } ); + onChoosePattern(); + } } + /> +
+ ); +} + +function StartModal( { slug, isCustom, onClose, postType } ) { + const fallbackContent = useFallbackTemplateContent( slug, isCustom ); + if ( ! fallbackContent ) { + return null; + } + return ( + +
+ { + onClose(); + } } + /> +
+
+ ); +} + +const START_TEMPLATE_MODAL_STATES = { + INITIAL: 'INITIAL', + CLOSED: 'CLOSED', +}; + +export default function StartTemplateOptions() { + const [ modalState, setModalState ] = useState( + START_TEMPLATE_MODAL_STATES.INITIAL + ); + const { shouldOpenModel, slug, isCustom, postType } = useSelect( + ( select ) => { + const { getEditedPostType, getEditedPostId } = + select( editSiteStore ); + const _postType = getEditedPostType(); + const postId = getEditedPostId(); + const { + __experimentalGetDirtyEntityRecords, + getEditedEntityRecord, + } = select( coreStore ); + const templateRecord = getEditedEntityRecord( + 'postType', + _postType, + postId + ); + + const hasDirtyEntityRecords = + __experimentalGetDirtyEntityRecords().length > 0; + + return { + shouldOpenModel: + ! hasDirtyEntityRecords && + '' === templateRecord.content && + 'wp_template' === _postType && + ! select( preferencesStore ).get( + 'core/edit-site', + 'welcomeGuide' + ), + slug: templateRecord.slug, + isCustom: templateRecord.is_custom, + postType: _postType, + }; + }, + [] + ); + + if ( + ( modalState === START_TEMPLATE_MODAL_STATES.INITIAL && + ! shouldOpenModel ) || + modalState === START_TEMPLATE_MODAL_STATES.CLOSED + ) { + return null; + } + + return ( + + setModalState( START_TEMPLATE_MODAL_STATES.CLOSED ) + } + /> + ); +} diff --git a/packages/edit-site/src/components/start-template-options/style.scss b/packages/edit-site/src/components/start-template-options/style.scss new file mode 100644 index 0000000000000..ca086ef085884 --- /dev/null +++ b/packages/edit-site/src/components/start-template-options/style.scss @@ -0,0 +1,76 @@ +.edit-site-start-template-options__modal.components-modal__frame { + // To keep modal dimensions consistent as subsections are navigated, width + // and height are used instead of max-(width/height). + @include break-small() { + width: calc(100% - #{ $grid-unit-20 * 2 }); + height: calc(100% - #{ $header-height * 2 }); + } + @include break-medium() { + width: 50%; + } + @include break-large() { + height: fit-content; + } +} + +.edit-site-start-template-options__modal-content .block-editor-block-patterns-list { + display: grid; + width: 100%; + margin-top: $grid-unit-05; + gap: $grid-unit-30; + grid-template-columns: repeat(auto-fit, minmax(min(100%/2, max(240px, 100%/10)), 1fr)); + .block-editor-block-patterns-list__list-item { + break-inside: avoid-column; + margin-bottom: 0; + width: 100%; + aspect-ratio: 3/4; + + .block-editor-block-preview__container { + height: 100%; + box-shadow: 0 0 0 1px $gray-300; + } + + .block-editor-block-preview__content { + width: 100%; + position: absolute; + } + + .block-editor-block-patterns-list__item-title { + display: none; + } + + &:hover { + .block-editor-block-preview__container { + box-shadow: 0 0 0 2px var(--wp-admin-theme-color); + } + } + + &:focus { + .block-editor-block-preview__container { + box-shadow: inset 0 0 0 1px $white, 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + + // Windows High Contrast mode will show this outline, but not the box-shadow. + outline: 2px solid transparent; + } + } + } + + // The start blank pattern is the last and we are selecting it. + .block-editor-block-patterns-list__list-item:nth-last-child(2) { + .block-editor-block-preview__container { + position: absolute; + padding: 0; + background: #f0f0f0; + &::after { + width: 100%; + top: 50%; + margin-top: -1em; + content: var(--wp-edit-site-start-template-options-start-blank); + text-align: center; + } + } + iframe { + display: none; + } + } +} diff --git a/packages/edit-site/src/style.scss b/packages/edit-site/src/style.scss index f73bc019f3036..950d1c286274d 100644 --- a/packages/edit-site/src/style.scss +++ b/packages/edit-site/src/style.scss @@ -17,6 +17,7 @@ @import "./components/create-template-part-modal/style.scss"; @import "./components/secondary-sidebar/style.scss"; @import "./components/welcome-guide/style.scss"; +@import "./components/start-template-options/style.scss"; @import "./components/keyboard-shortcut-help-modal/style.scss"; @import "./components/layout/style.scss"; @import "./components/save-panel/style.scss"; diff --git a/phpunit/class-gutenberg-rest-templates-controller-test.php b/phpunit/class-gutenberg-rest-templates-controller-test.php index 2c8521d63fd82..e8d770ee55b17 100644 --- a/phpunit/class-gutenberg-rest-templates-controller-test.php +++ b/phpunit/class-gutenberg-rest-templates-controller-test.php @@ -49,6 +49,12 @@ public function test_get_template_fallback() { $response = rest_get_server()->dispatch( $request ); $this->assertSame( 'singular', $response->get_data()['slug'], 'Should fallback to `singular.html`.' ); // Should fallback to `index.html`. + $request->set_param( 'slug', 'tag' ); + $request->set_param( 'ignore_empty', true ); + $request->set_param( 'is_custom', false ); + $response = rest_get_server()->dispatch( $request ); + $this->assertSame( 'index', $response->get_data()['slug'], 'Should fallback to `index.html`.' ); + // Should fallback to `index.html`. $request->set_param( 'slug', 'tag-rigas' ); $request->set_param( 'is_custom', false ); $request->set_param( 'template_prefix', 'tag' ); diff --git a/test/emptytheme/templates/tag.html b/test/emptytheme/templates/tag.html new file mode 100644 index 0000000000000..e69de29bb2d1d