From f0c8ad498b59694c62218a9ceaee14854f2faddb Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 2 Sep 2021 16:50:49 +0800 Subject: [PATCH 01/10] Add the blocks --- packages/edit-navigation/package.json | 2 + .../src/blocks/menu-item/block.json | 48 ++ .../src/blocks/menu-item/edit.js | 630 ++++++++++++++++++ .../blocks/menu-item/fallback-variations.js | 65 ++ .../src/blocks/menu-item/hooks.js | 68 ++ .../src/blocks/menu-item/icons.js | 16 + .../src/blocks/menu-item/index.js | 36 + .../src/blocks/menu-item/index.php | 113 ++++ .../src/blocks/menu/block.json | 17 + .../edit-navigation/src/blocks/menu/edit.js | 121 ++++ .../edit-navigation/src/blocks/menu/index.js | 20 + .../src/blocks/menu/placeholder.js | 1 + 12 files changed, 1137 insertions(+) create mode 100644 packages/edit-navigation/src/blocks/menu-item/block.json create mode 100644 packages/edit-navigation/src/blocks/menu-item/edit.js create mode 100644 packages/edit-navigation/src/blocks/menu-item/fallback-variations.js create mode 100644 packages/edit-navigation/src/blocks/menu-item/hooks.js create mode 100644 packages/edit-navigation/src/blocks/menu-item/icons.js create mode 100644 packages/edit-navigation/src/blocks/menu-item/index.js create mode 100644 packages/edit-navigation/src/blocks/menu-item/index.php create mode 100644 packages/edit-navigation/src/blocks/menu/block.json create mode 100644 packages/edit-navigation/src/blocks/menu/edit.js create mode 100644 packages/edit-navigation/src/blocks/menu/index.js create mode 100644 packages/edit-navigation/src/blocks/menu/placeholder.js diff --git a/packages/edit-navigation/package.json b/packages/edit-navigation/package.json index ba871b960dfa7..bdf304d9bd340 100644 --- a/packages/edit-navigation/package.json +++ b/packages/edit-navigation/package.json @@ -37,6 +37,7 @@ "@wordpress/core-data": "file:../core-data", "@wordpress/data": "file:../data", "@wordpress/data-controls": "file:../data-controls", + "@wordpress/dom": "file:../dom", "@wordpress/dom-ready": "file:../dom-ready", "@wordpress/element": "file:../element", "@wordpress/hooks": "file:../hooks", @@ -45,6 +46,7 @@ "@wordpress/icons": "file:../icons", "@wordpress/interface": "file:../interface", "@wordpress/keyboard-shortcuts": "file:../keyboard-shortcuts", + "@wordpress/keycodes": "file:../keycodes", "@wordpress/media-utils": "file:../media-utils", "@wordpress/notices": "file:../notices", "@wordpress/url": "file:../url", diff --git a/packages/edit-navigation/src/blocks/menu-item/block.json b/packages/edit-navigation/src/blocks/menu-item/block.json new file mode 100644 index 0000000000000..247a16c4c4c0f --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/block.json @@ -0,0 +1,48 @@ +{ + "apiVersion": 2, + "name": "edit-navigation/menu-item", + "title": "Menu Item", + "category": "design", + "parent": [ + "edit-navigation/menu" + ], + "description": "Add a page, link, or another item to your navigation.", + "textdomain": "default", + "attributes": { + "label": { + "type": "string" + }, + "type": { + "type": "string" + }, + "description": { + "type": "string" + }, + "rel": { + "type": "string" + }, + "id": { + "type": "number" + }, + "opensInNewTab": { + "type": "boolean", + "default": false + }, + "url": { + "type": "string" + }, + "title": { + "type": "string" + }, + "kind": { + "type": "string" + } + }, + "supports": { + "customClassName": false, + "reusable": false, + "html": false + }, + "editorStyle": "wp-block-navigation-link-editor", + "style": "wp-block-navigation-link" +} diff --git a/packages/edit-navigation/src/blocks/menu-item/edit.js b/packages/edit-navigation/src/blocks/menu-item/edit.js new file mode 100644 index 0000000000000..c97c37470a713 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/edit.js @@ -0,0 +1,630 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { escape } from 'lodash'; + +/** + * WordPress dependencies + */ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { + KeyboardShortcuts, + PanelBody, + Popover, + TextControl, + TextareaControl, + ToolbarButton, + ToolbarGroup, +} from '@wordpress/components'; +import { rawShortcut, displayShortcut } from '@wordpress/keycodes'; +import { __, sprintf } from '@wordpress/i18n'; +import { + BlockControls, + InnerBlocks, + __experimentalUseInnerBlocksProps as useInnerBlocksProps, + InspectorControls, + RichText, + __experimentalLinkControl as LinkControl, + useBlockProps, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { isURL, prependHTTP, safeDecodeURI } from '@wordpress/url'; +import { + Fragment, + useState, + useEffect, + useRef, + createInterpolateElement, +} from '@wordpress/element'; +import { placeCaretAtHorizontalEdge } from '@wordpress/dom'; +import { link as linkIcon, addSubmenu } from '@wordpress/icons'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +/** + * Internal dependencies + */ +import { ItemSubmenuIcon } from './icons'; +import { name } from './block.json'; + +const ALLOWED_BLOCKS = [ 'edit-navigation/menu-item' ]; + +/** + * A React hook to determine if it's dragging within the target element. + * + * @typedef {import('@wordpress/element').RefObject} RefObject + * + * @param {RefObject} elementRef The target elementRef object. + * + * @return {boolean} Is dragging within the target element. + */ +const useIsDraggingWithin = ( elementRef ) => { + const [ isDraggingWithin, setIsDraggingWithin ] = useState( false ); + + useEffect( () => { + const { ownerDocument } = elementRef.current; + + function handleDragStart( event ) { + // Check the first time when the dragging starts. + handleDragEnter( event ); + } + + // Set to false whenever the user cancel the drag event by either releasing the mouse or press Escape. + function handleDragEnd() { + setIsDraggingWithin( false ); + } + + function handleDragEnter( event ) { + // Check if the current target is inside the item element. + if ( elementRef.current.contains( event.target ) ) { + setIsDraggingWithin( true ); + } else { + setIsDraggingWithin( false ); + } + } + + // Bind these events to the document to catch all drag events. + // Ideally, we can also use `event.relatedTarget`, but sadly that + // doesn't work in Safari. + ownerDocument.addEventListener( 'dragstart', handleDragStart ); + ownerDocument.addEventListener( 'dragend', handleDragEnd ); + ownerDocument.addEventListener( 'dragenter', handleDragEnter ); + + return () => { + ownerDocument.removeEventListener( 'dragstart', handleDragStart ); + ownerDocument.removeEventListener( 'dragend', handleDragEnd ); + ownerDocument.removeEventListener( 'dragenter', handleDragEnter ); + }; + }, [] ); + + return isDraggingWithin; +}; + +/** + * Given the Link block's type attribute, return the query params to give to + * /wp/v2/search. + * + * @param {string} type Link block's type attribute. + * @param {string} kind Link block's entity of kind (post-type|taxonomy) + * @return {{ type?: string, subtype?: string }} Search query params. + */ +function getSuggestionsQuery( type, kind ) { + switch ( type ) { + case 'post': + case 'page': + return { type: 'post', subtype: type }; + case 'category': + return { type: 'term', subtype: 'category' }; + case 'tag': + return { type: 'term', subtype: 'post_tag' }; + case 'post_format': + return { type: 'post-format' }; + default: + if ( kind === 'taxonomy' ) { + return { type: 'term', subtype: type }; + } + if ( kind === 'post-type' ) { + return { type: 'post', subtype: type }; + } + return {}; + } +} + +/** + * @typedef {'post-type'|'custom'|'taxonomy'|'post-type-archive'} WPNavigationLinkKind + */ + +/** + * Navigation Link Block Attributes + * + * @typedef {Object} WPNavigationLinkBlockAttributes + * + * @property {string} [label] Link text. + * @property {WPNavigationLinkKind} [kind] Kind is used to differentiate between term and post ids to check post draft status. + * @property {string} [type] The type such as post, page, tag, category and other custom types. + * @property {string} [rel] The relationship of the linked URL. + * @property {number} [id] A post or term id. + * @property {boolean} [opensInNewTab] Sets link target to _blank when true. + * @property {string} [url] Link href. + * @property {string} [title] Link title attribute. + */ + +/** + * Link Control onChange handler that updates block attributes when a setting is changed. + * + * @param {Object} updatedValue New block attributes to update. + * @param {Function} setAttributes Block attribute update function. + * @param {WPNavigationLinkBlockAttributes} blockAttributes Current block attributes. + * + */ +export const updateNavigationLinkBlockAttributes = ( + updatedValue = {}, + setAttributes, + blockAttributes = {} +) => { + const { + label: originalLabel = '', + kind: originalKind = '', + type: originalType = '', + } = blockAttributes; + const { + title = '', + url = '', + opensInNewTab, + id, + kind: newKind = originalKind, + type: newType = originalType, + } = updatedValue; + + const normalizedTitle = title.replace( /http(s?):\/\//gi, '' ); + const normalizedURL = url.replace( /http(s?):\/\//gi, '' ); + const escapeTitle = + title !== '' && + normalizedTitle !== normalizedURL && + originalLabel !== title; + const label = escapeTitle + ? escape( title ) + : originalLabel || escape( normalizedURL ); + + // In https://github.com/WordPress/gutenberg/pull/24670 we decided to use "tag" in favor of "post_tag" + const type = newType === 'post_tag' ? 'tag' : newType.replace( '-', '_' ); + + const isBuiltInType = + [ 'post', 'page', 'tag', 'category' ].indexOf( type ) > -1; + + const isCustomLink = + ( ! newKind && ! isBuiltInType ) || newKind === 'custom'; + const kind = isCustomLink ? 'custom' : newKind; + + setAttributes( { + // Passed `url` may already be encoded. To prevent double encoding, decodeURI is executed to revert to the original string. + ...( url && { url: encodeURI( safeDecodeURI( url ) ) } ), + ...( label && { label } ), + ...( undefined !== opensInNewTab && { opensInNewTab } ), + ...( id && Number.isInteger( id ) && { id } ), + ...( kind && { kind } ), + ...( type && type !== 'URL' && { type } ), + } ); +}; + +export default function MenuItemEdit( { + attributes, + isSelected, + setAttributes, + insertBlocksAfter, + mergeBlocks, + onReplace, + clientId, +} ) { + const { + label, + type, + opensInNewTab, + url, + description, + rel, + title, + kind, + } = attributes; + const link = { + url, + opensInNewTab, + }; + const { saveEntityRecord } = useDispatch( coreStore ); + const { insertBlock } = useDispatch( blockEditorStore ); + const [ isLinkOpen, setIsLinkOpen ] = useState( false ); + const listItemRef = useRef( null ); + const isDraggingWithin = useIsDraggingWithin( listItemRef ); + const itemLabelPlaceholder = __( 'Add linkā€¦' ); + const ref = useRef(); + + const { + isTopLevelLink, + isParentOfSelectedBlock, + isImmediateParentOfSelectedBlock, + hasDescendants, + selectedBlockHasDescendants, + numberOfDescendants, + userCanCreatePages, + userCanCreatePosts, + } = useSelect( + ( select ) => { + const { + getClientIdsOfDescendants, + hasSelectedInnerBlock, + getSelectedBlockClientId, + getBlockParentsByBlockName, + } = select( blockEditorStore ); + + const selectedBlockId = getSelectedBlockClientId(); + + const descendants = getClientIdsOfDescendants( [ clientId ] ) + .length; + + return { + isTopLevelLink: + getBlockParentsByBlockName( clientId, name ).length === 0, + isParentOfSelectedBlock: hasSelectedInnerBlock( + clientId, + true + ), + isImmediateParentOfSelectedBlock: hasSelectedInnerBlock( + clientId, + false + ), + hasDescendants: !! descendants, + selectedBlockHasDescendants: !! getClientIdsOfDescendants( [ + selectedBlockId, + ] )?.length, + numberOfDescendants: descendants, + userCanCreatePages: select( coreStore ).canUser( + 'create', + 'pages' + ), + userCanCreatePosts: select( coreStore ).canUser( + 'create', + 'posts' + ), + }; + }, + [ clientId ] + ); + + // Store the colors from context as attributes for rendering + useEffect( () => setAttributes( { isTopLevelLink } ), [ isTopLevelLink ] ); + + /** + * Insert a link block when submenu is added. + */ + function insertLinkBlock() { + const insertionPoint = numberOfDescendants; + const blockToInsert = createBlock( 'edit-navigation/menu-item' ); + insertBlock( blockToInsert, insertionPoint, clientId ); + } + + // Show the LinkControl on mount if the URL is empty + // ( When adding a new menu item) + // This can't be done in the useState call because it conflicts + // with the autofocus behavior of the BlockListBlock component. + useEffect( () => { + if ( ! url ) { + setIsLinkOpen( true ); + } + }, [] ); + + /** + * The hook shouldn't be necessary but due to a focus loss happening + * when selecting a suggestion in the link popover, we force close on block unselection. + */ + useEffect( () => { + if ( ! isSelected ) { + setIsLinkOpen( false ); + } + }, [ isSelected ] ); + + // If the LinkControl popover is open and the URL has changed, close the LinkControl and focus the label text. + useEffect( () => { + if ( isLinkOpen && url ) { + // Does this look like a URL and have something TLD-ish? + if ( + isURL( prependHTTP( label ) ) && + /^.+\.[a-z]+/.test( label ) + ) { + // Focus and select the label text. + selectLabelText(); + } else { + // Focus it (but do not select). + placeCaretAtHorizontalEdge( ref.current, true ); + } + } + }, [ url ] ); + + /** + * Focus the Link label text and select it. + */ + function selectLabelText() { + ref.current.focus(); + const { ownerDocument } = ref.current; + const { defaultView } = ownerDocument; + const selection = defaultView.getSelection(); + const range = ownerDocument.createRange(); + // Get the range of the current ref contents so we can add this range to the selection. + range.selectNodeContents( ref.current ); + selection.removeAllRanges(); + selection.addRange( range ); + } + + /** + * Removes the current link if set. + */ + function removeLink() { + // Reset all attributes that comprise the link. + setAttributes( { + url: '', + label: '', + id: '', + kind: '', + type: '', + } ); + + // Close the link editing UI. + setIsLinkOpen( false ); + } + + let userCanCreate = false; + if ( ! type || type === 'page' ) { + userCanCreate = userCanCreatePages; + } else if ( type === 'post' ) { + userCanCreate = userCanCreatePosts; + } + + async function handleCreate( pageTitle ) { + const postType = type || 'page'; + + const page = await saveEntityRecord( 'postType', postType, { + title: pageTitle, + status: 'draft', + } ); + + return { + id: page.id, + type: postType, + title: page.title.rendered, + url: page.link, + kind: 'post-type', + }; + } + + const blockProps = useBlockProps( { + ref: listItemRef, + className: classnames( 'wp-block-menu-item', { + 'is-editing': isSelected || isParentOfSelectedBlock, + 'is-dragging-within': isDraggingWithin, + 'has-link': !! url, + 'has-child': hasDescendants, + } ), + } ); + + if ( ! url ) { + blockProps.onClick = () => setIsLinkOpen( true ); + } + + const innerBlocksProps = useInnerBlocksProps( + { + className: classnames( 'wp-block-menu-item__submenu-container', { + 'is-parent-of-selected-block': isParentOfSelectedBlock, + } ), + }, + { + allowedBlocks: ALLOWED_BLOCKS, + renderAppender: + ( isSelected && hasDescendants ) || + ( isImmediateParentOfSelectedBlock && + ! selectedBlockHasDescendants ) || + // Show the appender while dragging to allow inserting element between item and the appender. + hasDescendants + ? InnerBlocks.DefaultAppender + : false, + } + ); + + const classes = classnames( 'wp-block-menu-item__content', { + 'wp-block-menu-item__placeholder': ! url, + } ); + + let missingText = ''; + switch ( type ) { + case 'post': + /* translators: label for missing post in navigation link block */ + missingText = __( 'Select post' ); + break; + case 'page': + /* translators: label for missing page in navigation link block */ + missingText = __( 'Select page' ); + break; + case 'category': + /* translators: label for missing category in navigation link block */ + missingText = __( 'Select category' ); + break; + case 'tag': + /* translators: label for missing tag in navigation link block */ + missingText = __( 'Select tag' ); + break; + default: + /* translators: label for missing values in navigation link block */ + missingText = __( 'Add link' ); + } + + return ( + + + + + setIsLinkOpen( true ), + } } + /> + setIsLinkOpen( true ) } + /> + + + + + + { + setAttributes( { description: descriptionValue } ); + } } + label={ __( 'Description' ) } + help={ __( + 'The description will be displayed in the menu if the current theme supports it.' + ) } + /> + { + setAttributes( { title: titleValue } ); + } } + label={ __( 'Link title' ) } + autoComplete="off" + /> + { + setAttributes( { rel: relValue } ); + } } + label={ __( 'Link rel' ) } + autoComplete="off" + /> + + +
+ { hasDescendants && ( + + + + ) } + { /* eslint-disable jsx-a11y/anchor-is-valid */ } + + { /* eslint-enable */ } + { ! url ? ( +
+ + isSelected && setIsLinkOpen( true ), + } } + /> + { missingText } +
+ ) : ( + + setAttributes( { label: labelValue } ) + } + onMerge={ mergeBlocks } + onReplace={ onReplace } + __unstableOnSplitAtEnd={ () => + insertBlocksAfter( + createBlock( 'edit-navigation/menu-item' ) + ) + } + aria-label={ __( 'Menu item link text' ) } + placeholder={ itemLabelPlaceholder } + withoutInteractiveFormatting + allowedFormats={ [ + 'core/bold', + 'core/italic', + 'core/image', + 'core/strikethrough', + ] } + onClick={ () => { + if ( ! url ) { + setIsLinkOpen( true ); + } + } } + /> + ) } + { isLinkOpen && ( + setIsLinkOpen( false ) } + anchorRef={ listItemRef.current } + > + setIsLinkOpen( false ), + } } + /> + { + let format; + if ( type === 'post' ) { + /* translators: %s: search term. */ + format = __( + 'Create draft post: %s' + ); + } else { + /* translators: %s: search term. */ + format = __( + 'Create draft page: %s' + ); + } + return createInterpolateElement( + sprintf( format, searchTerm ), + { mark: } + ); + } } + noDirectEntry={ !! type } + noURLSuggestion={ !! type } + suggestionsQuery={ getSuggestionsQuery( + type, + kind + ) } + onChange={ ( updatedValue ) => + updateNavigationLinkBlockAttributes( + updatedValue, + setAttributes, + attributes + ) + } + onRemove={ removeLink } + /> + + ) } +
+
+
+ + ); +} diff --git a/packages/edit-navigation/src/blocks/menu-item/fallback-variations.js b/packages/edit-navigation/src/blocks/menu-item/fallback-variations.js new file mode 100644 index 0000000000000..7173cb9be9097 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/fallback-variations.js @@ -0,0 +1,65 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { + category as categoryIcon, + page as pageIcon, + postTitle as postIcon, + tag as tagIcon, +} from '@wordpress/icons'; + +// FALLBACK: this is only used when the server does not understand the variations property in the +// register_block_type call. see navigation-link/index.php. +// Delete this file when supported WP ranges understand the `variations` property when passed to +// register_block_type in index.php +const fallbackVariations = [ + { + name: 'link', + isDefault: true, + title: __( 'Custom Link' ), + description: __( 'A link to a custom URL.' ), + attributes: {}, + }, + { + name: 'post', + icon: postIcon, + title: __( 'Post Link' ), + description: __( 'A link to a post.' ), + attributes: { type: 'post', kind: 'post-type' }, + }, + { + name: 'page', + icon: pageIcon, + title: __( 'Page Link' ), + description: __( 'A link to a page.' ), + attributes: { type: 'page', kind: 'post-type' }, + }, + { + name: 'category', + icon: categoryIcon, + title: __( 'Category Link' ), + description: __( 'A link to a category.' ), + attributes: { type: 'category', kind: 'taxonomy' }, + }, + { + name: 'tag', + icon: tagIcon, + title: __( 'Tag Link' ), + description: __( 'A link to a tag.' ), + attributes: { type: 'tag', kind: 'taxonomy' }, + }, +]; + +/** + * Add `isActive` function to all `navigation link` variations, if not defined. + * `isActive` function is used to find a variation match from a created + * Block by providing its attributes. + */ +fallbackVariations.forEach( ( variation ) => { + if ( variation.isActive ) return; + variation.isActive = ( blockAttributes, variationAttributes ) => + blockAttributes.type === variationAttributes.type; +} ); + +export default fallbackVariations; diff --git a/packages/edit-navigation/src/blocks/menu-item/hooks.js b/packages/edit-navigation/src/blocks/menu-item/hooks.js new file mode 100644 index 0000000000000..0d5ee9d4b62f4 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/hooks.js @@ -0,0 +1,68 @@ +/** + * WordPress dependencies + */ +import { + category, + page, + postTitle, + tag, + customPostType, +} from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import fallbackVariations from './fallback-variations'; + +function getIcon( variationName ) { + switch ( variationName ) { + case 'post': + return postTitle; + case 'page': + return page; + case 'tag': + return tag; + case 'category': + return category; + default: + return customPostType; + } +} + +export function enhanceNavigationLinkVariations( settings, name ) { + if ( name !== 'core/navigation-link' ) { + return settings; + } + + // Fallback handling may be deleted after supported WP ranges understand the `variations` + // property when passed to register_block_type in index.php + if ( ! settings.variations ) { + return { + ...settings, + variations: fallbackVariations, + }; + } + + // Otherwise decorate server passed variations with an icon and isActive function + if ( settings.variations ) { + const isActive = ( blockAttributes, variationAttributes ) => { + return blockAttributes.type === variationAttributes.type; + }; + const variations = settings.variations.map( ( variation ) => { + return { + ...variation, + ...( ! variation.icon && { + icon: getIcon( variation.name ), + } ), + ...( ! variation.isActive && { + isActive, + } ), + }; + } ); + return { + ...settings, + variations, + }; + } + return settings; +} diff --git a/packages/edit-navigation/src/blocks/menu-item/icons.js b/packages/edit-navigation/src/blocks/menu-item/icons.js new file mode 100644 index 0000000000000..3a44a250b084e --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/icons.js @@ -0,0 +1,16 @@ +/** + * WordPress dependencies + */ +import { Path, SVG } from '@wordpress/components'; + +export const ItemSubmenuIcon = () => ( + + + +); diff --git a/packages/edit-navigation/src/blocks/menu-item/index.js b/packages/edit-navigation/src/blocks/menu-item/index.js new file mode 100644 index 0000000000000..2c84bffddf814 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/index.js @@ -0,0 +1,36 @@ +/** + * WordPress dependencies + */ +import { customLink as linkIcon } from '@wordpress/icons'; +import { addFilter } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; +import { enhanceNavigationLinkVariations } from './hooks'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + icon: linkIcon, + __experimentalLabel: ( { label } ) => label, + merge( leftAttributes, { label: rightLabel = '' } ) { + return { + ...leftAttributes, + label: leftAttributes.label + rightLabel, + }; + }, + edit, + save() {}, +}; + +// importing this file includes side effects. This is whitelisted in block-library/package.json under sideEffects +addFilter( + 'blocks.registerBlockType', + 'edit-navigation/menu-item', + enhanceNavigationLinkVariations +); diff --git a/packages/edit-navigation/src/blocks/menu-item/index.php b/packages/edit-navigation/src/blocks/menu-item/index.php new file mode 100644 index 0000000000000..813d3872e8a16 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu-item/index.php @@ -0,0 +1,113 @@ +labels, 'item_link' ) ) { + $title = $entity->labels->item_link; + } + if ( property_exists( $entity->labels, 'item_link_description' ) ) { + $description = $entity->labels->item_link_description; + } + + $variation = array( + 'name' => $entity->name, + 'title' => $title, + 'description' => $description, + 'attributes' => array( + 'type' => $entity->name, + 'kind' => $kind, + ), + ); + + // Tweak some value for the variations. + $variation_overrides = array( + 'post_tag' => array( + 'name' => 'tag', + 'attributes' => array( + 'type' => 'tag', + 'kind' => $kind, + ), + ), + 'post_format' => array( + // The item_link and item_link_description for post formats is the + // same as for tags, so need to be overridden. + 'title' => __( 'Post Format Link' ), + 'description' => __( 'A link to a post format' ), + 'attributes' => array( + 'type' => 'post_format', + 'kind' => $kind, + ), + ), + ); + + if ( array_key_exists( $entity->name, $variation_overrides ) ) { + $variation = array_merge( + $variation, + $variation_overrides[ $entity->name ] + ); + } + + return $variation; +} + +/** + * Register the navigation link block. + * + * @uses render_block_core_navigation() + * @throws WP_Error An WP_Error exception parsing the block definition. + */ +function register_block_edit_navigation_menu_item() { + $post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' ); + $taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' ); + + // Use two separate arrays as a way to order the variations in the UI. + // Known variations (like Post Link and Page Link) are added to the + // `built_ins` array. Variations for custom post types and taxonomies are + // added to the `variations` array and will always appear after `built-ins. + $built_ins = array(); + $variations = array(); + + if ( $post_types ) { + foreach ( $post_types as $post_type ) { + $variation = build_variation_for_navigation_link( $post_type, 'post-type' ); + if ( 'post' === $variation['name'] || 'page' === $variation['name'] ) { + $built_ins[] = $variation; + } else { + $variations[] = $variation; + } + } + } + if ( $taxonomies ) { + foreach ( $taxonomies as $taxonomy ) { + $variation = build_variation_for_navigation_link( $taxonomy, 'taxonomy' ); + if ( 'category' === $variation['name'] || 'tag' === $variation['name'] || 'post_format' === $variation['name'] ) { + $built_ins[] = $variation; + } else { + $variations[] = $variation; + } + } + } + + register_block_type_from_metadata( + __DIR__ . '/menu-item', + array( + 'variations' => array_merge( $built_ins, $variations ), + ) + ); +} +add_action( 'init', 'register_block_edit_navigation_menu_item' ); diff --git a/packages/edit-navigation/src/blocks/menu/block.json b/packages/edit-navigation/src/blocks/menu/block.json new file mode 100644 index 0000000000000..58c2c34b00d46 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/block.json @@ -0,0 +1,17 @@ +{ + "apiVersion": 2, + "name": "edit-navigation/menu", + "title": "Menu", + "category": "theme", + "description": "A collection of blocks that allow visitors to get around your site.", + "keywords": [ "menu", "navigation", "links" ], + "textdomain": "default", + "attributes": {}, + "supports": { + "customClassName": false, + "html": false, + "inserter": true + }, + "editorStyle": "wp-block-menu-editor", + "style": "wp-block-menu" +} diff --git a/packages/edit-navigation/src/blocks/menu/edit.js b/packages/edit-navigation/src/blocks/menu/edit.js new file mode 100644 index 0000000000000..7be3852432370 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/edit.js @@ -0,0 +1,121 @@ +/** + * WordPress dependencies + */ +import { useState } from '@wordpress/element'; +import { + InnerBlocks, + __experimentalUseInnerBlocksProps as useInnerBlocksProps, + useBlockProps, + store as blockEditorStore, +} from '@wordpress/block-editor'; +import { useDispatch, withSelect, withDispatch } from '@wordpress/data'; +import { compose } from '@wordpress/compose'; + +/** + * Internal dependencies + */ +import Placeholder from './placeholder'; + +const ALLOWED_BLOCKS = [ 'edit-navigation/menu-item' ]; + +function MenuEdit( { + selectedBlockHasDescendants, + clientId, + hasExistingNavItems, + isImmediateParentOfSelectedBlock, + isSelected, + updateInnerBlocks, + className, +} ) { + const [ isPlaceholderShown, setIsPlaceholderShown ] = useState( + ! hasExistingNavItems + ); + + const { selectBlock } = useDispatch( blockEditorStore ); + + const blockProps = useBlockProps( { className } ); + const innerBlocksProps = useInnerBlocksProps( + { + className: 'wp-block-menu__container', + }, + { + allowedBlocks: ALLOWED_BLOCKS, + renderAppender: + ( isImmediateParentOfSelectedBlock && + ! selectedBlockHasDescendants ) || + isSelected + ? InnerBlocks.DefaultAppender + : false, + __experimentalCaptureToolbars: true, + // Template lock set to false here so that the Nav + // Block on the experimental menus screen does not + // inherit templateLock={ 'all' }. + templateLock: false, + } + ); + + if ( isPlaceholderShown ) { + return ( +
+ { + setIsPlaceholderShown( false ); + updateInnerBlocks( blocks ); + if ( selectNavigationBlock ) { + selectBlock( clientId ); + } + } } + /> +
+ ); + } + + return ( + + ); +} + +export default compose( [ + withSelect( ( select, { clientId } ) => { + const innerBlocks = select( blockEditorStore ).getBlocks( clientId ); + const { + getClientIdsOfDescendants, + hasSelectedInnerBlock, + getSelectedBlockClientId, + } = select( blockEditorStore ); + const isImmediateParentOfSelectedBlock = hasSelectedInnerBlock( + clientId, + false + ); + const selectedBlockId = getSelectedBlockClientId(); + const selectedBlockHasDescendants = !! getClientIdsOfDescendants( [ + selectedBlockId, + ] )?.length; + + return { + isImmediateParentOfSelectedBlock, + selectedBlockHasDescendants, + hasExistingNavItems: !! innerBlocks.length, + + // This prop is already available but computing it here ensures it's + // fresh compared to isImmediateParentOfSelectedBlock + isSelected: selectedBlockId === clientId, + }; + } ), + withDispatch( ( dispatch, { clientId } ) => { + return { + updateInnerBlocks( blocks ) { + if ( blocks?.length === 0 ) { + return false; + } + dispatch( blockEditorStore ).replaceInnerBlocks( + clientId, + blocks, + true + ); + }, + }; + } ), +] )( MenuEdit ); diff --git a/packages/edit-navigation/src/blocks/menu/index.js b/packages/edit-navigation/src/blocks/menu/index.js new file mode 100644 index 0000000000000..45e31a678090e --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/index.js @@ -0,0 +1,20 @@ +/** + * WordPress dependencies + */ +import { navigation as icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import metadata from './block.json'; +import edit from './edit'; + +const { name } = metadata; + +export { metadata, name }; + +export const settings = { + icon, + edit, + save() {}, +}; diff --git a/packages/edit-navigation/src/blocks/menu/placeholder.js b/packages/edit-navigation/src/blocks/menu/placeholder.js new file mode 100644 index 0000000000000..ba7e3f53b6edc --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/placeholder.js @@ -0,0 +1 @@ +export default function Placeholder() {} From aa6e22f4104e5713212b804be2e155b50a209193 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 2 Sep 2021 17:07:20 +0800 Subject: [PATCH 02/10] Replace use of navigation block with menu block --- packages/edit-navigation/README.md | 10 ++--- .../src/blocks/menu-item/hooks.js | 2 +- .../src/blocks/menu-item/index.php | 2 +- .../src/filters/add-menu-name-editor.js | 2 +- ...disable-inserting-non-navigation-blocks.js | 6 ++- .../remove-edit-unsupported-features.js | 2 +- .../remove-settings-unsupported-features.js | 2 +- packages/edit-navigation/src/index.js | 37 +++++++++++++++---- .../src/store/menu-items-to-blocks.js | 4 +- .../edit-navigation/src/store/resolvers.js | 6 +-- .../edit-navigation/src/store/test/actions.js | 26 ++++++------- .../src/store/test/menu-items-to-blocks.js | 26 ++++++------- .../src/store/test/resolvers.js | 12 +++--- .../edit-navigation/src/store/test/utils.js | 16 ++++---- packages/edit-navigation/src/store/utils.js | 6 +-- 15 files changed, 91 insertions(+), 68 deletions(-) diff --git a/packages/edit-navigation/README.md b/packages/edit-navigation/README.md index 1a405fdd4f1e2..fd87be5dfd60b 100644 --- a/packages/edit-navigation/README.md +++ b/packages/edit-navigation/README.md @@ -51,15 +51,15 @@ Themes can opt into this behaviour by declaring: add_theme_support( 'block-nav-menus' ); ``` -This unlocks significant additional capabilities in the Navigation Editor. For example, by default, [the Navigation Editor screen only allows _link_ (`core/navigation-link`) blocks to be inserted into a navigation](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js). When a theme opts into `block-nav-menus` however, users are able to add non-link blocks to a navigation using the Navigation Editor screen, including: +This unlocks significant additional capabilities in the Navigation Editor. For example, by default, [the Navigation Editor screen only allows _link_ (`edit-navigation/menu-item`) blocks to be inserted into a navigation](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js). When a theme opts into `block-nav-menus` however, users are able to add non-link blocks to a navigation using the Navigation Editor screen, including: -- `core/navigation-link`. +- `edit-navigation/menu-item`. - `core/social`. - `core/search`. #### Technical Implementation details -By default, `core/navigation-link` items are serialized and persisted as `nav_menu_item` posts. No serialized block HTML is stored for these standard link blocks. +By default, `edit-navigation/menu-item` items are serialized and persisted as `nav_menu_item` posts. No serialized block HTML is stored for these standard link blocks. _Non_-link navigation items however, are [persisted as `nav_menu_items` with a special `type` of `block`](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/packages/edit-navigation/src/store/utils.js#L159-L166). These items have an [_additional_ `content` field which is used to store the serialized block markup](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/lib/navigation.php#L71-L101). @@ -160,8 +160,8 @@ return ( ## Glossary -- **Link block** - the basic `core/navigation-link` block which is the standard block used to add links within navigations. -- **Navigation block** - the root `core/navigation` block which can be used both with the Navigation Editor and outside (eg: Post / Site Editor). +- **Link block** - the basic `edit-navigation/menu-item` block which is the standard block used to add links within navigations. +- **Navigation block** - the root `edit-navigation/menu` block which can be used both with the Navigation Editor and outside (eg: Post / Site Editor). - **Navigation editor / screen** - the new screen provided by Gutenberg to allow the user to edit navigations using a block-based UI. - **Menus screen** - the current/existing [interface/screen for managing Menus](https://codex.wordpress.org/WordPress_Menu_User_Guide) in WordPress WPAdmin. diff --git a/packages/edit-navigation/src/blocks/menu-item/hooks.js b/packages/edit-navigation/src/blocks/menu-item/hooks.js index 0d5ee9d4b62f4..c1f98eda6f74c 100644 --- a/packages/edit-navigation/src/blocks/menu-item/hooks.js +++ b/packages/edit-navigation/src/blocks/menu-item/hooks.js @@ -30,7 +30,7 @@ function getIcon( variationName ) { } export function enhanceNavigationLinkVariations( settings, name ) { - if ( name !== 'core/navigation-link' ) { + if ( name !== 'edit-navigation/menu-item' ) { return settings; } diff --git a/packages/edit-navigation/src/blocks/menu-item/index.php b/packages/edit-navigation/src/blocks/menu-item/index.php index 813d3872e8a16..98e945c3a9b0b 100644 --- a/packages/edit-navigation/src/blocks/menu-item/index.php +++ b/packages/edit-navigation/src/blocks/menu-item/index.php @@ -1,6 +1,6 @@ ( props ) => { - if ( props.name !== 'core/navigation' ) { + if ( props.name !== 'edit-navigation/menu' ) { return ; } return ( diff --git a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js index c9a34fe168b0f..667ea13f3735f 100644 --- a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js +++ b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js @@ -8,7 +8,11 @@ import { addFilter } from '@wordpress/hooks'; import { set } from 'lodash'; function disableInsertingNonNavigationBlocks( settings, name ) { - if ( ! [ 'core/navigation', 'core/navigation-link' ].includes( name ) ) { + if ( + ! [ 'edit-navigation/menu', 'edit-navigation/menu-item' ].includes( + name + ) + ) { set( settings, [ 'supports', 'inserter' ], false ); } return settings; diff --git a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js index 2c58b995158d4..230d869eb1fbe 100644 --- a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js +++ b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js @@ -6,7 +6,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; const removeNavigationBlockEditUnsupportedFeatures = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - if ( props.name !== 'core/navigation' ) { + if ( props.name !== 'edit-navigation/menu' ) { return ; } diff --git a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js index 26d62f9ec6448..a8ad2a64a6c77 100644 --- a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js +++ b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js @@ -4,7 +4,7 @@ import { addFilter } from '@wordpress/hooks'; function removeNavigationBlockSettingsUnsupportedFeatures( settings, name ) { - if ( name !== 'core/navigation' ) { + if ( name !== 'edit-navigation/menu' ) { return settings; } diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index 54ecb2df73c88..e17f34a3c2736 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -1,16 +1,18 @@ /** * WordPress dependencies */ -import { - registerCoreBlocks, - __experimentalRegisterExperimentalCoreBlocks, -} from '@wordpress/block-library'; +/** + * WordPress dependencies + */ +import { registerBlockType } from '@wordpress/blocks'; import { render } from '@wordpress/element'; import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wordpress/core-data'; /** * Internal dependencies */ +import * as menuBlock from './blocks/menu'; +import * as menuItemBlock from './blocks/menu-item'; import { addFilters } from './filters'; /** @@ -19,13 +21,32 @@ import { addFilters } from './filters'; import Layout from './components/layout'; import './store'; +/** + * Function to register an individual block. + * + * @param {Object} block The block to be registered. + * + */ +const registerBlock = ( block ) => { + if ( ! block ) { + return; + } + const { metadata, settings, name } = block; + registerBlockType( { name, ...metadata }, settings ); +}; + export function initialize( id, settings ) { addFilters( ! settings.blockNavMenus ); - registerCoreBlocks(); - if ( process.env.GUTENBERG_PHASE === 2 ) { - __experimentalRegisterExperimentalCoreBlocks(); - } + registerBlock( menuBlock ); + registerBlock( menuItemBlock ); + + // Required for wp-nav-menus theme opt-in. + // registerCoreBlocks(); + // + // if ( process.env.GUTENBERG_PHASE === 2 ) { + // __experimentalRegisterExperimentalCoreBlocks(); + // } settings.__experimentalFetchLinkSuggestions = ( search, searchOptions ) => fetchLinkSuggestions( search, searchOptions, settings ); diff --git a/packages/edit-navigation/src/store/menu-items-to-blocks.js b/packages/edit-navigation/src/store/menu-items-to-blocks.js index 024c267d67cef..c858b8176e67c 100644 --- a/packages/edit-navigation/src/store/menu-items-to-blocks.js +++ b/packages/edit-navigation/src/store/menu-items-to-blocks.js @@ -67,7 +67,7 @@ function mapMenuItemsToBlocks( menuItems ) { // Create block with nested "innerBlocks". const block = createBlock( - 'core/navigation-link', + 'edit-navigation/menu-item', attributes, nestedBlocks ); @@ -125,7 +125,7 @@ function menuItemToBlockAttributes( { type: menuItemTypeField, target, } ) { - // For historical reasons, the `core/navigation-link` variation type is `tag` + // For historical reasons, the `edit-navigation/menu-item` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `blockAttributesToMenuItem`. diff --git a/packages/edit-navigation/src/store/resolvers.js b/packages/edit-navigation/src/store/resolvers.js index 25648239434d5..0b81dc13ccc50 100644 --- a/packages/edit-navigation/src/store/resolvers.js +++ b/packages/edit-navigation/src/store/resolvers.js @@ -93,10 +93,8 @@ function createNavigationBlock( menuItems ) { ); const navigationBlock = createBlock( - 'core/navigation', - { - orientation: 'vertical', - }, + 'edit-navigation/menu', + {}, innerBlocks ); return [ navigationBlock, menuItemIdToClientId ]; diff --git a/packages/edit-navigation/src/store/test/actions.js b/packages/edit-navigation/src/store/test/actions.js index 0c2bf7c90f681..6859b492e2e65 100644 --- a/packages/edit-navigation/src/store/test/actions.js +++ b/packages/edit-navigation/src/store/test/actions.js @@ -47,7 +47,7 @@ describe( 'createMissingMenuItems', () => { clientId: 'navigation-block-client-id', innerBlocks: [], isValid: true, - name: 'core/navigation', + name: 'edit-navigation/menu', }, ], }; @@ -131,7 +131,7 @@ describe( 'createMissingMenuItems', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu', }, { attributes: { @@ -142,11 +142,11 @@ describe( 'createMissingMenuItems', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu', }, ], isValid: true, - name: 'core/navigation', + name: 'edit-navigation/menu', }, ], }; @@ -262,7 +262,7 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -277,11 +277,11 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, ], isValid: true, - name: 'core/navigation', + name: 'edit-navigation/menu', }, ], }; @@ -410,7 +410,7 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -425,11 +425,11 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, ], isValid: true, - name: 'core/navigation', + name: 'edit-navigation/menu', }, ], }; @@ -551,7 +551,7 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -566,11 +566,11 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, ], isValid: true, - name: 'core/navigation', + name: 'edit-navigation/menu', }, ], }; diff --git a/packages/edit-navigation/src/store/test/menu-items-to-blocks.js b/packages/edit-navigation/src/store/test/menu-items-to-blocks.js index 599b30e679ff5..5b56a26557249 100644 --- a/packages/edit-navigation/src/store/test/menu-items-to-blocks.js +++ b/packages/edit-navigation/src/store/test/menu-items-to-blocks.js @@ -62,11 +62,11 @@ describe( 'converting menu items to blocks', () => { expect( actual ).toEqual( [ expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', innerBlocks: [], } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', innerBlocks: [], } ), ] ); @@ -186,32 +186,32 @@ describe( 'converting menu items to blocks', () => { expect( actual ).toEqual( [ expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Top Level', } ), innerBlocks: [ expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Child 1', } ), innerBlocks: [], } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Child 2', } ), innerBlocks: [ expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Sub Child', } ), innerBlocks: [ expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Sub Sub Child', } ), @@ -224,7 +224,7 @@ describe( 'converting menu items to blocks', () => { ], } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Top Level 2', } ), @@ -329,31 +329,31 @@ describe( 'converting menu items to blocks', () => { expect( actual ).toEqual( [ expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Ordered 1st', } ), } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Ordered 2nd', } ), } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Ordered 3rd', } ), } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Ordered 4th', } ), } ), expect.objectContaining( { - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', attributes: expect.objectContaining( { label: 'Ordered 5th', } ), diff --git a/packages/edit-navigation/src/store/test/resolvers.js b/packages/edit-navigation/src/store/test/resolvers.js index eced25a0396f9..fca104b206953 100644 --- a/packages/edit-navigation/src/store/test/resolvers.js +++ b/packages/edit-navigation/src/store/test/resolvers.js @@ -163,7 +163,7 @@ describe( 'getNavigationPostForMenu', () => { }, clientId: 'client-id-0', innerBlocks: [], - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -174,7 +174,7 @@ describe( 'getNavigationPostForMenu', () => { }, clientId: 'client-id-1', innerBlocks: [], - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -187,10 +187,10 @@ describe( 'getNavigationPostForMenu', () => { }, clientId: 'client-id-2', innerBlocks: [], - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, ], - name: 'core/navigation', + name: 'edit-navigation/menu', }, ], meta: { @@ -221,7 +221,7 @@ describe( 'getNavigationPostForMenu', () => { expect( generator.next().done ).toBe( true ); } ); - it( 'creates correct core/navigation-link block variations from menu objects', () => { + it( 'creates correct edit-navigation/menu-item block variations from menu objects', () => { const menuId = 123; const generator = getNavigationPostForMenu( menuId ); @@ -292,7 +292,7 @@ describe( 'getNavigationPostForMenu', () => { // Gen step: yield persistPost const persistPostAction = generator.next().value; - // Get the core/navigation-link blocks from the generated core/navigation block innerBlocks. + // Get the edit-navigation/menu-item blocks from the generated edit-navigation/menu block innerBlocks. const blockAttrs = persistPostAction.args[ 2 ].blocks[ 0 ].innerBlocks.map( ( block ) => block.attributes ); diff --git a/packages/edit-navigation/src/store/test/utils.js b/packages/edit-navigation/src/store/test/utils.js index f03f096a983e2..1087c617dc4d9 100644 --- a/packages/edit-navigation/src/store/test/utils.js +++ b/packages/edit-navigation/src/store/test/utils.js @@ -226,7 +226,7 @@ describe( 'computeCustomizedAttribute', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -241,7 +241,7 @@ describe( 'computeCustomizedAttribute', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, { attributes: { @@ -257,7 +257,7 @@ describe( 'computeCustomizedAttribute', () => { clientId: 'navigation-link-block-client-id-3', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, ]; @@ -375,7 +375,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, menuItem: { title: 'Example Page', @@ -407,7 +407,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, menuItem: { title: 'Example Post', @@ -440,7 +440,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-3', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, menuItem: { title: 'Example Category', @@ -470,7 +470,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-4', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, menuItem: { title: 'Example Tag', @@ -497,7 +497,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-5', innerBlocks: [], isValid: true, - name: 'core/navigation-link', + name: 'edit-navigation/menu-item', }, menuItem: { title: 'Example Custom Link', diff --git a/packages/edit-navigation/src/store/utils.js b/packages/edit-navigation/src/store/utils.js index 13da7255ad626..fca34270a2f67 100644 --- a/packages/edit-navigation/src/store/utils.js +++ b/packages/edit-navigation/src/store/utils.js @@ -156,7 +156,7 @@ export function computeCustomizedAttribute( let attributes; - if ( block.name === 'core/navigation-link' ) { + if ( block.name === 'edit-navigation/menu-item' ) { attributes = blockAttributesToMenuItem( block.attributes ); } else { attributes = { @@ -214,7 +214,7 @@ export const blockAttributesToMenuItem = ( { kind, opensInNewTab, } ) => { - // For historical reasons, the `core/navigation-link` variation type is `tag` + // For historical reasons, the `edit-navigation/menu-item` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `menuItemToBlockAttributes`. @@ -272,7 +272,7 @@ export const menuItemToBlockAttributes = ( { type: menuItemTypeField, target, } ) => { - // For historical reasons, the `core/navigation-link` variation type is `tag` + // For historical reasons, the `edit-navigation/menu-item` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `blockAttributesToMenuItem`. From 64b5166a62e235f3c03cb2abb10dc1b7eb9a79ad Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 2 Sep 2021 19:39:13 +0800 Subject: [PATCH 03/10] Change lots --- package-lock.json | 2 + packages/edit-navigation/README.md | 10 +- .../src/blocks/menu-item/block.json | 4 +- .../src/blocks/menu-item/edit.js | 6 +- .../src/blocks/menu-item/hooks.js | 2 +- .../src/blocks/menu-item/index.js | 2 +- .../src/blocks/menu-item/index.php | 2 +- .../src/blocks/menu-item/style.scss | 72 +++++++ .../src/blocks/menu/block.json | 2 +- .../edit-navigation/src/blocks/menu/edit.js | 2 +- .../src/blocks/menu/menu-items-to-blocks.js | 191 ++++++++++++++++++ .../src/blocks/menu/placeholder.js | 154 +++++++++++++- .../src/blocks/menu/style.scss | 27 +++ .../blocks/menu/use-navigation-entities.js | 142 +++++++++++++ .../src/components/editor/style.scss | 146 ------------- .../edit-navigation/src/constants/index.js | 2 +- .../src/filters/add-menu-name-editor.js | 2 +- ...disable-inserting-non-navigation-blocks.js | 6 +- .../remove-edit-unsupported-features.js | 2 +- .../remove-settings-unsupported-features.js | 2 +- .../src/store/menu-items-to-blocks.js | 8 +- .../edit-navigation/src/store/resolvers.js | 6 +- .../edit-navigation/src/store/test/actions.js | 26 +-- .../src/store/test/menu-items-to-blocks.js | 26 +-- .../src/store/test/resolvers.js | 12 +- .../edit-navigation/src/store/test/utils.js | 16 +- packages/edit-navigation/src/store/utils.js | 6 +- packages/edit-navigation/src/style.scss | 2 + 28 files changed, 655 insertions(+), 225 deletions(-) create mode 100644 packages/edit-navigation/src/blocks/menu-item/style.scss create mode 100644 packages/edit-navigation/src/blocks/menu/menu-items-to-blocks.js create mode 100644 packages/edit-navigation/src/blocks/menu/style.scss create mode 100644 packages/edit-navigation/src/blocks/menu/use-navigation-entities.js diff --git a/package-lock.json b/package-lock.json index def5e7fc6713d..50eff9dec7dce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18408,6 +18408,7 @@ "@wordpress/core-data": "file:packages/core-data", "@wordpress/data": "file:packages/data", "@wordpress/data-controls": "file:packages/data-controls", + "@wordpress/dom": "file:packages/dom", "@wordpress/dom-ready": "file:packages/dom-ready", "@wordpress/element": "file:packages/element", "@wordpress/hooks": "file:packages/hooks", @@ -18416,6 +18417,7 @@ "@wordpress/icons": "file:packages/icons", "@wordpress/interface": "file:packages/interface", "@wordpress/keyboard-shortcuts": "file:packages/keyboard-shortcuts", + "@wordpress/keycodes": "file:packages/keycodes", "@wordpress/media-utils": "file:packages/media-utils", "@wordpress/notices": "file:packages/notices", "@wordpress/url": "file:packages/url", diff --git a/packages/edit-navigation/README.md b/packages/edit-navigation/README.md index fd87be5dfd60b..5b4c1fbe08401 100644 --- a/packages/edit-navigation/README.md +++ b/packages/edit-navigation/README.md @@ -51,15 +51,15 @@ Themes can opt into this behaviour by declaring: add_theme_support( 'block-nav-menus' ); ``` -This unlocks significant additional capabilities in the Navigation Editor. For example, by default, [the Navigation Editor screen only allows _link_ (`edit-navigation/menu-item`) blocks to be inserted into a navigation](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js). When a theme opts into `block-nav-menus` however, users are able to add non-link blocks to a navigation using the Navigation Editor screen, including: +This unlocks significant additional capabilities in the Navigation Editor. For example, by default, [the Navigation Editor screen only allows _link_ (`core/menu-item`) blocks to be inserted into a navigation](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js). When a theme opts into `block-nav-menus` however, users are able to add non-link blocks to a navigation using the Navigation Editor screen, including: -- `edit-navigation/menu-item`. +- `core/menu-item`. - `core/social`. - `core/search`. #### Technical Implementation details -By default, `edit-navigation/menu-item` items are serialized and persisted as `nav_menu_item` posts. No serialized block HTML is stored for these standard link blocks. +By default, `core/menu-item` items are serialized and persisted as `nav_menu_item` posts. No serialized block HTML is stored for these standard link blocks. _Non_-link navigation items however, are [persisted as `nav_menu_items` with a special `type` of `block`](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/packages/edit-navigation/src/store/utils.js#L159-L166). These items have an [_additional_ `content` field which is used to store the serialized block markup](https://github.com/WordPress/gutenberg/blob/7fcd57c9a62c232899e287f6d96416477d810d5e/lib/navigation.php#L71-L101). @@ -160,8 +160,8 @@ return ( ## Glossary -- **Link block** - the basic `edit-navigation/menu-item` block which is the standard block used to add links within navigations. -- **Navigation block** - the root `edit-navigation/menu` block which can be used both with the Navigation Editor and outside (eg: Post / Site Editor). +- **Link block** - the basic `core/menu-item` block which is the standard block used to add links within navigations. +- **Navigation block** - the root `core/menu` block which can be used both with the Navigation Editor and outside (eg: Post / Site Editor). - **Navigation editor / screen** - the new screen provided by Gutenberg to allow the user to edit navigations using a block-based UI. - **Menus screen** - the current/existing [interface/screen for managing Menus](https://codex.wordpress.org/WordPress_Menu_User_Guide) in WordPress WPAdmin. diff --git a/packages/edit-navigation/src/blocks/menu-item/block.json b/packages/edit-navigation/src/blocks/menu-item/block.json index 247a16c4c4c0f..a85a67d0af608 100644 --- a/packages/edit-navigation/src/blocks/menu-item/block.json +++ b/packages/edit-navigation/src/blocks/menu-item/block.json @@ -1,10 +1,10 @@ { "apiVersion": 2, - "name": "edit-navigation/menu-item", + "name": "core/menu-item", "title": "Menu Item", "category": "design", "parent": [ - "edit-navigation/menu" + "core/menu" ], "description": "Add a page, link, or another item to your navigation.", "textdomain": "default", diff --git a/packages/edit-navigation/src/blocks/menu-item/edit.js b/packages/edit-navigation/src/blocks/menu-item/edit.js index c97c37470a713..d8c5a16eb572a 100644 --- a/packages/edit-navigation/src/blocks/menu-item/edit.js +++ b/packages/edit-navigation/src/blocks/menu-item/edit.js @@ -54,7 +54,7 @@ import { store as coreStore } from '@wordpress/core-data'; import { ItemSubmenuIcon } from './icons'; import { name } from './block.json'; -const ALLOWED_BLOCKS = [ 'edit-navigation/menu-item' ]; +const ALLOWED_BLOCKS = [ 'core/menu-item' ]; /** * A React hook to determine if it's dragging within the target element. @@ -305,7 +305,7 @@ export default function MenuItemEdit( { */ function insertLinkBlock() { const insertionPoint = numberOfDescendants; - const blockToInsert = createBlock( 'edit-navigation/menu-item' ); + const blockToInsert = createBlock( 'core/menu-item' ); insertBlock( blockToInsert, insertionPoint, clientId ); } @@ -550,7 +550,7 @@ export default function MenuItemEdit( { onReplace={ onReplace } __unstableOnSplitAtEnd={ () => insertBlocksAfter( - createBlock( 'edit-navigation/menu-item' ) + createBlock( 'core/menu-item' ) ) } aria-label={ __( 'Menu item link text' ) } diff --git a/packages/edit-navigation/src/blocks/menu-item/hooks.js b/packages/edit-navigation/src/blocks/menu-item/hooks.js index c1f98eda6f74c..bc5a0762e835a 100644 --- a/packages/edit-navigation/src/blocks/menu-item/hooks.js +++ b/packages/edit-navigation/src/blocks/menu-item/hooks.js @@ -30,7 +30,7 @@ function getIcon( variationName ) { } export function enhanceNavigationLinkVariations( settings, name ) { - if ( name !== 'edit-navigation/menu-item' ) { + if ( name !== 'core/menu-item' ) { return settings; } diff --git a/packages/edit-navigation/src/blocks/menu-item/index.js b/packages/edit-navigation/src/blocks/menu-item/index.js index 2c84bffddf814..7f70daa03ac08 100644 --- a/packages/edit-navigation/src/blocks/menu-item/index.js +++ b/packages/edit-navigation/src/blocks/menu-item/index.js @@ -31,6 +31,6 @@ export const settings = { // importing this file includes side effects. This is whitelisted in block-library/package.json under sideEffects addFilter( 'blocks.registerBlockType', - 'edit-navigation/menu-item', + 'core/menu-item', enhanceNavigationLinkVariations ); diff --git a/packages/edit-navigation/src/blocks/menu-item/index.php b/packages/edit-navigation/src/blocks/menu-item/index.php index 98e945c3a9b0b..b3efa051103aa 100644 --- a/packages/edit-navigation/src/blocks/menu-item/index.php +++ b/packages/edit-navigation/src/blocks/menu-item/index.php @@ -1,6 +1,6 @@ .wp-block-menu-item__submenu-container { + display: block; + padding-left: $grid-unit-15; + border: none; + + &::before { + display: none; + } + } + + // Apply focus outlines. + &.is-selected > .wp-block-menu-item__content, + &.is-selected:hover > .wp-block-menu-item__content { + box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); + } + + .wp-block-menu-item__content { + display: block; + color: inherit; + padding: 0.5em 1em; + margin-bottom: 6px; + border-radius: $radius-block-ui; + + &:hover { + box-shadow: 0 0 0 $border-width $gray-300; + } + } + + .wp-block-menu-item__label { + cursor: text; + } + + .wp-block-menu-item__label, + .wp-block-menu-item__placeholder-text { + padding: $grid-unit-05; + padding-left: $grid-unit-10; + } + + // Submenu icon indicator. + .wp-block-menu-item__submenu-icon { + position: absolute; + top: 6px; + padding: 6px; + pointer-events: none; + + svg { + display: inline-block; + stroke: currentColor; + + // Point rightwards. + transform: rotate(-90deg); + transition: transform 0.2s ease; + @include reduce-motion("transition"); + } + } +} + +// Rotate icon downwards when a submenu is open. +.is-selected.has-child > .wp-block-menu-item__submenu-icon svg, +.has-child-selected.has-child > .wp-block-menu-item__submenu-icon svg { + transform: rotate(0deg); +} diff --git a/packages/edit-navigation/src/blocks/menu/block.json b/packages/edit-navigation/src/blocks/menu/block.json index 58c2c34b00d46..836ae5d28412f 100644 --- a/packages/edit-navigation/src/blocks/menu/block.json +++ b/packages/edit-navigation/src/blocks/menu/block.json @@ -1,6 +1,6 @@ { "apiVersion": 2, - "name": "edit-navigation/menu", + "name": "core/menu", "title": "Menu", "category": "theme", "description": "A collection of blocks that allow visitors to get around your site.", diff --git a/packages/edit-navigation/src/blocks/menu/edit.js b/packages/edit-navigation/src/blocks/menu/edit.js index 7be3852432370..b69936e96ea26 100644 --- a/packages/edit-navigation/src/blocks/menu/edit.js +++ b/packages/edit-navigation/src/blocks/menu/edit.js @@ -16,7 +16,7 @@ import { compose } from '@wordpress/compose'; */ import Placeholder from './placeholder'; -const ALLOWED_BLOCKS = [ 'edit-navigation/menu-item' ]; +const ALLOWED_BLOCKS = [ 'core/menu-item' ]; function MenuEdit( { selectedBlockHasDescendants, diff --git a/packages/edit-navigation/src/blocks/menu/menu-items-to-blocks.js b/packages/edit-navigation/src/blocks/menu/menu-items-to-blocks.js new file mode 100644 index 0000000000000..43843d547e1fb --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/menu-items-to-blocks.js @@ -0,0 +1,191 @@ +/** + * External dependencies + */ +import { sortBy } from 'lodash'; + +/** + * WordPress dependencies + */ +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; + +/** + * Convert a flat menu item structure to a nested blocks structure. + * + * @param {Object[]} menuItems An array of menu items. + * + * @return {WPBlock[]} An array of blocks. + */ +export default function menuItemsToBlocks( menuItems ) { + if ( ! menuItems ) { + return null; + } + + const menuTree = createDataTree( menuItems ); + return mapMenuItemsToBlocks( menuTree ); +} + +/** + * A recursive function that maps menu item nodes to blocks. + * + * @param {WPNavMenuItem[]} menuItems An array of WPNavMenuItem items. + * @return {Object} Object containing innerBlocks and mapping. + */ +function mapMenuItemsToBlocks( menuItems ) { + let mapping = {}; + + // The menuItem should be in menu_order sort order. + const sortedItems = sortBy( menuItems, 'menu_order' ); + + const innerBlocks = sortedItems.map( ( menuItem ) => { + const attributes = menuItemToBlockAttributes( menuItem ); + + // If there are children recurse to build those nested blocks. + const { + innerBlocks: nestedBlocks = [], // alias to avoid shadowing + mapping: nestedMapping = {}, // alias to avoid shadowing + } = menuItem.children?.length + ? mapMenuItemsToBlocks( menuItem.children ) + : {}; + + // Update parent mapping with nested mapping. + mapping = { + ...mapping, + ...nestedMapping, + }; + + // Create block with nested "innerBlocks". + const block = createBlock( 'core/menu-item', attributes, nestedBlocks ); + + // Create mapping for menuItem -> block + mapping[ menuItem.id ] = block.clientId; + + return block; + } ); + + return { + innerBlocks, + mapping, + }; +} + +/** + * A WP nav_menu_item object. + * For more documentation on the individual fields present on a menu item please see: + * https://core.trac.wordpress.org/browser/tags/5.7.1/src/wp-includes/nav-menu.php#L789 + * + * Changes made here should also be mirrored in packages/edit-navigation/src/store/utils.js. + * + * @typedef WPNavMenuItem + * + * @property {Object} title stores the raw and rendered versions of the title/label for this menu item. + * @property {Array} xfn the XFN relationships expressed in the link of this menu item. + * @property {Array} classes the HTML class attributes for this menu item. + * @property {string} attr_title the HTML title attribute for this menu item. + * @property {string} object The type of object originally represented, such as 'category', 'post', or 'attachment'. + * @property {string} object_id The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories. + * @property {string} description The description of this menu item. + * @property {string} url The URL to which this menu item points. + * @property {string} type The family of objects originally represented, such as 'post_type' or 'taxonomy'. + * @property {string} target The target attribute of the link element for this menu item. + */ + +/** + * Convert block attributes to menu item. + * + * @param {WPNavMenuItem} menuItem the menu item to be converted to block attributes. + * @return {Object} the block attributes converted from the WPNavMenuItem item. + */ +function menuItemToBlockAttributes( { + title: menuItemTitleField, + xfn, + classes, + // eslint-disable-next-line camelcase + attr_title, + object, + // eslint-disable-next-line camelcase + object_id, + description, + url, + type: menuItemTypeField, + target, +} ) { + // For historical reasons, the `core/navigation-link` variation type is `tag` + // whereas WP Core expects `post_tag` as the `object` type. + // To avoid writing a block migration we perform a conversion here. + // See also inverse equivalent in `blockAttributesToMenuItem`. + if ( object && object === 'post_tag' ) { + object = 'tag'; + } + + return { + label: menuItemTitleField?.rendered || '', + ...( object?.length && { + type: object, + } ), + kind: menuItemTypeField?.replace( '_', '-' ) || 'custom', + url: url || '', + ...( xfn?.length && + xfn.join( ' ' ).trim() && { + rel: xfn.join( ' ' ).trim(), + } ), + ...( classes?.length && + classes.join( ' ' ).trim() && { + className: classes.join( ' ' ).trim(), + } ), + ...( attr_title?.length && { + title: attr_title, + } ), + // eslint-disable-next-line camelcase + ...( object_id && + 'custom' !== object && { + id: object_id, + } ), + ...( description?.length && { + description, + } ), + ...( target === '_blank' && { + opensInNewTab: true, + } ), + }; +} + +/** + * Creates a nested, hierarchical tree representation from unstructured data that + * has an inherent relationship defined between individual items. + * + * For example, by default, each element in the dataset should have an `id` and + * `parent` property where the `parent` property indicates a relationship between + * the current item and another item with a matching `id` properties. + * + * This is useful for building linked lists of data from flat data structures. + * + * @param {Array} dataset linked data to be rearranged into a hierarchical tree based on relational fields. + * @param {string} id the property which uniquely identifies each entry within the array. + * @param {*} relation the property which identifies how the current item is related to other items in the data (if at all). + * @return {Array} a nested array of parent/child relationships + */ +function createDataTree( dataset, id = 'id', relation = 'parent' ) { + const hashTable = Object.create( null ); + const dataTree = []; + + for ( const data of dataset ) { + hashTable[ data[ id ] ] = { + ...data, + children: [], + }; + } + for ( const data of dataset ) { + if ( data[ relation ] ) { + hashTable[ data[ relation ] ].children.push( + hashTable[ data[ id ] ] + ); + } else { + dataTree.push( hashTable[ data[ id ] ] ); + } + } + + return dataTree; +} diff --git a/packages/edit-navigation/src/blocks/menu/placeholder.js b/packages/edit-navigation/src/blocks/menu/placeholder.js index ba7e3f53b6edc..fd71a7e052a1e 100644 --- a/packages/edit-navigation/src/blocks/menu/placeholder.js +++ b/packages/edit-navigation/src/blocks/menu/placeholder.js @@ -1 +1,153 @@ -export default function Placeholder() {} +/** + * WordPress dependencies + */ +import { createBlock } from '@wordpress/blocks'; +import { + Placeholder, + Button, + DropdownMenu, + MenuGroup, + MenuItem, + Spinner, +} from '@wordpress/components'; + +import { + forwardRef, + useCallback, + useState, + useEffect, +} from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; +import { navigation, chevronDown, Icon } from '@wordpress/icons'; + +/** + * Internal dependencies + */ +import useNavigationEntities from './use-navigation-entities'; +import menuItemsToBlocks from './menu-items-to-blocks'; + +function NavigationPlaceholder( { onCreate }, ref ) { + const [ selectedMenu, setSelectedMenu ] = useState(); + + const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false ); + + const { + isResolvingPages, + menus, + isResolvingMenus, + menuItems, + hasResolvedMenuItems, + hasPages, + hasMenus, + } = useNavigationEntities( selectedMenu ); + + const isLoading = isResolvingPages || isResolvingMenus; + + const createFromMenu = useCallback( () => { + const { innerBlocks: blocks } = menuItemsToBlocks( menuItems ); + const selectNavigationBlock = true; + onCreate( blocks, selectNavigationBlock ); + } ); + + const onCreateFromMenu = () => { + // If we have menu items, create the block right away. + if ( hasResolvedMenuItems ) { + createFromMenu(); + return; + } + + // Otherwise, create the block when resolution finishes. + setIsCreatingFromMenu( true ); + }; + + const onCreateEmptyMenu = () => { + onCreate( [] ); + }; + + const onCreateAllPages = () => { + // TODO - don't create a page list. + const block = [ createBlock( 'core/page-list' ) ]; + const selectNavigationBlock = true; + onCreate( block, selectNavigationBlock ); + }; + + useEffect( () => { + // If the user selected a menu but we had to wait for menu items to + // finish resolving, then create the block once resolution finishes. + if ( isCreatingFromMenu && hasResolvedMenuItems ) { + createFromMenu(); + setIsCreatingFromMenu( false ); + } + }, [ isCreatingFromMenu, hasResolvedMenuItems ] ); + + const toggleProps = { + variant: 'primary', + className: 'wp-block-menu-placeholder__actions__dropdown', + }; + + return ( + +
+ { isLoading && ( +
+ +
+ ) } + { ! isLoading && ( +
+
+ { __( 'Navigation' ) } +
+ { hasMenus ? ( + + { ( { onClose } ) => ( + + { menus.map( ( menu ) => { + return ( + { + setSelectedMenu( + menu.id + ); + onCreateFromMenu(); + } } + onClose={ onClose } + key={ menu.id } + > + { menu.name } + + ); + } ) } + + ) } + + ) : undefined } + { hasPages ? ( + + ) : undefined } + +
+ ) } +
+
+ ); +} + +export default forwardRef( NavigationPlaceholder ); diff --git a/packages/edit-navigation/src/blocks/menu/style.scss b/packages/edit-navigation/src/blocks/menu/style.scss new file mode 100644 index 0000000000000..b17ec1a89b75f --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/style.scss @@ -0,0 +1,27 @@ +.wp-block-menu { + // Horizontal layout + display: flex; + flex-direction: column; + align-items: flex-start; + margin: 0; + font-size: 15px; + padding: $grid-unit-15; + + // This is the default font that is going to be used in the content of the areas (blocks). + font-family: $default-font; + + // Normalize list styles. + ul, + ul li { + list-style: none; + + // Overrides generic ".entry-content li" styles on the front end. + padding: 0; + } + + // Add buttons + .block-editor-button-block-appender.block-list-appender__toggle { + margin: 0 0 0 $grid-unit-20; + padding: 0; + } +} diff --git a/packages/edit-navigation/src/blocks/menu/use-navigation-entities.js b/packages/edit-navigation/src/blocks/menu/use-navigation-entities.js new file mode 100644 index 0000000000000..17806deadd9a8 --- /dev/null +++ b/packages/edit-navigation/src/blocks/menu/use-navigation-entities.js @@ -0,0 +1,142 @@ +/** + * WordPress dependencies + */ +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * @typedef {Object} NavigationEntitiesData + * @property {Array|undefined} pages - a collection of WP Post entity objects of post type "Page". + * @property {boolean} isResolvingPages - indicates whether the request to fetch pages is currently resolving. + * @property {boolean} hasResolvedPages - indicates whether the request to fetch pages has finished resolving. + * @property {Array|undefined} menus - a collection of Menu entity objects. + * @property {boolean} isResolvingMenus - indicates whether the request to fetch menus is currently resolving. + * @property {boolean} hasResolvedMenus - indicates whether the request to fetch menus has finished resolving. + * @property {Array|undefined} menusItems - a collection of Menu Item entity objects for the current menuId. + * @property {boolean} hasResolvedMenuItems - indicates whether the request to fetch menuItems has finished resolving. + * @property {boolean} hasPages - indicates whether there is currently any data for pages. + * @property {boolean} hasMenus - indicates whether there is currently any data for menus. + */ + +/** + * Manages fetching and resolution state for all entities required + * for the Navigation block. + * + * @param {number} menuId the menu for which to retrieve menuItem data. + * @return { NavigationEntitiesData } the entity data. + */ +export default function useNavigationEntities( menuId ) { + return { + ...usePageEntities(), + ...useMenuEntities(), + ...useMenuItemEntities( menuId ), + }; +} + +function useMenuEntities() { + const { menus, isResolvingMenus, hasResolvedMenus } = useSelect( + ( select ) => { + const { getMenus, isResolving, hasFinishedResolution } = select( + coreStore + ); + + const menusParameters = [ { per_page: -1 } ]; + + return { + menus: getMenus( ...menusParameters ), + isResolvingMenus: isResolving( 'getMenus', menusParameters ), + hasResolvedMenus: hasFinishedResolution( + 'getMenus', + menusParameters + ), + }; + }, + [] + ); + + return { + menus, + isResolvingMenus, + hasResolvedMenus, + hasMenus: !! ( hasResolvedMenus && menus?.length ), + }; +} + +function useMenuItemEntities( menuId ) { + const { menuItems, hasResolvedMenuItems } = useSelect( + ( select ) => { + const { getMenuItems, hasFinishedResolution } = select( coreStore ); + + const hasSelectedMenu = menuId !== undefined; + const menuItemsParameters = hasSelectedMenu + ? [ + { + menus: menuId, + per_page: -1, + }, + ] + : undefined; + + return { + menuItems: hasSelectedMenu + ? getMenuItems( ...menuItemsParameters ) + : undefined, + hasResolvedMenuItems: hasSelectedMenu + ? hasFinishedResolution( + 'getMenuItems', + menuItemsParameters + ) + : false, + }; + }, + [ menuId ] + ); + + return { + menuItems, + hasResolvedMenuItems, + }; +} + +function usePageEntities() { + const { pages, isResolvingPages, hasResolvedPages } = useSelect( + ( select ) => { + const { + getEntityRecords, + isResolving, + hasFinishedResolution, + } = select( coreStore ); + + const pagesParameters = [ + 'postType', + 'page', + { + parent: 0, + order: 'asc', + orderby: 'id', + per_page: -1, + }, + ]; + + return { + pages: getEntityRecords( ...pagesParameters ) || null, + isResolvingPages: isResolving( + 'getEntityRecords', + pagesParameters + ), + hasResolvedPages: hasFinishedResolution( + 'getEntityRecords', + pagesParameters + ), + }; + }, + [] + ); + + return { + pages, + isResolvingPages, + hasResolvedPages, + hasPages: !! ( hasResolvedPages && pages?.length ), + }; +} diff --git a/packages/edit-navigation/src/components/editor/style.scss b/packages/edit-navigation/src/components/editor/style.scss index 5c5d813b02d38..0823aad220def 100644 --- a/packages/edit-navigation/src/components/editor/style.scss +++ b/packages/edit-navigation/src/components/editor/style.scss @@ -18,150 +18,4 @@ display: block; margin: $grid-unit-15 auto; } - - // Adapt the layout of the Navigation and Link blocks. - // to work better in the context of the Navigation Screen. - .wp-block-navigation { - margin: 0; - font-size: 15px; - padding: $grid-unit-15; - - // This is the default font that is going to be used in the content of the areas (blocks). - font-family: $default-font; - - // Increase specificity. - .wp-block-navigation-item { - display: block; - - // Show submenus on click. - > .wp-block-navigation__submenu-container { - // This unsets some styles inherited from the block, meant to only show submenus on click, not hover, when inside the editor. - opacity: 1; - visibility: visible; - display: none; - right: auto; - box-sizing: border-box; - } - - // Fix focus outlines. - &.is-selected > .wp-block-navigation-item__content, - &.is-selected:hover > .wp-block-navigation-item__content { - box-shadow: 0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color); - } - - &.block-editor-block-list__block:not([contenteditable]):focus::after { - display: none; - } - - // Menu items. - // This needs high specificity to override inherited values. - &.wp-block-navigation-item.wp-block-navigation-item { - margin-right: 0; - } - - .wp-block-navigation-item__content.wp-block-navigation-item__content.wp-block-navigation-item__content { - padding: 0.5em 1em; - margin-bottom: 6px; - margin-right: 0; - border-radius: $radius-block-ui; - - &:hover { - box-shadow: 0 0 0 $border-width $gray-300; - } - } - - .wp-block-navigation-link__label, - .wp-block-navigation-link__placeholder-text { - padding: $grid-unit-05; - padding-left: $grid-unit-10; - } - - .wp-block-navigation-link__label { - // Without this Links with submenus display a pointer. - cursor: text; - } - } - - - // Basic Page List support. - ul.wp-block-page-list { - // Make it inert. - background: $gray-100; - border-radius: $radius-block-ui; - pointer-events: none; - margin-right: 0; - - .wp-block-navigation-item { - color: $gray-700; - margin-bottom: 6px; - border-radius: $radius-block-ui; - padding: $grid-unit-05; - padding-left: $grid-unit-10; - } - } - - // Submenu icon indicator. - .wp-block-navigation__submenu-icon { - position: absolute; - top: 6px; - left: 0; - padding: 6px; - pointer-events: none; - - svg { - // Point rightwards. - transform: rotate(-90deg); - - transition: transform 0.2s ease; - @include reduce-motion("transition"); - } - } - - // Point downwards when open. - .is-selected.has-child > .wp-block-navigation__submenu-icon svg, - .has-child-selected.has-child > .wp-block-navigation__submenu-icon svg { - transform: rotate(0deg); - } - - // Override inherited values to optimize menu items for the screen context. - .wp-block-navigation-item.has-child { - cursor: default; - border-radius: $radius-block-ui; - } - - // Override for deeply nested submenus. - .has-child .wp-block-navigation__container .wp-block-navigation__container, - .has-child .wp-block-navigation__container .wp-block-navigation__submenu-container { - left: auto; - } - - // When editing a link with children, highlight the parent - // and adjust the spacing and submenu icon. - .wp-block-navigation-item.has-child.is-editing { - > .wp-block-navigation__container, - > .wp-block-navigation__submenu-container { - opacity: 1; - visibility: visible; - position: relative; - background: transparent; - top: auto; - left: auto; - padding-left: $grid-unit-15; - min-width: auto; - width: 100%; - border: none; - display: block; - - &::before { - display: none; - } - } - } - - // Add buttons - .block-editor-button-block-appender.block-list-appender__toggle { - margin: 0 0 0 $grid-unit-20; - padding: 0; - } - } } diff --git a/packages/edit-navigation/src/constants/index.js b/packages/edit-navigation/src/constants/index.js index aa43ab0b44693..bb3c7f5cd3e73 100644 --- a/packages/edit-navigation/src/constants/index.js +++ b/packages/edit-navigation/src/constants/index.js @@ -38,7 +38,7 @@ export const SIDEBAR_SCOPE = 'core/edit-navigation'; * * @type {string} */ -export const SIDEBAR_MENU = 'edit-navigation/menu'; +export const SIDEBAR_MENU = 'core/menu'; /** * The identifier of the editor's block complementary area. diff --git a/packages/edit-navigation/src/filters/add-menu-name-editor.js b/packages/edit-navigation/src/filters/add-menu-name-editor.js index 8a86f54d5aa34..2d7614a80f153 100644 --- a/packages/edit-navigation/src/filters/add-menu-name-editor.js +++ b/packages/edit-navigation/src/filters/add-menu-name-editor.js @@ -10,7 +10,7 @@ import NameDisplay from '../components/name-display'; const addMenuNameEditor = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - if ( props.name !== 'edit-navigation/menu' ) { + if ( props.name !== 'core/menu' ) { return ; } return ( diff --git a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js index 667ea13f3735f..4b25a1d2ca992 100644 --- a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js +++ b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js @@ -8,11 +8,7 @@ import { addFilter } from '@wordpress/hooks'; import { set } from 'lodash'; function disableInsertingNonNavigationBlocks( settings, name ) { - if ( - ! [ 'edit-navigation/menu', 'edit-navigation/menu-item' ].includes( - name - ) - ) { + if ( ! [ 'core/menu', 'core/menu-item' ].includes( name ) ) { set( settings, [ 'supports', 'inserter' ], false ); } return settings; diff --git a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js index 230d869eb1fbe..95aef50f0f0e6 100644 --- a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js +++ b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js @@ -6,7 +6,7 @@ import { createHigherOrderComponent } from '@wordpress/compose'; const removeNavigationBlockEditUnsupportedFeatures = createHigherOrderComponent( ( BlockEdit ) => ( props ) => { - if ( props.name !== 'edit-navigation/menu' ) { + if ( props.name !== 'core/menu' ) { return ; } diff --git a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js index a8ad2a64a6c77..efa5b6105eeb0 100644 --- a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js +++ b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js @@ -4,7 +4,7 @@ import { addFilter } from '@wordpress/hooks'; function removeNavigationBlockSettingsUnsupportedFeatures( settings, name ) { - if ( name !== 'edit-navigation/menu' ) { + if ( name !== 'core/menu' ) { return settings; } diff --git a/packages/edit-navigation/src/store/menu-items-to-blocks.js b/packages/edit-navigation/src/store/menu-items-to-blocks.js index c858b8176e67c..f46c9f0848b3a 100644 --- a/packages/edit-navigation/src/store/menu-items-to-blocks.js +++ b/packages/edit-navigation/src/store/menu-items-to-blocks.js @@ -66,11 +66,7 @@ function mapMenuItemsToBlocks( menuItems ) { }; // Create block with nested "innerBlocks". - const block = createBlock( - 'edit-navigation/menu-item', - attributes, - nestedBlocks - ); + const block = createBlock( 'core/menu-item', attributes, nestedBlocks ); // Create mapping for menuItem -> block mapping[ menuItem.id ] = block.clientId; @@ -125,7 +121,7 @@ function menuItemToBlockAttributes( { type: menuItemTypeField, target, } ) { - // For historical reasons, the `edit-navigation/menu-item` variation type is `tag` + // For historical reasons, the `core/menu-item` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `blockAttributesToMenuItem`. diff --git a/packages/edit-navigation/src/store/resolvers.js b/packages/edit-navigation/src/store/resolvers.js index 0b81dc13ccc50..e79143760a9c9 100644 --- a/packages/edit-navigation/src/store/resolvers.js +++ b/packages/edit-navigation/src/store/resolvers.js @@ -92,10 +92,6 @@ function createNavigationBlock( menuItems ) { menuItems ); - const navigationBlock = createBlock( - 'edit-navigation/menu', - {}, - innerBlocks - ); + const navigationBlock = createBlock( 'core/menu', {}, innerBlocks ); return [ navigationBlock, menuItemIdToClientId ]; } diff --git a/packages/edit-navigation/src/store/test/actions.js b/packages/edit-navigation/src/store/test/actions.js index 6859b492e2e65..2a2006a9091a5 100644 --- a/packages/edit-navigation/src/store/test/actions.js +++ b/packages/edit-navigation/src/store/test/actions.js @@ -47,7 +47,7 @@ describe( 'createMissingMenuItems', () => { clientId: 'navigation-block-client-id', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, ], }; @@ -131,7 +131,7 @@ describe( 'createMissingMenuItems', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, { attributes: { @@ -142,11 +142,11 @@ describe( 'createMissingMenuItems', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, ], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, ], }; @@ -262,7 +262,7 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -277,11 +277,11 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, ], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, ], }; @@ -410,7 +410,7 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -425,11 +425,11 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, ], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, ], }; @@ -551,7 +551,7 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -566,11 +566,11 @@ describe( 'saveNavigationPost', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, ], isValid: true, - name: 'edit-navigation/menu', + name: 'core/menu', }, ], }; diff --git a/packages/edit-navigation/src/store/test/menu-items-to-blocks.js b/packages/edit-navigation/src/store/test/menu-items-to-blocks.js index 5b56a26557249..a346532b55187 100644 --- a/packages/edit-navigation/src/store/test/menu-items-to-blocks.js +++ b/packages/edit-navigation/src/store/test/menu-items-to-blocks.js @@ -62,11 +62,11 @@ describe( 'converting menu items to blocks', () => { expect( actual ).toEqual( [ expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', innerBlocks: [], } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', innerBlocks: [], } ), ] ); @@ -186,32 +186,32 @@ describe( 'converting menu items to blocks', () => { expect( actual ).toEqual( [ expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Top Level', } ), innerBlocks: [ expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Child 1', } ), innerBlocks: [], } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Child 2', } ), innerBlocks: [ expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Sub Child', } ), innerBlocks: [ expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Sub Sub Child', } ), @@ -224,7 +224,7 @@ describe( 'converting menu items to blocks', () => { ], } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Top Level 2', } ), @@ -329,31 +329,31 @@ describe( 'converting menu items to blocks', () => { expect( actual ).toEqual( [ expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Ordered 1st', } ), } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Ordered 2nd', } ), } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Ordered 3rd', } ), } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Ordered 4th', } ), } ), expect.objectContaining( { - name: 'edit-navigation/menu-item', + name: 'core/menu-item', attributes: expect.objectContaining( { label: 'Ordered 5th', } ), diff --git a/packages/edit-navigation/src/store/test/resolvers.js b/packages/edit-navigation/src/store/test/resolvers.js index fca104b206953..ee68dfc72ab2c 100644 --- a/packages/edit-navigation/src/store/test/resolvers.js +++ b/packages/edit-navigation/src/store/test/resolvers.js @@ -163,7 +163,7 @@ describe( 'getNavigationPostForMenu', () => { }, clientId: 'client-id-0', innerBlocks: [], - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -174,7 +174,7 @@ describe( 'getNavigationPostForMenu', () => { }, clientId: 'client-id-1', innerBlocks: [], - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -187,10 +187,10 @@ describe( 'getNavigationPostForMenu', () => { }, clientId: 'client-id-2', innerBlocks: [], - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, ], - name: 'edit-navigation/menu', + name: 'core/menu', }, ], meta: { @@ -221,7 +221,7 @@ describe( 'getNavigationPostForMenu', () => { expect( generator.next().done ).toBe( true ); } ); - it( 'creates correct edit-navigation/menu-item block variations from menu objects', () => { + it( 'creates correct core/menu-item block variations from menu objects', () => { const menuId = 123; const generator = getNavigationPostForMenu( menuId ); @@ -292,7 +292,7 @@ describe( 'getNavigationPostForMenu', () => { // Gen step: yield persistPost const persistPostAction = generator.next().value; - // Get the edit-navigation/menu-item blocks from the generated edit-navigation/menu block innerBlocks. + // Get the core/menu-item blocks from the generated core/menu block innerBlocks. const blockAttrs = persistPostAction.args[ 2 ].blocks[ 0 ].innerBlocks.map( ( block ) => block.attributes ); diff --git a/packages/edit-navigation/src/store/test/utils.js b/packages/edit-navigation/src/store/test/utils.js index 1087c617dc4d9..63e23c820bd19 100644 --- a/packages/edit-navigation/src/store/test/utils.js +++ b/packages/edit-navigation/src/store/test/utils.js @@ -226,7 +226,7 @@ describe( 'computeCustomizedAttribute', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -241,7 +241,7 @@ describe( 'computeCustomizedAttribute', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, { attributes: { @@ -257,7 +257,7 @@ describe( 'computeCustomizedAttribute', () => { clientId: 'navigation-link-block-client-id-3', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, ]; @@ -375,7 +375,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-1', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, menuItem: { title: 'Example Page', @@ -407,7 +407,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-2', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, menuItem: { title: 'Example Post', @@ -440,7 +440,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-3', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, menuItem: { title: 'Example Category', @@ -470,7 +470,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-4', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, menuItem: { title: 'Example Tag', @@ -497,7 +497,7 @@ describe( 'Mapping block attributes and menu item fields', () => { clientId: 'navigation-link-block-client-id-5', innerBlocks: [], isValid: true, - name: 'edit-navigation/menu-item', + name: 'core/menu-item', }, menuItem: { title: 'Example Custom Link', diff --git a/packages/edit-navigation/src/store/utils.js b/packages/edit-navigation/src/store/utils.js index fca34270a2f67..53acc41350c3b 100644 --- a/packages/edit-navigation/src/store/utils.js +++ b/packages/edit-navigation/src/store/utils.js @@ -156,7 +156,7 @@ export function computeCustomizedAttribute( let attributes; - if ( block.name === 'edit-navigation/menu-item' ) { + if ( block.name === 'core/menu-item' ) { attributes = blockAttributesToMenuItem( block.attributes ); } else { attributes = { @@ -214,7 +214,7 @@ export const blockAttributesToMenuItem = ( { kind, opensInNewTab, } ) => { - // For historical reasons, the `edit-navigation/menu-item` variation type is `tag` + // For historical reasons, the `core/menu-item` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `menuItemToBlockAttributes`. @@ -272,7 +272,7 @@ export const menuItemToBlockAttributes = ( { type: menuItemTypeField, target, } ) => { - // For historical reasons, the `edit-navigation/menu-item` variation type is `tag` + // For historical reasons, the `core/menu-item` variation type is `tag` // whereas WP Core expects `post_tag` as the `object` type. // To avoid writing a block migration we perform a conversion here. // See also inverse equivalent in `blockAttributesToMenuItem`. diff --git a/packages/edit-navigation/src/style.scss b/packages/edit-navigation/src/style.scss index d3865a1dfff8e..ad4ea3ed8791b 100644 --- a/packages/edit-navigation/src/style.scss +++ b/packages/edit-navigation/src/style.scss @@ -7,6 +7,8 @@ $navigation-editor-spacing-top: $grid-unit-50 * 2; box-sizing: border-box; } +@import "./blocks/menu/style.scss"; +@import "./blocks/menu-item/style.scss"; @import "./components/add-menu/style.scss"; @import "../../interface/src/style.scss"; @import "./components/editor/style.scss"; From 2f668fcabf5e721c3cdcbafbacdad54c623907bc Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 2 Sep 2021 19:51:47 +0800 Subject: [PATCH 04/10] Style adjustments --- .../src/blocks/menu-item/style.scss | 14 +++++++------- .../edit-navigation/src/blocks/menu/style.scss | 14 ++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/edit-navigation/src/blocks/menu-item/style.scss b/packages/edit-navigation/src/blocks/menu-item/style.scss index 5ba4b130226ca..01e944e5c874d 100644 --- a/packages/edit-navigation/src/blocks/menu-item/style.scss +++ b/packages/edit-navigation/src/blocks/menu-item/style.scss @@ -1,6 +1,7 @@ // Menu item container. .wp-block-menu-item { display: block; + margin: $grid-unit-10 0; // Hide submenus by default. .wp-block-menu-item__submenu-container { @@ -10,7 +11,7 @@ // Show submenus on click. &.has-child.is-editing > .wp-block-menu-item__submenu-container { display: block; - padding-left: $grid-unit-15; + padding-left: $grid-unit-20 + $grid-unit-05; border: none; &::before { @@ -28,7 +29,6 @@ display: block; color: inherit; padding: 0.5em 1em; - margin-bottom: 6px; border-radius: $radius-block-ui; &:hover { @@ -63,10 +63,10 @@ @include reduce-motion("transition"); } } -} -// Rotate icon downwards when a submenu is open. -.is-selected.has-child > .wp-block-menu-item__submenu-icon svg, -.has-child-selected.has-child > .wp-block-menu-item__submenu-icon svg { - transform: rotate(0deg); + // Rotate icon downwards when a submenu is open. + &.is-selected.has-child > .wp-block-menu-item__submenu-icon svg, + &.has-child-selected.has-child > .wp-block-menu-item__submenu-icon svg { + transform: rotate(0deg); + } } diff --git a/packages/edit-navigation/src/blocks/menu/style.scss b/packages/edit-navigation/src/blocks/menu/style.scss index b17ec1a89b75f..8924932198a49 100644 --- a/packages/edit-navigation/src/blocks/menu/style.scss +++ b/packages/edit-navigation/src/blocks/menu/style.scss @@ -19,9 +19,19 @@ padding: 0; } - // Add buttons + // Appender styles + .block-list-appender { + // Make appender rows the same height as items and center the button vertically. + display: flex; + flex-direction: column; + justify-content: center; + height: $grid-unit-50; + margin: $grid-unit-10 0; + } + .block-editor-button-block-appender.block-list-appender__toggle { - margin: 0 0 0 $grid-unit-20; + margin: 0 0 0 $grid-unit-30; padding: 0; } } + From 62cbe01088ee42214c7dad8098c23f8327b45417 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Thu, 2 Sep 2021 19:54:48 +0800 Subject: [PATCH 05/10] Remove navigation block filters --- ...disable-inserting-non-navigation-blocks.js | 22 -------------- packages/edit-navigation/src/filters/index.js | 12 +------- .../remove-edit-unsupported-features.js | 30 ------------------- .../remove-settings-unsupported-features.js | 28 ----------------- packages/edit-navigation/src/index.js | 2 +- 5 files changed, 2 insertions(+), 92 deletions(-) delete mode 100644 packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js delete mode 100644 packages/edit-navigation/src/filters/remove-edit-unsupported-features.js delete mode 100644 packages/edit-navigation/src/filters/remove-settings-unsupported-features.js diff --git a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js b/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js deleted file mode 100644 index 4b25a1d2ca992..0000000000000 --- a/packages/edit-navigation/src/filters/disable-inserting-non-navigation-blocks.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter } from '@wordpress/hooks'; -/** - * External dependencies - */ -import { set } from 'lodash'; - -function disableInsertingNonNavigationBlocks( settings, name ) { - if ( ! [ 'core/menu', 'core/menu-item' ].includes( name ) ) { - set( settings, [ 'supports', 'inserter' ], false ); - } - return settings; -} - -export default () => - addFilter( - 'blocks.registerBlockType', - 'core/edit-navigation/disable-inserting-non-navigation-blocks', - disableInsertingNonNavigationBlocks - ); diff --git a/packages/edit-navigation/src/filters/index.js b/packages/edit-navigation/src/filters/index.js index 96315b46b7eb8..65dec1f38306f 100644 --- a/packages/edit-navigation/src/filters/index.js +++ b/packages/edit-navigation/src/filters/index.js @@ -2,17 +2,7 @@ * Internal dependencies */ import addMenuNameEditor from './add-menu-name-editor'; -import disableInsertingNonNavigationBlocks from './disable-inserting-non-navigation-blocks'; -import removeEditUnsupportedFeatures from './remove-edit-unsupported-features'; -import removeSettingsUnsupportedFeatures from './remove-settings-unsupported-features'; -export const addFilters = ( - shouldAddDisableInsertingNonNavigationBlocksFilter -) => { +export const addFilters = () => { addMenuNameEditor(); - if ( shouldAddDisableInsertingNonNavigationBlocksFilter ) { - disableInsertingNonNavigationBlocks(); - } - removeEditUnsupportedFeatures(); - removeSettingsUnsupportedFeatures(); }; diff --git a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js b/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js deleted file mode 100644 index 95aef50f0f0e6..0000000000000 --- a/packages/edit-navigation/src/filters/remove-edit-unsupported-features.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter } from '@wordpress/hooks'; -import { createHigherOrderComponent } from '@wordpress/compose'; - -const removeNavigationBlockEditUnsupportedFeatures = createHigherOrderComponent( - ( BlockEdit ) => ( props ) => { - if ( props.name !== 'core/menu' ) { - return ; - } - - return ( - - ); - }, - 'removeNavigationBlockEditUnsupportedFeatures' -); - -export default () => - addFilter( - 'editor.BlockEdit', - 'core/edit-navigation/remove-navigation-block-edit-unsupported-features', - removeNavigationBlockEditUnsupportedFeatures - ); diff --git a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js b/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js deleted file mode 100644 index efa5b6105eeb0..0000000000000 --- a/packages/edit-navigation/src/filters/remove-settings-unsupported-features.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * WordPress dependencies - */ -import { addFilter } from '@wordpress/hooks'; - -function removeNavigationBlockSettingsUnsupportedFeatures( settings, name ) { - if ( name !== 'core/menu' ) { - return settings; - } - - return { - ...settings, - supports: { - customClassName: false, - html: false, - inserter: true, - }, - // Remove any block variations. - variations: undefined, - }; -} - -export default () => - addFilter( - 'blocks.registerBlockType', - 'core/edit-navigation/remove-navigation-block-settings-unsupported-features', - removeNavigationBlockSettingsUnsupportedFeatures - ); diff --git a/packages/edit-navigation/src/index.js b/packages/edit-navigation/src/index.js index e17f34a3c2736..e3f5bf17ba312 100644 --- a/packages/edit-navigation/src/index.js +++ b/packages/edit-navigation/src/index.js @@ -36,7 +36,7 @@ const registerBlock = ( block ) => { }; export function initialize( id, settings ) { - addFilters( ! settings.blockNavMenus ); + addFilters(); registerBlock( menuBlock ); registerBlock( menuItemBlock ); From f1fcc6da74afb055f910c6691453515ef0d8e282 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 3 Sep 2021 10:50:32 +0800 Subject: [PATCH 06/10] Update placeholder appearance --- .../src/blocks/menu/placeholder.js | 57 +++++++++++-------- .../src/blocks/menu/style.scss | 42 ++++++++++++++ 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/packages/edit-navigation/src/blocks/menu/placeholder.js b/packages/edit-navigation/src/blocks/menu/placeholder.js index fd71a7e052a1e..01099f071ff89 100644 --- a/packages/edit-navigation/src/blocks/menu/placeholder.js +++ b/packages/edit-navigation/src/blocks/menu/placeholder.js @@ -10,7 +10,7 @@ import { MenuItem, Spinner, } from '@wordpress/components'; - +import { useSelect } from '@wordpress/data'; import { forwardRef, useCallback, @@ -18,19 +18,25 @@ import { useEffect, } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; -import { navigation, chevronDown, Icon } from '@wordpress/icons'; +import { chevronDown } from '@wordpress/icons'; /** * Internal dependencies */ +import { store as navigationStore } from '../../store'; +import { useMenuEntityProp } from '../../hooks'; import useNavigationEntities from './use-navigation-entities'; import menuItemsToBlocks from './menu-items-to-blocks'; function NavigationPlaceholder( { onCreate }, ref ) { const [ selectedMenu, setSelectedMenu ] = useState(); - const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false ); + const selectedMenuId = useSelect( ( select ) => + select( navigationStore ).getSelectedMenuId() + ); + const [ menuName ] = useMenuEntityProp( 'name', selectedMenuId ); + const { isResolvingPages, menus, @@ -81,12 +87,18 @@ function NavigationPlaceholder( { onCreate }, ref ) { }, [ isCreatingFromMenu, hasResolvedMenuItems ] ); const toggleProps = { - variant: 'primary', - className: 'wp-block-menu-placeholder__actions__dropdown', + variant: 'tertiary', }; return ( - +
{ isLoading && (
@@ -98,12 +110,23 @@ function NavigationPlaceholder( { onCreate }, ref ) { ref={ ref } className="wp-block-menu-placeholder__actions" > -
- { __( 'Navigation' ) } -
+ + { hasPages ? ( + + ) : undefined } { hasMenus ? ( @@ -129,20 +152,6 @@ function NavigationPlaceholder( { onCreate }, ref ) { ) } ) : undefined } - { hasPages ? ( - - ) : undefined } -
) }
diff --git a/packages/edit-navigation/src/blocks/menu/style.scss b/packages/edit-navigation/src/blocks/menu/style.scss index 8924932198a49..62011c78899f0 100644 --- a/packages/edit-navigation/src/blocks/menu/style.scss +++ b/packages/edit-navigation/src/blocks/menu/style.scss @@ -35,3 +35,45 @@ } } +.wp-block-menu-placeholder { + // The navigation editor already has a border around content. + // Hide the placeholder's border. Requires extra specificity. + &.wp-block-menu-placeholder { + box-shadow: none; + } + + // Show placeholder instructions when it's a medium size. + &.is-medium .components-placeholder__instructions { + display: block; + } + + // Display buttons in a column when placeholder is small. + .wp-block-menu-placeholder__actions { + display: flex; + flex-direction: column; + + .components-button { + margin-bottom: $grid-unit-15; + margin-right: 0; + + // Avoid bottom margin on the dropdown since it makes the + // menu anchor itself too far away from the button. + &.components-dropdown-menu__toggle { + margin-bottom: 0; + } + } + } + + @include break-medium() { + .wp-block-menu-placeholder__actions { + flex-direction: row; + } + + // Change the default button margin. Again use extra specificity. + &.wp-block-menu-placeholder.is-medium .components-button { + margin-bottom: 0; + margin-right: $grid-unit-15; + } + } + +} From ee31795d61b6111c365475e0e454aa8b33c86d52 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 3 Sep 2021 10:56:21 +0800 Subject: [PATCH 07/10] Change sidebar menu name back --- packages/edit-navigation/src/constants/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-navigation/src/constants/index.js b/packages/edit-navigation/src/constants/index.js index bb3c7f5cd3e73..aa43ab0b44693 100644 --- a/packages/edit-navigation/src/constants/index.js +++ b/packages/edit-navigation/src/constants/index.js @@ -38,7 +38,7 @@ export const SIDEBAR_SCOPE = 'core/edit-navigation'; * * @type {string} */ -export const SIDEBAR_MENU = 'core/menu'; +export const SIDEBAR_MENU = 'edit-navigation/menu'; /** * The identifier of the editor's block complementary area. From 7031de9b683a20c5b64e0538172fe17bd2b37f11 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 3 Sep 2021 13:39:41 +0800 Subject: [PATCH 08/10] Repair Add all pages option --- .../src/blocks/menu/placeholder.js | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/edit-navigation/src/blocks/menu/placeholder.js b/packages/edit-navigation/src/blocks/menu/placeholder.js index 01099f071ff89..e0cb906299c84 100644 --- a/packages/edit-navigation/src/blocks/menu/placeholder.js +++ b/packages/edit-navigation/src/blocks/menu/placeholder.js @@ -28,6 +28,29 @@ import { useMenuEntityProp } from '../../hooks'; import useNavigationEntities from './use-navigation-entities'; import menuItemsToBlocks from './menu-items-to-blocks'; +/** + * Convert pages to blocks. + * + * @param {Object[]} pages An array of pages. + * + * @return {WPBlock[]} An array of blocks. + */ +function convertPagesToBlocks( pages ) { + if ( ! pages ) { + return null; + } + + return pages.map( ( { title, type, link: url, id } ) => + createBlock( 'core/menu-item', { + type, + id, + url, + label: ! title.rendered ? __( '(no title)' ) : title.rendered, + opensInNewTab: false, + } ) + ); +} + function NavigationPlaceholder( { onCreate }, ref ) { const [ selectedMenu, setSelectedMenu ] = useState(); const [ isCreatingFromMenu, setIsCreatingFromMenu ] = useState( false ); @@ -43,6 +66,7 @@ function NavigationPlaceholder( { onCreate }, ref ) { isResolvingMenus, menuItems, hasResolvedMenuItems, + pages, hasPages, hasMenus, } = useNavigationEntities( selectedMenu ); @@ -71,10 +95,9 @@ function NavigationPlaceholder( { onCreate }, ref ) { }; const onCreateAllPages = () => { - // TODO - don't create a page list. - const block = [ createBlock( 'core/page-list' ) ]; + const blocks = convertPagesToBlocks( pages ); const selectNavigationBlock = true; - onCreate( block, selectNavigationBlock ); + onCreate( blocks, selectNavigationBlock ); }; useEffect( () => { From 9cdd937ddd2cea00c60326a19bc319412f4a67eb Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 3 Sep 2021 13:52:36 +0800 Subject: [PATCH 09/10] Fix unit tests --- packages/edit-navigation/src/store/test/resolvers.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/edit-navigation/src/store/test/resolvers.js b/packages/edit-navigation/src/store/test/resolvers.js index ee68dfc72ab2c..674b033202b62 100644 --- a/packages/edit-navigation/src/store/test/resolvers.js +++ b/packages/edit-navigation/src/store/test/resolvers.js @@ -146,9 +146,7 @@ describe( 'getNavigationPostForMenu', () => { type: 'page', blocks: [ { - attributes: { - orientation: 'vertical', - }, + attributes: {}, clientId: expect.stringMatching( /client-id-\d+/ ), innerBlocks: [ { From 9c6f594d665909c00300f1d45b89afeba69b6797 Mon Sep 17 00:00:00 2001 From: Daniel Richards Date: Fri, 3 Sep 2021 14:17:58 +0800 Subject: [PATCH 10/10] Fix e2e tests --- .../navigation-editor.test.js.snap | 56 +++++++------------ .../experiments/navigation-editor.test.js | 31 ++++++---- .../edit-navigation/src/blocks/menu/index.js | 5 +- .../src/blocks/menu/placeholder.js | 2 +- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap b/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap index 89f22e7ad5236..74600fe72cbac 100644 --- a/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap +++ b/packages/e2e-tests/specs/experiments/__snapshots__/navigation-editor.test.js.snap @@ -1,45 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Navigation editor allows creation of a menu when there are existing menu items 1`] = `""`; +exports[`Navigation editor allows creation of a menu when there are existing menus 1`] = `""`; exports[`Navigation editor allows creation of a menu when there are no current menu items 1`] = ` -" - -" +" + +" `; exports[`Navigation editor displays the first menu from the REST response when at least one menu exists 1`] = ` -" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -" +" + + + + + + + + + + + + + +" `; diff --git a/packages/e2e-tests/specs/experiments/navigation-editor.test.js b/packages/e2e-tests/specs/experiments/navigation-editor.test.js index fa8bdf21d852a..86ae3a1ed40df 100644 --- a/packages/e2e-tests/specs/experiments/navigation-editor.test.js +++ b/packages/e2e-tests/specs/experiments/navigation-editor.test.js @@ -193,7 +193,18 @@ describe( 'Navigation editor', () => { POST: menuPostResponse, } ), ...getMenuItemMocks( { GET: [] } ), - ...getPagesMocks( { GET: [ {} ] } ), // mock a single page + ...getPagesMocks( { + GET: [ + { + type: 'page', + id: 1, + link: 'https://example.com/1', + title: { + rendered: 'My page', + }, + }, + ], + } ), ] ); await page.keyboard.type( 'Main Menu' ); @@ -207,7 +218,7 @@ describe( 'Navigation editor', () => { // Select the navigation block and create a block from existing pages. const navigationBlock = await page.waitForSelector( - 'div[aria-label="Block: Navigation"]' + 'div[aria-label="Block: Menu"]' ); await navigationBlock.click(); @@ -218,12 +229,12 @@ describe( 'Navigation editor', () => { // When the block is created the root element changes from a div (for the placeholder) // to a nav (for the navigation itself). Wait for this to happen. - await page.waitForSelector( 'nav[aria-label="Block: Navigation"]' ); + await page.waitForSelector( 'nav[aria-label="Block: Menu"]' ); expect( await getSerializedBlocks() ).toMatchSnapshot(); } ); - it( 'allows creation of a menu when there are existing menu items', async () => { + it( 'allows creation of a menu when there are existing menus', async () => { const menuPostResponse = { id: 4, description: '', @@ -281,7 +292,7 @@ describe( 'Navigation editor', () => { await page.waitForXPath( '//div[contains(., "Menu created")]' ); // An empty navigation block will appear. - await page.waitForSelector( 'div[aria-label="Block: Navigation"]' ); + await page.waitForSelector( 'div[aria-label="Block: Menu"]' ); expect( await getSerializedBlocks() ).toMatchSnapshot(); } ); @@ -299,7 +310,7 @@ describe( 'Navigation editor', () => { } ); // Wait for the block to be present. - await page.waitForSelector( 'nav[aria-label="Block: Navigation"]' ); + await page.waitForSelector( 'nav[aria-label="Block: Menu"]' ); expect( await getSerializedBlocks() ).toMatchSnapshot(); } ); @@ -313,7 +324,7 @@ describe( 'Navigation editor', () => { // Select a link block with nested links in a submenu. const parentLinkXPath = - '//div[@aria-label="Block: Custom Link" and contains(.,"WordPress.org")]'; + '//div[@aria-label="Block: Menu Item" and contains(.,"WordPress.org")]'; const linkBlock = await page.waitForXPath( parentLinkXPath ); await linkBlock.click(); @@ -322,7 +333,7 @@ describe( 'Navigation editor', () => { // Submenus are hidden using `visibility: hidden` and shown using // `visibility: visible` so the visible/hidden options must be used // when selecting the elements. - const submenuLinkXPath = `${ parentLinkXPath }//div[@aria-label="Block: Custom Link"]`; + const submenuLinkXPath = `${ parentLinkXPath }//div[@aria-label="Block: Menu Item"]`; const submenuLinkVisible = await page.waitForXPath( submenuLinkXPath, { visible: true, } ); @@ -350,11 +361,11 @@ describe( 'Navigation editor', () => { // Wait for the block to be present and start an empty block. const navBlock = await page.waitForSelector( - 'div[aria-label="Block: Navigation"]' + 'div[aria-label="Block: Menu"]' ); await navBlock.click(); const startEmptyButton = await page.waitForXPath( - '//button[.="Start empty"]' + '//button[.="Start blank"]' ); await startEmptyButton.click(); diff --git a/packages/edit-navigation/src/blocks/menu/index.js b/packages/edit-navigation/src/blocks/menu/index.js index 45e31a678090e..4ff887998f4e7 100644 --- a/packages/edit-navigation/src/blocks/menu/index.js +++ b/packages/edit-navigation/src/blocks/menu/index.js @@ -1,6 +1,7 @@ /** * WordPress dependencies */ +import { InnerBlocks } from '@wordpress/block-editor'; import { navigation as icon } from '@wordpress/icons'; /** @@ -16,5 +17,7 @@ export { metadata, name }; export const settings = { icon, edit, - save() {}, + save() { + return ; + }, }; diff --git a/packages/edit-navigation/src/blocks/menu/placeholder.js b/packages/edit-navigation/src/blocks/menu/placeholder.js index e0cb906299c84..68ac10ef69e73 100644 --- a/packages/edit-navigation/src/blocks/menu/placeholder.js +++ b/packages/edit-navigation/src/blocks/menu/placeholder.js @@ -36,7 +36,7 @@ import menuItemsToBlocks from './menu-items-to-blocks'; * @return {WPBlock[]} An array of blocks. */ function convertPagesToBlocks( pages ) { - if ( ! pages ) { + if ( ! pages?.length ) { return null; }