From c538786e2aa1c657f39ec38fdc99145be72cf96f Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 26 Jul 2022 17:12:38 +0100 Subject: [PATCH] Add has_archive checks --- lib/compat/wordpress-6.1/rest-api.php | 32 ++++++ .../add-new-template/new-template.js | 2 + .../src/components/add-new-template/utils.js | 101 ++++++++++++++---- 3 files changed, 115 insertions(+), 20 deletions(-) diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index 88b6d3bbb9a4db..f3391389d9e38a 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -61,3 +61,35 @@ function gutenberg_add_site_icon_url_to_index( WP_REST_Response $response ) { return $response; } add_action( 'rest_index', 'gutenberg_add_site_icon_url_to_index' ); + +/** + * Returns the has_archive post type field. + * + * @param array $type The response data. + * @param string $field_name The field name. The function handles field has_archive. + */ +function gutenberg_get_post_type_has_archive_field( $type, $field_name ) { + if ( ! empty( $type ) && ! empty( $type['slug'] ) && 'has_archive' === $field_name ) { + $post_type_object = get_post_type_object( $type['slug'] ); + return $post_type_object->has_archive; + } +} + +/** + * Registers the has_archive post type REST API field. + */ +function gutenberg_register_has_archive_on_post_types_endpoint() { + register_rest_field( + 'type', + 'has_archive', + array( + 'get_callback' => 'gutenberg_get_post_type_has_archive_field', + 'schema' => array( + 'description' => __( 'If the value is a string, the value will be used as the archive slug. If the value is false the post type has no archive.', 'gutenberg' ), + 'type' => array( 'string', 'boolean' ), + 'context' => array( 'view', 'edit' ), + ), + ) + ); +} +add_action( 'rest_api_init', 'gutenberg_register_has_archive_on_post_types_endpoint' ); 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 0fdbf8a3c3d1f0..ea7a1a840024e3 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 @@ -41,6 +41,7 @@ import { useTaxonomiesMenuItems, usePostTypeMenuItems, useAuthorMenuItem, + usePostTypeArchiveMenuItems, } from './utils'; import AddCustomGenericTemplateModal from './add-custom-generic-template-modal'; import { useHistory } from '../routes'; @@ -301,6 +302,7 @@ function useMissingTemplates( } ); const missingTemplates = [ ...enhancedMissingDefaultTemplateTypes, + ...usePostTypeArchiveMenuItems(), ...postTypesMenuItems, ...taxonomiesMenuItems, ]; 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 607848291bdb16..4fa707ae2e7ce7 100644 --- a/packages/edit-site/src/components/add-new-template/utils.js +++ b/packages/edit-site/src/components/add-new-template/utils.js @@ -12,7 +12,7 @@ import { store as editorStore } from '@wordpress/editor'; import { decodeEntities } from '@wordpress/html-entities'; import { useMemo, useCallback } from '@wordpress/element'; import { __, sprintf } from '@wordpress/i18n'; -import { blockMeta, post } from '@wordpress/icons'; +import { blockMeta, post, archive } from '@wordpress/icons'; /** * @typedef IHasNameAndId @@ -86,10 +86,89 @@ const usePublicTaxonomies = () => { }, [ taxonomies ] ); }; +function usePostTypeNeedsUniqueIdentifier( publicPostTypes ) { + const postTypeLabels = useMemo( () => + publicPostTypes?.reduce( ( accumulator, { labels } ) => { + const singularName = labels.singular_name.toLowerCase(); + accumulator[ singularName ] = + ( accumulator[ singularName ] || 0 ) + 1; + return accumulator; + }, {} ) + ); + return useCallback( + ( { labels, slug } ) => { + const singularName = labels.singular_name.toLowerCase(); + return postTypeLabels[ singularName ] > 1 && singularName !== slug; + }, + [ postTypeLabels ] + ); +} + +export function usePostTypeArchiveMenuItems() { + const publicPostTypes = usePublicPostTypes(); + const postTypesWithArchives = useMemo( + () => publicPostTypes?.filter( ( postType ) => postType.has_archive ), + [ publicPostTypes ] + ); + const existingTemplates = useExistingTemplates(); + const needsUniqueIdentifier = usePostTypeNeedsUniqueIdentifier( + postTypesWithArchives + ); + return useMemo( + () => + postTypesWithArchives + ?.filter( + ( postType ) => + ! existingTemplates.some( + ( existingTemplate ) => + existingTemplate.slug === + 'archive-' + postType.slug + ) + ) + .map( ( postType ) => { + let title; + if ( needsUniqueIdentifier( postType ) ) { + title = sprintf( + // translators: %1s: Name of the post type e.g: "Post"; %2s: Slug of the post type e.g: "book". + __( 'Archive: %1$s (%2$s)' ), + postType.labels.singular_name, + postType.slug + ); + } else { + title = sprintf( + // translators: %s: Name of the post type e.g: "Post". + __( 'Archive: %s' ), + postType.labels.singular_name + ); + } + return { + slug: 'archive-' + postType.slug, + description: sprintf( + // translators: %s: Name of the post type e.g: "Post". + __( + 'Displays an archive with the latests posts of type: %s.' + ), + postType.labels.singular_name + ), + title, + // `icon` is the `menu_icon` property of a post type. We + // only handle `dashicons` for now, even if the `menu_icon` + // also supports urls and svg as values. + icon: postType.icon?.startsWith( 'dashicons-' ) + ? postType.icon.slice( 10 ) + : archive, + }; + } ) || [], + [ postTypesWithArchives, existingTemplates, needsUniqueIdentifier ] + ); +} + export const usePostTypeMenuItems = ( onClickMenuItem ) => { const publicPostTypes = usePublicPostTypes(); const existingTemplates = useExistingTemplates(); const defaultTemplateTypes = useDefaultTemplateTypes(); + const needsUniqueIdentifier = + usePostTypeNeedsUniqueIdentifier( publicPostTypes ); // `page`is a special case in template hierarchy. const templatePrefixes = useMemo( () => @@ -103,21 +182,6 @@ export const usePostTypeMenuItems = ( onClickMenuItem ) => { }, {} ), [ publicPostTypes ] ); - // We need to keep track of naming conflicts. If a conflict - // occurs, we need to add slug. - const postTypeLabels = publicPostTypes?.reduce( - ( accumulator, { labels } ) => { - const singularName = labels.singular_name.toLowerCase(); - accumulator[ singularName ] = - ( accumulator[ singularName ] || 0 ) + 1; - return accumulator; - }, - {} - ); - const needsUniqueIdentifier = ( labels, slug ) => { - const singularName = labels.singular_name.toLowerCase(); - return postTypeLabels[ singularName ] > 1 && singularName !== slug; - }; const postTypesInfo = useEntitiesInfo( 'postType', templatePrefixes ); const existingTemplateSlugs = ( existingTemplates || [] ).map( ( { slug } ) => slug @@ -134,10 +198,7 @@ export const usePostTypeMenuItems = ( onClickMenuItem ) => { ); const hasGeneralTemplate = existingTemplateSlugs?.includes( generalTemplateSlug ); - const _needsUniqueIdentifier = needsUniqueIdentifier( - labels, - slug - ); + const _needsUniqueIdentifier = needsUniqueIdentifier( postType ); let menuItemTitle = sprintf( // translators: %s: Name of the post type e.g: "Post". __( 'Single item: %s' ),