diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index a4c26c19dd5a2..eb283a5a9709b 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -34,6 +34,7 @@ import { __experimentalToggleGroupControlOption as ToggleGroupControlOption, ToolbarGroup, ToolbarDropdownMenu, + Button, } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; @@ -48,7 +49,6 @@ import ResponsiveWrapper from './responsive-wrapper'; import NavigationInnerBlocks from './inner-blocks'; import NavigationMenuSelector from './navigation-menu-selector'; import NavigationMenuNameControl from './navigation-menu-name-control'; -import NavigationMenuPublishButton from './navigation-menu-publish-button'; import UnsavedInnerBlocks from './unsaved-inner-blocks'; import NavigationMenuDeleteControl from './navigation-menu-delete-control'; @@ -281,6 +281,17 @@ function Navigation( { setIsPlaceholderShown( ! isEntityAvailable ); }, [ isEntityAvailable ] ); + const startWithEmptyMenu = useCallback( () => { + replaceInnerBlocks( clientId, [] ); + if ( navigationArea ) { + setAreaMenu( 0 ); + } + setAttributes( { + navigationMenuId: undefined, + } ); + setIsPlaceholderShown( true ); + }, [ clientId ] ); + // If the block has inner blocks, but no menu id, this was an older // navigation block added before the block used a wp_navigation entity. // Either this block was saved in the content or inserted by a pattern. @@ -306,6 +317,23 @@ function Navigation( { ); } + // Show a warning if the selected menu is no longer available. + // TODO - the user should be able to select a new one? + if ( navigationMenuId && isNavigationMenuMissing ) { + return ( +
+ + { __( + 'Navigation menu has been deleted or is unavailable. ' + ) } + + +
+ ); + } + if ( isEntityAvailable && hasAlreadyRendered ) { return (
@@ -341,26 +369,13 @@ function Navigation( { setNavigationMenuId( id ); onClose(); } } - onCreateNew={ () => { - if ( navigationArea ) { - setAreaMenu( 0 ); - } - setAttributes( { - navigationMenuId: undefined, - } ); - setIsPlaceholderShown( true ); - } } + onCreateNew={ startWithEmptyMenu } /> ) } ) } { listViewToolbarButton } - { isDraftNavigationMenu && ( - - - - ) } { listViewModal } @@ -480,17 +495,20 @@ function Navigation( { ) }
) } - { isNewMenuModalVisible && ( - { - setIsNewMenuModalVisible( false ); - } } - onFinish={ - createEmpty ? onCreateEmptyMenu : onCreateAllPages - } - /> - ) } ); } diff --git a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js index 798197a05d779..76b641d7ec072 100644 --- a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js +++ b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js @@ -7,18 +7,16 @@ import classnames from 'classnames'; * WordPress dependencies */ import { useInnerBlocksProps } from '@wordpress/block-editor'; -import { serialize } from '@wordpress/blocks'; import { Disabled, Spinner } from '@wordpress/components'; import { store as coreStore } from '@wordpress/core-data'; -import { useDispatch, useSelect } from '@wordpress/data'; -import { useCallback, useContext, useEffect, useRef } from '@wordpress/element'; -import { __, sprintf } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { useContext, useEffect, useRef } from '@wordpress/element'; /** * Internal dependencies */ import useNavigationMenu from '../use-navigation-menu'; -import useTemplatePartAreaLabel from '../use-template-part-area-label'; +import useCreateNavigationMenu from './use-create-navigation-menu'; const NOOP = () => {}; const EMPTY_OBJECT = {}; @@ -51,7 +49,6 @@ export default function UnsavedInnerBlocks( { onChange: NOOP, onInput: NOOP, } ); - const { saveEntityRecord } = useDispatch( coreStore ); const { isSaving, @@ -83,29 +80,7 @@ export default function UnsavedInnerBlocks( { const { hasResolvedNavigationMenus, navigationMenus } = useNavigationMenu(); - const createNavigationMenu = useCallback( - async ( title ) => { - const record = { - title, - content: serialize( blocks ), - status: 'draft', - }; - - const navigationMenu = await saveEntityRecord( - 'postType', - 'wp_navigation', - record - ); - - return navigationMenu; - }, - [ blocks, serialize, saveEntityRecord ] - ); - - // Because we can't conditionally call hooks, pass an undefined client id - // arg to bypass the expensive `useTemplateArea` code. The hook will return - // early. - const area = useTemplatePartAreaLabel( isDisabled ? undefined : clientId ); + const createNavigationMenu = useCreateNavigationMenu( clientId ); // Automatically save the uncontrolled blocks. useEffect( async () => { @@ -134,33 +109,7 @@ export default function UnsavedInnerBlocks( { } savingLock.current = true; - const title = area - ? sprintf( - // translators: %s: the name of a menu (e.g. Header navigation). - __( '%s navigation' ), - area - ) - : // translators: 'navigation' as in website navigation. - __( 'Navigation' ); - - // Determine how many menus start with the automatic title. - const matchingMenuTitleCount = [ - ...draftNavigationMenus, - ...navigationMenus, - ].reduce( - ( count, menu ) => - menu?.title?.raw?.startsWith( title ) ? count + 1 : count, - 0 - ); - - // Append a number to the end of the title if a menu with - // the same name exists. - const titleWithCount = - matchingMenuTitleCount > 0 - ? `${ title } ${ matchingMenuTitleCount + 1 }` - : title; - - const menu = await createNavigationMenu( titleWithCount ); + const menu = await createNavigationMenu( null, blocks ); onSave( menu ); savingLock.current = false; }, [ @@ -172,7 +121,7 @@ export default function UnsavedInnerBlocks( { navigationMenus, hasSelection, createNavigationMenu, - area, + blocks, ] ); return ( diff --git a/packages/block-library/src/navigation/edit/use-create-navigation-menu.js b/packages/block-library/src/navigation/edit/use-create-navigation-menu.js new file mode 100644 index 0000000000000..39d54953587f0 --- /dev/null +++ b/packages/block-library/src/navigation/edit/use-create-navigation-menu.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { serialize } from '@wordpress/blocks'; +import { store as coreStore } from '@wordpress/core-data'; +import { useDispatch } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; + +/** + * Internal dependencies + */ +import useGenerateDefaultNavigationTitle from './use-generate-default-navigation-title'; + +export default function useCreateNavigationMenu( clientId ) { + const { saveEntityRecord } = useDispatch( coreStore ); + const generateDefaultTitle = useGenerateDefaultNavigationTitle( clientId ); + + // This callback uses data from the two placeholder steps and only creates + // a new navigation menu when the user completes the final step. + return useCallback( + async ( title = null, blocks = [] ) => { + if ( ! title ) { + title = await generateDefaultTitle(); + } + const record = { + title, + content: serialize( blocks ), + status: 'publish', + }; + + return await saveEntityRecord( + 'postType', + 'wp_navigation', + record + ); + }, + [ serialize, saveEntityRecord ] + ); +} diff --git a/packages/block-library/src/navigation/edit/use-generate-default-navigation-title.js b/packages/block-library/src/navigation/edit/use-generate-default-navigation-title.js new file mode 100644 index 0000000000000..e268ed288f154 --- /dev/null +++ b/packages/block-library/src/navigation/edit/use-generate-default-navigation-title.js @@ -0,0 +1,79 @@ +/** + * WordPress dependencies + */ +import { Disabled } from '@wordpress/components'; +import { store as coreStore } from '@wordpress/core-data'; +import { useRegistry } from '@wordpress/data'; +import { useContext, useCallback } from '@wordpress/element'; +import { __, sprintf } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import useTemplatePartAreaLabel from '../use-template-part-area-label'; + +const DRAFT_MENU_PARAMS = [ + 'postType', + 'wp_navigation', + { status: 'draft', per_page: -1 }, +]; + +const PUBLISHED_MENU_PARAMS = [ + 'postType', + 'wp_navigation', + { per_page: -1, status: 'publish' }, +]; + +export default function useGenerateDefaultNavigationTitle( clientId ) { + // The block will be disabled in a block preview, use this as a way of + // avoiding the side-effects of this component for block previews. + const isDisabled = useContext( Disabled.Context ); + + // Because we can't conditionally call hooks, pass an undefined client id + // arg to bypass the expensive `useTemplateArea` code. The hook will return + // early. + const area = useTemplatePartAreaLabel( isDisabled ? undefined : clientId ); + + const registry = useRegistry(); + return useCallback( async () => { + // Ensure other navigation menus have loaded so an + // accurate name can be created. + if ( isDisabled ) { + return ''; + } + const { getEntityRecords } = registry.resolveSelect( coreStore ); + + const [ draftNavigationMenus, navigationMenus ] = await Promise.all( [ + getEntityRecords( ...DRAFT_MENU_PARAMS ), + getEntityRecords( ...PUBLISHED_MENU_PARAMS ), + ] ); + + const title = area + ? sprintf( + // translators: %s: the name of a menu (e.g. Header navigation). + __( '%s navigation' ), + area + ) + : // translators: 'navigation' as in website navigation. + __( 'Navigation' ); + + // Determine how many menus start with the automatic title. + const matchingMenuTitleCount = [ + ...draftNavigationMenus, + ...navigationMenus, + ].reduce( + ( count, menu ) => + menu?.title?.raw?.startsWith( title ) ? count + 1 : count, + 0 + ); + + // Append a number to the end of the title if a menu with + // the same name exists. + const titleWithCount = + matchingMenuTitleCount > 0 + ? `${ title } ${ matchingMenuTitleCount + 1 }` + : title; + + return titleWithCount || ''; + }, [ isDisabled, area ] ); +} diff --git a/packages/block-library/src/navigation/use-navigation-menu.js b/packages/block-library/src/navigation/use-navigation-menu.js index 90e32c88ffb83..3d6ff446eae7b 100644 --- a/packages/block-library/src/navigation/use-navigation-menu.js +++ b/packages/block-library/src/navigation/use-navigation-menu.js @@ -22,9 +22,16 @@ export default function useNavigationMenu( navigationMenuId ) { const rawNavigationMenu = navigationMenuId ? getEntityRecord( ...navigationMenuSingleArgs ) : null; - const navigationMenu = navigationMenuId + let navigationMenu = navigationMenuId ? getEditedEntityRecord( ...navigationMenuSingleArgs ) : null; + + // getEditedEntityRecord will return the post regardless of status. + // Therefore if the found post is not published then we should ignore it. + if ( navigationMenu?.status !== 'publish' ) { + navigationMenu = null; + } + const hasResolvedNavigationMenu = navigationMenuId ? hasFinishedResolution( 'getEditedEntityRecord', @@ -35,7 +42,7 @@ export default function useNavigationMenu( navigationMenuId ) { const navigationMenuMultipleArgs = [ 'postType', 'wp_navigation', - { per_page: -1 }, + { per_page: -1, status: 'publish' }, ]; const navigationMenus = getEntityRecords( ...navigationMenuMultipleArgs