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 screen: Add opt-in Navigation block rendering #24503

Merged
merged 8 commits into from
Aug 18, 2020

Conversation

noisysocks
Copy link
Member

@noisysocks noisysocks commented Aug 12, 2020

Implements the majority of #24364. Continues on from the work started in #22656.

demo

Allows themes to opt-in to having wp_nav_menu() render a complete Navigation block in place of the menu. This allows arbitrarily complex Navigation block structures can be used in an existing theme.

When a theme declares add_theme_support( 'block-nav-menus' ), users are able to add non-link blocks to a Navigation using the Navigation screen.

This block tree is stored as menu items and re-assembled into a Navigation block for display using render_block() in the frontend.

If the user switches to a theme that does not support block menus, or disables this functionality, non-link blocks are no longer rendered in the frontend. Care is taken, however, to ensure that users can still see their data in nav-menus.php.

See #24364 for more information on the approach.

How to test

  1. Note that in Gutenberg > Navigation (beta), you can only add Link blocks to your Navigation. Note also that the frontend renders regular nav menu markup.

  2. Patch your current theme to support block navigation. Here's how that looks for Twenty Twenty:

Index: src/wp-content/themes/twentytwenty/functions.php
===================================================================
--- src/wp-content/themes/twentytwenty/functions.php	(revision 48655)
+++ src/wp-content/themes/twentytwenty/functions.php	(working copy)
@@ -142,6 +142,7 @@
 	$loader = new TwentyTwenty_Script_Loader();
 	add_filter( 'script_loader_tag', array( $loader, 'filter_script_loader_tag' ), 10, 2 );
 
+	add_theme_support( 'block-nav-menus' );
 }
 
 add_action( 'after_setup_theme', 'twentytwenty_theme_support' );
