diff --git a/lib/rest-api.php b/lib/rest-api.php index 424927acf1f4a0..f79724492132cd 100644 --- a/lib/rest-api.php +++ b/lib/rest-api.php @@ -36,3 +36,23 @@ function gutenberg_register_edit_site_export_controller_endpoints() { $edit_site_export_controller->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' ); + + +function gutenberg_register_archive_link_field() { + register_rest_field( + 'type', + 'archive_link', + array( + 'get_callback' => static function ( $post_object ) { + return (string) get_post_type_archive_link( $post_object['slug'] ); + }, + 'update_callback' => null, + 'schema' => array( + 'description' => __( 'Link to the post type archive page', 'default' ), + 'type' => 'string', + 'context' => array( 'view', 'edit' ), + ), + ) + ); +} +add_action( 'rest_api_init', 'gutenberg_register_archive_link_field' ); diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js index 27084e5e77b96a..e4fd82fe02782a 100644 --- a/packages/block-editor/src/components/link-control/search-item.js +++ b/packages/block-editor/src/components/link-control/search-item.js @@ -13,6 +13,7 @@ import { file, home, verse, + customPostType, } from '@wordpress/icons'; import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; import { safeDecodeURI, filterURLForDisplay, getPath } from '@wordpress/url'; @@ -42,18 +43,16 @@ function SearchItemIcon( { isURL, suggestion } ) { icon = verse; } } + } else { + icon = customPostType; } - if ( icon ) { - return ( - - ); - } - - return null; + return ( + + ); } /** @@ -156,6 +155,10 @@ function getVisualTypeName( suggestion ) { return 'blog home'; } + if ( suggestion.kind === 'post-type-archive' ) { + return 'archive'; + } + // Rename 'post_tag' to 'tag'. Ideally, the API would return the localised CPT or taxonomy label. return suggestion.type === 'post_tag' ? 'tag' : suggestion.type; } diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php index 81df2099dfc188..92f5115c1300de 100644 --- a/packages/block-library/src/navigation-link/index.php +++ b/packages/block-library/src/navigation-link/index.php @@ -386,6 +386,7 @@ function block_core_navigation_link_filter_variations( $variations, $block_type * @return array */ function block_core_navigation_link_build_variations() { + $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' ); $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' ); @@ -407,6 +408,28 @@ function block_core_navigation_link_build_variations() { $variations[] = $variation; } } + + // If any of the post types have `has_archive` set to true then add a post-type-archive variation. + $has_archive = array(); + + foreach ( $post_types as $post_type ) { + if ( $post_type->has_archive ) { + $has_archive[] = $post_type; + } + } + + if ( $has_archive ) { + $variation = array( + 'name' => 'post-type-archive', + 'title' => __( 'Post Type Archive Link' ), + 'description' => __( 'A link to a post type archive' ), + 'attributes' => array( + 'type' => 'all', + 'kind' => 'post-type-archive', + ), + ); + $variations[] = $variation; + } } if ( $taxonomies ) { foreach ( $taxonomies as $taxonomy ) { diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js index 52db034c6f980c..3637438c7a8b0e 100644 --- a/packages/block-library/src/navigation-link/link-ui.js +++ b/packages/block-library/src/navigation-link/link-ui.js @@ -63,6 +63,9 @@ export function getSuggestionsQuery( type, kind ) { if ( kind === 'taxonomy' ) { return { type: 'term', subtype: type }; } + if ( kind === 'post-type-archive' ) { + return { type: 'post-type-archive' }; + } if ( kind === 'post-type' ) { return { type: 'post', subtype: type }; } diff --git a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts index 29012197589035..198bb71884ae06 100644 --- a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts +++ b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts @@ -21,7 +21,7 @@ export type SearchOptions = { /** * Filters by search type. */ - type?: 'attachment' | 'post' | 'term' | 'post-format'; + type?: 'attachment' | 'post' | 'term' | 'post-format' | 'post-type-archive'; /** * Slug of the post-type or taxonomy. */ @@ -58,6 +58,12 @@ type MediaAPIResult = { type: string; }; +type PostTypesAPIResult = { + slug: string; + name: string; + archive_link: string; +}; + export type SearchResult = { /** * Post or term id. @@ -240,6 +246,31 @@ export default async function fetchLinkSuggestions( ); } + if ( ! type || type === 'post-type-archive' ) { + queries.push( + apiFetch< PostTypesAPIResult[] >( { + path: addQueryArgs( '/wp/v2/types' ), + } ) + .then( ( results ) => { + const resultValues = Object.values( results ); + return resultValues + .filter( ( result ) => !! result.archive_link ) // Filter out results with falsy archive_link, including empty strings + .map( ( result, index ) => { + return { + id: index + 1, // avoid results being filtered due to falsy id + url: result.archive_link, + title: + decodeEntities( result.name || '' ) || + __( '(no title)' ), + type: result.slug, + kind: 'post-type-archive', + }; + } ); + } ) + .catch( () => [] ) // Fail by returning no results. + ); + } + const responses = await Promise.all( queries ); let results = responses.flat(); diff --git a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js index ad0014ff86ecb8..182a97fa306ff5 100644 --- a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js +++ b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js @@ -86,6 +86,16 @@ jest.mock( '@wordpress/api-fetch', () => 'http://localhost:8888/wp-content/uploads/2022/03/test-pdf.pdf', }, ] ); + case '/wp/v2/search?search=&per_page=20&type=post-type-archive': + return Promise.resolve( [ + { + id: 'books', + title: 'All Books', + url: 'http://wordpress.local/books/', + type: 'books-archive', + kind: 'post-type-archive', + }, + ] ); default: return Promise.resolve( [ { @@ -187,7 +197,7 @@ describe( 'fetchLinkSuggestions', () => { ); } ); - it( 'returns suggestions from post, term, post-format and media', () => { + it( 'returns suggestions from post, term, post-format, media and post-type-archive', () => { return fetchLinkSuggestions( '', {} ).then( ( suggestions ) => expect( suggestions ).toEqual( [ { @@ -232,6 +242,13 @@ describe( 'fetchLinkSuggestions', () => { type: 'attachment', kind: 'media', }, + { + id: 'books', + title: 'All Books', + url: 'http://wordpress.local/books/', + type: 'books-archive', + kind: 'post-type-archive', + }, ] ) ); } );