From 085acc816b38704915ef18c2387a2276f9703065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:19:02 +0100 Subject: [PATCH 1/6] Generate sidebar for patterns/parts from view list --- ...enberg-rest-view-config-controller-7-1.php | 137 +++++++++++++ .../index.js | 182 ++++++++++++------ 2 files changed, 261 insertions(+), 58 deletions(-) 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..13508d5b4a2ebc 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,141 @@ 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', '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. + $categories = array(); + $seen_names = array(); + $registry = WP_Block_Pattern_Categories_Registry::get_instance(); + $registered = $registry->get_all_registered(); + + foreach ( $registered as $category ) { + if ( ! isset( $seen_names[ $category['name'] ] ) ) { + $categories[] = array( + 'name' => $category['name'], + 'label' => $category['label'], + ); + $seen_names[ $category['name'] ] = true; + } + } + + // Ensure "Uncategorized" is always included for patterns + // that have no category assigned. + if ( ! isset( $seen_names['uncategorized'] ) ) { + $categories[] = array( + 'name' => 'uncategorized', + 'label' => __( 'Uncategorized', 'gutenberg' ), + ); + $seen_names['uncategorized'] = true; + } + + // 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 ) { + if ( ! isset( $seen_names[ $term->slug ] ) ) { + $categories[] = array( + 'name' => $term->slug, + 'label' => $term->name, + ); + $seen_names[ $term->slug ] = true; + } + } + } + + // Sort categories alphabetically by label. + usort( + $categories, + function ( $a, $b ) { + return strnatcasecmp( $a['label'], $b['label'] ); + } + ); + + foreach ( $categories as $category ) { + $view_list[] = array( + 'title' => $category['label'], + 'slug' => $category['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..a240fa8ade370c 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 @@ -5,10 +5,13 @@ import { __experimentalItemGroup as ItemGroup, __experimentalItem as Item, } from '@wordpress/components'; +import { useEntityRecords } from '@wordpress/core-data'; 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 @@ -20,78 +23,127 @@ import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, TEMPLATE_PART_ALL_AREAS_CATEGORY, + TEMPLATE_PART_AREA_DEFAULT_CATEGORY, } from '../../utils/constants'; -import usePatternCategories from './use-pattern-categories'; -import useTemplatePartAreas from './use-template-part-areas'; +import useThemePatterns from './use-theme-patterns'; +import usePatterns from '../page-patterns/use-patterns'; import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); +function useTemplatePartCounts() { + const { records: templateParts, isResolving: isLoading } = useEntityRecords( + 'postType', + TEMPLATE_PART_POST_TYPE, + { + per_page: -1, + } + ); + + const counts = useMemo( () => { + if ( ! templateParts ) { + return {}; + } + const result = { [ TEMPLATE_PART_ALL_AREAS_CATEGORY ]: 0 }; + templateParts.forEach( ( part ) => { + const area = part.area || TEMPLATE_PART_AREA_DEFAULT_CATEGORY; + result[ area ] = ( result[ area ] || 0 ) + 1; + result[ TEMPLATE_PART_ALL_AREAS_CATEGORY ] += 1; + } ); + return result; + }, [ templateParts ] ); + + return { counts, isLoading }; +} + +function usePatternCounts() { + const themePatterns = useThemePatterns(); + const { patterns: userPatterns, categories: userPatternCategories } = + usePatterns( PATTERN_TYPES.user ); + + const counts = useMemo( () => { + const result = { + [ PATTERN_DEFAULT_CATEGORY ]: + themePatterns.length + userPatterns.length, + 'my-patterns': userPatterns.length, + }; + + // Count theme patterns per category. + themePatterns.forEach( ( pattern ) => { + pattern.categories?.forEach( ( cat ) => { + result[ cat ] = ( result[ cat ] || 0 ) + 1; + } ); + if ( ! pattern.categories?.length ) { + result.uncategorized = ( result.uncategorized || 0 ) + 1; + } + } ); + + // Count user patterns per category. + userPatterns.forEach( ( pattern ) => { + pattern.wp_pattern_category?.forEach( ( catId ) => { + const category = userPatternCategories.find( + ( cat ) => cat.id === catId + ); + if ( category ) { + result[ category.name ] = + ( result[ category.name ] || 0 ) + 1; + } + } ); + if ( + ! pattern.wp_pattern_category?.length || + ! pattern.wp_pattern_category?.some( ( catId ) => + userPatternCategories.find( ( cat ) => cat.id === catId ) + ) + ) { + result.uncategorized = ( result.uncategorized || 0 ) + 1; + } + } ); + + return result; + }, [ themePatterns, userPatterns, userPatternCategories ] ); + + return counts; +} + 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 +162,21 @@ export default function SidebarNavigationScreenPatterns( { backPath } ) { ? PATTERN_DEFAULT_CATEGORY : TEMPLATE_PART_ALL_AREAS_CATEGORY ); - const { templatePartAreas, hasTemplateParts, isLoading } = - useTemplatePartAreas(); - const { patternCategories, hasPatterns } = usePatternCategories(); + const { view_list: templatePartViews } = useViewConfig( { + kind: 'postType', + name: TEMPLATE_PART_POST_TYPE, + } ); + const { view_list: patternViews } = useViewConfig( { + kind: 'postType', + name: PATTERN_TYPES.user, + } ); + + const { counts: templatePartCounts, isLoading } = useTemplatePartCounts(); + const patternCounts = usePatternCounts(); + + const hasTemplateParts = + templatePartCounts[ TEMPLATE_PART_ALL_AREAS_CATEGORY ] > 0; + const hasPatterns = patternCounts[ PATTERN_DEFAULT_CATEGORY ] > 0; return ( ) } From 1eaf77317517b66e7fe1221c3e78494cffd4f5f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:13:58 +0100 Subject: [PATCH 2/6] Simplify --- .../index.js | 61 +++---------------- 1 file changed, 9 insertions(+), 52 deletions(-) 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 a240fa8ade370c..d9a6ac60476e75 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 @@ -25,8 +25,7 @@ import { TEMPLATE_PART_ALL_AREAS_CATEGORY, TEMPLATE_PART_AREA_DEFAULT_CATEGORY, } from '../../utils/constants'; -import useThemePatterns from './use-theme-patterns'; -import usePatterns from '../page-patterns/use-patterns'; +import usePatternCategories from './use-pattern-categories'; import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); @@ -56,55 +55,6 @@ function useTemplatePartCounts() { return { counts, isLoading }; } -function usePatternCounts() { - const themePatterns = useThemePatterns(); - const { patterns: userPatterns, categories: userPatternCategories } = - usePatterns( PATTERN_TYPES.user ); - - const counts = useMemo( () => { - const result = { - [ PATTERN_DEFAULT_CATEGORY ]: - themePatterns.length + userPatterns.length, - 'my-patterns': userPatterns.length, - }; - - // Count theme patterns per category. - themePatterns.forEach( ( pattern ) => { - pattern.categories?.forEach( ( cat ) => { - result[ cat ] = ( result[ cat ] || 0 ) + 1; - } ); - if ( ! pattern.categories?.length ) { - result.uncategorized = ( result.uncategorized || 0 ) + 1; - } - } ); - - // Count user patterns per category. - userPatterns.forEach( ( pattern ) => { - pattern.wp_pattern_category?.forEach( ( catId ) => { - const category = userPatternCategories.find( - ( cat ) => cat.id === catId - ); - if ( category ) { - result[ category.name ] = - ( result[ category.name ] || 0 ) + 1; - } - } ); - if ( - ! pattern.wp_pattern_category?.length || - ! pattern.wp_pattern_category?.some( ( catId ) => - userPatternCategories.find( ( cat ) => cat.id === catId ) - ) - ) { - result.uncategorized = ( result.uncategorized || 0 ) + 1; - } - } ); - - return result; - }, [ themePatterns, userPatterns, userPatternCategories ] ); - - return counts; -} - function CategoriesGroup( { templatePartViews, patternViews, @@ -172,7 +122,14 @@ export default function SidebarNavigationScreenPatterns( { backPath } ) { } ); const { counts: templatePartCounts, isLoading } = useTemplatePartCounts(); - const patternCounts = usePatternCounts(); + const { patternCategories } = usePatternCategories(); + const patternCounts = useMemo( () => { + const counts = {}; + patternCategories.forEach( ( cat ) => { + counts[ cat.name ] = cat.count; + } ); + return counts; + }, [ patternCategories ] ); const hasTemplateParts = templatePartCounts[ TEMPLATE_PART_ALL_AREAS_CATEGORY ] > 0; From 6a5e7bfacee1542a8be4ba9bfc9a161fd25ea21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:22:32 +0100 Subject: [PATCH 3/6] Simplify --- .../index.js | 44 ++++++------------- 1 file changed, 14 insertions(+), 30 deletions(-) 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 d9a6ac60476e75..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 @@ -5,7 +5,6 @@ import { __experimentalItemGroup as ItemGroup, __experimentalItem as Item, } from '@wordpress/components'; -import { useEntityRecords } from '@wordpress/core-data'; import { getTemplatePartIcon } from '@wordpress/editor'; import { useMemo } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; @@ -23,38 +22,13 @@ import { PATTERN_TYPES, TEMPLATE_PART_POST_TYPE, TEMPLATE_PART_ALL_AREAS_CATEGORY, - TEMPLATE_PART_AREA_DEFAULT_CATEGORY, } from '../../utils/constants'; import usePatternCategories from './use-pattern-categories'; +import useTemplatePartAreas from './use-template-part-areas'; import { unlock } from '../../lock-unlock'; const { useLocation } = unlock( routerPrivateApis ); -function useTemplatePartCounts() { - const { records: templateParts, isResolving: isLoading } = useEntityRecords( - 'postType', - TEMPLATE_PART_POST_TYPE, - { - per_page: -1, - } - ); - - const counts = useMemo( () => { - if ( ! templateParts ) { - return {}; - } - const result = { [ TEMPLATE_PART_ALL_AREAS_CATEGORY ]: 0 }; - templateParts.forEach( ( part ) => { - const area = part.area || TEMPLATE_PART_AREA_DEFAULT_CATEGORY; - result[ area ] = ( result[ area ] || 0 ) + 1; - result[ TEMPLATE_PART_ALL_AREAS_CATEGORY ] += 1; - } ); - return result; - }, [ templateParts ] ); - - return { counts, isLoading }; -} - function CategoriesGroup( { templatePartViews, patternViews, @@ -121,7 +95,19 @@ export default function SidebarNavigationScreenPatterns( { backPath } ) { name: PATTERN_TYPES.user, } ); - const { counts: templatePartCounts, isLoading } = useTemplatePartCounts(); + const { templatePartAreas, isLoading, hasTemplateParts } = + useTemplatePartAreas(); + 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 = {}; @@ -131,8 +117,6 @@ export default function SidebarNavigationScreenPatterns( { backPath } ) { return counts; }, [ patternCategories ] ); - const hasTemplateParts = - templatePartCounts[ TEMPLATE_PART_ALL_AREAS_CATEGORY ] > 0; const hasPatterns = patternCounts[ PATTERN_DEFAULT_CATEGORY ] > 0; return ( From 401b7d3f39d8ed92e8abf34ccedb432cf5644d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:17:56 +0100 Subject: [PATCH 4/6] Add missing navigation-overlay part --- .../class-gutenberg-rest-view-config-controller-7-1.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 13508d5b4a2ebc..6a46e9993b701a 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 @@ -809,7 +809,7 @@ private function get_view_list_for_wp_template_part() { $areas = get_allowed_block_template_part_areas(); // Ensure default areas appear in a consistent order. - $preferred_order = array( 'header', 'footer', 'sidebar', 'uncategorized' ); + $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' ); $ordered_areas = array(); $remaining_areas = array(); foreach ( $areas as $area ) { From cbba167934844537bd32384de29078a1709e690e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:29:50 +0100 Subject: [PATCH 5/6] Track categories differently, per feedback --- ...enberg-rest-view-config-controller-7-1.php | 43 ++++--------------- 1 file changed, 9 insertions(+), 34 deletions(-) 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 6a46e9993b701a..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 @@ -863,30 +863,16 @@ private function get_view_list_for_wp_block() { ); // Gather categories from the block pattern categories registry. - $categories = array(); - $seen_names = array(); $registry = WP_Block_Pattern_Categories_Registry::get_instance(); - $registered = $registry->get_all_registered(); + $categories = array(); - foreach ( $registered as $category ) { - if ( ! isset( $seen_names[ $category['name'] ] ) ) { - $categories[] = array( - 'name' => $category['name'], - 'label' => $category['label'], - ); - $seen_names[ $category['name'] ] = true; - } + foreach ( $registry->get_all_registered() as $category ) { + $categories[ $category['name'] ] = $category['label']; } // Ensure "Uncategorized" is always included for patterns // that have no category assigned. - if ( ! isset( $seen_names['uncategorized'] ) ) { - $categories[] = array( - 'name' => 'uncategorized', - 'label' => __( 'Uncategorized', 'gutenberg' ), - ); - $seen_names['uncategorized'] = true; - } + $categories['uncategorized'] ??= __( 'Uncategorized', 'gutenberg' ); // Also gather user-created pattern categories (wp_pattern_category taxonomy). $user_terms = get_terms( @@ -898,28 +884,17 @@ private function get_view_list_for_wp_block() { if ( ! is_wp_error( $user_terms ) ) { foreach ( $user_terms as $term ) { - if ( ! isset( $seen_names[ $term->slug ] ) ) { - $categories[] = array( - 'name' => $term->slug, - 'label' => $term->name, - ); - $seen_names[ $term->slug ] = true; - } + $categories[ $term->slug ] = $term->name; } } // Sort categories alphabetically by label. - usort( - $categories, - function ( $a, $b ) { - return strnatcasecmp( $a['label'], $b['label'] ); - } - ); + asort( $categories, SORT_NATURAL | SORT_FLAG_CASE ); - foreach ( $categories as $category ) { + foreach ( $categories as $name => $label ) { $view_list[] = array( - 'title' => $category['label'], - 'slug' => $category['name'], + 'title' => $label, + 'slug' => $name, ); } From 8c87579a0e51004d6dbdbcee85c37d8edba38189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Maneiro?= <583546+oandregal@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:44:47 +0100 Subject: [PATCH 6/6] Update backport changelog --- backport-changelog/7.1/11272.md | 1 + 1 file changed, 1 insertion(+) 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