Index: src/wp-content/themes/twentytwenty/header.php
===================================================================
--- src/wp-content/themes/twentytwenty/header.php	(revision 48655)
+++ src/wp-content/themes/twentytwenty/header.php	(working copy)
@@ -83,25 +83,25 @@
 
 					<?php
 					if ( has_nav_menu( 'primary' ) || ! has_nav_menu( 'expanded' ) ) {
-						?>
 
-							<nav class="primary-menu-wrapper" aria-label="<?php esc_attr_e( 'Horizontal', 'twentytwenty' ); ?>" role="navigation">
+						if ( has_nav_menu( 'primary' ) ) {
 
-								<ul class="primary-menu reset-list-style">
+							wp_nav_menu(
+								array(
+									'container'      => '',
+									'items_wrap'     => '%3$s',
+									'theme_location' => 'primary',
+								)
+							);
 
-								<?php
-								if ( has_nav_menu( 'primary' ) ) {
+						} elseif ( ! has_nav_menu( 'expanded' ) ) {
+							?>
 
-									wp_nav_menu(
-										array(
-											'container'  => '',
-											'items_wrap' => '%3$s',
-											'theme_location' => 'primary',
-										)
-									);
+							<nav class="primary-menu-wrapper" aria-label="<?php esc_attr_e( 'Horizontal', 'twentytwenty' ); ?>" role="navigation">
 
-								} elseif ( ! has_nav_menu( 'expanded' ) ) {
+								<ul class="primary-menu reset-list-style">
 
+									<?php
 									wp_list_pages(
 										array(
 											'match_menu_classes' => true,
@@ -110,15 +110,14 @@
 											'walker'   => new TwentyTwenty_Walker_Page(),
 										)
 									);
+									?>
 
-								}
-								?>
-
 								</ul>
 
 							</nav><!-- .primary-menu-wrapper -->
 
-						<?php
+							<?php
+						}
 					}
 
 					if ( true === $enable_header_search || has_nav_menu( 'expanded' ) ) {
  1. The frontend should now render Navigation block markup.

  2. Go back to Gutenberg > Navigation (beta) and add a Search block. The Search block should now display on the frontend.

  3. Switch to a different theme. The frontend should now display regular nav menu markup without a Search block.

  4. Go back to Gutenberg > Navigation (beta). The editor should now not allow a Search block to be inserted. The existing Search block should still be displayed.

  5. Go to Appearance > Menus. A Gutenberg Block widget should appear containing the Search block.

Follow up tasks

@manmotive
Copy link

@noisysocks I'd like to see a way here for wp_nav_menu to be used in conjunction with blocks, and for plugins to be able to switch it on block support without affecting other locations in the theme.

I don't know the exact answer, but I think there needs to be further thought on how adding theme_support('block-nav-menus') applies to each instance of wp_nav_menu. Turning it on would mean the theme author would need to update every menu on their theme, but they won't be able to account for custom menus that their users have added, and it would wipe out any user modifications made using filters/actions/plugins that only walkers support (on that note, there would need to be some mechanism for the user to opt out of the block html navigation and go back to the walker). And the other way around, if a plugin added theme_support('block-nav-menus'), it would apply to all menu locations in the theme.

In short I think a more granular approach is needed:

  1. add_theme_support( 'block-nav-menus' ) enables blocks to be added to the nav-menus.php page
  2. a wp_nav_menu argument needs to be turned on to allow blocks to be output or to use the gutenberg block (additionally, wp_nav_menus using a custom walker should never use the block html output). E.g.
    $args->block_support = false (walker without blocks) / walker (walker with blocks) / true (use navigation block html)

(Terrible naming but you get the idea!)

Allowing 3 situations to be accounted for:

  1. A walker menu without blocks
  2. A walker menu with blocks
  3. A navigation block

(all 3 may be used in conjunction on the same site/theme)

@noisysocks
Copy link
Member Author

Hey @manmotive, thanks for the feedback. Making this setting more granular is a good idea. How about we make it a per menu location setting instead of a per theme setting by updating register_nav_menu to accept a supports argument?

register_nav_menu(
	'primary',
	array(
		'description' => __( 'Desktop Horizontal Menu' ),
		'supports'    => array(
			'blocks' => true,
		),
	)
);

It's a straightforward change but it will require Core changes because register_nav_menu and register_nav_menus are not filterable. For this reason, let's tackle it separately to this PR.

@noisysocks noisysocks force-pushed the try/have-themes-opt-in-to-blocks-in-navigation branch from 0715328 to 825bcae Compare August 13, 2020 04:18
@noisysocks noisysocks changed the title Navigation screen: Have themes opt in to blocks in navigation Navigation screen: Add opt-in Navigation block rendering Aug 13, 2020
@noisysocks noisysocks marked this pull request as ready for review August 13, 2020 04:38
Allows themes to opt-in to having `wp_nav_menu()` render a complete
Navigation block in place of the menu. This allows arbitrarily complex
Navigation block structures can be used in an existing theme.

When a theme declares `add_theme_support( 'block-nav-menus' )`, users
are able to add non-link blocks to a Navigation using the Navigation
screen.

This block tree is stored as menu items and re-assembled into a
Navigation block for display using `render_block()` in the frontend.

Non-link blocks are stored using a menu item with type `'block'` which
renders properly in `nav-menus.php`. This allows users to switch to a
theme that does not support block menus and still see their data in WP
Admin.
@noisysocks noisysocks force-pushed the try/have-themes-opt-in-to-blocks-in-navigation branch from 825bcae to 3a47a1a Compare August 13, 2020 04:39
@github-actions
Copy link

github-actions bot commented Aug 13, 2020

Size Change: -32 B (0%)

Total Size: 1.16 MB

Filename Size Change
build/api-fetch/index.js 3.44 kB +1 B
build/block-directory/index.js 7.96 kB -1 B
build/block-editor/index.js 126 kB +288 B (0%)
build/block-editor/style-rtl.css 10.7 kB +25 B (0%)
build/block-editor/style.css 10.7 kB +25 B (0%)
build/block-library/index.js 132 kB -110 B (0%)
build/block-library/style-rtl.css 7.47 kB -16 B (0%)
build/block-library/style.css 7.48 kB -16 B (0%)
build/blocks/index.js 48.4 kB +63 B (0%)
build/components/index.js 200 kB -640 B (0%)
build/components/style-rtl.css 15.7 kB +55 B (0%)
build/components/style.css 15.7 kB +56 B (0%)
build/compose/index.js 9.68 kB +1 B
build/core-data/index.js 11.8 kB -9 B (0%)
build/data/index.js 8.56 kB +107 B (1%)
build/date/index.js 5.38 kB -1 B
build/edit-navigation/index.js 11 kB +130 B (1%)
build/edit-post/index.js 304 kB -2 B (0%)
build/edit-post/style-rtl.css 5.62 kB -19 B (0%)
build/edit-post/style.css 5.61 kB -19 B (0%)
build/edit-site/index.js 17 kB -7 B (0%)
build/edit-widgets/index.js 11.7 kB +2 B (0%)
build/editor/index.js 45.3 kB -2 B (0%)
build/element/index.js 4.65 kB +1 B
build/format-library/index.js 7.71 kB -4 B (0%)
build/list-reusable-blocks/index.js 3.11 kB -1 B
build/media-utils/index.js 5.33 kB +2 B (0%)
build/plugins/index.js 2.56 kB -1 B
build/rich-text/index.js 13.9 kB +1 B
build/server-side-render/index.js 2.77 kB +59 B (2%)
build/token-list/index.js 1.27 kB +2 B (0%)
build/url/index.js 4.06 kB -2 B (0%)
ℹ️ View Unchanged
Filename Size Change
build/a11y/index.js 1.14 kB 0 B
build/annotations/index.js 3.67 kB 0 B
build/autop/index.js 2.82 kB 0 B
build/blob/index.js 620 B 0 B
build/block-directory/style-rtl.css 953 B 0 B
build/block-directory/style.css 952 B 0 B
build/block-library/editor-rtl.css 8.36 kB 0 B
build/block-library/editor.css 8.36 kB 0 B
build/block-library/theme-rtl.css 729 B 0 B
build/block-library/theme.css 730 B 0 B
build/block-serialization-default-parser/index.js 1.88 kB 0 B
build/block-serialization-spec-parser/index.js 3.1 kB 0 B
build/data-controls/index.js 1.29 kB 0 B
build/deprecated/index.js 772 B 0 B
build/dom-ready/index.js 568 B 0 B
build/dom/index.js 3.23 kB 0 B
build/edit-navigation/style-rtl.css 1.08 kB 0 B
build/edit-navigation/style.css 1.08 kB 0 B
build/edit-site/style-rtl.css 3.06 kB 0 B
build/edit-site/style.css 3.06 kB 0 B
build/edit-widgets/style-rtl.css 2.45 kB 0 B
build/edit-widgets/style.css 2.45 kB 0 B
build/editor/editor-styles-rtl.css 537 B 0 B
build/editor/editor-styles.css 539 B 0 B
build/editor/style-rtl.css 3.8 kB 0 B
build/editor/style.css 3.79 kB 0 B
build/escape-html/index.js 733 B 0 B
build/format-library/style-rtl.css 547 B 0 B
build/format-library/style.css 548 B 0 B
build/hooks/index.js 2.13 kB 0 B
build/html-entities/index.js 621 B 0 B
build/i18n/index.js 3.56 kB 0 B
build/is-shallow-equal/index.js 711 B 0 B
build/keyboard-shortcuts/index.js 2.52 kB 0 B
build/keycodes/index.js 1.94 kB 0 B
build/list-reusable-blocks/style-rtl.css 476 B 0 B
build/list-reusable-blocks/style.css 476 B 0 B
build/notices/index.js 1.79 kB 0 B
build/nux/index.js 3.4 kB 0 B
build/nux/style-rtl.css 671 B 0 B
build/nux/style.css 668 B 0 B
build/primitives/index.js 1.41 kB 0 B
build/priority-queue/index.js 789 B 0 B
build/redux-routine/index.js 2.85 kB 0 B
build/shortcode/index.js 1.7 kB 0 B
build/viewport/index.js 1.85 kB 0 B
build/warning/index.js 1.14 kB 0 B
build/wordcount/index.js 1.17 kB 0 B

compressed-size-action

@noisysocks
Copy link
Member Author

This is ready for review. I updated the PR description with a demo GIF and testing steps.

This now supports storing arbitrarily complex block trees which you can test for yourself by adding e.g. core/group to the list of allowed blocks in packages/block-library/src/navigation/edit.js. This gives us a great deal of flexibility for future changes to the Navigation block. For example, we can have container blocks in Navigation and still use them in existing themes.

Here's a very contrived example of how a Group > Columns > Column (x3) > Image | Link structure looks when viewed in nav-menus.php. Note that the deeply nested Link blocks are still converted to a menu item that older themes can understand.

Screen Shot 2020-08-13 at 14 44 19

*/
function gutenberg_setup_block_nav_menu_item( $menu_item ) {
if ( 'block' === $menu_item->type ) {
$menu_item->type_label = __( 'Gutenberg Block', 'gutenberg' );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could probably just go for 'Block' here (and for the title).

At least, I don't think the word 'Gutenberg' should be in the UI.

It'd be awesome to have the block name, but that'd probably be hard work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I second the point to remove the word "Gutenberg" :) Just "Block", or yes, ideally, the block's name.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be awesome to have the block name, but that'd probably be hard work.

We could display the block's name (e.g. "core/image") but I'm not sure that that's very useful to users.

I changed it to 'Block' in 50d192f. I updated the widgets screen, too, for consistency.

*
* @see https://core.trac.wordpress.org/ticket/50544
*
* @param array $menu_items The menu items, sorted by each menu item's menu order.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice some of the functions are missing @return documentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

),
);
}
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For safety, I feel like it'd be good to check the type here as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's OK. The type is either going to be one of the Core types (custom, object, etc.) or a totally custom one that a plugin adds. Either way we should use title and url to create a link. At least until #22919, that is.

