diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index e2c56da7797b9e..027b3b87a0a8a7 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -107,9 +107,12 @@ function Navigation( { const ref = attributes.ref; - const setRef = ( postId ) => { - setAttributes( { ref: postId } ); - }; + const setRef = useCallback( + ( postId ) => { + setAttributes( { ref: postId } ); + }, + [ setAttributes ] + ); const recursionId = `navigationMenu/${ ref }`; const hasAlreadyRendered = useHasRecursion( recursionId ); @@ -210,6 +213,17 @@ function Navigation( { [ navigationMenus ] ); + const handleUpdateMenu = useCallback( + ( menuId, options = { focusNavigationBlock: false } ) => { + const { focusNavigationBlock } = options; + setRef( menuId ); + if ( focusNavigationBlock ) { + selectBlock( clientId ); + } + }, + [ selectBlock, clientId, setRef ] + ); + // This useEffect adds snackbar and speak status notices when menus are created. // If there are no fallback navigation menus then we don't show these messages, // because this means that we are creating the first, fallback navigation menu. @@ -221,7 +235,7 @@ function Navigation( { } if ( createNavigationMenuIsSuccess ) { - handleUpdateMenu( createNavigationMenuPost.id, { + handleUpdateMenu( createNavigationMenuPost?.id, { focusNavigationBlock: true, } ); @@ -238,9 +252,13 @@ function Navigation( { ); } }, [ - createNavigationMenuStatus, - createNavigationMenuError, - createNavigationMenuPost, + createNavigationMenuIsError, + createNavigationMenuIsSuccess, + handleUpdateMenu, + hideNavigationMenuStatusNotice, + isCreatingNavigationMenu, + showNavigationMenuStatusNotice, + createNavigationMenuPost?.id, fallbackNavigationMenus, ] ); @@ -272,9 +290,11 @@ function Navigation( { setRef( fallbackNavigationMenus[ 0 ].id ); }, [ ref, + setRef, isCreatingNavigationMenu, fallbackNavigationMenus, hasUncontrolledInnerBlocks, + __unstableMarkNextChangeAsNotPersistent, ] ); const isEntityAvailable = @@ -342,7 +362,16 @@ function Navigation( { 'publish' ); } - }, [ hasResolvedNavigationMenus, hasUnsavedBlocks ] ); + }, [ + hasResolvedNavigationMenus, + hasUnsavedBlocks, + classicMenus, + convertClassicMenu, + createNavigationMenu, + fallbackNavigationMenus?.length, + isConvertingClassicMenu, + ref, + ] ); const navRef = useRef(); @@ -413,17 +442,6 @@ function Navigation( { ] = useState(); const [ detectedOverlayColor, setDetectedOverlayColor ] = useState(); - const handleUpdateMenu = useCallback( - ( menuId, options = { focusNavigationBlock: false } ) => { - const { focusNavigationBlock } = options; - setRef( menuId ); - if ( focusNavigationBlock ) { - selectBlock( clientId ); - } - }, - [ selectBlock, clientId ] - ); - const onSelectClassicMenu = async ( classicMenu ) => { const navMenu = await convertClassicMenu( classicMenu.id, @@ -449,7 +467,7 @@ function Navigation( { } if ( createNavigationMenuIsSuccess ) { - handleUpdateMenu( createNavigationMenuPost.id, { + handleUpdateMenu( createNavigationMenuPost?.id, { focusNavigationBlock: true, } ); @@ -466,7 +484,7 @@ function Navigation( { }, [ createNavigationMenuStatus, createNavigationMenuError, - createNavigationMenuPost, + createNavigationMenuPost?.id, createNavigationMenuIsError, createNavigationMenuIsSuccess, isCreatingNavigationMenu, @@ -492,7 +510,12 @@ function Navigation( { __( 'Classic menu import failed.' ) ); } - }, [ classicMenuConversionStatus, classicMenuConversionError ] ); + }, [ + classicMenuConversionStatus, + classicMenuConversionError, + hideClassicMenuConversionNotice, + showClassicMenuConversionNotice, + ] ); // Spacer block needs orientation from context. This is a patch until // https://github.com/WordPress/gutenberg/issues/36197 is addressed. @@ -501,7 +524,11 @@ function Navigation( { __unstableMarkNextChangeAsNotPersistent(); setAttributes( { orientation } ); } - }, [ orientation ] ); + }, [ + orientation, + __unstableMarkNextChangeAsNotPersistent, + setAttributes, + ] ); useEffect( () => { if ( ! enableContrastChecking ) { @@ -531,7 +558,11 @@ function Navigation( { setDetectedOverlayBackgroundColor ); } - } ); + }, [ + enableContrastChecking, + overlayTextColor.color, + overlayBackgroundColor.color, + ] ); useEffect( () => { if ( ! isSelected && ! isInnerBlockSelected ) { @@ -572,6 +603,9 @@ function Navigation( { canUserCreateNavigationMenu, hasResolvedCanUserCreateNavigationMenu, ref, + hideNavigationMenuPermissionsNotice, + showNavigationMenuPermissionsNotice, + navMenuResolvedButMissing, ] ); const hasManagePermissions = diff --git a/packages/block-library/src/navigation/edit/navigation-menu-selector.js b/packages/block-library/src/navigation/edit/navigation-menu-selector.js index eb221cbd27a7c2..a7458fd3011654 100644 --- a/packages/block-library/src/navigation/edit/navigation-menu-selector.js +++ b/packages/block-library/src/navigation/edit/navigation-menu-selector.js @@ -103,10 +103,14 @@ function NavigationMenuSelector( { setIsCreatingMenu( false ); } }, [ - isCreatingMenu, - createNavigationMenuIsError, + hasResolvedNavigationMenus, createNavigationMenuIsSuccess, - setIsCreatingMenu, + canUserCreateNavigationMenu, + createNavigationMenuIsError, + isCreatingMenu, + menuUnavailable, + noBlockMenus, + noMenuSelected, ] ); const NavigationMenuSelectorDropdown = ( 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 f83f29621579b1..167b43b9e5bcd5 100644 --- a/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js +++ b/packages/block-library/src/navigation/edit/unsaved-inner-blocks.js @@ -120,40 +120,53 @@ export default function UnsavedInnerBlocks( { const { hasResolvedNavigationMenus } = useNavigationMenu(); // Automatically save the uncontrolled blocks. - useEffect( () => { - // The block will be disabled when used in a BlockPreview. - // In this case avoid automatic creation of a wp_navigation post. - // Otherwise the user will be spammed with lots of menus! - // - // Also ensure other navigation menus have loaded so an - // accurate name can be created. - // - // Don't try saving when another save is already - // in progress. - // - // And finally only create the menu when the block is selected, - // which is an indication they want to start editing. - if ( - isDisabled || - isSaving || - ! hasResolvedDraftNavigationMenus || - ! hasResolvedNavigationMenus || - ! hasSelection || - ! innerBlocksAreDirty - ) { - return; - } + useEffect( + () => { + // The block will be disabled when used in a BlockPreview. + // In this case avoid automatic creation of a wp_navigation post. + // Otherwise the user will be spammed with lots of menus! + // + // Also ensure other navigation menus have loaded so an + // accurate name can be created. + // + // Don't try saving when another save is already + // in progress. + // + // And finally only create the menu when the block is selected, + // which is an indication they want to start editing. + if ( + isDisabled || + isSaving || + ! hasResolvedDraftNavigationMenus || + ! hasResolvedNavigationMenus || + ! hasSelection || + ! innerBlocksAreDirty + ) { + return; + } - createNavigationMenu( null, blocks ); - }, [ - createNavigationMenu, - isDisabled, - isSaving, - hasResolvedDraftNavigationMenus, - hasResolvedNavigationMenus, - innerBlocksAreDirty, - hasSelection, - ] ); + createNavigationMenu( null, blocks ); + }, + /* The dependency "blocks" is intentionally omitted here. + * This is because making blocks a dependency would cause + * createNavigationMenu to run on every block change whereas + * we only want it to run when the blocks are first detected + * as dirty. + * A better solution might be to add a hard saving lock using + * a ref to avoid having to disbale theses eslint rules. + */ + /* eslint-disable react-hooks/exhaustive-deps */ + [ + createNavigationMenu, + isDisabled, + isSaving, + hasResolvedDraftNavigationMenus, + hasResolvedNavigationMenus, + innerBlocksAreDirty, + hasSelection, + ] + /* eslint-enable react-hooks/exhaustive-deps */ + ); const Wrapper = isSaving ? Disabled : 'div'; diff --git a/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js b/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js index 8731cb5c32045e..29be140d345daf 100644 --- a/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js +++ b/packages/block-library/src/navigation/edit/use-convert-classic-menu-to-block-menu.js @@ -36,89 +36,88 @@ function useConvertClassicToBlockMenu( clientId ) { const [ status, setStatus ] = useState( CLASSIC_MENU_CONVERSION_IDLE ); const [ error, setError ] = useState( null ); - async function convertClassicMenuToBlockMenu( - menuId, - menuName, - postStatus = 'publish' - ) { - let navigationMenu; - let classicMenuItems; - - // 1. Fetch the classic Menu items. - try { - classicMenuItems = await registry - .resolveSelect( coreStore ) - .getMenuItems( { - menus: menuId, - per_page: -1, - context: 'view', - } ); - } catch ( err ) { - throw new Error( - sprintf( - // translators: %s: the name of a menu (e.g. Header navigation). - __( `Unable to fetch classic menu "%s" from API.` ), - menuName - ), - { - cause: err, - } - ); - } - - // Handle offline response which resolves to `null`. - if ( classicMenuItems === null ) { - throw new Error( - sprintf( - // translators: %s: the name of a menu (e.g. Header navigation). - __( `Unable to fetch classic menu "%s" from API.` ), - menuName - ) - ); - } - - // 2. Convert the classic items into blocks. - const { innerBlocks } = menuItemsToBlocks( classicMenuItems ); - - // 3. Create the `wp_navigation` Post with the blocks. - try { - navigationMenu = await createNavigationMenu( - menuName, - innerBlocks, - postStatus - ); - - /** - * Immediately trigger editEntityRecord to change the wp_navigation post status to 'publish'. - * This status change causes the menu to be displayed on the front of the site and sets the post state to be "dirty". - * The problem being solved is if saveEditedEntityRecord was used here, the menu would be updated on the frontend and the editor _automatically_, - * without user interaction. - * If the user abandons the site editor without saving, there would still be a wp_navigation post created as draft. - */ - await editEntityRecord( - 'postType', - 'wp_navigation', - navigationMenu.id, - { - status: postStatus, - }, - { throwOnError: true } - ); - } catch ( err ) { - throw new Error( - sprintf( - // translators: %s: the name of a menu (e.g. Header navigation). - __( `Unable to create Navigation Menu "%s".` ), - menuName - ), - { - cause: err, - } - ); - } - - return navigationMenu; - } + const convertClassicMenuToBlockMenu = useCallback( + async ( menuId, menuName, postStatus = 'publish' ) => { + let navigationMenu; + let classicMenuItems; + + // 1. Fetch the classic Menu items. + try { + classicMenuItems = await registry + .resolveSelect( coreStore ) + .getMenuItems( { + menus: menuId, + per_page: -1, + context: 'view', + } ); + } catch ( err ) { + throw new Error( + sprintf( + // translators: %s: the name of a menu (e.g. Header navigation). + __( `Unable to fetch classic menu "%s" from API.` ), + menuName + ), + { + cause: err, + } + ); + } + + // Handle offline response which resolves to `null`. + if ( classicMenuItems === null ) { + throw new Error( + sprintf( + // translators: %s: the name of a menu (e.g. Header navigation). + __( `Unable to fetch classic menu "%s" from API.` ), + menuName + ) + ); + } + + // 2. Convert the classic items into blocks. + const { innerBlocks } = menuItemsToBlocks( classicMenuItems ); + + // 3. Create the `wp_navigation` Post with the blocks. + try { + navigationMenu = await createNavigationMenu( + menuName, + innerBlocks, + postStatus + ); + + /** + * Immediately trigger editEntityRecord to change the wp_navigation post status to 'publish'. + * This status change causes the menu to be displayed on the front of the site and sets the post state to be "dirty". + * The problem being solved is if saveEditedEntityRecord was used here, the menu would be updated on the frontend and the editor _automatically_, + * without user interaction. + * If the user abandons the site editor without saving, there would still be a wp_navigation post created as draft. + */ + await editEntityRecord( + 'postType', + 'wp_navigation', + navigationMenu.id, + { + status: postStatus, + }, + { throwOnError: true } + ); + } catch ( err ) { + throw new Error( + sprintf( + // translators: %s: the name of a menu (e.g. Header navigation). + __( `Unable to create Navigation Menu "%s".` ), + menuName + ), + { + cause: err, + } + ); + } + + return navigationMenu; + }, + [ createNavigationMenu, editEntityRecord, registry ] + ); const convert = useCallback( async ( menuId, menuName, postStatus ) => { 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 index 79fcd7d6925451..b4e7372f03cbc5 100644 --- a/packages/block-library/src/navigation/edit/use-create-navigation-menu.js +++ b/packages/block-library/src/navigation/edit/use-create-navigation-menu.js @@ -90,7 +90,7 @@ export default function useCreateNavigationMenu( clientId ) { } ); } ); }, - [ serialize, saveEntityRecord, editEntityRecord, generateDefaultTitle ] + [ saveEntityRecord, editEntityRecord, generateDefaultTitle ] ); return { diff --git a/packages/block-library/src/navigation/edit/use-navigation-notice.js b/packages/block-library/src/navigation/edit/use-navigation-notice.js index 490e11b1a221f9..6d25f041775af9 100644 --- a/packages/block-library/src/navigation/edit/use-navigation-notice.js +++ b/packages/block-library/src/navigation/edit/use-navigation-notice.js @@ -23,7 +23,7 @@ function useNavigationNotice( { name, message = '' } = {} ) { type: 'snackbar', } ); }, - [ noticeRef, createWarningNotice ] + [ noticeRef, createWarningNotice, message, name ] ); const hideNotice = useCallback( () => {