diff --git a/lib/blocks.php b/lib/blocks.php
index add72e77062cb3..ada0e86f73a879 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -352,3 +352,15 @@ function gutenberg_register_legacy_social_link_blocks() {
}
add_action( 'init', 'gutenberg_register_legacy_social_link_blocks' );
+
+register_block_pattern(
+ 'custom-pattern',
+ array(
+ 'title' => _x( 'Start post pattern', 'Block pattern title', 'gutenberg' ),
+ 'blockTypes' => array( 'core/paragraph', 'core/post-content' ),
+ // 'postTypes' => array( 'page' ),
+ 'content' => '
+
A start post pattern
+',
+ )
+);
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
index b91ff4180613eb..9931ec2b69188a 100644
--- 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
@@ -17,8 +17,7 @@ class Gutenberg_REST_Templates_Controller_6_2 extends Gutenberg_REST_Templates_C
* @return void
*/
public function register_routes() {
- parent::register_routes();
- // Get fallback template content.
+
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/lookup',
@@ -41,10 +40,17 @@ public function register_routes() {
'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',
),
+ 'ignore_empty' => array(
+ 'description' => __( 'If true templates with empty content are ignored.', 'gutenberg' ),
+ 'type' => 'boolean',
+ 'default' => false,
+ ),
),
),
)
);
+ parent::register_routes();
+ // Get fallback template content.
}
/**
@@ -56,8 +62,16 @@ public function register_routes() {
*/
public function get_template_fallback( $request ) {
$hierarchy = get_template_hierarchy( $request['slug'], $request['is_custom'], $request['template_prefix'] );
- $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
- $response = $this->prepare_item_for_response( $fallback_template, $request );
+ $fallback_template = null;
+ if ( true === $request['ignore_empty'] ) {
+ do {
+ $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
+ array_shift( $hierarchy );
+ } while ( ! empty( $hierarchy ) && empty( $fallback_template->content ) );
+ } else {
+ $fallback_template = resolve_block_template( $request['slug'], $hierarchy, '' );
+ }
+ $response = $this->prepare_item_for_response( $fallback_template, $request );
return rest_ensure_response( $response );
}
}
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 ccd5448e4d81db..006d1135bb7570 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,
@@ -107,19 +105,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',
@@ -129,7 +115,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 fe733f49a19590..47c2866e0d05fd 100644
--- a/packages/edit-site/src/components/editor/index.js
+++ b/packages/edit-site/src/components/editor/index.js
@@ -33,6 +33,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';
@@ -165,6 +166,7 @@ export default function Editor() {
+
{
+ 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 [ resizeObserver, sizes ] = useResizeObserver();
+ const [ gridHeight, setGridHeight ] = useState( '320px' );
+ 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 );
+ useEffect( () => {
+ const elementOffSetWidth = window?.document?.querySelector(
+ '.edit-site-start-template-options__pattern-container .block-editor-block-patterns-list__list-item'
+ )?.offsetWidth;
+ if ( elementOffSetWidth ) {
+ setGridHeight( `${ ( elementOffSetWidth * 4 ) / 3 }px` );
+ }
+ }, [ blockPatterns, sizes.width ] );
+ return (
+
+ { resizeObserver }
+ {
+ 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',
+ PATTERN: 'PATTERN',
+ 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:
+ modalState === START_TEMPLATE_MODAL_STATES.INITIAL &&
+ ! hasDirtyEntityRecords &&
+ '' === templateRecord.content &&
+ 'wp_template' === _postType &&
+ ! select( preferencesStore ).get(
+ 'core/edit-site',
+ 'welcomeGuide'
+ ),
+ slug: templateRecord.slug,
+ isCustom: templateRecord.is_custom,
+ postType: _postType,
+ };
+ },
+ [ modalState ]
+ );
+
+ useEffect( () => {
+ if ( shouldOpenModel ) {
+ setModalState( START_TEMPLATE_MODAL_STATES.PATTERN );
+ }
+ }, [ shouldOpenModel ] );
+
+ if (
+ modalState === START_TEMPLATE_MODAL_STATES.INITIAL ||
+ 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 00000000000000..fcdb630bb9be23
--- /dev/null
+++ b/packages/edit-site/src/components/start-template-options/style.scss
@@ -0,0 +1,56 @@
+.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: 70%;
+ }
+ @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-10;
+ grid-template-columns: repeat(auto-fit, minmax(min(100%/2, max(240px, 100%/10)), 1fr));
+ grid-auto-rows: var(--wp-edit-site-start-template-options-grid-height);
+ .block-editor-block-patterns-list__list-item {
+ break-inside: avoid-column;
+ margin-bottom: $grid-unit-30;
+ width: 100%;
+
+ .block-editor-block-preview__container {
+ height: 100%;
+ }
+
+ .block-editor-block-preview__content {
+ width: 100%;
+ position: absolute;
+ }
+ }
+
+ // 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 b98f6b817e368c..a3a5faeb397e31 100644
--- a/packages/edit-site/src/style.scss
+++ b/packages/edit-site/src/style.scss
@@ -16,6 +16,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/sidebar/style.scss";