diff --git a/backport-changelog/7.1/11272.md b/backport-changelog/7.1/11272.md index d615b1c64a32a2..177a833e6334d4 100644 --- a/backport-changelog/7.1/11272.md +++ b/backport-changelog/7.1/11272.md @@ -3,3 +3,4 @@ https://github.com/WordPress/wordpress-develop/pull/11272 * https://github.com/WordPress/gutenberg/pull/76573 * https://github.com/WordPress/gutenberg/pull/76734 * https://github.com/WordPress/gutenberg/pull/76622 +* https://github.com/WordPress/gutenberg/pull/76823 diff --git a/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php b/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php index 6146aa42ce2854..929e87b6483b72 100644 --- a/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php +++ b/lib/compat/wordpress-7.1/class-gutenberg-rest-view-config-controller-7-1.php @@ -119,9 +119,11 @@ public function get_items( $request ) { } elseif ( 'postType' === $kind && 'wp_block' === $name ) { $default_layouts = $this->get_default_layouts_for_wp_block(); $default_view = $this->get_default_view_for_wp_block( $default_layouts ); + $view_list = $this->get_view_list_for_wp_block(); } elseif ( 'postType' === $kind && 'wp_template_part' === $name ) { $default_layouts = $this->get_default_layouts_for_wp_template_part(); $default_view = $this->get_default_view_for_wp_template_part( $default_layouts ); + $view_list = $this->get_view_list_for_wp_template_part(); } elseif ( 'postType' === $kind && 'wp_template' === $name ) { $default_view = $this->get_default_view_for_wp_template(); $default_layouts = $this->get_default_layouts_for_wp_template(); @@ -789,6 +791,116 @@ private static function get_wp_templates_author_text_field( $template_object ) { return ''; } + /** + * Returns the view list for the wp_template_part post type. + * + * Builds entries from the registered template part areas (header, footer, etc.). + * + * @return array View list entries. + */ + private function get_view_list_for_wp_template_part() { + $view_list = array( + array( + 'title' => __( 'All template parts', 'gutenberg' ), + 'slug' => 'all-parts', + ), + ); + + $areas = get_allowed_block_template_part_areas(); + + // Ensure default areas appear in a consistent order. + $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' ); + $ordered_areas = array(); + $remaining_areas = array(); + foreach ( $areas as $area ) { + $position = array_search( $area['area'], $preferred_order, true ); + if ( false !== $position ) { + $ordered_areas[ $position ] = $area; + } else { + $remaining_areas[] = $area; + } + } + ksort( $ordered_areas ); + $areas = array_merge( array_values( $ordered_areas ), $remaining_areas ); + + foreach ( $areas as $area ) { + $view_list[] = array( + 'title' => $area['label'], + 'slug' => $area['area'], + 'view' => array( + 'filters' => array( + array( + 'field' => 'area', + 'operator' => 'is', + 'value' => $area['area'], + 'isLocked' => true, + ), + ), + ), + ); + } + + return $view_list; + } + + /** + * Returns the view list for the wp_block (patterns) post type. + * + * Builds entries from registered block pattern categories and user pattern categories. + * + * @return array View list entries. + */ + private function get_view_list_for_wp_block() { + $view_list = array( + array( + 'title' => __( 'All patterns', 'gutenberg' ), + 'slug' => 'all-patterns', + ), + array( + 'title' => __( 'My patterns', 'gutenberg' ), + 'slug' => 'my-patterns', + ), + ); + + // Gather categories from the block pattern categories registry. + $registry = WP_Block_Pattern_Categories_Registry::get_instance(); + $categories = array(); + + foreach ( $registry->get_all_registered() as $category ) { + $categories[ $category['name'] ] = $category['label']; + } + + // Ensure "Uncategorized" is always included for patterns + // that have no category assigned. + $categories['uncategorized'] ??= __( 'Uncategorized', 'gutenberg' ); + + // Also gather user-created pattern categories (wp_pattern_category taxonomy). + $user_terms = get_terms( + array( + 'taxonomy' => 'wp_pattern_category', + 'hide_empty' => false, + ) + ); + + if ( ! is_wp_error( $user_terms ) ) { + foreach ( $user_terms as $term ) { + $categories[ $term->slug ] = $term->name; + } + } + + // Sort categories alphabetically by label. + asort( $categories, SORT_NATURAL | SORT_FLAG_CASE ); + + foreach ( $categories as $name => $label ) { + $view_list[] = array( + 'title' => $label, + 'slug' => $name, + ); + } + + return $view_list; + } + private function get_default_view_for_wp_template() { return array( 'type' => 'grid', diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js index e1770bfa81f2ee..270b6be76376f2 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-patterns/index.js @@ -6,9 +6,11 @@ import { __experimentalItem as Item, } from '@wordpress/components'; import { getTemplatePartIcon } from '@wordpress/editor'; +import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { file } from '@wordpress/icons'; import { privateApis as routerPrivateApis } from '@wordpress/router'; +import { useViewConfig } from '@wordpress/views'; /** * Internal dependencies @@ -28,70 +30,44 @@ import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); function CategoriesGroup( { - templatePartAreas, - patternCategories, + templatePartViews, + patternViews, + templatePartCounts, + patternCounts, currentCategory, currentType, } ) { - const [ allPatterns, ...otherPatterns ] = patternCategories; - return ( - templateParts?.length || 0 ) - .reduce( ( acc, val ) => acc + val, 0 ) } - icon={ getTemplatePartIcon() } /* no name, so it provides the fallback icon */ - label={ __( 'All template parts' ) } - id={ TEMPLATE_PART_ALL_AREAS_CATEGORY } - type={ TEMPLATE_PART_POST_TYPE } - isActive={ - currentCategory === TEMPLATE_PART_ALL_AREAS_CATEGORY && - currentType === TEMPLATE_PART_POST_TYPE - } - /> - { Object.entries( templatePartAreas ).map( - ( [ area, { label, templateParts, icon } ] ) => ( - - ) - ) } -
- { allPatterns && ( + { templatePartViews?.map( ( view ) => ( - ) } - { otherPatterns.map( ( category ) => ( + ) ) } +
+ { patternViews?.map( ( view ) => ( @@ -110,9 +86,38 @@ export default function SidebarNavigationScreenPatterns( { backPath } ) { ? PATTERN_DEFAULT_CATEGORY : TEMPLATE_PART_ALL_AREAS_CATEGORY ); - const { templatePartAreas, hasTemplateParts, isLoading } = + const { view_list: templatePartViews } = useViewConfig( { + kind: 'postType', + name: TEMPLATE_PART_POST_TYPE, + } ); + const { view_list: patternViews } = useViewConfig( { + kind: 'postType', + name: PATTERN_TYPES.user, + } ); + + const { templatePartAreas, isLoading, hasTemplateParts } = useTemplatePartAreas(); - const { patternCategories, hasPatterns } = usePatternCategories(); + const templatePartCounts = useMemo( () => { + const counts = { [ TEMPLATE_PART_ALL_AREAS_CATEGORY ]: 0 }; + Object.entries( templatePartAreas ).forEach( + ( [ area, { templateParts } ] ) => { + const count = templateParts?.length || 0; + counts[ area ] = count; + counts[ TEMPLATE_PART_ALL_AREAS_CATEGORY ] += count; + } + ); + return counts; + }, [ templatePartAreas ] ); + const { patternCategories } = usePatternCategories(); + const patternCounts = useMemo( () => { + const counts = {}; + patternCategories.forEach( ( cat ) => { + counts[ cat.name ] = cat.count; + } ); + return counts; + }, [ patternCategories ] ); + + const hasPatterns = patternCounts[ PATTERN_DEFAULT_CATEGORY ] > 0; return ( ) }