From f7a4f3f054e99e204b76393293fc4775b9596313 Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Tue, 26 Nov 2024 09:58:08 +0100 Subject: [PATCH] Move default template types and template part areas to REST API (#66459) * move default template types and template part areas to REST API * fix logic * fix E2E test * move default template types and template part areas to REST API * fix error * remove not necessary file * fix naming * remove duplicate code * remove duplicated code * improve logic * fix naming * fix unit test * update doc * add unit test for getTemplateInfo function * restore not necessary changes * fix e2e test * remove not necessary variable * replace add_action with add_filter * improve readibility code * make getTemplateInfo private * make templateAreas optional * add default_template_part_areas and default_template_types * move code to rest-api.php file * remove not used import --- lib/compat/wordpress-6.8/preload.php | 2 + lib/compat/wordpress-6.8/rest-api.php | 38 +++ .../use-template-part-area-label.js | 20 +- .../template-part/edit/advanced-controls.js | 26 +- .../edit/utils/get-template-part-icon.js | 20 ++ .../src/template-part/edit/utils/hooks.js | 9 +- .../src/template-part/variations.js | 20 +- packages/core-data/src/entities.js | 2 + .../src/components/add-new-template/utils.js | 4 +- .../src/components/editor/use-editor-title.js | 21 +- .../src/components/page-patterns/header.js | 5 +- .../components/page-patterns/use-patterns.js | 12 +- .../use-template-part-areas.js | 8 +- packages/edit-site/src/index.js | 14 +- .../create-template-part-modal/index.js | 5 +- .../src/components/document-bar/index.js | 13 +- .../entity-record-item.js | 16 +- .../src/components/post-card-panel/index.js | 23 +- packages/editor/src/private-apis.js | 3 +- .../editor/src/store/private-selectors.js | 16 +- packages/editor/src/store/selectors.js | 142 +++++----- packages/editor/src/store/test/selectors.js | 255 ------------------ .../editor/src/utils/get-template-info.js | 52 ++++ .../src/utils/test/get-template-info.js | 224 +++++++++++++++ 24 files changed, 535 insertions(+), 415 deletions(-) create mode 100644 packages/block-library/src/template-part/edit/utils/get-template-part-icon.js create mode 100644 packages/editor/src/utils/get-template-info.js create mode 100644 packages/editor/src/utils/test/get-template-info.js diff --git a/lib/compat/wordpress-6.8/preload.php b/lib/compat/wordpress-6.8/preload.php index aabe0d4fb574cc..ae6c738c6627c5 100644 --- a/lib/compat/wordpress-6.8/preload.php +++ b/lib/compat/wordpress-6.8/preload.php @@ -32,6 +32,8 @@ function gutenberg_block_editor_preload_paths_6_8( $paths, $context ) { 'site_logo', 'timezone_string', 'url', + 'default_template_part_areas', + 'default_template_types', ) ); $paths[] = '/wp/v2/templates/lookup?slug=front-page'; diff --git a/lib/compat/wordpress-6.8/rest-api.php b/lib/compat/wordpress-6.8/rest-api.php index 080e4003f57b38..b94e42d5f2ccd0 100644 --- a/lib/compat/wordpress-6.8/rest-api.php +++ b/lib/compat/wordpress-6.8/rest-api.php @@ -49,3 +49,41 @@ function ( $taxonomy ) { remove_filter( "rest_{$taxonomy}_query", 'gutenberg_respect_taxonomy_default_args_in_rest_api' ); } ); + +/** + * Adds the default template part areas to the REST API index. + * + * This function exposes the default template part areas through the WordPress REST API. + * Note: This function backports into the wp-includes/rest-api/class-wp-rest-server.php file. + * + * @param WP_REST_Response $response REST API response. + * @return WP_REST_Response Modified REST API response with default template part areas. + */ +function gutenberg_add_default_template_part_areas_to_index( WP_REST_Response $response ) { + $response->data['default_template_part_areas'] = get_allowed_block_template_part_areas(); + return $response; +} + +add_filter( 'rest_index', 'gutenberg_add_default_template_part_areas_to_index' ); + +/** + * Adds the default template types to the REST API index. + * + * This function exposes the default template types through the WordPress REST API. + * Note: This function backports into the wp-includes/rest-api/class-wp-rest-server.php file. + * + * @param WP_REST_Response $response REST API response. + * @return WP_REST_Response Modified REST API response with default template part areas. + */ +function gutenberg_add_default_template_types_to_index( WP_REST_Response $response ) { + $indexed_template_types = array(); + foreach ( get_default_block_template_types() as $slug => $template_type ) { + $template_type['slug'] = (string) $slug; + $indexed_template_types[] = $template_type; + } + + $response->data['default_template_types'] = $indexed_template_types; + return $response; +} + +add_filter( 'rest_index', 'gutenberg_add_default_template_types_to_index' ); diff --git a/packages/block-library/src/navigation/use-template-part-area-label.js b/packages/block-library/src/navigation/use-template-part-area-label.js index 48763a38ac62d1..7b4d514975e113 100644 --- a/packages/block-library/src/navigation/use-template-part-area-label.js +++ b/packages/block-library/src/navigation/use-template-part-area-label.js @@ -11,6 +11,7 @@ import { useSelect } from '@wordpress/data'; // TODO: this util should perhaps be refactored somewhere like core-data. import { createTemplatePartId } from '../template-part/edit/utils/create-template-part-id'; +import { getTemplatePartIcon } from '../template-part/edit/utils/get-template-part-icon'; export default function useTemplatePartAreaLabel( clientId ) { return useSelect( @@ -35,16 +36,15 @@ export default function useTemplatePartAreaLabel( clientId ) { return; } - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor. - // This code is lifted from this file: - // packages/block-library/src/template-part/edit/advanced-controls.js - /* eslint-disable @wordpress/data-no-store-string-literals */ - const definedAreas = - select( - 'core/editor' - ).__experimentalGetDefaultTemplatePartAreas(); - /* eslint-enable @wordpress/data-no-store-string-literals */ + const defaultTemplatePartAreas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + + const definedAreas = defaultTemplatePartAreas.map( ( item ) => ( { + ...item, + icon: getTemplatePartIcon( item.icon ), + } ) ); + const { getCurrentTheme, getEditedEntityRecord } = select( coreStore ); diff --git a/packages/block-library/src/template-part/edit/advanced-controls.js b/packages/block-library/src/template-part/edit/advanced-controls.js index 3c319a7ec0fe73..04c6d3387346ac 100644 --- a/packages/block-library/src/template-part/edit/advanced-controls.js +++ b/packages/block-library/src/template-part/edit/advanced-controls.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useEntityProp } from '@wordpress/core-data'; +import { useEntityProp, store as coreStore } from '@wordpress/core-data'; import { SelectControl, TextControl } from '@wordpress/components'; import { sprintf, __ } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; @@ -54,19 +54,19 @@ export function TemplatePartAdvancedControls( { templatePartId ); - const definedAreas = useSelect( ( select ) => { - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor. - /* eslint-disable-next-line @wordpress/data-no-store-string-literals */ - return select( - 'core/editor' - ).__experimentalGetDefaultTemplatePartAreas(); - }, [] ); + const defaultTemplatePartAreas = useSelect( + ( select ) => + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], + [] + ); - const areaOptions = definedAreas.map( ( { label, area: _area } ) => ( { - label, - value: _area, - } ) ); + const areaOptions = defaultTemplatePartAreas.map( + ( { label, area: _area } ) => ( { + label, + value: _area, + } ) + ); return ( <> diff --git a/packages/block-library/src/template-part/edit/utils/get-template-part-icon.js b/packages/block-library/src/template-part/edit/utils/get-template-part-icon.js new file mode 100644 index 00000000000000..bb13a8840c9458 --- /dev/null +++ b/packages/block-library/src/template-part/edit/utils/get-template-part-icon.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { + header as headerIcon, + footer as footerIcon, + sidebar as sidebarIcon, + symbolFilled as symbolFilledIcon, +} from '@wordpress/icons'; + +export const getTemplatePartIcon = ( iconName ) => { + if ( 'header' === iconName ) { + return headerIcon; + } else if ( 'footer' === iconName ) { + return footerIcon; + } else if ( 'sidebar' === iconName ) { + return sidebarIcon; + } + return symbolFilledIcon; +}; diff --git a/packages/block-library/src/template-part/edit/utils/hooks.js b/packages/block-library/src/template-part/edit/utils/hooks.js index 39daa4080c8160..c71327db0290c4 100644 --- a/packages/block-library/src/template-part/edit/utils/hooks.js +++ b/packages/block-library/src/template-part/edit/utils/hooks.js @@ -136,14 +136,9 @@ export function useCreateTemplatePartFromBlocks( area, setAttributes ) { export function useTemplatePartArea( area ) { return useSelect( ( select ) => { - // FIXME: @wordpress/block-library should not depend on @wordpress/editor. - // Blocks can be loaded into a *non-post* block editor. - /* eslint-disable @wordpress/data-no-store-string-literals */ const definedAreas = - select( - 'core/editor' - ).__experimentalGetDefaultTemplatePartAreas(); - /* eslint-enable @wordpress/data-no-store-string-literals */ + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; const selectedArea = definedAreas.find( ( definedArea ) => definedArea.area === area diff --git a/packages/block-library/src/template-part/variations.js b/packages/block-library/src/template-part/variations.js index acd8af13508ba9..6f62057bec33dd 100644 --- a/packages/block-library/src/template-part/variations.js +++ b/packages/block-library/src/template-part/variations.js @@ -3,23 +3,11 @@ */ import { store as coreDataStore } from '@wordpress/core-data'; import { select } from '@wordpress/data'; -import { - header as headerIcon, - footer as footerIcon, - sidebar as sidebarIcon, - symbolFilled as symbolFilledIcon, -} from '@wordpress/icons'; -function getTemplatePartIcon( iconName ) { - if ( 'header' === iconName ) { - return headerIcon; - } else if ( 'footer' === iconName ) { - return footerIcon; - } else if ( 'sidebar' === iconName ) { - return sidebarIcon; - } - return symbolFilledIcon; -} +/** + * Internal dependencies + */ +import { getTemplatePartIcon } from './edit/utils/get-template-part-icon'; export function enhanceTemplatePartVariations( settings, name ) { if ( name !== 'core/template-part' ) { diff --git a/packages/core-data/src/entities.js b/packages/core-data/src/entities.js index 3c10676750a2ab..2730cdf3d64bf4 100644 --- a/packages/core-data/src/entities.js +++ b/packages/core-data/src/entities.js @@ -31,6 +31,8 @@ export const rootEntitiesConfig = [ 'site_icon_url', 'site_logo', 'timezone_string', + 'default_template_part_areas', + 'default_template_types', 'url', ].join( ',' ), }, diff --git a/packages/edit-site/src/components/add-new-template/utils.js b/packages/edit-site/src/components/add-new-template/utils.js index e3e2faf9457926..759f3f478cadaf 100644 --- a/packages/edit-site/src/components/add-new-template/utils.js +++ b/packages/edit-site/src/components/add-new-template/utils.js @@ -3,7 +3,6 @@ */ import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; import { useMemo, useCallback } from '@wordpress/element'; import { __, _x, sprintf } from '@wordpress/i18n'; @@ -69,7 +68,8 @@ export const useExistingTemplates = () => { export const useDefaultTemplateTypes = () => { return useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplateTypes(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_types || [], [] ); }; diff --git a/packages/edit-site/src/components/editor/use-editor-title.js b/packages/edit-site/src/components/editor/use-editor-title.js index 2ad4e94ccc3e80..727b190117e02a 100644 --- a/packages/edit-site/src/components/editor/use-editor-title.js +++ b/packages/edit-site/src/components/editor/use-editor-title.js @@ -4,33 +4,46 @@ import { _x, sprintf } from '@wordpress/i18n'; import { useSelect } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; /** * Internal dependencies */ import useTitle from '../routes/use-title'; import { POST_TYPE_LABELS, TEMPLATE_POST_TYPE } from '../../utils/constants'; +import { unlock } from '../../lock-unlock'; + +const { getTemplateInfo } = unlock( editorPrivateApis ); function useEditorTitle( postType, postId ) { const { title, isLoaded } = useSelect( ( select ) => { const { getEditedEntityRecord, hasFinishedResolution } = select( coreStore ); - const { __experimentalGetTemplateInfo: getTemplateInfo } = - select( editorStore ); + const _record = getEditedEntityRecord( 'postType', postType, postId ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + ) ?? {}; + + const templateInfo = getTemplateInfo( { + template: _record, + templateTypes, + } ); + const _isLoaded = hasFinishedResolution( 'getEditedEntityRecord', [ 'postType', postType, postId, ] ); - const templateInfo = getTemplateInfo( _record ); return { title: templateInfo.title, diff --git a/packages/edit-site/src/components/page-patterns/header.js b/packages/edit-site/src/components/page-patterns/header.js index 641dc577cb7fe0..0d3763aec62c1a 100644 --- a/packages/edit-site/src/components/page-patterns/header.js +++ b/packages/edit-site/src/components/page-patterns/header.js @@ -9,7 +9,7 @@ import { __experimentalText as Text, __experimentalVStack as VStack, } from '@wordpress/components'; -import { store as editorStore } from '@wordpress/editor'; +import { store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; import { __, sprintf } from '@wordpress/i18n'; import { moreVertical } from '@wordpress/icons'; @@ -32,7 +32,8 @@ export default function PatternsHeader( { const { patternCategories } = usePatternCategories(); const templatePartAreas = useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], [] ); diff --git a/packages/edit-site/src/components/page-patterns/use-patterns.js b/packages/edit-site/src/components/page-patterns/use-patterns.js index e226298857c4da..3b3e33d5650e63 100644 --- a/packages/edit-site/src/components/page-patterns/use-patterns.js +++ b/packages/edit-site/src/components/page-patterns/use-patterns.js @@ -4,7 +4,6 @@ import { parse } from '@wordpress/blocks'; import { useSelect, createSelector } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; -import { store as editorStore } from '@wordpress/editor'; import { useMemo } from '@wordpress/element'; /** @@ -28,8 +27,7 @@ const selectTemplateParts = createSelector( ( select, categoryId, search = '' ) => { const { getEntityRecords, isResolving: isResolvingSelector } = select( coreStore ); - const { __experimentalGetDefaultTemplatePartAreas } = - select( editorStore ); + const query = { per_page: -1 }; const templateParts = getEntityRecords( 'postType', TEMPLATE_PART_POST_TYPE, query ) ?? @@ -38,7 +36,10 @@ const selectTemplateParts = createSelector( // In the case where a custom template part area has been removed we need // the current list of areas to cross check against so orphaned template // parts can be treated as uncategorized. - const knownAreas = __experimentalGetDefaultTemplatePartAreas() || []; + const knownAreas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + const templatePartAreas = knownAreas.map( ( area ) => area.area ); const templatePartHasCategory = ( item, category ) => { @@ -78,7 +79,8 @@ const selectTemplateParts = createSelector( TEMPLATE_PART_POST_TYPE, { per_page: -1 }, ] ), - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas, ] ); diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js index 77cbf87b3d439e..6a67a8f8a30fb9 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/use-template-part-areas.js @@ -1,9 +1,8 @@ /** * WordPress dependencies */ -import { useEntityRecords } from '@wordpress/core-data'; +import { useEntityRecords, store as coreStore } from '@wordpress/core-data'; import { useSelect } from '@wordpress/data'; -import { store as editorStore } from '@wordpress/editor'; /** * Internal dependencies @@ -18,7 +17,8 @@ const useTemplatePartsGroupedByArea = ( items ) => { const templatePartAreas = useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], [] ); @@ -43,7 +43,7 @@ const useTemplatePartsGroupedByArea = ( items ) => { const key = accumulator[ item.area ] ? item.area : TEMPLATE_PART_AREA_DEFAULT_CATEGORY; - accumulator[ key ].templateParts.push( item ); + accumulator[ key ]?.templateParts?.push( item ); return accumulator; }, knownAreas ); diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index 7f124e6b5f7ac6..b96ce5cb67f5e1 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -10,10 +10,7 @@ import { import { dispatch } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { createRoot, StrictMode } from '@wordpress/element'; -import { - store as editorStore, - privateApis as editorPrivateApis, -} from '@wordpress/editor'; +import { privateApis as editorPrivateApis } from '@wordpress/editor'; import { store as preferencesStore } from '@wordpress/preferences'; import { registerLegacyWidgetBlock, @@ -88,15 +85,6 @@ export function initializeEditor( id, settings ) { dispatch( editSiteStore ).updateSettings( settings ); - // Keep the defaultTemplateTypes in the core/editor settings too, - // so that they can be selected with core/editor selectors in any editor. - // This is needed because edit-site doesn't initialize with EditorProvider, - // which internally uses updateEditorSettings as well. - dispatch( editorStore ).updateEditorSettings( { - defaultTemplateTypes: settings.defaultTemplateTypes, - defaultTemplatePartAreas: settings.defaultTemplatePartAreas, - } ); - // Prevent the default browser action for files dropped outside of dropzones. window.addEventListener( 'dragover', ( e ) => e.preventDefault(), false ); window.addEventListener( 'drop', ( e ) => e.preventDefault(), false ); diff --git a/packages/editor/src/components/create-template-part-modal/index.js b/packages/editor/src/components/create-template-part-modal/index.js index 5d594cd646cc10..660439ded2300f 100644 --- a/packages/editor/src/components/create-template-part-modal/index.js +++ b/packages/editor/src/components/create-template-part-modal/index.js @@ -27,7 +27,6 @@ import { serialize } from '@wordpress/blocks'; /** * Internal dependencies */ -import { store as editorStore } from '../../store'; import { TEMPLATE_PART_POST_TYPE, TEMPLATE_PART_AREA_DEFAULT_CATEGORY, @@ -81,9 +80,11 @@ export function CreateTemplatePartModalContents( { const templatePartAreas = useSelect( ( select ) => - select( editorStore ).__experimentalGetDefaultTemplatePartAreas(), + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || [], [] ); + async function createTemplatePart() { if ( ! title || isSubmitting ) { return; diff --git a/packages/editor/src/components/document-bar/index.js b/packages/editor/src/components/document-bar/index.js index 9fffba941a4355..7b94a6fbeb3be9 100644 --- a/packages/editor/src/components/document-bar/index.js +++ b/packages/editor/src/components/document-bar/index.js @@ -29,6 +29,7 @@ import { decodeEntities } from '@wordpress/html-entities'; import { TEMPLATE_POST_TYPES } from '../../store/constants'; import { store as editorStore } from '../../store'; import usePageTypeBadge from '../../utils/pageTypeBadge'; +import { getTemplateInfo } from '../../utils/get-template-info'; /** @typedef {import("@wordpress/components").IconType} IconType */ @@ -65,9 +66,9 @@ export default function DocumentBar( props ) { getCurrentPostType, getCurrentPostId, getEditorSettings, - __experimentalGetTemplateInfo: getTemplateInfo, getRenderingMode, } = select( editorStore ); + const { getEditedEntityRecord, getPostType, @@ -80,7 +81,15 @@ export default function DocumentBar( props ) { _postType, _postId ); - const _templateInfo = getTemplateInfo( _document ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) ?? + {}; + + const _templateInfo = getTemplateInfo( { + templateTypes, + template: _document, + } ); const _postTypeLabel = getPostType( _postType )?.labels?.singular_name; return { diff --git a/packages/editor/src/components/entities-saved-states/entity-record-item.js b/packages/editor/src/components/entities-saved-states/entity-record-item.js index ca9fb2e0b169c3..e8219c4cca7ae1 100644 --- a/packages/editor/src/components/entities-saved-states/entity-record-item.js +++ b/packages/editor/src/components/entities-saved-states/entity-record-item.js @@ -12,6 +12,7 @@ import { decodeEntities } from '@wordpress/html-entities'; */ import { store as editorStore } from '../../store'; import { unlock } from '../../lock-unlock'; +import { getTemplateInfo } from '../../utils/get-template-info'; export default function EntityRecordItem( { record, checked, onChange } ) { const { name, kind, title, key } = record; @@ -33,11 +34,18 @@ export default function EntityRecordItem( { record, checked, onChange } ) { name, key ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + ) ?? {}; + return { - entityRecordTitle: - select( editorStore ).__experimentalGetTemplateInfo( - template - ).title, + entityRecordTitle: getTemplateInfo( { + template, + templateTypes, + } ).title, hasPostMetaChanges: unlock( select( editorStore ) ).hasPostMetaChanges( name, key ), diff --git a/packages/editor/src/components/post-card-panel/index.js b/packages/editor/src/components/post-card-panel/index.js index 410b8cfd4447d9..8fcca6c6bd6d40 100644 --- a/packages/editor/src/components/post-card-panel/index.js +++ b/packages/editor/src/components/post-card-panel/index.js @@ -22,6 +22,7 @@ import { import { unlock } from '../../lock-unlock'; import PostActions from '../post-actions'; import usePageTypeBadge from '../../utils/pageTypeBadge'; +import { getTemplateInfo } from '../../utils/get-template-info'; export default function PostCardPanel( { postType, @@ -30,17 +31,29 @@ export default function PostCardPanel( { } ) { const { title, icon } = useSelect( ( select ) => { - const { __experimentalGetTemplateInfo } = select( editorStore ); const { getEditedEntityRecord } = select( coreStore ); const _record = getEditedEntityRecord( 'postType', postType, postId ); - const _templateInfo = - [ TEMPLATE_POST_TYPE, TEMPLATE_PART_POST_TYPE ].includes( - postType - ) && __experimentalGetTemplateInfo( _record ); + + const { default_template_types: templateTypes = [] } = + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + ) ?? {}; + + const _templateInfo = [ + TEMPLATE_POST_TYPE, + TEMPLATE_PART_POST_TYPE, + ].includes( postType ) + ? getTemplateInfo( { + template: _record, + templateTypes, + } ) + : {}; + return { title: _templateInfo?.title || _record?.title, icon: unlock( select( editorStore ) ).getPostIcon( postType, { diff --git a/packages/editor/src/private-apis.js b/packages/editor/src/private-apis.js index b49b2a69a3bf2b..2699858b3164f6 100644 --- a/packages/editor/src/private-apis.js +++ b/packages/editor/src/private-apis.js @@ -25,6 +25,7 @@ import { GlobalStylesProvider, } from './components/global-styles-provider'; import { registerCoreBlockBindingsSources } from './bindings/api'; +import { getTemplateInfo } from './utils/get-template-info'; const { store: interfaceStore, ...remainingInterfaceApis } = interfaceApis; @@ -46,7 +47,7 @@ lock( privateApis, { ViewMoreMenuGroup, ResizableEditor, registerCoreBlockBindingsSources, - + getTemplateInfo, // This is a temporary private API while we're updating the site editor to use EditorProvider. interfaceStore, ...remainingInterfaceApis, diff --git a/packages/editor/src/store/private-selectors.js b/packages/editor/src/store/private-selectors.js index 9af0512c19dbdd..56fa6728371489 100644 --- a/packages/editor/src/store/private-selectors.js +++ b/packages/editor/src/store/private-selectors.js @@ -20,11 +20,7 @@ import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ -import { - getRenderingMode, - getCurrentPost, - __experimentalGetDefaultTemplatePartAreas, -} from './selectors'; +import { getRenderingMode, getCurrentPost } from './selectors'; import { getEntityActions as _getEntityActions, getEntityFields as _getEntityFields, @@ -102,9 +98,13 @@ export const getPostIcon = createRegistrySelector( postType === 'wp_template' ) { return ( - __experimentalGetDefaultTemplatePartAreas( state ).find( - ( item ) => options.area === item.area - )?.icon || layout + ( + select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + )?.default_template_part_areas || [] + ).find( ( item ) => options.area === item.area )?.icon || + layout ); } if ( CARD_ICONS[ postType ] ) { diff --git a/packages/editor/src/store/selectors.js b/packages/editor/src/store/selectors.js index db452be5c17abb..c17e80f771ea36 100644 --- a/packages/editor/src/store/selectors.js +++ b/packages/editor/src/store/selectors.js @@ -12,7 +12,6 @@ import { addQueryArgs, cleanForSlug } from '@wordpress/url'; import { createSelector, createRegistrySelector } from '@wordpress/data'; import deprecated from '@wordpress/deprecated'; import { Platform } from '@wordpress/element'; -import { layout } from '@wordpress/icons'; import { store as blockEditorStore } from '@wordpress/block-editor'; import { store as coreStore } from '@wordpress/core-data'; import { store as preferencesStore } from '@wordpress/preferences'; @@ -29,6 +28,7 @@ import { import { getPostRawValue } from './reducer'; import { getTemplatePartIcon } from '../utils/get-template-part-icon'; import { unlock } from '../lock-unlock'; +import { getTemplateInfo } from '../utils/get-template-info'; /** * Shared reference to an empty object for cases where it is important to avoid @@ -1702,16 +1702,20 @@ export const getBlockListSettings = getBlockEditorSelector( 'getBlockListSettings' ); -/** - * Returns the default template types. - * - * @param {Object} state Global application state. - * - * @return {Object} The template types. - */ -export function __experimentalGetDefaultTemplateTypes( state ) { - return getEditorSettings( state )?.defaultTemplateTypes; -} +export const __experimentalGetDefaultTemplateTypes = createRegistrySelector( + ( select ) => () => { + deprecated( + "select('core/editor').__experimentalGetDefaultTemplateTypes", + { + since: '6.7', + alternative: + "select('core/core-data').getEntityRecord( 'root', '__unstableBase' )?.default_template_types", + } + ); + return select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_types; + } +); /** * Returns the default template part areas. @@ -1720,15 +1724,26 @@ export function __experimentalGetDefaultTemplateTypes( state ) { * * @return {Array} The template part areas. */ -export const __experimentalGetDefaultTemplatePartAreas = createSelector( - ( state ) => { - const areas = - getEditorSettings( state )?.defaultTemplatePartAreas ?? []; - return areas.map( ( item ) => { - return { ...item, icon: getTemplatePartIcon( item.icon ) }; - } ); - }, - ( state ) => [ getEditorSettings( state )?.defaultTemplatePartAreas ] +export const __experimentalGetDefaultTemplatePartAreas = createRegistrySelector( + ( select ) => + createSelector( () => { + deprecated( + "select('core/editor').__experimentalGetDefaultTemplatePartAreas", + { + since: '6.7', + alternative: + "select('core/core-data').getEntityRecord( 'root', '__unstableBase' )?.default_template_part_areas", + } + ); + + const areas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + + return areas.map( ( item ) => { + return { ...item, icon: getTemplatePartIcon( item.icon ) }; + } ); + } ) ); /** @@ -1739,20 +1754,30 @@ export const __experimentalGetDefaultTemplatePartAreas = createSelector( * * @return {Object} The template type. */ -export const __experimentalGetDefaultTemplateType = createSelector( - ( state, slug ) => { - const templateTypes = __experimentalGetDefaultTemplateTypes( state ); - if ( ! templateTypes ) { - return EMPTY_OBJECT; - } +export const __experimentalGetDefaultTemplateType = createRegistrySelector( + ( select ) => + createSelector( ( state, slug ) => { + deprecated( + "select('core/editor').__experimentalGetDefaultTemplateType", + { + since: '6.7', + } + ); + const templateTypes = select( coreStore ).getEntityRecord( + 'root', + '__unstableBase' + )?.default_template_types; + + if ( ! templateTypes ) { + return EMPTY_OBJECT; + } - return ( - Object.values( templateTypes ).find( - ( type ) => type.slug === slug - ) ?? EMPTY_OBJECT - ); - }, - ( state ) => [ __experimentalGetDefaultTemplateTypes( state ) ] + return ( + Object.values( templateTypes ).find( + ( type ) => type.slug === slug + ) ?? EMPTY_OBJECT + ); + } ) ); /** @@ -1763,38 +1788,31 @@ export const __experimentalGetDefaultTemplateType = createSelector( * @param {Object} template The template for which we need information. * @return {Object} Information about the template, including title, description, and icon. */ -export const __experimentalGetTemplateInfo = createSelector( - ( state, template ) => { - if ( ! template ) { - return EMPTY_OBJECT; - } +export const __experimentalGetTemplateInfo = createRegistrySelector( + ( select ) => + createSelector( ( state, template ) => { + deprecated( "select('core/editor').__experimentalGetTemplateInfo", { + since: '6.7', + } ); - const { description, slug, title, area } = template; - const { title: defaultTitle, description: defaultDescription } = - __experimentalGetDefaultTemplateType( state, slug ); + if ( ! template ) { + return EMPTY_OBJECT; + } - const templateTitle = - typeof title === 'string' ? title : title?.rendered; - const templateDescription = - typeof description === 'string' ? description : description?.raw; - const templateIcon = - __experimentalGetDefaultTemplatePartAreas( state ).find( - ( item ) => area === item.area - )?.icon || layout; + const templateTypes = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_types || []; - return { - title: - templateTitle && templateTitle !== slug - ? templateTitle - : defaultTitle || slug, - description: templateDescription || defaultDescription, - icon: templateIcon, - }; - }, - ( state ) => [ - __experimentalGetDefaultTemplateTypes( state ), - __experimentalGetDefaultTemplatePartAreas( state ), - ] + const templateAreas = + select( coreStore ).getEntityRecord( 'root', '__unstableBase' ) + ?.default_template_part_areas || []; + + return getTemplateInfo( { + template, + templateAreas, + templateTypes, + } ); + } ) ); /** diff --git a/packages/editor/src/store/test/selectors.js b/packages/editor/src/store/test/selectors.js index 1de25604ebd7e3..2a36fabcd2ec97 100644 --- a/packages/editor/src/store/test/selectors.js +++ b/packages/editor/src/store/test/selectors.js @@ -16,7 +16,6 @@ import { getBlockTypes, } from '@wordpress/blocks'; import { RawHTML } from '@wordpress/element'; -import { layout, footer, header } from '@wordpress/icons'; /** * Internal dependencies @@ -188,43 +187,11 @@ const { isPostAutosavingLocked, canUserUseUnfilteredHTML, getPostTypeLabel, - __experimentalGetDefaultTemplateType, - __experimentalGetDefaultTemplateTypes, - __experimentalGetTemplateInfo, - __experimentalGetDefaultTemplatePartAreas, isEditorPanelRemoved, isInserterOpened, isListViewOpened, } = selectors; -const defaultTemplateTypes = [ - { - title: 'Default (Index)', - description: 'Main template', - slug: 'index', - }, - { - title: '404 (Not Found)', - description: 'Applied when content cannot be found', - slug: '404', - }, -]; - -const defaultTemplatePartAreas = [ - { - area: 'header', - label: 'Header', - description: 'Some description of a header', - icon: 'header', - }, - { - area: 'footer', - label: 'Footer', - description: 'Some description of a footer', - icon: 'footer', - }, -]; - describe( 'selectors', () => { let cachedSelectors; @@ -2766,228 +2733,6 @@ describe( 'selectors', () => { } ); } ); - describe( '__experimentalGetDefaultTemplateTypes', () => { - const state = { editorSettings: { defaultTemplateTypes } }; - - it( 'returns undefined if there are no default template types', () => { - const emptyState = { editorSettings: {} }; - expect( - __experimentalGetDefaultTemplateTypes( emptyState ) - ).toBeUndefined(); - } ); - - it( 'returns a list of default template types if present in state', () => { - expect( - __experimentalGetDefaultTemplateTypes( state ) - ).toHaveLength( 2 ); - } ); - } ); - - describe( '__experimentalGetDefaultTemplatePartAreas', () => { - const state = { editorSettings: { defaultTemplatePartAreas } }; - - it( 'returns empty array if there are no default template part areas', () => { - const emptyState = { editorSettings: {} }; - expect( - __experimentalGetDefaultTemplatePartAreas( emptyState ) - ).toHaveLength( 0 ); - } ); - - it( 'returns a list of default template part areas if present in state', () => { - expect( - __experimentalGetDefaultTemplatePartAreas( state ) - ).toHaveLength( 2 ); - } ); - - it( 'assigns an icon to each area', () => { - const templatePartAreas = - __experimentalGetDefaultTemplatePartAreas( state ); - templatePartAreas.forEach( ( area ) => - expect( area.icon ).not.toBeNull() - ); - } ); - } ); - - describe( '__experimentalGetDefaultTemplateType', () => { - const state = { editorSettings: { defaultTemplateTypes } }; - - it( 'returns an empty object if there are no default template types', () => { - const emptyState = { editorSettings: {} }; - expect( - __experimentalGetDefaultTemplateType( emptyState, 'slug' ) - ).toEqual( {} ); - } ); - - it( 'returns an empty object if the requested slug is not found', () => { - expect( - __experimentalGetDefaultTemplateType( state, 'foobar' ) - ).toEqual( {} ); - } ); - - it( 'returns the requested default template type', () => { - expect( - __experimentalGetDefaultTemplateType( state, 'index' ) - ).toEqual( { - title: 'Default (Index)', - description: 'Main template', - slug: 'index', - } ); - } ); - - it( 'returns the requested default template type even when the slug is numeric', () => { - expect( - __experimentalGetDefaultTemplateType( state, '404' ) - ).toEqual( { - title: '404 (Not Found)', - description: 'Applied when content cannot be found', - slug: '404', - } ); - } ); - } ); - - describe( '__experimentalGetTemplateInfo', () => { - const state = { - editorSettings: { defaultTemplateTypes, defaultTemplatePartAreas }, - }; - - it( 'should return an empty object if no template is passed', () => { - expect( __experimentalGetTemplateInfo( state, null ) ).toEqual( - {} - ); - expect( __experimentalGetTemplateInfo( state, undefined ) ).toEqual( - {} - ); - expect( __experimentalGetTemplateInfo( state, false ) ).toEqual( - {} - ); - } ); - - it( 'should return the default title if none is defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { slug: 'index' } ).title - ).toEqual( 'Default (Index)' ); - } ); - - it( 'should return the rendered title if defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - title: { rendered: 'test title' }, - } ).title - ).toEqual( 'test title' ); - } ); - - it( 'should return the slug if no title is found', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'not a real template', - } ).title - ).toEqual( 'not a real template' ); - } ); - - it( 'should return the default description if none is defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { slug: 'index' } ) - .description - ).toEqual( 'Main template' ); - } ); - - it( 'should return the raw excerpt as description if defined on the template', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - description: { raw: 'test description' }, - } ).description - ).toEqual( 'test description' ); - } ); - - it( 'should return a title, description, and icon', () => { - expect( - __experimentalGetTemplateInfo( state, { slug: 'index' } ) - ).toEqual( { - title: 'Default (Index)', - description: 'Main template', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - title: { rendered: 'test title' }, - } ) - ).toEqual( { - title: 'test title', - description: 'Main template', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - description: { raw: 'test description' }, - } ) - ).toEqual( { - title: 'Default (Index)', - description: 'test description', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'index', - title: { rendered: 'test title' }, - description: { raw: 'test description' }, - } ) - ).toEqual( { - title: 'test title', - description: 'test description', - icon: layout, - } ); - } ); - - it( 'should return correct icon based on area', () => { - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = uncategorized', - area: 'uncategorized', - } ) - ).toEqual( { - title: 'template part, area = uncategorized', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = invalid', - area: 'invalid', - } ) - ).toEqual( { - title: 'template part, area = invalid', - icon: layout, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = header', - area: 'header', - } ) - ).toEqual( { - title: 'template part, area = header', - icon: header, - } ); - - expect( - __experimentalGetTemplateInfo( state, { - slug: 'template part, area = footer', - area: 'footer', - } ) - ).toEqual( { - title: 'template part, area = footer', - icon: footer, - } ); - } ); - } ); - describe( 'getPostTypeLabel', () => { it( 'should return the correct label for the current post type', () => { const postTypes = [ diff --git a/packages/editor/src/utils/get-template-info.js b/packages/editor/src/utils/get-template-info.js new file mode 100644 index 00000000000000..bc84c06c9399d4 --- /dev/null +++ b/packages/editor/src/utils/get-template-info.js @@ -0,0 +1,52 @@ +/** + * WordPress dependencies + */ +import { layout } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { getTemplatePartIcon } from './get-template-part-icon'; +const EMPTY_OBJECT = {}; + +/** + * Helper function to retrieve the corresponding template info for a given template. + * @param {Object} params + * @param {Array} params.templateTypes + * @param {Array} [params.templateAreas] + * @param {Object} params.template + */ +export const getTemplateInfo = ( params ) => { + if ( ! params ) { + return EMPTY_OBJECT; + } + + const { templateTypes, templateAreas, template } = params; + + const { description, slug, title, area } = template; + + const { title: defaultTitle, description: defaultDescription } = + Object.values( templateTypes ).find( ( type ) => type.slug === slug ) ?? + EMPTY_OBJECT; + + const templateTitle = typeof title === 'string' ? title : title?.rendered; + const templateDescription = + typeof description === 'string' ? description : description?.raw; + + const templateAreasWithIcon = templateAreas?.map( ( item ) => ( { + ...item, + icon: getTemplatePartIcon( item.icon ), + } ) ); + + const templateIcon = + templateAreasWithIcon?.find( ( item ) => area === item.area )?.icon || + layout; + + return { + title: + templateTitle && templateTitle !== slug + ? templateTitle + : defaultTitle || slug, + description: templateDescription || defaultDescription, + icon: templateIcon, + }; +}; diff --git a/packages/editor/src/utils/test/get-template-info.js b/packages/editor/src/utils/test/get-template-info.js new file mode 100644 index 00000000000000..d0ffe15aa902ad --- /dev/null +++ b/packages/editor/src/utils/test/get-template-info.js @@ -0,0 +1,224 @@ +/** + * WordPress dependencies + */ +import { footer, header, layout } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { getTemplateInfo } from '../get-template-info'; + +describe( '__experimentalGetTemplateInfo', () => { + const defaultTemplateTypes = [ + { + title: 'Default (Index)', + description: 'Main template', + slug: 'index', + }, + { + title: '404 (Not Found)', + description: 'Applied when content cannot be found', + slug: '404', + }, + ]; + + const defaultTemplatePartAreas = [ + { + area: 'header', + label: 'Header', + description: 'Some description of a header', + icon: 'header', + }, + { + area: 'footer', + label: 'Footer', + description: 'Some description of a footer', + icon: 'footer', + }, + ]; + + it( 'should return an empty object if no template is passed', () => { + expect( getTemplateInfo( undefined ) ).toEqual( {} ); + expect( getTemplateInfo( null ) ).toEqual( {} ); + expect( getTemplateInfo( false ) ).toEqual( {} ); + } ); + + it( 'should return the default title if none is defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + }, + } ).title + ).toEqual( 'Default (Index)' ); + } ); + + it( 'should return the rendered title if defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + title: { rendered: 'test title' }, + }, + } ).title + ).toEqual( 'test title' ); + } ); + + it( 'should return the slug if no title is found', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'not a real template', + }, + } ).title + ).toEqual( 'not a real template' ); + } ); + + it( 'should return the default description if none is defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + }, + } ).description + ).toEqual( 'Main template' ); + } ); + + it( 'should return the raw excerpt as description if defined on the template', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + description: { raw: 'test description' }, + }, + } ).description + ).toEqual( 'test description' ); + } ); + + it( 'should return a title, description, and icon', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { slug: 'index' }, + } ) + ).toEqual( { + title: 'Default (Index)', + description: 'Main template', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + title: { rendered: 'test title' }, + }, + } ) + ).toEqual( { + title: 'test title', + description: 'Main template', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + description: { raw: 'test description' }, + }, + } ) + ).toEqual( { + title: 'Default (Index)', + description: 'test description', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'index', + title: { rendered: 'test title' }, + description: { raw: 'test description' }, + }, + } ) + ).toEqual( { + title: 'test title', + description: 'test description', + icon: layout, + } ); + } ); + + it( 'should return correct icon based on area', () => { + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = uncategorized', + area: 'uncategorized', + }, + } ) + ).toEqual( { + title: 'template part, area = uncategorized', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = invalid', + area: 'invalid', + }, + } ) + ).toEqual( { + title: 'template part, area = invalid', + icon: layout, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = header', + area: 'header', + }, + } ) + ).toEqual( { + title: 'template part, area = header', + icon: header, + } ); + + expect( + getTemplateInfo( { + templateAreas: defaultTemplatePartAreas, + templateTypes: defaultTemplateTypes, + template: { + slug: 'template part, area = footer', + area: 'footer', + }, + } ) + ).toEqual( { + title: 'template part, area = footer', + icon: footer, + } ); + } ); +} );