Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Navigation: Fallback to a classic menu if one is available #44173

Merged
merged 33 commits into from
Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
132a1c0
Navigation: Fallback to a classic menu if one is availiable
scruffian Sep 14, 2022
858795f
only use classic menus if there is one
scruffian Sep 15, 2022
75955e0
only import classic menus if there's only one of them
scruffian Sep 15, 2022
4923746
fix gutenberg_convert_menu_items_to_blocks
scruffian Sep 16, 2022
33e65ca
refactor
scruffian Sep 16, 2022
5657427
only create navigation once, and create it published
scruffian Sep 16, 2022
567b8c6
remove the code that makes the block dirty
scruffian Sep 16, 2022
3d0df38
convert navigation with submenu items correctly
scruffian Sep 19, 2022
2a50c49
php lint
scruffian Sep 19, 2022
8e085c1
use a different function to convert classic menus to blocks
scruffian Sep 20, 2022
cdb473a
add error handling
scruffian Sep 20, 2022
13945bc
Update packages/block-library/src/navigation/index.php
scruffian Sep 20, 2022
30df68a
Update packages/block-library/src/navigation/index.php
scruffian Sep 20, 2022
73061c6
Update packages/block-library/src/navigation/index.php
scruffian Sep 20, 2022
93d5a94
pass second param as true
scruffian Sep 20, 2022
2758284
Use existing classic menu conversion state
getdave Sep 21, 2022
6c22417
Update packages/block-library/src/navigation/edit/index.js
scruffian Sep 21, 2022
7910757
rename function
scruffian Sep 21, 2022
225b7fd
Fixes multiple menu creation on import. Also:
draganescu Sep 21, 2022
fcb2111
return early when we won't continue
scruffian Sep 22, 2022
2ad500b
a comment about where the duplicated code starts
scruffian Sep 22, 2022
a919668
Update packages/block-library/src/navigation/index.php
scruffian Sep 21, 2022
92b861c
Update packages/block-library/src/navigation/index.php
scruffian Sep 21, 2022
eba030c
make the callback from the useEffect return a function not a promise
scruffian Sep 22, 2022
6249285
remove await as this doesn't need to be async
scruffian Sep 22, 2022
ff6e9da
Prevent classic menus from importing twice'
scruffian Oct 5, 2022
98bb748
revert unnecessary changes
scruffian Oct 5, 2022
20a418b
lint fix
scruffian Oct 5, 2022
2a40f96
Rename variable for clarity
getdave Oct 5, 2022
1989a62
Fix var rename
getdave Oct 5, 2022
f38b19a
Reset the menu id on error
getdave Oct 5, 2022
a98cfc9
Revert testing code
getdave Oct 5, 2022
27107c7
serialize blocks in the function that gets the classic menu
scruffian Oct 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 45 additions & 22 deletions packages/block-library/src/navigation/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function Navigation( {

// Preload classic menus, so that they don't suddenly pop-in when viewing
// the Select Menu dropdown.
useNavigationEntities();
const { menus: classicMenus } = useNavigationEntities();

const [ showNavigationMenuStatusNotice, hideNavigationMenuStatusNotice ] =
useNavigationNotice( {
Expand Down Expand Up @@ -216,6 +216,20 @@ function Navigation( {
const navMenuResolvedButMissing =
hasResolvedNavigationMenus && isNavigationMenuMissing;

const {
convert: convertClassicMenu,
status: classicMenuConversionStatus,
error: classicMenuConversionError,
} = useConvertClassicToBlockMenu( clientId );

const isConvertingClassicMenu =
classicMenuConversionStatus === CLASSIC_MENU_CONVERSION_PENDING;

// Only autofallback to published menus.
const fallbackNavigationMenus = navigationMenus?.filter(
( menu ) => menu.status === 'publish'
);

// Attempt to retrieve and prioritize any existing navigation menu unless:
// - the are uncontrolled inner blocks already present in the block.
// - the user is creating a new menu.
Expand All @@ -228,23 +242,17 @@ function Navigation( {
hasUncontrolledInnerBlocks ||
isCreatingNavigationMenu ||
ref ||
! navigationMenus?.length
! fallbackNavigationMenus?.length
) {
return;
}

navigationMenus.sort( ( menuA, menuB ) => {
fallbackNavigationMenus.sort( ( menuA, menuB ) => {
const menuADate = new Date( menuA.date );
const menuBDate = new Date( menuB.date );
return menuADate.getTime() < menuBDate.getTime();
} );

// Only autofallback to published menus.
const fallbackNavigationMenus = navigationMenus.filter(
( menu ) => menu.status === 'publish'
);
if ( fallbackNavigationMenus.length === 0 ) return;

/**
* This fallback displays (both in editor and on front)
* a list of pages only if no menu (user assigned or
Expand All @@ -256,16 +264,26 @@ function Navigation( {
setRef( fallbackNavigationMenus[ 0 ].id );
}, [ navigationMenus ] );

const navRef = useRef();
useEffect( () => {
if (
! hasResolvedNavigationMenus ||
isConvertingClassicMenu ||
fallbackNavigationMenus?.length > 0 ||
classicMenus?.length !== 1
) {
return false;
}

const {
convert: convertClassicMenu,
status: classicMenuConversionStatus,
error: classicMenuConversionError,
} = useConvertClassicToBlockMenu( clientId );
// If there's non fallback navigation menus and
// only one classic menu then create a new navigation menu based on it.
convertClassicMenu(
classicMenus[ 0 ].id,
classicMenus[ 0 ].name,
'publish'
);
}, [ hasResolvedNavigationMenus ] );

const isConvertingClassicMenu =
classicMenuConversionStatus === CLASSIC_MENU_CONVERSION_PENDING;
const navRef = useRef();

// The standard HTML5 tag for the block wrapper.
const TagName = 'nav';
Expand All @@ -280,10 +298,11 @@ function Navigation( {
! isCreatingNavigationMenu &&
! isConvertingClassicMenu &&
hasResolvedNavigationMenus &&
classicMenus?.length === 0 &&
! hasUncontrolledInnerBlocks;

useEffect( () => {
if ( isPlaceholder && ! ref ) {
if ( isPlaceholder ) {
/**
* this fallback only displays (both in editor and on front)
* the list of pages block if no menu is available as a fallback.
Expand Down Expand Up @@ -642,7 +661,8 @@ function Navigation( {
onSelectClassicMenu={ async ( classicMenu ) => {
const navMenu = await convertClassicMenu(
classicMenu.id,
classicMenu.name
classicMenu.name,
'draft'
getdave marked this conversation as resolved.
Show resolved Hide resolved
);
if ( navMenu ) {
handleUpdateMenu( navMenu.id, {
Expand Down Expand Up @@ -723,7 +743,8 @@ function Navigation( {
onSelectClassicMenu={ async ( classicMenu ) => {
const navMenu = await convertClassicMenu(
classicMenu.id,
classicMenu.name
classicMenu.name,
'draft'
);
if ( navMenu ) {
handleUpdateMenu( navMenu.id, {
Expand Down Expand Up @@ -808,7 +829,8 @@ function Navigation( {
onSelectClassicMenu={ async ( classicMenu ) => {
const navMenu = await convertClassicMenu(
classicMenu.id,
classicMenu.name
classicMenu.name,
'draft'
);
if ( navMenu ) {
handleUpdateMenu( navMenu.id, {
Expand Down Expand Up @@ -836,7 +858,8 @@ function Navigation( {
onSelectClassicMenu={ async ( classicMenu ) => {
const navMenu = await convertClassicMenu(
classicMenu.id,
classicMenu.name
classicMenu.name,
'draft'
);
if ( navMenu ) {
handleUpdateMenu( navMenu.id, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const CLASSIC_MENU_CONVERSION_ERROR = 'error';
export const CLASSIC_MENU_CONVERSION_PENDING = 'pending';
export const CLASSIC_MENU_CONVERSION_IDLE = 'idle';

// This is needed to ensure that multiple components using this hook
// do not import the same classic menu twice.
getdave marked this conversation as resolved.
Show resolved Hide resolved
let classicMenuBeingConvertedId = null;

function useConvertClassicToBlockMenu( clientId ) {
/*
* The wp_navigation post is created as a draft so the changes on the frontend and
Expand All @@ -32,7 +36,11 @@ function useConvertClassicToBlockMenu( clientId ) {
const [ status, setStatus ] = useState( CLASSIC_MENU_CONVERSION_IDLE );
const [ error, setError ] = useState( null );

async function convertClassicMenuToBlockMenu( menuId, menuName ) {
async function convertClassicMenuToBlockMenu(
menuId,
menuName,
postStatus = 'publish'
draganescu marked this conversation as resolved.
Show resolved Hide resolved
) {
let navigationMenu;
let classicMenuItems;

Expand Down Expand Up @@ -76,7 +84,8 @@ function useConvertClassicToBlockMenu( clientId ) {
try {
navigationMenu = await createNavigationMenu(
menuName,
innerBlocks
innerBlocks,
postStatus
);

/**
Expand All @@ -91,7 +100,7 @@ function useConvertClassicToBlockMenu( clientId ) {
'wp_navigation',
navigationMenu.id,
{
status: 'publish',
status: postStatus,
},
{ throwOnError: true }
);
Expand All @@ -111,7 +120,15 @@ function useConvertClassicToBlockMenu( clientId ) {
return navigationMenu;
}

const convert = useCallback( async ( menuId, menuName ) => {
const convert = useCallback( async ( menuId, menuName, postStatus ) => {
// Check whether this classic menu is being imported already.
if ( classicMenuBeingConvertedId === menuId ) {
return;
}

// Set the ID for the currently importing classic menu.
getdave marked this conversation as resolved.
Show resolved Hide resolved
isConvertingClassicMenu = menuId;

if ( ! menuId || ! menuName ) {
setError( 'Unable to convert menu. Missing menu details.' );
setStatus( CLASSIC_MENU_CONVERSION_ERROR );
Expand All @@ -121,13 +138,20 @@ function useConvertClassicToBlockMenu( clientId ) {
setStatus( CLASSIC_MENU_CONVERSION_PENDING );
setError( null );

return await convertClassicMenuToBlockMenu( menuId, menuName )
return await convertClassicMenuToBlockMenu(
menuId,
menuName,
postStatus
)
.then( ( navigationMenu ) => {
setStatus( CLASSIC_MENU_CONVERSION_SUCCESS );
// Reset the ID for the currently importing classic menu.
isConvertingClassicMenu = null;
return navigationMenu;
} )
.catch( ( err ) => {
setError( err?.message );
// Reset the ID for the currently importing classic menu.
getdave marked this conversation as resolved.
Show resolved Hide resolved
setStatus( CLASSIC_MENU_CONVERSION_ERROR );

// Rethrow error for debugging.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ export const CREATE_NAVIGATION_MENU_ERROR = 'error';
export const CREATE_NAVIGATION_MENU_PENDING = 'pending';
export const CREATE_NAVIGATION_MENU_IDLE = 'idle';

export default function useCreateNavigationMenu(
clientId,
postStatus = 'publish'
) {
export default function useCreateNavigationMenu( clientId ) {
const [ status, setStatus ] = useState( CREATE_NAVIGATION_MENU_IDLE );
const [ value, setValue ] = useState( null );
const [ error, setError ] = useState( null );
Expand All @@ -30,7 +27,7 @@ export default function useCreateNavigationMenu(
// This callback uses data from the two placeholder steps and only creates
// a new navigation menu when the user completes the final step.
const create = useCallback(
async ( title = null, blocks = [] ) => {
async ( title = null, blocks = [], postStatus ) => {
// Guard against creating Navigations without a title.
// Note you can pass no title, but if one is passed it must be
// a string otherwise the title may end up being empty.
Expand Down
105 changes: 103 additions & 2 deletions packages/block-library/src/navigation/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -248,14 +248,109 @@ function block_core_navigation_render_submenu_icon() {
return '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none" aria-hidden="true" focusable="false"><path d="M1.50002 4L6.00002 8L10.5 4" stroke-width="1.5"></path></svg>';
}

/**
* Get the classic navigation menu to use as a fallback.
*
* @return object WP_Term The classic navigation.
*/
function block_core_navigation_get_classic_menu_fallback() {
$classic_nav_menus = wp_get_nav_menus();

// If menus exist.
if ( $classic_nav_menus && ! is_wp_error( $classic_nav_menus ) && count( $classic_nav_menus ) === 1 ) {
// Use the first classic menu only. Handles simple use case where user has a single
// classic menu and switches to a block theme. In future this maybe expanded to
// determine the most appropriate classic menu to be used based on location.
return $classic_nav_menus[0];
}
}

/**
* Converts a classic navigation to blocks.
*
* @param object $classic_nav_menu WP_Term The classic navigation object to convert.
* @return array the normalized parsed blocks.
*/
function block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu ) {
// BEGIN: Code that already exists in wp_nav_menu().
$menu_items = wp_get_nav_menu_items( $classic_nav_menu->term_id, array( 'update_post_term_cache' => false ) );

// Set up the $menu_item variables.
_wp_menu_item_classes_by_context( $menu_items );

$sorted_menu_items = array();
foreach ( (array) $menu_items as $menu_item ) {
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
}

unset( $menu_items, $menu_item );

// END: Code that already exists in wp_nav_menu().
getdave marked this conversation as resolved.
Show resolved Hide resolved

$menu_items_by_parent_id = array();
foreach ( $sorted_menu_items as $menu_item ) {
$menu_items_by_parent_id[ $menu_item->menu_item_parent ][] = $menu_item;
}

$inner_blocks = block_core_navigation_parse_blocks_from_menu_items(
isset( $menu_items_by_parent_id[0] )
? $menu_items_by_parent_id[0]
: array(),
$menu_items_by_parent_id
);

return $inner_blocks;
}

/**
* If there's a the classic menu then use it as a fallback.
*
* @return array the normalized parsed blocks.
*/
function block_core_navigation_maybe_use_classic_menu_fallback() {
// See if we have a classic menu.
$classic_nav_menu = block_core_navigation_get_classic_menu_fallback();

if ( ! $classic_nav_menu ) {
return;
}

// If we have a classic menu then convert it to blocks.
$classic_nav_menu_blocks = block_core_navigation_get_classic_menu_fallback_blocks( $classic_nav_menu );
$classic_nav_menu_blocks_serialized = serialize_blocks( $classic_nav_menu_blocks );
scruffian marked this conversation as resolved.
Show resolved Hide resolved

if ( empty( $classic_nav_menu_blocks_serialized ) ) {
return;
}

// Create a new navigation menu from the classic menu.
$wp_insert_post_result = wp_insert_post(
array(
'post_content' => $classic_nav_menu_blocks_serialized,
'post_title' => $classic_nav_menu->slug,
'post_name' => $classic_nav_menu->slug,
'post_status' => 'publish',
'post_type' => 'wp_navigation',
),
true // So that we can check whether the result is an error.
);

if ( is_wp_error( $wp_insert_post_result ) ) {
return;
}

// Fetch the most recently published navigation which will be the classic one created above.
return block_core_navigation_get_most_recently_published_navigation();
}

/**
* Finds the most recently published `wp_navigation` Post.
*
* @return WP_Post|null the first non-empty Navigation or null.
*/
function block_core_navigation_get_most_recently_published_navigation() {
// We default to the most recently created menu.

// Default to the most recently created menu.
$parsed_args = array(
'post_type' => 'wp_navigation',
'no_found_rows' => true,
Expand Down Expand Up @@ -322,7 +417,13 @@ function block_core_navigation_get_fallback_blocks() {

$navigation_post = block_core_navigation_get_most_recently_published_navigation();

// Prefer using the first non-empty Navigation as fallback if available.
// If there are no navigation posts then try to find a classic menu
// and convert it into a block based navigation menu.
if ( ! $navigation_post ) {
$navigation_post = block_core_navigation_maybe_use_classic_menu_fallback();
}

// Use the first non-empty Navigation as fallback if available.
if ( $navigation_post ) {
$maybe_fallback = block_core_navigation_filter_out_empty_blocks( parse_blocks( $navigation_post->post_content ) );

Expand Down