diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js
index a4c26c19dd5a25..eb283a5a9709b4 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. '
+ ) }
+
+ { __( 'Create a new menu?' ) }
+
+
+
+ );
+ }
+
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( {
) }
- { ! isEntityAvailable && isPlaceholderShown && (
+ { isPlaceholderShown && (
{
setIsPlaceholderShown( false );
- setNavigationMenuId( post.id );
+ if ( post ) {
+ setNavigationMenuId( post.id );
+ }
selectBlock( clientId );
} }
canSwitchNavigationMenu={ canSwitchNavigationMenu }
hasResolvedNavigationMenus={
hasResolvedNavigationMenus
}
+ clientId={ clientId }
/>
) }
{ ! isEntityAvailable && ! isPlaceholderShown && (
diff --git a/packages/block-library/src/navigation/edit/navigation-menu-name-modal.js b/packages/block-library/src/navigation/edit/navigation-menu-name-modal.js
deleted file mode 100644
index 443df3d199e3f3..00000000000000
--- a/packages/block-library/src/navigation/edit/navigation-menu-name-modal.js
+++ /dev/null
@@ -1,69 +0,0 @@
-/**
- * WordPress dependencies
- */
-import {
- Button,
- Flex,
- FlexItem,
- Modal,
- TextControl,
-} from '@wordpress/components';
-import { useState } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-
-export default function NavigationMenuNameModal( {
- title,
- finishButtonText = __( 'Create' ),
- onFinish,
- onRequestClose,
- value = '',
-} ) {
- const [ name, setName ] = useState( value );
-
- return (
-
-
-
- );
-}
diff --git a/packages/block-library/src/navigation/edit/navigation-menu-publish-button.js b/packages/block-library/src/navigation/edit/navigation-menu-publish-button.js
deleted file mode 100644
index fd4a766d1f0935..00000000000000
--- a/packages/block-library/src/navigation/edit/navigation-menu-publish-button.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
- * WordPress dependencies
- */
-import { ToolbarButton } from '@wordpress/components';
-import {
- useEntityId,
- useEntityProp,
- store as coreStore,
-} from '@wordpress/core-data';
-import { useDispatch } from '@wordpress/data';
-import { useState } from '@wordpress/element';
-import { __ } from '@wordpress/i18n';
-
-/**
- * Internal dependencies
- */
-import NavigationMenuNameModal from './navigation-menu-name-modal';
-
-export default function NavigationMenuPublishButton() {
- const [ isNameModalVisible, setIsNameModalVisible ] = useState( false );
- const id = useEntityId( 'postType', 'wp_navigation' );
- const [ navigationMenuTitle ] = useEntityProp(
- 'postType',
- 'wp_navigation',
- 'title'
- );
- const { editEntityRecord, saveEditedEntityRecord } = useDispatch(
- coreStore
- );
-
- return (
- <>
- setIsNameModalVisible( true ) }>
- { __( 'Save as' ) }
-
- { isNameModalVisible && (
- setIsNameModalVisible( false ) }
- finishButtonText={ __( 'Save' ) }
- onFinish={ ( updatedTitle ) => {
- editEntityRecord( 'postType', 'wp_navigation', id, {
- title: updatedTitle,
- status: 'publish',
- } );
- saveEditedEntityRecord(
- 'postType',
- 'wp_navigation',
- id
- );
- } }
- />
- ) }
- >
- );
-}
diff --git a/packages/block-library/src/navigation/edit/placeholder/index.js b/packages/block-library/src/navigation/edit/placeholder/index.js
index 94e341177dad43..a5ef385dfaa6df 100644
--- a/packages/block-library/src/navigation/edit/placeholder/index.js
+++ b/packages/block-library/src/navigation/edit/placeholder/index.js
@@ -1,7 +1,7 @@
/**
* WordPress dependencies
*/
-import { serialize, createBlock } from '@wordpress/blocks';
+import { createBlock } from '@wordpress/blocks';
import {
Placeholder,
Button,
@@ -9,8 +9,6 @@ import {
MenuGroup,
MenuItem,
} from '@wordpress/components';
-import { store as coreStore } from '@wordpress/core-data';
-import { useDispatch } from '@wordpress/data';
import { useCallback, useState, useEffect } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { navigation, Icon } from '@wordpress/icons';
@@ -23,8 +21,8 @@ import { decodeEntities } from '@wordpress/html-entities';
import useNavigationEntities from '../../use-navigation-entities';
import PlaceholderPreview from './placeholder-preview';
import menuItemsToBlocks from '../../menu-items-to-blocks';
-import NavigationMenuNameModal from '../navigation-menu-name-modal';
import useNavigationMenu from '../../use-navigation-menu';
+import useCreateNavigationMenu from '../use-create-navigation-menu';
const ExistingMenusDropdown = ( {
canSwitchNavigationMenu,
@@ -90,51 +88,25 @@ const ExistingMenusDropdown = ( {
};
export default function NavigationPlaceholder( {
+ clientId,
onFinish,
canSwitchNavigationMenu,
hasResolvedNavigationMenus,
} ) {
const [ selectedMenu, setSelectedMenu ] = useState();
-
const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false );
-
const [ menuName, setMenuName ] = useState( '' );
+ const createNavigationMenu = useCreateNavigationMenu( clientId );
- const [ isNewMenuModalVisible, setIsNewMenuModalVisible ] = useState(
- false
- );
-
- const [ createEmpty, setCreateEmpty ] = useState( false );
-
- const { saveEntityRecord } = useDispatch( coreStore );
-
- // This callback uses data from the two placeholder steps and only creates
- // a new navigation menu when the user completes the final step.
- const createNavigationMenu = useCallback(
- async ( title = __( 'Untitled Navigation Menu' ), blocks = [] ) => {
- const record = {
- title,
- content: serialize( blocks ),
- status: 'publish',
- };
-
- const navigationMenu = await saveEntityRecord(
- 'postType',
- 'wp_navigation',
- record
- );
-
- return navigationMenu;
- },
- [ serialize, saveEntityRecord ]
- );
-
- const onFinishMenuCreation = async ( navigationMenuTitle, blocks ) => {
+ const onFinishMenuCreation = async (
+ blocks,
+ navigationMenuTitle = null
+ ) => {
const navigationMenu = await createNavigationMenu(
navigationMenuTitle,
blocks
);
- onFinish( navigationMenu );
+ onFinish( navigationMenu, blocks );
};
const {
@@ -152,7 +124,7 @@ export default function NavigationPlaceholder( {
const createFromMenu = useCallback(
( name ) => {
const { innerBlocks: blocks } = menuItemsToBlocks( menuItems );
- onFinishMenuCreation( name, blocks );
+ onFinishMenuCreation( blocks, name );
},
[ menuItems, menuItemsToBlocks, onFinish ]
);
@@ -170,14 +142,13 @@ export default function NavigationPlaceholder( {
setMenuName( name );
};
- const onCreateEmptyMenu = ( name ) => {
- onFinishMenuCreation( name, [] );
+ const onCreateEmptyMenu = () => {
+ onFinishMenuCreation( [] );
};
- const onCreateAllPages = ( name ) => {
+ const onCreateAllPages = () => {
const block = [ createBlock( 'core/page-list' ) ];
- onFinishMenuCreation( name, block );
- setIsNewMenuModalVisible( true );
+ onFinishMenuCreation( block );
};
useEffect( () => {
@@ -225,10 +196,7 @@ export default function NavigationPlaceholder( {
<>
{
- setIsNewMenuModalVisible( true );
- setCreateEmpty( false );
- } }
+ onClick={ onCreateAllPages }
>
{ __( 'Add all pages' ) }
@@ -237,10 +205,7 @@ export default function NavigationPlaceholder( {
) : undefined }
{
- setIsNewMenuModalVisible( true );
- setCreateEmpty( true );
- } }
+ onClick={ onCreateEmptyMenu }
>
{ __( 'Start empty' ) }
@@ -248,17 +213,6 @@ export default function NavigationPlaceholder( {
) }
- { 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 798197a05d7799..76b641d7ec0728 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 00000000000000..39d54953587f06
--- /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 00000000000000..e268ed288f1547
--- /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 90e32c88ffb830..3d6ff446eae7b1 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