Comment on lines +185 to +191
$block = array(
'blockName' => 'core/navigation-link',
'attrs' => array(
'label' => $menu_item->title,
'url' => $menu_item->url,
),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a good reason to use variations if this issue is implemented:
#22919

Otherwise each block will require a way to handle the menu item types.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes 100%!

if ( 'block' === $item->type ) {
?>
<p class="description description-wide">
<textarea readonly><?php echo esc_textarea( trim( $item->content ) ); ?></textarea>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this needs a label.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 334 to 354
function gutenberg_add_block_menu_item_styles_to_nav_menus( $hook ) {
if ( 'nav-menus.php' === $hook ) {
$css = <<<CSS
/**
* HACK: We're hiding the description field using CSS because this
* cannot be done using a filter. When merged to Core, we should
* actually remove the field from
* `Walker_Nav_Menu_Edit::start_el()`.
*/
.menu-item-block .description:first-child {
display: none;
}

.menu-item-block textarea {
width: 100%;
min-height: 100px;
}
CSS;
wp_add_inline_style( 'nav-menus', $css );
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't take into account that the user might have other fields enabled in screen options.

Might be best to add a unique class to the custom field wrapper, and then use :not to hide all the others.

Having said that, I wonder if we can get away with just CSS hacks. An issue I spotted is that it's possible to nest blocks in the nav-menus screen in a way that's incorrect (e.g. adding a link to search) ...

Copy link
Contributor

@draganescu draganescu Aug 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's possible to nest blocks in the nav-menus screen in a way that's incorrect (e.g. adding a link to search) .

That is something that feels funny for some reason. If they're menu items I am not sure we can make them un-nestable.

Copy link
Member Author

@noisysocks noisysocks Aug 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be best to add a unique class to the custom field wrapper, and then use :not to hide all the others.

Done in 641aebb. It relies on extra fields having the .description class which isn't necessarily the case for e.g. third party plugins. I think it's the best we can do without patching Core.

Having said that, I wonder if we can get away with just CSS hacks. An issue I spotted is that it's possible to nest blocks in the nav-menus screen in a way that's incorrect (e.g. adding a link to search) ...

That's an interesting point... maybe we could disable re-ordering Block menu items? I'm not sure.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should not allow using this UI at all if the menu contains blocks.

Is there a purpose to have multiple interfaces for the same thing?

Copy link
Member Author

@noisysocks noisysocks Aug 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to support disabling the new screen and functionality entirely (e.g. using the Classic Editor plugin) because there will be some users that prefer the old screen and some setups that rely on the nav-menus.php DOM structure.

If the new screen and functionality is disabled, I don't want any blocks that were inserted into a menu to go "missing". Hence the need to display them (in a very limited way) in nav-menus.php.

Comment on lines +27 to +33
function disableInsertingNonNavigationBlocks( settings, name ) {
if ( ! [ 'core/navigation', 'core/navigation-link' ].includes( name ) ) {
set( settings, [ 'supports', 'inserter' ], false );
}
return settings;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could also just register the blocks we want instead of registering every core and experimental block and then disabling inserter support.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is to have them registered but not insertable so that, when a user switches away from a theme that supports blocks, they'll still see the blocks that they previously added to their menu. This way we're not "losing" any of the user's data.

We'll need to add some sort of UI which indicates that the invalid blocks won't display in the frontend. I think best to do that separately since it requires design.

Copy link
Contributor

@talldan talldan Aug 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see what you mean, though not registering the blocks seems fairly close to what you describe. If they're not registered, we wouldn't lose the data, but the block would be shown as unrecognized/missing.

For this page we could also consider registering a different 'missing' block that handles the messaging better:
https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src/missing

Perhaps move this particular part out into a separate issue.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this page we could also consider registering a different 'missing' block that handles the messaging better:
https://github.com/WordPress/gutenberg/tree/master/packages/block-library/src/missing

Good idea!

Perhaps move this particular part out into a separate issue.

Agreed.

Copy link
Contributor

@draganescu draganescu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have demoed this twice by now and it works quite (amazingly) well! Aside from @talldan 's review bits, it is a solid next step and quite merge-able.

Copy link
Contributor

@talldan talldan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, nice work here! Just fix that issue with the name attribute before merging. 🚀

I think there are a couple of follow-ups:

  • Decide how to deal with non-navigation blocks on the old nav screen (e.g. disallow moving them to prevent invalid nesting?)
  • Decide how to deal with non-navigation blocks on the new nav screen for themes that opt out of the feature.

@@ -323,7 +336,7 @@ function gutenberg_output_block_menu_item_custom_fields( $item_id, $item ) {
<p class="field-content description description-wide">
<label for="edit-menu-item-content-<?php echo $item_id; ?>">
<?php _e( 'Content', 'gutenberg' ); ?><br />
<textarea readonly><?php echo esc_textarea( trim( $item->content ) ); ?></textarea>
<textarea readonly name="menu-item-content[<?php echo $item_id; ?>]"><?php echo esc_textarea( trim( $item->content ) ); ?></textarea>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing some square brackets in the in the interpolation for the name here:
Screenshot 2020-08-18 at 5 39 26 pm

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct. PHP interprets form values with [] as an array.

https://www.php.net/manual/en/reserved.variables.post.php#87650

@draganescu draganescu merged commit 10e25f6 into master Aug 18, 2020
@draganescu draganescu deleted the try/have-themes-opt-in-to-blocks-in-navigation branch August 18, 2020 15:00
@github-actions github-actions bot added this to the Gutenberg 8.9 milestone Aug 18, 2020
@noisysocks
Copy link
Member Author

Thanks all! I created #24638, #24639 and #24640 as follow up issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants