diff --git a/lib/init.php b/lib/init.php
index 88dcba4525f6e2..56c413d693b59e 100644
--- a/lib/init.php
+++ b/lib/init.php
@@ -57,3 +57,121 @@ function gutenberg_menu() {
);
}
add_action( 'admin_menu', 'gutenberg_menu', 9 );
+
+if ( ! function_exists( 'html_contains_block' ) ) {
+ /**
+ * Returns whether the given HTML contains a block
+ * of the given type and, if provided,
+ * a given attribute and attribute value.
+ *
+ * Note that it's not possible to search for an attribute
+ * whose value is `null`.
+ *
+ * @param string $html The html to search in.
+ * @param string $block_name Find this block type,
+ * with an optional "core/" namespace,
+ * e.g. "paragraph", "core/paragraph",
+ * "my_plugin/my_block".
+ * @param string $attribute_name If provided, the block must also
+ * contain this attribute.
+ * @param string $attribute_value If provided, the given attribute's
+ * value must also match this.
+ *
+ * @return bool True if block is found, false otherwise
+ */
+ function html_contains_block( $html, $block_name, $attribute_name = null, $attribute_value = null ) {
+ $at = 0;
+
+ /**
+ * This is the same regex as the one used in the block parser.
+ * It is better to use this solution to look for a block's existence
+ * in a document compared to having to parsing the blocks in the
+ * document, avoiding all the performance drawbacks of achieving
+ * a full representation of block content just to check if one block
+ * is there.
+ *
+ * @see WP_Block_Parser.
+ */
+ $pattern = sprintf(
+ '~).)*+)?}\s+)?/?-->~s',
+ preg_quote( str_replace( 'core/', '', $block_name ), '~' )
+ );
+
+ while ( 1 === preg_match( $pattern, $html, $matches, PREG_OFFSET_CAPTURE, $at ) ) {
+ $at = $matches[0][1] + strlen( $matches[0][0] );
+
+ if ( ! isset( $attribute_name ) ) {
+ return true;
+ }
+
+ $attrs = json_decode( $matches['attrs'][0], /* as-associative */ true );
+ if ( ! isset( $attrs[ $attribute_name ] ) ) {
+ continue;
+ }
+
+ if ( ! isset( $attribute_value ) ) {
+ return true;
+ }
+
+ if ( $attribute_value === $attrs[ $attribute_name ] ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+if ( ! function_exists( 'get_template_parts_that_use_menu' ) ) {
+ /**
+ * Get all template parts that use a menu.
+ *
+ * @param int $wp_navigation_id The menu id.
+ *
+ * @return array template parts that use the menu
+ */
+ function get_template_parts_that_use_menu( $wp_navigation_id ) {
+
+ $wp_template_part_posts = get_posts(
+ array(
+ 'post_type' => 'wp_template_part',
+ 'posts_per_page' => -1,
+ )
+ );
+
+ $wp_template_part_posts_with_navigation = array();
+ foreach ( $wp_template_part_posts as $wp_template_part_post ) {
+ $found_navigation = html_contains_block(
+ $wp_template_part_post->post_content,
+ 'navigation',
+ 'ref',
+ $wp_navigation_id
+ );
+ if ( $found_navigation ) {
+ $wp_template_part_posts_with_navigation[] = $wp_template_part_post->ID;
+ }
+ }
+ return $wp_template_part_posts_with_navigation;
+ }
+}
+
+if ( ! function_exists( 'register_template_parts_that_use_menu_field' ) ) {
+ /**
+ * Register a rest field for posts that returns the template parts that use the menu.
+ */
+ function register_template_parts_that_use_menu_field() {
+ register_rest_field(
+ 'wp_navigation',
+ 'template_parts_that_use_menu',
+ array(
+ 'get_callback' => function ( $post ) {
+ return get_template_parts_that_use_menu( $post['id'] );
+ },
+ 'schema' => array(
+ 'type' => 'array',
+ 'context' => array( 'edit' ),
+ ),
+ )
+ );
+ }
+}
+add_action( 'rest_api_init', 'register_template_parts_that_use_menu_field' );
diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js
index 960e0363f2e588..a747fc79c55eff 100644
--- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js
+++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/single-navigation-menu.js
@@ -3,14 +3,57 @@
*/
import { __ } from '@wordpress/i18n';
import { decodeEntities } from '@wordpress/html-entities';
+import {
+ __experimentalItemGroup as ItemGroup,
+ __experimentalTruncate as Truncate,
+} from '@wordpress/components';
+import { header, footer, layout } from '@wordpress/icons';
+import { useSelect } from '@wordpress/data';
+import { store as coreStore } from '@wordpress/core-data';
/**
* Internal dependencies
*/
+import {
+ SidebarNavigationScreenDetailsPanel,
+ SidebarNavigationScreenDetailsPanelRow,
+} from '../sidebar-navigation-screen-details-panel';
+import SidebarNavigationItem from '../sidebar-navigation-item';
import { SidebarNavigationScreenWrapper } from '../sidebar-navigation-screen-navigation-menus';
import ScreenNavigationMoreMenu from './more-menu';
import NavigationMenuEditor from './navigation-menu-editor';
import buildNavigationLabel from '../sidebar-navigation-screen-navigation-menus/build-navigation-label';
import EditButton from './edit-button';
+import { useLink } from '../routes/link';
+import { TEMPLATE_PART_POST_TYPE } from '../../utils/constants';
+
+function TemplateAreaButton( { postId, icon, title } ) {
+ const icons = {
+ header,
+ footer,
+ };
+ const linkInfo = useLink( {
+ postType: TEMPLATE_PART_POST_TYPE,
+ postId,
+ } );
+
+ return (
+
+
+ { decodeEntities( title ) }
+
+
+ );
+}
export default function SingleNavigationMenu( {
navigationMenu,
@@ -20,6 +63,22 @@ export default function SingleNavigationMenu( {
} ) {
const menuTitle = navigationMenu?.title?.rendered;
+ const templatePartsIds = navigationMenu?.template_parts_that_use_menu ?? [];
+
+ const templateParts = useSelect(
+ ( select ) =>
+ select( coreStore ).getEntityRecords(
+ 'postType',
+ TEMPLATE_PART_POST_TYPE
+ ),
+ []
+ );
+
+ const templatePartsData =
+ templateParts?.filter( ( templatePart ) =>
+ templatePartsIds.includes( templatePart.wp_id )
+ ) ?? [];
+
return (
+
+ { templatePartsData.length > 0 && (
+
+
+ { templatePartsData.map(
+ ( { wp_id: wpId, theme, slug, title } ) => (
+
+
+
+ )
+ ) }
+
+
+ ) }
);
}