diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index a6263b6c573b4..a2610c2647c65 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -328,6 +328,18 @@ _Parameters_ - _isOpen_ `boolean`: If true, opens the save view. If false, closes it. It does not toggle the state, but sets it directly. +### setNavigationMenu + +Action that sets a navigation menu. + +_Parameters_ + +- _navigationMenuId_ `string`: The Navigation Menu Post ID. + +_Returns_ + +- `Object`: Action object. + ### setNavigationPanelActiveMenu > **Deprecated** diff --git a/lib/compat/wordpress-6.3/link-template.php b/lib/compat/wordpress-6.3/link-template.php index 366dedba2b5aa..19d703839125e 100644 --- a/lib/compat/wordpress-6.3/link-template.php +++ b/lib/compat/wordpress-6.3/link-template.php @@ -28,7 +28,33 @@ function gutenberg_update_get_edit_post_link( $link, $post_id ) { $slug = urlencode( get_stylesheet() . '//' . $post->post_name ); $link = admin_url( sprintf( $post_type_object->_edit_link, $slug ) ); } + return $link; } add_filter( 'get_edit_post_link', 'gutenberg_update_get_edit_post_link', 10, 2 ); + + + +/** + * Modifies the edit link for the `wp_navigation` custom post type. + * + * This has not been backported to Core. + * + * @param string $link The edit link. + * @param int $post_id Post ID. + * @return string|null The edit post link for the given post. Null if the post type does not exist + * or does not allow an editing UI. + */ +function gutenberg_update_navigation_get_edit_post_link( $link, $post_id ) { + $post = get_post( $post_id ); + + if ( 'wp_navigation' === $post->post_type ) { + $post_type_object = get_post_type_object( $post->post_type ); + $id = $post->ID; + $link = admin_url( sprintf( $post_type_object->_edit_link, $id ) ); + } + + return $link; +} +add_filter( 'get_edit_post_link', 'gutenberg_update_navigation_get_edit_post_link', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index 9dfecc5ec2c53..a2dd66c5e922f 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -9,8 +9,14 @@ * Updates `wp_template` and `wp_template_part` post types to use * Gutenberg's REST controllers * - * Adds `_edit_link` to the `wp_global_styles`, `wp_template`, - * and `wp_template_part` post type schemata. See https://github.com/WordPress/gutenberg/issues/48065 + * Adds `_edit_link` to the following post type schemata: + * + * - wp_global_styles + * - wp_template + * - wp_template_part + * - wp_navigation + * + * See https://github.com/WordPress/gutenberg/issues/48065 * * @param array $args Array of arguments for registering a post type. * @param string $post_type Post type key. @@ -31,6 +37,18 @@ function gutenberg_update_templates_template_parts_rest_controller( $args, $post if ( in_array( $post_type, array( 'wp_global_styles' ), true ) ) { $args['_edit_link'] = '/site-editor.php?canvas=edit'; } + + if ( 'wp_navigation' === $post_type ) { + $navigation_edit_link = 'site-editor.php?' . build_query( + array( + 'postId' => '%s', + 'postType' => 'wp_navigation', + 'canvas' => 'edit', + ) + ); + $args['_edit_link'] = $navigation_edit_link; + } + return $args; } add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index d44be2cb7ccaf..2fe86825bb6b1 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -26,6 +26,7 @@ import { __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, __experimentalUseBlockOverlayActive as useBlockOverlayActive, __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, + privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; @@ -67,6 +68,9 @@ import { detectColors } from './utils'; import ManageMenusButton from './manage-menus-button'; import MenuInspectorControls from './menu-inspector-controls'; import DeletedNavigationWarning from './deleted-navigation-warning'; +import { unlock } from '../../lock-unlock'; + +const { useBlockEditingMode } = unlock( blockEditorPrivateApis ); function Navigation( { attributes, @@ -114,6 +118,8 @@ function Navigation( { const recursionId = `navigationMenu/${ ref }`; const hasAlreadyRendered = useHasRecursion( recursionId ); + const blockEditingMode = useBlockEditingMode(); + // Preload classic menus, so that they don't suddenly pop-in when viewing // the Select Menu dropdown. const { menus: classicMenus } = useNavigationEntities(); @@ -652,8 +658,9 @@ function Navigation( { onSelectClassicMenu={ onSelectClassicMenu } onSelectNavigationMenu={ onSelectNavigationMenu } isLoading={ isLoading } + blockEditingMode={ blockEditingMode } /> - { stylingInspectorControls } + { blockEditingMode === 'default' && stylingInspectorControls } - { stylingInspectorControls } - { isEntityAvailable && ( + { blockEditingMode === 'default' && stylingInspectorControls } + { blockEditingMode === 'default' && isEntityAvailable && ( { hasResolvedCanUserUpdateNavigationMenu && canUserUpdateNavigationMenu && ( diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js index 52e7805c0f0fb..8743b51fc6720 100644 --- a/packages/block-library/src/navigation/edit/menu-inspector-controls.js +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -138,6 +138,7 @@ const MenuInspectorControls = ( props ) => { onSelectClassicMenu, onSelectNavigationMenu, isManageMenusButtonDisabled, + blockEditingMode, } = props; return ( @@ -150,22 +151,24 @@ const MenuInspectorControls = ( props ) => { > { __( 'Menu' ) } - + { blockEditingMode === 'default' && ( + + ) } diff --git a/packages/edit-site/src/components/block-editor/back-button.js b/packages/edit-site/src/components/block-editor/back-button.js index b7c281031f15b..ebe5af44c0250 100644 --- a/packages/edit-site/src/components/block-editor/back-button.js +++ b/packages/edit-site/src/components/block-editor/back-button.js @@ -17,9 +17,10 @@ function BackButton() { const location = useLocation(); const history = useHistory(); const isTemplatePart = location.params.postType === 'wp_template_part'; + const isNavigationMenu = location.params.postType === 'wp_navigation'; const previousTemplateId = location.state?.fromTemplateId; - const isFocusMode = isTemplatePart; + const isFocusMode = isTemplatePart || isNavigationMenu; if ( ! isFocusMode || ! previousTemplateId ) { return null; diff --git a/packages/edit-site/src/components/block-editor/constants.js b/packages/edit-site/src/components/block-editor/constants.js index 5985167c1e7d3..9f076eea1ca31 100644 --- a/packages/edit-site/src/components/block-editor/constants.js +++ b/packages/edit-site/src/components/block-editor/constants.js @@ -1 +1 @@ -export const FOCUSABLE_ENTITIES = [ 'wp_template_part' ]; +export const FOCUSABLE_ENTITIES = [ 'wp_template_part', 'wp_navigation' ]; diff --git a/packages/edit-site/src/components/block-editor/get-block-editor-provider.js b/packages/edit-site/src/components/block-editor/get-block-editor-provider.js new file mode 100644 index 0000000000000..df8185605f13a --- /dev/null +++ b/packages/edit-site/src/components/block-editor/get-block-editor-provider.js @@ -0,0 +1,29 @@ +/** + * Internal dependencies + */ +import DefaultBlockEditor from './providers/default-block-editor-provider'; +import NavigationBlockEditor from './providers/navigation-block-editor-provider'; + +/** + * Factory to isolate choosing the appropriate block editor + * component to handle a given entity type. + * + * @param {string} entityType the entity type being edited + * @return {JSX.Element} the block editor component to use. + */ +export default function getBlockEditorProvider( entityType ) { + let Provider = null; + + switch ( entityType ) { + case 'wp_navigation': + Provider = NavigationBlockEditor; + break; + case 'wp_template': + case 'wp_template_part': + default: + Provider = DefaultBlockEditor; + break; + } + + return Provider; +} diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 08a316028ba80..4f4dd2011302f 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -13,28 +13,31 @@ import { ReusableBlocksMenuItems } from '@wordpress/reusable-blocks'; /** * Internal dependencies */ - import TemplatePartConverter from '../template-part-converter'; import { SidebarInspectorFill } from '../sidebar-edit-mode'; import { store as editSiteStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { DisableNonPageContentBlocks } from '../page-content-focus'; import SiteEditorCanvas from './site-editor-canvas'; -import DefaultBlockEditorProvider from './providers/default-block-editor-provider'; +import getBlockEditorProvider from './get-block-editor-provider'; export default function BlockEditor() { - const { hasPageContentFocus } = useSelect( ( select ) => { - const { hasPageContentFocus: _hasPageContentFocus } = unlock( - select( editSiteStore ) - ); + const { entityType, hasPageContentFocus } = useSelect( ( select ) => { + const { getEditedPostType, hasPageContentFocus: _hasPageContentFocus } = + unlock( select( editSiteStore ) ); return { + entityType: getEditedPostType(), hasPageContentFocus: _hasPageContentFocus(), }; }, [] ); + // Choose the provider based on the entity type currently + // being edited. + const BlockEditorProvider = getBlockEditorProvider( entityType ); + return ( - + { hasPageContentFocus && } @@ -44,6 +47,6 @@ export default function BlockEditor() { - + ); } diff --git a/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js b/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js new file mode 100644 index 0000000000000..ea9e0c8c966ec --- /dev/null +++ b/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js @@ -0,0 +1,114 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useMemo, useEffect } from '@wordpress/element'; +import { useEntityId } from '@wordpress/core-data'; +import { + store as blockEditorStore, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { unlock } from '../../../lock-unlock'; +import useSiteEditorSettings from '../use-site-editor-settings'; +import { store as editSiteStore } from '../../../store'; + +const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); + +const noop = () => {}; + +/** + * Block editor component for editing navigation menus. + * + * Note: Navigation entities require a wrapping Navigation block to provide + * them with some basic layout and styling. Therefore we create a "ghost" block + * and provide it will a reference to the navigation entity ID being edited. + * + * In this scenario it is the **block** that handles syncing the entity content + * whereas for other entities this is handled by entity block editor. + * + * @param {number} navigationMenuId the navigation menu ID + * @return {[WPBlock[], Function, Function]} The block array and setters. + */ +export default function NavigationBlockEditorProvider( { children } ) { + const defaultSettings = useSiteEditorSettings(); + + const navigationMenuId = useEntityId( 'postType', 'wp_navigation' ); + + const blocks = useMemo( () => { + return [ + createBlock( 'core/navigation', { + ref: navigationMenuId, + // As the parent editor is locked with `templateLock`, the template locking + // must be explicitly "unset" on the block itself to allow the user to modify + // the block's content. + templateLock: false, + } ), + ]; + }, [ navigationMenuId ] ); + + const { isEditMode } = useSelect( ( select ) => { + const { getCanvasMode } = unlock( select( editSiteStore ) ); + + return { + isEditMode: getCanvasMode() === 'edit', + }; + }, [] ); + + const { selectBlock, setBlockEditingMode, unsetBlockEditingMode } = unlock( + useDispatch( blockEditorStore ) + ); + + const navigationBlockClientId = blocks && blocks[ 0 ]?.clientId; + + const settings = useMemo( () => { + return { + ...defaultSettings, + // Lock the editor to allow the root ("ghost") Navigation block only. + templateLock: 'insert', + template: [ [ 'core/navigation', {}, [] ] ], + }; + }, [ defaultSettings ] ); + + // Auto-select the Navigation block when entering Navigation focus mode. + useEffect( () => { + if ( navigationBlockClientId && isEditMode ) { + selectBlock( navigationBlockClientId ); + } + }, [ navigationBlockClientId, isEditMode, selectBlock ] ); + + // Set block editing mode to contentOnly when entering Navigation focus mode. + // This ensures that non-content controls on the block will be hidden and thus + // the user can focus on editing the Navigation Menu content only. + useEffect( () => { + if ( ! navigationBlockClientId ) { + return; + } + + setBlockEditingMode( navigationBlockClientId, 'contentOnly' ); + + return () => { + unsetBlockEditingMode( navigationBlockClientId ); + }; + }, [ + navigationBlockClientId, + unsetBlockEditingMode, + setBlockEditingMode, + ] ); + + return ( + + { children } + + ); +} diff --git a/packages/edit-site/src/components/block-editor/site-editor-canvas.js b/packages/edit-site/src/components/block-editor/site-editor-canvas.js index 204ff85443fb8..29e980d113434 100644 --- a/packages/edit-site/src/components/block-editor/site-editor-canvas.js +++ b/packages/edit-site/src/components/block-editor/site-editor-canvas.js @@ -42,7 +42,7 @@ const LAYOUT = { export default function SiteEditorCanvas() { const { clearSelectedBlock } = useDispatch( blockEditorStore ); - const { isFocusMode, isViewMode } = useSelect( ( select ) => { + const { templateType, isFocusMode, isViewMode } = useSelect( ( select ) => { const { getEditedPostType, getCanvasMode } = unlock( select( editSiteStore ) ); @@ -50,6 +50,7 @@ export default function SiteEditorCanvas() { const _templateType = getEditedPostType(); return { + templateType: _templateType, isFocusMode: FOCUSABLE_ENTITIES.includes( _templateType ), isViewMode: getCanvasMode() === 'view', }; @@ -84,8 +85,17 @@ export default function SiteEditorCanvas() { usePageContentFocusNotifications(), ] ); - // Hide the appender when in view mode (i.e. not editing). - const showBlockAppender = hasBlocks || isViewMode ? false : undefined; + const isTemplateTypeNavigation = templateType === 'wp_navigation'; + + const isNavigationFocusMode = isTemplateTypeNavigation && isFocusMode; + + // Hide the appender when: + // - In navigation focus mode (should only allow the root Nav block). + // - In view mode (i.e. not editing). + const showBlockAppender = + ( isNavigationFocusMode && hasBlocks ) || isViewMode + ? false + : undefined; return ( @@ -122,7 +132,13 @@ export default function SiteEditorCanvas() { > { resizeObserver } diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss index f974f6d914b16..dce224998c0c0 100644 --- a/packages/edit-site/src/components/block-editor/style.scss +++ b/packages/edit-site/src/components/block-editor/style.scss @@ -12,6 +12,12 @@ } } +// Navigation focus mode requires padding around the root Navigation block +// for presentational purposes. +.edit-site-block-editor__block-list.is-navigation-block { + padding: $grid-unit-30; +} + .edit-site-visual-editor { position: relative; height: 100%; diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js index 4ce186ad9d6bb..af3f5ccba3498 100644 --- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js +++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js @@ -78,6 +78,14 @@ export default function useSiteEditorSettings( templateType ) { inserterMediaCategories, __experimentalBlockPatterns: blockPatterns, __experimentalBlockPatternCategories: blockPatternCategories, + // Temporary fix for bug in Block Editor Provider: + // see: https://github.com/WordPress/gutenberg/issues/51489. + // Some Site Editor entities (e.g. `wp_navigation`) may utilise + // template locking in their settings. Therefore this must be + // explicitly "unset" to avoid the template locking UI remaining + // active for all entities. + templateLock: false, + template: false, }; }, [ storedSettings, blockPatterns, blockPatternCategories ] ); } diff --git a/packages/edit-site/src/components/header-edit-mode/document-actions/index.js b/packages/edit-site/src/components/header-edit-mode/document-actions/index.js index 09307393eb399..f31c789043e72 100644 --- a/packages/edit-site/src/components/header-edit-mode/document-actions/index.js +++ b/packages/edit-site/src/components/header-edit-mode/document-actions/index.js @@ -19,6 +19,7 @@ import { store as commandsStore } from '@wordpress/commands'; import { chevronLeftSmall as chevronLeftSmallIcon, page as pageIcon, + navigation as navigationIcon, } from '@wordpress/icons'; import { displayShortcut } from '@wordpress/keycodes'; import { useState, useEffect, useRef } from '@wordpress/element'; @@ -115,15 +116,12 @@ function TemplateDocumentActions( { className, onBack } ) { ); } - const entityLabel = - record.type === 'wp_template_part' - ? __( 'template part' ) - : __( 'template' ); + const entityLabel = getEntityLabel( record.type ); return ( @@ -177,3 +175,20 @@ function BaseDocumentActions( { className, icon, children, onBack } ) { ); } + +function getEntityLabel( entityType ) { + let label = ''; + switch ( entityType ) { + case 'wp_navigation': + label = 'navigation menu'; + break; + case 'wp_template_part': + label = 'template part'; + break; + default: + label = 'template'; + break; + } + + return label; +} diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 987046a6da33b..fd6661c8a476c 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -142,7 +142,8 @@ export default function HeaderEditMode() { const hasDefaultEditorCanvasView = ! useHasEditorCanvasContainer(); - const isFocusMode = templateType === 'wp_template_part'; + const isFocusMode = + templateType === 'wp_template_part' || templateType === 'wp_navigation'; /* translators: button label text should, if possible, be under 16 characters. */ const longLabel = _x( diff --git a/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js b/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js index b1fdb69a5ba9e..10ce08e8e7401 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; @@ -19,9 +19,18 @@ import { SIDEBAR_BLOCK, SIDEBAR_TEMPLATE } from '../constants'; import { store as editSiteStore } from '../../../store'; const SettingsHeader = ( { sidebarName } ) => { - const hasPageContentFocus = useSelect( ( select ) => - select( editSiteStore ).hasPageContentFocus() - ); + const { hasPageContentFocus, entityType } = useSelect( ( select ) => { + const { getEditedPostType, hasPageContentFocus: _hasPageContentFocus } = + select( editSiteStore ); + + return { + hasPageContentFocus: _hasPageContentFocus(), + entityType: getEditedPostType(), + }; + } ); + + const entityLabel = + entityType === 'wp_navigation' ? __( 'Navigation' ) : __( 'Template' ); const { enableComplementaryArea } = useDispatch( interfaceStore ); const openTemplateSettings = () => @@ -41,9 +50,9 @@ const SettingsHeader = ( { sidebarName } ) => { templateAriaLabel = sidebarName === SIDEBAR_TEMPLATE ? // translators: ARIA label for the Template sidebar tab, selected. - __( 'Template (selected)' ) + sprintf( __( '%s (selected)' ), entityLabel ) : // translators: ARIA label for the Template Settings Sidebar tab, not selected. - __( 'Template' ); + entityLabel; } /* Use a list so screen readers will announce how many tabs there are. */ @@ -60,10 +69,10 @@ const SettingsHeader = ( { sidebarName } ) => { ) } aria-label={ templateAriaLabel } data-label={ - hasPageContentFocus ? __( 'Page' ) : __( 'Template' ) + hasPageContentFocus ? __( 'Page' ) : entityLabel } > - { hasPageContentFocus ? __( 'Page' ) : __( 'Template' ) } + { hasPageContentFocus ? __( 'Page' ) : entityLabel }
  • diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js index 1c369703be5d7..2fcdcbb2c7e1b 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js @@ -7,6 +7,7 @@ import { store as editorStore } from '@wordpress/editor'; import { store as coreStore } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; import { __ } from '@wordpress/i18n'; +import { navigation as navigationIcon } from '@wordpress/icons'; /** * Internal dependencies @@ -20,7 +21,7 @@ import SidebarCard from '../sidebar-card'; export default function TemplatePanel() { const { info: { title, description, icon }, - template, + record, } = useSelect( ( select ) => { const { getEditedPostType, getEditedPostId } = select( editSiteStore ); const { getEditedEntityRecord } = select( coreStore ); @@ -29,11 +30,11 @@ export default function TemplatePanel() { const postType = getEditedPostType(); const postId = getEditedPostId(); - const record = getEditedEntityRecord( 'postType', postType, postId ); + const _record = getEditedEntityRecord( 'postType', postType, postId ); - const info = record ? getTemplateInfo( record ) : {}; + const info = _record ? getTemplateInfo( _record ) : {}; - return { info, template: record }; + return { info, record: _record }; }, [] ); if ( ! title && ! description ) { @@ -45,9 +46,11 @@ export default function TemplatePanel() { } + actions={ } > diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js new file mode 100644 index 0000000000000..7d084b6db4e26 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { pencil } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../store'; +import SidebarButton from '../sidebar-button'; +import { unlock } from '../../lock-unlock'; + +export default function EditButton() { + const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); + + return ( + setCanvasMode( 'edit' ) } + label={ __( 'Edit' ) } + icon={ pencil } + /> + ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js index e3b8b2a85d672..7f0085ba3aa22 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js @@ -9,7 +9,6 @@ import { import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { decodeEntities } from '@wordpress/html-entities'; - import { store as noticesStore } from '@wordpress/notices'; /** @@ -18,6 +17,7 @@ import { store as noticesStore } from '@wordpress/notices'; import { SidebarNavigationScreenWrapper } from '../sidebar-navigation-screen-navigation-menus'; import ScreenNavigationMoreMenu from './more-menu'; import NavigationMenuEditor from './navigation-menu-editor'; +import EditButton from './edit-button'; export const noop = () => {}; @@ -217,17 +217,33 @@ export default function SidebarNavigationScreenNavigationMenu() { return ( + <> + + + } title={ decodeEntities( menuTitle ) } - description={ __( - 'Navigation menus are a curated collection of blocks that allow visitors to get around your site.' - ) } + description={ + <> +

    + { sprintf( + /* translators: %s: Navigation menu title */ + 'This is your "%s" navigation menu. ', + decodeEntities( menuTitle ) + ) } +

    +

    + { __( + 'You can edit this menu here, but be aware that visual styles might be applied separately in templates or template parts, so the preview shown here can be incomplete.' + ) } +

    + + } >
    diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index 510932d7c4f37..32413a943bb9a 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -31,7 +31,7 @@ export default function useInitEditedEntityFromURL() { }; }, [] ); - const { setTemplate, setTemplatePart, setPage } = + const { setTemplate, setTemplatePart, setPage, setNavigationMenu } = useDispatch( editSiteStore ); useEffect( () => { @@ -43,6 +43,9 @@ export default function useInitEditedEntityFromURL() { case 'wp_template_part': setTemplatePart( postId ); break; + case 'wp_navigation': + setNavigationMenu( postId ); + break; default: setPage( { context: { postType, postId }, @@ -71,5 +74,6 @@ export default function useInitEditedEntityFromURL() { setPage, setTemplate, setTemplatePart, + setNavigationMenu, ] ); } diff --git a/packages/edit-site/src/hooks/index.js b/packages/edit-site/src/hooks/index.js index 513634c55b8f0..4e871f4e3824e 100644 --- a/packages/edit-site/src/hooks/index.js +++ b/packages/edit-site/src/hooks/index.js @@ -4,3 +4,4 @@ import './components'; import './push-changes-to-global-styles'; import './template-part-edit'; +import './navigation-menu-edit'; diff --git a/packages/edit-site/src/hooks/navigation-menu-edit.js b/packages/edit-site/src/hooks/navigation-menu-edit.js new file mode 100644 index 0000000000000..6b2c1166c9aa2 --- /dev/null +++ b/packages/edit-site/src/hooks/navigation-menu-edit.js @@ -0,0 +1,95 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { + BlockControls, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { ToolbarButton } from '@wordpress/components'; +import { addFilter } from '@wordpress/hooks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { useLink } from '../components/routes/link'; +import { unlock } from '../lock-unlock'; + +const { useLocation } = unlock( routerPrivateApis ); +const { useBlockEditingMode } = unlock( blockEditorPrivateApis ); + +function NavigationMenuEdit( { attributes } ) { + const { ref } = attributes; + const { params } = useLocation(); + const blockEditingMode = useBlockEditingMode(); + const navigationMenu = useSelect( + ( select ) => { + return select( coreStore ).getEntityRecord( + 'postType', + 'wp_navigation', + // Ideally this should be an official public API. + ref + ); + }, + [ ref ] + ); + + const linkProps = useLink( + { + postId: navigationMenu?.id, + postType: navigationMenu?.type, + canvas: 'edit', + }, + { + // this applies to Navigation Menus as well. + fromTemplateId: params.postId, + } + ); + + // A non-default setting for block editing mode indicates that the + // editor should restrict "editing" actions. Therefore the `Edit` button + // should not be displayed. + if ( ! navigationMenu || blockEditingMode !== 'default' ) { + return null; + } + + return ( + + { + linkProps.onClick( event ); + } } + > + { __( 'Edit' ) } + + + ); +} + +export const withEditBlockControls = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes, name } = props; + const isDisplayed = name === 'core/navigation' && attributes.ref; + + return ( + <> + + { isDisplayed && ( + + ) } + + ); + }, + 'withEditBlockControls' +); + +addFilter( + 'editor.BlockEdit', + 'core/edit-site/navigation-edit-button', + withEditBlockControls +); diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 0e4e1ff00770f..eba997f1a6f68 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -176,6 +176,21 @@ export function setTemplatePart( templatePartId ) { }; } +/** + * Action that sets a navigation menu. + * + * @param {string} navigationMenuId The Navigation Menu Post ID. + * + * @return {Object} Action object. + */ +export function setNavigationMenu( navigationMenuId ) { + return { + type: 'SET_EDITED_POST', + postType: 'wp_navigation', + id: navigationMenuId, + }; +} + /** * @deprecated */