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";