From 6557ac77efcdd626d7b95d3f1a375e688cf4a462 Mon Sep 17 00:00:00 2001 From: Dave Smith Date: Fri, 16 Jun 2023 10:29:27 +0100 Subject: [PATCH] Add focus mode for Navigation Menus (#39286) * Add new state handlers for setting Nav Menu Fixing unlocking Avoid unnecessarily effectful code Fix bug with persistent Nav block locking Reinstate effect to limit calls to selectBlock Reinstate effect to run selectively Isolate Nav specific code Refactor settings to hook Isolate Editor Canvas to component Extract mode statuses to hook Colocate editor canvas dependencies Remove requirement for Nav hook to return data Extract entire canvas to component Get blocks directly from the store Use factory to provide default editor component Extract independent components for default and wp_navigation Remove template dep from Navigation focus hook Delete redundant hook Remove need for settings prop Extract hooks to files Export boolean to avoid render Remove redundant prop and var Extract Site Editor Canvas component to file Extract factory to file Remove need to pass props to abstract component Improve abstract component and factory naming Improve usage of constants Be explicit about entity mapping in factory Remove templateType prop from SiteEditorCanvas component Improve variable naming Use more performant selector Improve documenting rule for showing appender Move Navigation specific editor settings to relevant provider Remove useSiteEditorMode hook See https://github.com/WordPress/gutenberg/pull/39286#discussion_r1228632953 Reintstate bug fix with comment Fix error in rebase Add edit button Use Navigation icon and label Use correct labels * Add descriptive text as instructed See https://github.com/WordPress/gutenberg/pull/39286/#issuecomment-1592791623 * Update edit link for Navigation post type Fixes revisions link * Fix PHPCS --- .../data/data-core-edit-site.md | 12 ++ lib/compat/wordpress-6.3/link-template.php | 26 ++++ lib/compat/wordpress-6.3/rest-api.php | 22 +++- .../src/navigation/edit/index.js | 15 ++- .../edit/menu-inspector-controls.js | 35 +++--- .../components/block-editor/back-button.js | 3 +- .../src/components/block-editor/constants.js | 2 +- .../block-editor/get-block-editor-provider.js | 29 +++++ .../src/components/block-editor/index.js | 19 +-- .../navigation-block-editor-provider.js | 114 ++++++++++++++++++ .../block-editor/site-editor-canvas.js | 24 +++- .../src/components/block-editor/style.scss | 6 + .../block-editor/use-site-editor-settings.js | 8 ++ .../document-actions/index.js | 25 +++- .../src/components/header-edit-mode/index.js | 3 +- .../settings-header/index.js | 25 ++-- .../sidebar-edit-mode/template-panel/index.js | 15 ++- .../edit-button.js | 24 ++++ .../index.js | 35 ++++-- .../use-init-edited-entity-from-url.js | 6 +- packages/edit-site/src/hooks/index.js | 1 + .../src/hooks/navigation-menu-edit.js | 95 +++++++++++++++ packages/edit-site/src/store/actions.js | 15 +++ 23 files changed, 494 insertions(+), 65 deletions(-) create mode 100644 packages/edit-site/src/components/block-editor/get-block-editor-provider.js create mode 100644 packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js create mode 100644 packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js create mode 100644 packages/edit-site/src/hooks/navigation-menu-edit.js diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index a6263b6c573b4..a2610c2647c65 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -328,6 +328,18 @@ _Parameters_ - _isOpen_ `boolean`: If true, opens the save view. If false, closes it. It does not toggle the state, but sets it directly. +### setNavigationMenu + +Action that sets a navigation menu. + +_Parameters_ + +- _navigationMenuId_ `string`: The Navigation Menu Post ID. + +_Returns_ + +- `Object`: Action object. + ### setNavigationPanelActiveMenu > **Deprecated** diff --git a/lib/compat/wordpress-6.3/link-template.php b/lib/compat/wordpress-6.3/link-template.php index 366dedba2b5aa..19d703839125e 100644 --- a/lib/compat/wordpress-6.3/link-template.php +++ b/lib/compat/wordpress-6.3/link-template.php @@ -28,7 +28,33 @@ function gutenberg_update_get_edit_post_link( $link, $post_id ) { $slug = urlencode( get_stylesheet() . '//' . $post->post_name ); $link = admin_url( sprintf( $post_type_object->_edit_link, $slug ) ); } + return $link; } add_filter( 'get_edit_post_link', 'gutenberg_update_get_edit_post_link', 10, 2 ); + + + +/** + * Modifies the edit link for the `wp_navigation` custom post type. + * + * This has not been backported to Core. + * + * @param string $link The edit link. + * @param int $post_id Post ID. + * @return string|null The edit post link for the given post. Null if the post type does not exist + * or does not allow an editing UI. + */ +function gutenberg_update_navigation_get_edit_post_link( $link, $post_id ) { + $post = get_post( $post_id ); + + if ( 'wp_navigation' === $post->post_type ) { + $post_type_object = get_post_type_object( $post->post_type ); + $id = $post->ID; + $link = admin_url( sprintf( $post_type_object->_edit_link, $id ) ); + } + + return $link; +} +add_filter( 'get_edit_post_link', 'gutenberg_update_navigation_get_edit_post_link', 10, 2 ); diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index 9dfecc5ec2c53..a2dd66c5e922f 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -9,8 +9,14 @@ * Updates `wp_template` and `wp_template_part` post types to use * Gutenberg's REST controllers * - * Adds `_edit_link` to the `wp_global_styles`, `wp_template`, - * and `wp_template_part` post type schemata. See https://github.com/WordPress/gutenberg/issues/48065 + * Adds `_edit_link` to the following post type schemata: + * + * - wp_global_styles + * - wp_template + * - wp_template_part + * - wp_navigation + * + * See https://github.com/WordPress/gutenberg/issues/48065 * * @param array $args Array of arguments for registering a post type. * @param string $post_type Post type key. @@ -31,6 +37,18 @@ function gutenberg_update_templates_template_parts_rest_controller( $args, $post if ( in_array( $post_type, array( 'wp_global_styles' ), true ) ) { $args['_edit_link'] = '/site-editor.php?canvas=edit'; } + + if ( 'wp_navigation' === $post_type ) { + $navigation_edit_link = 'site-editor.php?' . build_query( + array( + 'postId' => '%s', + 'postType' => 'wp_navigation', + 'canvas' => 'edit', + ) + ); + $args['_edit_link'] = $navigation_edit_link; + } + return $args; } add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); diff --git a/packages/block-library/src/navigation/edit/index.js b/packages/block-library/src/navigation/edit/index.js index 76fa4deec759f..1daa863437153 100644 --- a/packages/block-library/src/navigation/edit/index.js +++ b/packages/block-library/src/navigation/edit/index.js @@ -26,6 +26,7 @@ import { __experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown, __experimentalUseBlockOverlayActive as useBlockOverlayActive, __experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients, + privateApis as blockEditorPrivateApis, } from '@wordpress/block-editor'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; @@ -67,6 +68,9 @@ import { detectColors } from './utils'; import ManageMenusButton from './manage-menus-button'; import MenuInspectorControls from './menu-inspector-controls'; import DeletedNavigationWarning from './deleted-navigation-warning'; +import { unlock } from '../../lock-unlock'; + +const { useBlockEditingMode } = unlock( blockEditorPrivateApis ); function Navigation( { attributes, @@ -115,6 +119,8 @@ function Navigation( { const recursionId = `navigationMenu/${ ref }`; const hasAlreadyRendered = useHasRecursion( recursionId ); + const blockEditingMode = useBlockEditingMode(); + // Preload classic menus, so that they don't suddenly pop-in when viewing // the Select Menu dropdown. const { menus: classicMenus } = useNavigationEntities(); @@ -653,8 +659,9 @@ function Navigation( { onSelectClassicMenu={ onSelectClassicMenu } onSelectNavigationMenu={ onSelectNavigationMenu } isLoading={ isLoading } + blockEditingMode={ blockEditingMode } /> - { stylingInspectorControls } + { blockEditingMode === 'default' && stylingInspectorControls } - { stylingInspectorControls } - { isEntityAvailable && ( + { blockEditingMode === 'default' && stylingInspectorControls } + { blockEditingMode === 'default' && isEntityAvailable && ( { hasResolvedCanUserUpdateNavigationMenu && canUserUpdateNavigationMenu && ( diff --git a/packages/block-library/src/navigation/edit/menu-inspector-controls.js b/packages/block-library/src/navigation/edit/menu-inspector-controls.js index 52e7805c0f0fb..8743b51fc6720 100644 --- a/packages/block-library/src/navigation/edit/menu-inspector-controls.js +++ b/packages/block-library/src/navigation/edit/menu-inspector-controls.js @@ -138,6 +138,7 @@ const MenuInspectorControls = ( props ) => { onSelectClassicMenu, onSelectNavigationMenu, isManageMenusButtonDisabled, + blockEditingMode, } = props; return ( @@ -150,22 +151,24 @@ const MenuInspectorControls = ( props ) => { > { __( 'Menu' ) } - + { blockEditingMode === 'default' && ( + + ) } diff --git a/packages/edit-site/src/components/block-editor/back-button.js b/packages/edit-site/src/components/block-editor/back-button.js index b7c281031f15b..ebe5af44c0250 100644 --- a/packages/edit-site/src/components/block-editor/back-button.js +++ b/packages/edit-site/src/components/block-editor/back-button.js @@ -17,9 +17,10 @@ function BackButton() { const location = useLocation(); const history = useHistory(); const isTemplatePart = location.params.postType === 'wp_template_part'; + const isNavigationMenu = location.params.postType === 'wp_navigation'; const previousTemplateId = location.state?.fromTemplateId; - const isFocusMode = isTemplatePart; + const isFocusMode = isTemplatePart || isNavigationMenu; if ( ! isFocusMode || ! previousTemplateId ) { return null; diff --git a/packages/edit-site/src/components/block-editor/constants.js b/packages/edit-site/src/components/block-editor/constants.js index 5985167c1e7d3..9f076eea1ca31 100644 --- a/packages/edit-site/src/components/block-editor/constants.js +++ b/packages/edit-site/src/components/block-editor/constants.js @@ -1 +1 @@ -export const FOCUSABLE_ENTITIES = [ 'wp_template_part' ]; +export const FOCUSABLE_ENTITIES = [ 'wp_template_part', 'wp_navigation' ]; diff --git a/packages/edit-site/src/components/block-editor/get-block-editor-provider.js b/packages/edit-site/src/components/block-editor/get-block-editor-provider.js new file mode 100644 index 0000000000000..df8185605f13a --- /dev/null +++ b/packages/edit-site/src/components/block-editor/get-block-editor-provider.js @@ -0,0 +1,29 @@ +/** + * Internal dependencies + */ +import DefaultBlockEditor from './providers/default-block-editor-provider'; +import NavigationBlockEditor from './providers/navigation-block-editor-provider'; + +/** + * Factory to isolate choosing the appropriate block editor + * component to handle a given entity type. + * + * @param {string} entityType the entity type being edited + * @return {JSX.Element} the block editor component to use. + */ +export default function getBlockEditorProvider( entityType ) { + let Provider = null; + + switch ( entityType ) { + case 'wp_navigation': + Provider = NavigationBlockEditor; + break; + case 'wp_template': + case 'wp_template_part': + default: + Provider = DefaultBlockEditor; + break; + } + + return Provider; +} diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index 08a316028ba80..4f4dd2011302f 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -13,28 +13,31 @@ import { ReusableBlocksMenuItems } from '@wordpress/reusable-blocks'; /** * Internal dependencies */ - import TemplatePartConverter from '../template-part-converter'; import { SidebarInspectorFill } from '../sidebar-edit-mode'; import { store as editSiteStore } from '../../store'; import { unlock } from '../../lock-unlock'; import { DisableNonPageContentBlocks } from '../page-content-focus'; import SiteEditorCanvas from './site-editor-canvas'; -import DefaultBlockEditorProvider from './providers/default-block-editor-provider'; +import getBlockEditorProvider from './get-block-editor-provider'; export default function BlockEditor() { - const { hasPageContentFocus } = useSelect( ( select ) => { - const { hasPageContentFocus: _hasPageContentFocus } = unlock( - select( editSiteStore ) - ); + const { entityType, hasPageContentFocus } = useSelect( ( select ) => { + const { getEditedPostType, hasPageContentFocus: _hasPageContentFocus } = + unlock( select( editSiteStore ) ); return { + entityType: getEditedPostType(), hasPageContentFocus: _hasPageContentFocus(), }; }, [] ); + // Choose the provider based on the entity type currently + // being edited. + const BlockEditorProvider = getBlockEditorProvider( entityType ); + return ( - + { hasPageContentFocus && } @@ -44,6 +47,6 @@ export default function BlockEditor() { - + ); } diff --git a/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js b/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js new file mode 100644 index 0000000000000..ea9e0c8c966ec --- /dev/null +++ b/packages/edit-site/src/components/block-editor/providers/navigation-block-editor-provider.js @@ -0,0 +1,114 @@ +/** + * WordPress dependencies + */ +import { useSelect, useDispatch } from '@wordpress/data'; +import { useMemo, useEffect } from '@wordpress/element'; +import { useEntityId } from '@wordpress/core-data'; +import { + store as blockEditorStore, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import { unlock } from '../../../lock-unlock'; +import useSiteEditorSettings from '../use-site-editor-settings'; +import { store as editSiteStore } from '../../../store'; + +const { ExperimentalBlockEditorProvider } = unlock( blockEditorPrivateApis ); + +const noop = () => {}; + +/** + * Block editor component for editing navigation menus. + * + * Note: Navigation entities require a wrapping Navigation block to provide + * them with some basic layout and styling. Therefore we create a "ghost" block + * and provide it will a reference to the navigation entity ID being edited. + * + * In this scenario it is the **block** that handles syncing the entity content + * whereas for other entities this is handled by entity block editor. + * + * @param {number} navigationMenuId the navigation menu ID + * @return {[WPBlock[], Function, Function]} The block array and setters. + */ +export default function NavigationBlockEditorProvider( { children } ) { + const defaultSettings = useSiteEditorSettings(); + + const navigationMenuId = useEntityId( 'postType', 'wp_navigation' ); + + const blocks = useMemo( () => { + return [ + createBlock( 'core/navigation', { + ref: navigationMenuId, + // As the parent editor is locked with `templateLock`, the template locking + // must be explicitly "unset" on the block itself to allow the user to modify + // the block's content. + templateLock: false, + } ), + ]; + }, [ navigationMenuId ] ); + + const { isEditMode } = useSelect( ( select ) => { + const { getCanvasMode } = unlock( select( editSiteStore ) ); + + return { + isEditMode: getCanvasMode() === 'edit', + }; + }, [] ); + + const { selectBlock, setBlockEditingMode, unsetBlockEditingMode } = unlock( + useDispatch( blockEditorStore ) + ); + + const navigationBlockClientId = blocks && blocks[ 0 ]?.clientId; + + const settings = useMemo( () => { + return { + ...defaultSettings, + // Lock the editor to allow the root ("ghost") Navigation block only. + templateLock: 'insert', + template: [ [ 'core/navigation', {}, [] ] ], + }; + }, [ defaultSettings ] ); + + // Auto-select the Navigation block when entering Navigation focus mode. + useEffect( () => { + if ( navigationBlockClientId && isEditMode ) { + selectBlock( navigationBlockClientId ); + } + }, [ navigationBlockClientId, isEditMode, selectBlock ] ); + + // Set block editing mode to contentOnly when entering Navigation focus mode. + // This ensures that non-content controls on the block will be hidden and thus + // the user can focus on editing the Navigation Menu content only. + useEffect( () => { + if ( ! navigationBlockClientId ) { + return; + } + + setBlockEditingMode( navigationBlockClientId, 'contentOnly' ); + + return () => { + unsetBlockEditingMode( navigationBlockClientId ); + }; + }, [ + navigationBlockClientId, + unsetBlockEditingMode, + setBlockEditingMode, + ] ); + + return ( + + { children } + + ); +} diff --git a/packages/edit-site/src/components/block-editor/site-editor-canvas.js b/packages/edit-site/src/components/block-editor/site-editor-canvas.js index 204ff85443fb8..29e980d113434 100644 --- a/packages/edit-site/src/components/block-editor/site-editor-canvas.js +++ b/packages/edit-site/src/components/block-editor/site-editor-canvas.js @@ -42,7 +42,7 @@ const LAYOUT = { export default function SiteEditorCanvas() { const { clearSelectedBlock } = useDispatch( blockEditorStore ); - const { isFocusMode, isViewMode } = useSelect( ( select ) => { + const { templateType, isFocusMode, isViewMode } = useSelect( ( select ) => { const { getEditedPostType, getCanvasMode } = unlock( select( editSiteStore ) ); @@ -50,6 +50,7 @@ export default function SiteEditorCanvas() { const _templateType = getEditedPostType(); return { + templateType: _templateType, isFocusMode: FOCUSABLE_ENTITIES.includes( _templateType ), isViewMode: getCanvasMode() === 'view', }; @@ -84,8 +85,17 @@ export default function SiteEditorCanvas() { usePageContentFocusNotifications(), ] ); - // Hide the appender when in view mode (i.e. not editing). - const showBlockAppender = hasBlocks || isViewMode ? false : undefined; + const isTemplateTypeNavigation = templateType === 'wp_navigation'; + + const isNavigationFocusMode = isTemplateTypeNavigation && isFocusMode; + + // Hide the appender when: + // - In navigation focus mode (should only allow the root Nav block). + // - In view mode (i.e. not editing). + const showBlockAppender = + ( isNavigationFocusMode && hasBlocks ) || isViewMode + ? false + : undefined; return ( @@ -122,7 +132,13 @@ export default function SiteEditorCanvas() { > { resizeObserver } diff --git a/packages/edit-site/src/components/block-editor/style.scss b/packages/edit-site/src/components/block-editor/style.scss index f974f6d914b16..dce224998c0c0 100644 --- a/packages/edit-site/src/components/block-editor/style.scss +++ b/packages/edit-site/src/components/block-editor/style.scss @@ -12,6 +12,12 @@ } } +// Navigation focus mode requires padding around the root Navigation block +// for presentational purposes. +.edit-site-block-editor__block-list.is-navigation-block { + padding: $grid-unit-30; +} + .edit-site-visual-editor { position: relative; height: 100%; diff --git a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js index 4ce186ad9d6bb..af3f5ccba3498 100644 --- a/packages/edit-site/src/components/block-editor/use-site-editor-settings.js +++ b/packages/edit-site/src/components/block-editor/use-site-editor-settings.js @@ -78,6 +78,14 @@ export default function useSiteEditorSettings( templateType ) { inserterMediaCategories, __experimentalBlockPatterns: blockPatterns, __experimentalBlockPatternCategories: blockPatternCategories, + // Temporary fix for bug in Block Editor Provider: + // see: https://github.com/WordPress/gutenberg/issues/51489. + // Some Site Editor entities (e.g. `wp_navigation`) may utilise + // template locking in their settings. Therefore this must be + // explicitly "unset" to avoid the template locking UI remaining + // active for all entities. + templateLock: false, + template: false, }; }, [ storedSettings, blockPatterns, blockPatternCategories ] ); } diff --git a/packages/edit-site/src/components/header-edit-mode/document-actions/index.js b/packages/edit-site/src/components/header-edit-mode/document-actions/index.js index 09307393eb399..f31c789043e72 100644 --- a/packages/edit-site/src/components/header-edit-mode/document-actions/index.js +++ b/packages/edit-site/src/components/header-edit-mode/document-actions/index.js @@ -19,6 +19,7 @@ import { store as commandsStore } from '@wordpress/commands'; import { chevronLeftSmall as chevronLeftSmallIcon, page as pageIcon, + navigation as navigationIcon, } from '@wordpress/icons'; import { displayShortcut } from '@wordpress/keycodes'; import { useState, useEffect, useRef } from '@wordpress/element'; @@ -115,15 +116,12 @@ function TemplateDocumentActions( { className, onBack } ) { ); } - const entityLabel = - record.type === 'wp_template_part' - ? __( 'template part' ) - : __( 'template' ); + const entityLabel = getEntityLabel( record.type ); return ( @@ -177,3 +175,20 @@ function BaseDocumentActions( { className, icon, children, onBack } ) { ); } + +function getEntityLabel( entityType ) { + let label = ''; + switch ( entityType ) { + case 'wp_navigation': + label = 'navigation menu'; + break; + case 'wp_template_part': + label = 'template part'; + break; + default: + label = 'template'; + break; + } + + return label; +} diff --git a/packages/edit-site/src/components/header-edit-mode/index.js b/packages/edit-site/src/components/header-edit-mode/index.js index 987046a6da33b..fd6661c8a476c 100644 --- a/packages/edit-site/src/components/header-edit-mode/index.js +++ b/packages/edit-site/src/components/header-edit-mode/index.js @@ -142,7 +142,8 @@ export default function HeaderEditMode() { const hasDefaultEditorCanvasView = ! useHasEditorCanvasContainer(); - const isFocusMode = templateType === 'wp_template_part'; + const isFocusMode = + templateType === 'wp_template_part' || templateType === 'wp_navigation'; /* translators: button label text should, if possible, be under 16 characters. */ const longLabel = _x( diff --git a/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js b/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js index b1fdb69a5ba9e..10ce08e8e7401 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/settings-header/index.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { Button } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; import { useSelect, useDispatch } from '@wordpress/data'; import { store as interfaceStore } from '@wordpress/interface'; @@ -19,9 +19,18 @@ import { SIDEBAR_BLOCK, SIDEBAR_TEMPLATE } from '../constants'; import { store as editSiteStore } from '../../../store'; const SettingsHeader = ( { sidebarName } ) => { - const hasPageContentFocus = useSelect( ( select ) => - select( editSiteStore ).hasPageContentFocus() - ); + const { hasPageContentFocus, entityType } = useSelect( ( select ) => { + const { getEditedPostType, hasPageContentFocus: _hasPageContentFocus } = + select( editSiteStore ); + + return { + hasPageContentFocus: _hasPageContentFocus(), + entityType: getEditedPostType(), + }; + } ); + + const entityLabel = + entityType === 'wp_navigation' ? __( 'Navigation' ) : __( 'Template' ); const { enableComplementaryArea } = useDispatch( interfaceStore ); const openTemplateSettings = () => @@ -41,9 +50,9 @@ const SettingsHeader = ( { sidebarName } ) => { templateAriaLabel = sidebarName === SIDEBAR_TEMPLATE ? // translators: ARIA label for the Template sidebar tab, selected. - __( 'Template (selected)' ) + sprintf( __( '%s (selected)' ), entityLabel ) : // translators: ARIA label for the Template Settings Sidebar tab, not selected. - __( 'Template' ); + entityLabel; } /* Use a list so screen readers will announce how many tabs there are. */ @@ -60,10 +69,10 @@ const SettingsHeader = ( { sidebarName } ) => { ) } aria-label={ templateAriaLabel } data-label={ - hasPageContentFocus ? __( 'Page' ) : __( 'Template' ) + hasPageContentFocus ? __( 'Page' ) : entityLabel } > - { hasPageContentFocus ? __( 'Page' ) : __( 'Template' ) } + { hasPageContentFocus ? __( 'Page' ) : entityLabel }
  • diff --git a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js index 1c369703be5d7..2fcdcbb2c7e1b 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/template-panel/index.js @@ -7,6 +7,7 @@ import { store as editorStore } from '@wordpress/editor'; import { store as coreStore } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; import { __ } from '@wordpress/i18n'; +import { navigation as navigationIcon } from '@wordpress/icons'; /** * Internal dependencies @@ -20,7 +21,7 @@ import SidebarCard from '../sidebar-card'; export default function TemplatePanel() { const { info: { title, description, icon }, - template, + record, } = useSelect( ( select ) => { const { getEditedPostType, getEditedPostId } = select( editSiteStore ); const { getEditedEntityRecord } = select( coreStore ); @@ -29,11 +30,11 @@ export default function TemplatePanel() { const postType = getEditedPostType(); const postId = getEditedPostId(); - const record = getEditedEntityRecord( 'postType', postType, postId ); + const _record = getEditedEntityRecord( 'postType', postType, postId ); - const info = record ? getTemplateInfo( record ) : {}; + const info = _record ? getTemplateInfo( _record ) : {}; - return { info, template: record }; + return { info, record: _record }; }, [] ); if ( ! title && ! description ) { @@ -45,9 +46,11 @@ export default function TemplatePanel() { } + actions={ } > diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js new file mode 100644 index 0000000000000..7d084b6db4e26 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/edit-button.js @@ -0,0 +1,24 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { pencil } from '@wordpress/icons'; +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../store'; +import SidebarButton from '../sidebar-button'; +import { unlock } from '../../lock-unlock'; + +export default function EditButton() { + const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); + + return ( + setCanvasMode( 'edit' ) } + label={ __( 'Edit' ) } + icon={ pencil } + /> + ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js index 576b96ab3f296..d4027331d5cb0 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menu/index.js @@ -17,6 +17,7 @@ import { store as noticesStore } from '@wordpress/notices'; import { SidebarNavigationScreenWrapper } from '../sidebar-navigation-screen-navigation-menus'; import ScreenNavigationMoreMenu from './more-menu'; import NavigationMenuEditor from './navigation-menu-editor'; +import EditButton from './edit-button'; export default function SidebarNavigationScreenNavigationMenu() { const { @@ -214,17 +215,33 @@ export default function SidebarNavigationScreenNavigationMenu() { return ( + <> + + + } title={ decodeEntities( menuTitle ) } - description={ __( - 'Navigation menus are a curated collection of blocks that allow visitors to get around your site.' - ) } + description={ + <> +

    + { sprintf( + /* translators: %s: Navigation menu title */ + 'This is your "%s" navigation menu. ', + decodeEntities( menuTitle ) + ) } +

    +

    + { __( + 'You can edit this menu here, but be aware that visual styles might be applied separately in templates or template parts, so the preview shown here can be incomplete.' + ) } +

    + + } >
    diff --git a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js index 510932d7c4f37..32413a943bb9a 100644 --- a/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js +++ b/packages/edit-site/src/components/sync-state-with-url/use-init-edited-entity-from-url.js @@ -31,7 +31,7 @@ export default function useInitEditedEntityFromURL() { }; }, [] ); - const { setTemplate, setTemplatePart, setPage } = + const { setTemplate, setTemplatePart, setPage, setNavigationMenu } = useDispatch( editSiteStore ); useEffect( () => { @@ -43,6 +43,9 @@ export default function useInitEditedEntityFromURL() { case 'wp_template_part': setTemplatePart( postId ); break; + case 'wp_navigation': + setNavigationMenu( postId ); + break; default: setPage( { context: { postType, postId }, @@ -71,5 +74,6 @@ export default function useInitEditedEntityFromURL() { setPage, setTemplate, setTemplatePart, + setNavigationMenu, ] ); } diff --git a/packages/edit-site/src/hooks/index.js b/packages/edit-site/src/hooks/index.js index 513634c55b8f0..4e871f4e3824e 100644 --- a/packages/edit-site/src/hooks/index.js +++ b/packages/edit-site/src/hooks/index.js @@ -4,3 +4,4 @@ import './components'; import './push-changes-to-global-styles'; import './template-part-edit'; +import './navigation-menu-edit'; diff --git a/packages/edit-site/src/hooks/navigation-menu-edit.js b/packages/edit-site/src/hooks/navigation-menu-edit.js new file mode 100644 index 0000000000000..6b2c1166c9aa2 --- /dev/null +++ b/packages/edit-site/src/hooks/navigation-menu-edit.js @@ -0,0 +1,95 @@ +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; +import { + BlockControls, + privateApis as blockEditorPrivateApis, +} from '@wordpress/block-editor'; +import { store as coreStore } from '@wordpress/core-data'; +import { ToolbarButton } from '@wordpress/components'; +import { addFilter } from '@wordpress/hooks'; +import { createHigherOrderComponent } from '@wordpress/compose'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { useLink } from '../components/routes/link'; +import { unlock } from '../lock-unlock'; + +const { useLocation } = unlock( routerPrivateApis ); +const { useBlockEditingMode } = unlock( blockEditorPrivateApis ); + +function NavigationMenuEdit( { attributes } ) { + const { ref } = attributes; + const { params } = useLocation(); + const blockEditingMode = useBlockEditingMode(); + const navigationMenu = useSelect( + ( select ) => { + return select( coreStore ).getEntityRecord( + 'postType', + 'wp_navigation', + // Ideally this should be an official public API. + ref + ); + }, + [ ref ] + ); + + const linkProps = useLink( + { + postId: navigationMenu?.id, + postType: navigationMenu?.type, + canvas: 'edit', + }, + { + // this applies to Navigation Menus as well. + fromTemplateId: params.postId, + } + ); + + // A non-default setting for block editing mode indicates that the + // editor should restrict "editing" actions. Therefore the `Edit` button + // should not be displayed. + if ( ! navigationMenu || blockEditingMode !== 'default' ) { + return null; + } + + return ( + + { + linkProps.onClick( event ); + } } + > + { __( 'Edit' ) } + + + ); +} + +export const withEditBlockControls = createHigherOrderComponent( + ( BlockEdit ) => ( props ) => { + const { attributes, name } = props; + const isDisplayed = name === 'core/navigation' && attributes.ref; + + return ( + <> + + { isDisplayed && ( + + ) } + + ); + }, + 'withEditBlockControls' +); + +addFilter( + 'editor.BlockEdit', + 'core/edit-site/navigation-edit-button', + withEditBlockControls +); diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 0e4e1ff00770f..eba997f1a6f68 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -176,6 +176,21 @@ export function setTemplatePart( templatePartId ) { }; } +/** + * Action that sets a navigation menu. + * + * @param {string} navigationMenuId The Navigation Menu Post ID. + * + * @return {Object} Action object. + */ +export function setNavigationMenu( navigationMenuId ) { + return { + type: 'SET_EDITED_POST', + postType: 'wp_navigation', + id: navigationMenuId, + }; +} + /** * @deprecated */