diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 1bd0a26ff63b5..e63f3697b34a2 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -76,55 +76,6 @@ function gutenberg_get_editor_styles() { return $styles; } -/** - * Initialize the Gutenberg Templates List Page. - * - * @param array $settings The editor settings. - */ -function gutenberg_edit_site_list_init( $settings ) { - wp_enqueue_script( 'wp-edit-site' ); - wp_enqueue_style( 'wp-edit-site' ); - wp_enqueue_media(); - - $post_type = get_post_type_object( $_GET['postType'] ); - - if ( ! $post_type ) { - wp_die( __( 'Invalid post type.', 'gutenberg' ) ); - } - - $preload_data = array_reduce( - array( - '/', - "/wp/v2/types/$post_type->name?context=edit", - '/wp/v2/types?context=edit', - "/wp/v2/$post_type->rest_base?context=edit&per_page=-1", - ), - 'rest_preload_api_request', - array() - ); - - wp_add_inline_script( - 'wp-api-fetch', - sprintf( - 'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );', - wp_json_encode( $preload_data ) - ), - 'after' - ); - - wp_add_inline_script( - 'wp-edit-site', - sprintf( - 'wp.domReady( function() { - wp.editSite.initializeList( "%s", "%s", %s ); - } );', - 'edit-site-editor', - $post_type->name, - wp_json_encode( $settings ) - ) - ); -} - /** * Initialize the Gutenberg Site Editor. * @@ -139,6 +90,14 @@ function gutenberg_edit_site_init( $hook ) { return; } + if ( gutenberg_is_edit_site_list_page() ) { + $post_type = get_post_type_object( $_GET['postType'] ); + + if ( ! $post_type ) { + wp_die( __( 'Invalid post type.', 'gutenberg' ) ); + } + } + // Default to is-fullscreen-mode to avoid rendering wp-admin navigation menu while loading and // having jumps in the UI. add_filter( @@ -158,10 +117,6 @@ static function( $classes ) { '__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(), ); - if ( gutenberg_is_edit_site_list_page() ) { - return gutenberg_edit_site_list_init( $custom_settings ); - } - /** * Make the WP Screen object aware that this is a block editor page. * Since custom blocks check whether the screen is_block_editor, @@ -183,13 +138,15 @@ static function( $classes ) { array( '/wp/v2/media', 'OPTIONS' ), '/', '/wp/v2/types?context=edit', + '/wp/v2/types/wp_template?context=edit', + '/wp/v2/types/wp_template-part?context=edit', '/wp/v2/taxonomies?context=edit', '/wp/v2/pages?context=edit', '/wp/v2/categories?context=edit', '/wp/v2/posts?context=edit', '/wp/v2/tags?context=edit', - '/wp/v2/templates?context=edit', - '/wp/v2/template-parts?context=edit', + '/wp/v2/templates?context=edit&per_page=-1', + '/wp/v2/template-parts?context=edit&per_page=-1', '/wp/v2/settings', '/wp/v2/themes?context=edit&status=active', '/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit', diff --git a/package-lock.json b/package-lock.json index b1a0b613c6afd..08e18503fd281 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19233,6 +19233,7 @@ "classnames": "^2.3.1", "downloadjs": "^1.4.7", "file-saver": "^2.0.2", + "history": "^5.1.0", "jszip": "^3.2.2", "lodash": "^4.17.21", "rememo": "^3.0.0" @@ -38627,6 +38628,14 @@ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", "dev": true }, + "history": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/history/-/history-5.1.0.tgz", + "integrity": "sha512-zPuQgPacm2vH2xdORvGGz1wQMuHSIB56yNAy5FnLuwOwgSYyPKptJtcMm6Ev+hRGeS+GzhbmRacHzvlESbFwDg==", + "requires": { + "@babel/runtime": "^7.7.6" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 14cb876755a19..0459bb7523301 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -56,6 +56,7 @@ "classnames": "^2.3.1", "downloadjs": "^1.4.7", "file-saver": "^2.0.2", + "history": "^5.1.0", "jszip": "^3.2.2", "lodash": "^4.17.21", "rememo": "^3.0.0" diff --git a/packages/edit-site/src/components/add-new-template/new-template-part.js b/packages/edit-site/src/components/add-new-template/new-template-part.js index 5f751e9aa50fb..1f820246b3982 100644 --- a/packages/edit-site/src/components/add-new-template/new-template-part.js +++ b/packages/edit-site/src/components/add-new-template/new-template-part.js @@ -7,21 +7,24 @@ import { kebabCase } from 'lodash'; * WordPress dependencies */ import { useState } from '@wordpress/element'; -import { useDispatch } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; import { Button } from '@wordpress/components'; -import { addQueryArgs } from '@wordpress/url'; -import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; +import { store as coreStore } from '@wordpress/core-data'; /** * Internal dependencies */ +import { useHistory } from '../routes'; import CreateTemplatePartModal from '../create-template-part-modal'; export default function NewTemplatePart( { postType } ) { + const history = useHistory(); const [ isModalOpen, setIsModalOpen ] = useState( false ); const { createErrorNotice } = useDispatch( noticesStore ); + const { saveEntityRecord } = useDispatch( coreStore ); + const { getLastEntitySaveError } = useSelect( coreStore ); async function createTemplatePart( { title, area } ) { if ( ! title ) { @@ -32,22 +35,35 @@ export default function NewTemplatePart( { postType } ) { } try { - const templatePart = await apiFetch( { - path: '/wp/v2/template-parts', - method: 'POST', - data: { + const templatePart = await saveEntityRecord( + 'postType', + 'wp_template_part', + { slug: kebabCase( title ), title, content: '', area, - }, - } ); + } + ); + + const lastEntitySaveError = getLastEntitySaveError( + 'postType', + 'wp_template_part', + templatePart.id + ); + if ( lastEntitySaveError ) { + throw lastEntitySaveError; + } + + setIsModalOpen( false ); // Navigate to the created template part editor. - window.location.href = addQueryArgs( window.location.href, { + history.push( { postId: templatePart.id, - postType: 'wp_template_part', + postType: templatePart.type, } ); + + // TODO: Add a success notice? } catch ( error ) { const errorMessage = error.message && error.code !== 'unknown_error' @@ -57,6 +73,8 @@ export default function NewTemplatePart( { postType } ) { ); createErrorNotice( errorMessage, { type: 'snackbar' } ); + + setIsModalOpen( false ); } } diff --git a/packages/edit-site/src/components/add-new-template/new-template.js b/packages/edit-site/src/components/add-new-template/new-template.js index 174d09c0482b2..1d3d6897e9fa7 100644 --- a/packages/edit-site/src/components/add-new-template/new-template.js +++ b/packages/edit-site/src/components/add-new-template/new-template.js @@ -15,11 +15,14 @@ import { import { useSelect, useDispatch } from '@wordpress/data'; import { store as coreStore } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; -import { addQueryArgs } from '@wordpress/url'; -import apiFetch from '@wordpress/api-fetch'; import { __ } from '@wordpress/i18n'; import { store as noticesStore } from '@wordpress/notices'; +/** + * Internal dependencies + */ +import { useHistory } from '../routes'; + const DEFAULT_TEMPLATE_SLUGS = [ 'front-page', 'single-post', @@ -31,6 +34,7 @@ const DEFAULT_TEMPLATE_SLUGS = [ ]; export default function NewTemplate( { postType } ) { + const history = useHistory(); const { templates, defaultTemplateTypes } = useSelect( ( select ) => ( { templates: select( coreStore ).getEntityRecords( @@ -44,7 +48,9 @@ export default function NewTemplate( { postType } ) { } ), [] ); + const { saveEntityRecord } = useDispatch( coreStore ); const { createErrorNotice } = useDispatch( noticesStore ); + const { getLastEntitySaveError } = useSelect( coreStore ); async function createTemplate( { slug } ) { try { @@ -52,26 +58,34 @@ export default function NewTemplate( { postType } ) { slug, } ); - const template = await apiFetch( { - path: '/wp/v2/templates', - method: 'POST', - data: { + const template = await saveEntityRecord( + 'postType', + 'wp_template', + { excerpt: description, // Slugs need to be strings, so this is for template `404` slug: slug.toString(), status: 'publish', title, - }, - } ); + } + ); + + const lastEntitySaveError = getLastEntitySaveError( + 'postType', + 'wp_template', + template.id + ); + if ( lastEntitySaveError ) { + throw lastEntitySaveError; + } // Navigate to the created template editor. - window.location.href = addQueryArgs( window.location.href, { + history.push( { postId: template.id, - postType: 'wp_template', + postType: template.type, } ); - // Wait for async navigation to happen before closing the modal. - await new Promise( () => {} ); + // TODO: Add a success notice? } catch ( error ) { const errorMessage = error.message && error.code !== 'unknown_error' diff --git a/packages/edit-site/src/components/app/index.js b/packages/edit-site/src/components/app/index.js new file mode 100644 index 0000000000000..bf367f36740ba --- /dev/null +++ b/packages/edit-site/src/components/app/index.js @@ -0,0 +1,47 @@ +/** + * WordPress dependencies + */ +import { SlotFillProvider } from '@wordpress/components'; +import { UnsavedChangesWarning } from '@wordpress/editor'; + +/** + * Internal dependencies + */ +import { Routes } from '../routes'; +import Editor from '../editor'; +import List from '../list'; +import NavigationSidebar from '../navigation-sidebar'; +import getIsListPage from '../../utils/get-is-list-page'; + +export default function EditSiteApp( { reboot } ) { + return ( + + + + + { ( { params } ) => { + const isListPage = getIsListPage( params ); + + return ( + <> + { isListPage ? ( + + ) : ( + + ) } + { /* Keep the instance of the sidebar to ensure focus will not be lost + * when navigating to other pages. */ } + + + ); + } } + + + ); +} 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 2b03282417134..422f429b8192a 100644 --- a/packages/edit-site/src/components/block-editor/back-button.js +++ b/packages/edit-site/src/components/block-editor/back-button.js @@ -3,26 +3,18 @@ */ import { Button } from '@wordpress/components'; import { arrowLeft } from '@wordpress/icons'; -import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; /** * Internal dependencies */ -import { store as editSiteStore } from '../../store'; +import { useLocation, useHistory } from '../routes'; function BackButton() { - const { isTemplatePart, previousTemplateId } = useSelect( ( select ) => { - const { getEditedPostType, getPreviousEditedPostId } = select( - editSiteStore - ); - - return { - isTemplatePart: getEditedPostType() === 'wp_template_part', - previousTemplateId: getPreviousEditedPostId(), - }; - }, [] ); - const { goBack } = useDispatch( editSiteStore ); + const location = useLocation(); + const history = useHistory(); + const isTemplatePart = location.params.postType === 'wp_template_part'; + const previousTemplateId = location.state?.fromTemplateId; if ( ! isTemplatePart || ! previousTemplateId ) { return null; @@ -33,7 +25,7 @@ function BackButton() { className="edit-site-visual-editor__back-button" icon={ arrowLeft } onClick={ () => { - goBack(); + history.back(); } } > { __( 'Back' ) } diff --git a/packages/edit-site/src/components/create-template-part-modal/index.js b/packages/edit-site/src/components/create-template-part-modal/index.js index bbdf7434a466b..dd02aa8b270a4 100644 --- a/packages/edit-site/src/components/create-template-part-modal/index.js +++ b/packages/edit-site/src/components/create-template-part-modal/index.js @@ -52,8 +52,6 @@ export default function CreateTemplatePartModal( { closeModal, onCreate } ) { } setIsSubmitting( true ); await onCreate( { title, area } ); - setIsSubmitting( false ); - closeModal(); } } > { const block = select( blockEditorStore ).getBlock( @@ -50,7 +52,15 @@ function EditTemplatePartMenuItem( { selectedClientId, onClose } ) { [ selectedClientId ] ); - const { pushTemplatePart } = useDispatch( editSiteStore ); + const linkProps = useLink( + { + postId: selectedTemplatePart?.id, + postType: selectedTemplatePart?.type, + }, + { + fromTemplateId: params.postId, + } + ); if ( ! selectedTemplatePart ) { return null; @@ -58,8 +68,9 @@ function EditTemplatePartMenuItem( { selectedClientId, onClose } ) { return ( { - pushTemplatePart( selectedTemplatePart.id ); + { ...linkProps } + onClick={ ( event ) => { + linkProps.onClick( event ); onClose(); } } > diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index d69820882bf22..1a5e2a6dcaa32 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -3,16 +3,10 @@ */ import { useEffect, useState, useMemo, useCallback } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; -import { - SlotFillProvider, - Popover, - Button, - Notice, -} from '@wordpress/components'; +import { Popover, Button, Notice } from '@wordpress/components'; import { EntityProvider, store as coreStore } from '@wordpress/core-data'; import { BlockContextProvider, BlockBreadcrumb } from '@wordpress/block-editor'; import { - FullscreenMode, InterfaceSkeleton, ComplementaryArea, store as interfaceStore, @@ -21,8 +15,6 @@ import { EditorNotices, EditorSnackbars, EntitiesSavedStates, - UnsavedChangesWarning, - store as editorStore, } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { PluginArea } from '@wordpress/plugins'; @@ -47,13 +39,14 @@ import WelcomeGuide from '../welcome-guide'; import { store as editSiteStore } from '../../store'; import { GlobalStylesRenderer } from './global-styles-renderer'; import { GlobalStylesProvider } from '../global-styles/global-styles-provider'; +import useTitle from '../routes/use-title'; const interfaceLabels = { secondarySidebar: __( 'Block Library' ), drawer: __( 'Navigation Sidebar' ), }; -function Editor( { initialSettings, onError } ) { +function Editor( { onError } ) { const { isInserterOpen, isListViewOpen, @@ -111,26 +104,7 @@ function Editor( { initialSettings, onError } ) { ).getAllShortcutKeyCombinations( 'core/edit-site/next-region' ), }; }, [] ); - const { updateEditorSettings } = useDispatch( editorStore ); - const { setPage, setIsInserterOpened, updateSettings } = useDispatch( - editSiteStore - ); - - useEffect( () => { - updateSettings( initialSettings ); - }, [] ); - - // Keep the defaultTemplateTypes in the core/editor settings too, - // so that they can be selected with core/editor selectors in any editor. - // This is needed because edit-site doesn't initialize with EditorProvider, - // which internally uses updateEditorSettings as well. - const { defaultTemplateTypes, defaultTemplatePartAreas } = settings; - useEffect( () => { - updateEditorSettings( { - defaultTemplateTypes, - defaultTemplatePartAreas, - } ); - }, [ defaultTemplateTypes, defaultTemplatePartAreas ] ); + const { setPage, setIsInserterOpened } = useDispatch( editSiteStore ); const [ isEntitiesSavedStatesOpen, @@ -189,120 +163,120 @@ function Editor( { initialSettings, onError } ) { return null; }; + // Only announce the title once the editor is ready to prevent "Replace" + // action in from double-announcing. + useTitle( isReady && __( 'Editor (beta)' ) ); + return ( <> { isReady && ( - - - - - - - - - - - - - ) - } - drawer={ } - header={ -
+ + + + + + + + + ) + } + drawer={ + + } + header={ +
+ } + notices={ } + content={ + <> + + { template && ( + + ) } + { templateResolved && + ! template && + settings?.siteUrl && + entityId && ( + + { __( + "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" + ) } + + ) } + - } - notices={ } - content={ - <> - - { template && ( - - ) } - { templateResolved && - ! template && - settings?.siteUrl && - entityId && ( - - { __( - "You attempted to edit an item that doesn't exist. Perhaps it was deleted?" - ) } - - ) } - + } + actions={ + <> + { isEntitiesSavedStatesOpen ? ( + - - } - actions={ - <> - { isEntitiesSavedStatesOpen ? ( - + - - ) } - - } - footer={ } - shortcuts={ { - previous: previousShortcut, - next: nextShortcut, - } } - /> - - - - - - - + aria-expanded={ + false + } + > + { __( + 'Open save panel' + ) } + + + ) } + + } + footer={ } + shortcuts={ { + previous: previousShortcut, + next: nextShortcut, + } } + /> + + + + + + - + ) } diff --git a/packages/edit-site/src/components/list/index.js b/packages/edit-site/src/components/list/index.js index d02de1c519ad5..2b56930142ad4 100644 --- a/packages/edit-site/src/components/list/index.js +++ b/packages/edit-site/src/components/list/index.js @@ -21,8 +21,14 @@ import Header from './header'; import NavigationSidebar from '../navigation-sidebar'; import Table from './table'; import { store as editSiteStore } from '../../store'; +import { useLocation } from '../routes'; +import useTitle from '../routes/use-title'; + +export default function List() { + const { + params: { postType: templateType }, + } = useLocation(); -export default function List( { templateType } ) { useRegisterShortcuts(); const { previousShortcut, nextShortcut, isNavigationOpen } = useSelect( @@ -47,6 +53,8 @@ export default function List( { templateType } ) { [ templateType ] ); + useTitle( postType?.labels?.name ); + // `postType` could load in asynchronously. Only provide the detailed region labels if // the postType has loaded, otherwise `InterfaceSkeleton` will fallback to the defaults. const itemsListLabel = postType?.labels?.items_list; @@ -75,18 +83,9 @@ export default function List( { templateType } ) { ...detailedRegionLabels, } } header={
} - drawer={ - - } + drawer={ } notices={ } - content={ -
- - - } + content={
} shortcuts={ { previous: previousShortcut, next: nextShortcut, diff --git a/packages/edit-site/src/components/list/style.scss b/packages/edit-site/src/components/list/style.scss index f5ab68cd8bfe3..0ad240e19f768 100644 --- a/packages/edit-site/src/components/list/style.scss +++ b/packages/edit-site/src/components/list/style.scss @@ -44,18 +44,13 @@ .interface-interface-skeleton__content { background: $white; - } - } -} + align-items: center; + padding: $grid-unit-20; -.edit-site-list-main { - display: flex; - align-items: center; - justify-content: center; - padding: $grid-unit-20; - - @include break-medium() { - padding: $grid-unit * 9; + @include break-medium() { + padding: $grid-unit * 9; + } + } } } diff --git a/packages/edit-site/src/components/list/table.js b/packages/edit-site/src/components/list/table.js index 3d790d0a3979a..f9a387de92a08 100644 --- a/packages/edit-site/src/components/list/table.js +++ b/packages/edit-site/src/components/list/table.js @@ -8,11 +8,11 @@ import { VisuallyHidden, __experimentalHeading as Heading, } from '@wordpress/components'; -import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ +import Link from '../routes/link'; import Actions from './actions'; import AddedBy from './added-by'; @@ -92,15 +92,15 @@ export default function Table( { templateType } ) { > diff --git a/packages/edit-site/src/components/navigation-sidebar/index.js b/packages/edit-site/src/components/navigation-sidebar/index.js index 4d6cbfc7a6170..9d03ec1ca7662 100644 --- a/packages/edit-site/src/components/navigation-sidebar/index.js +++ b/packages/edit-site/src/components/navigation-sidebar/index.js @@ -18,30 +18,31 @@ export const { Slot: NavigationPanelPreviewSlot, } = createSlotFill( 'EditSiteNavigationPanelPreview' ); -export default function NavigationSidebar( { - isDefaultOpen = false, - activeTemplateType, -} ) { +const { + Fill: NavigationSidebarFill, + Slot: NavigationSidebarSlot, +} = createSlotFill( 'EditSiteNavigationSidebar' ); + +function NavigationSidebar( { isDefaultOpen = false, activeTemplateType } ) { const isDesktopViewport = useViewportMatch( 'medium' ); const { setIsNavigationPanelOpened } = useDispatch( editSiteStore ); - useEffect( () => { - // When transitioning to desktop open the navigation if `isDefaultOpen` is true. - if ( isDefaultOpen && isDesktopViewport ) { - setIsNavigationPanelOpened( true ); - } - - // When transitioning to mobile/tablet, close the navigation. - if ( ! isDesktopViewport ) { - setIsNavigationPanelOpened( false ); - } - }, [ isDefaultOpen, isDesktopViewport, setIsNavigationPanelOpened ] ); + useEffect( + function autoOpenNavigationPanelOnViewportChange() { + setIsNavigationPanelOpened( isDefaultOpen && isDesktopViewport ); + }, + [ isDefaultOpen, isDesktopViewport, setIsNavigationPanelOpened ] + ); return ( - <> + - + ); } + +NavigationSidebar.Slot = NavigationSidebarSlot; + +export default NavigationSidebar; diff --git a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js index b42a66a89396a..f26ef70ec211d 100644 --- a/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js +++ b/packages/edit-site/src/components/navigation-sidebar/navigation-panel/index.js @@ -15,11 +15,9 @@ import { } from '@wordpress/components'; import { store as coreDataStore } from '@wordpress/core-data'; import { useSelect, useDispatch } from '@wordpress/data'; -import { useEffect, useRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { ESCAPE } from '@wordpress/keycodes'; import { decodeEntities } from '@wordpress/html-entities'; -import { addQueryArgs } from '@wordpress/url'; import { home as siteIcon, layout as templateIcon, @@ -29,11 +27,18 @@ import { /** * Internal dependencies */ +import { useLink } from '../../routes/link'; import MainDashboardButton from '../../main-dashboard-button'; import { store as editSiteStore } from '../../../store'; const SITE_EDITOR_KEY = 'site-editor'; +function NavLink( { params, replace, ...props } ) { + const linkProps = useLink( params, replace ); + + return ; +} + const NavigationPanel = ( { activeItem = SITE_EDITOR_KEY } ) => { const { isNavigationOpen, siteTitle } = useSelect( ( select ) => { const { getEntityRecord } = select( coreDataStore ); @@ -48,15 +53,6 @@ const NavigationPanel = ( { activeItem = SITE_EDITOR_KEY } ) => { }, [] ); const { setIsNavigationPanelOpened } = useDispatch( editSiteStore ); - // Ensures focus is moved to the panel area when it is activated - // from a separate component (such as document actions in the header). - const panelRef = useRef(); - useEffect( () => { - if ( isNavigationOpen ) { - panelRef.current.focus(); - } - }, [ activeItem, isNavigationOpen ] ); - const closeOnEscape = ( event ) => { if ( event.keyCode === ESCAPE && ! event.defaultPrevented ) { event.preventDefault(); @@ -70,8 +66,6 @@ const NavigationPanel = ( { activeItem = SITE_EDITOR_KEY } ) => { className={ classnames( `edit-site-navigation-panel`, { 'is-open': isNavigationOpen, } ) } - ref={ panelRef } - tabIndex="-1" onKeyDown={ closeOnEscape } >
@@ -92,32 +86,32 @@ const NavigationPanel = ( { activeItem = SITE_EDITOR_KEY } ) => { - - - diff --git a/packages/edit-site/src/components/routes/index.js b/packages/edit-site/src/components/routes/index.js new file mode 100644 index 0000000000000..b24fa069f1efd --- /dev/null +++ b/packages/edit-site/src/components/routes/index.js @@ -0,0 +1,53 @@ +/** + * WordPress dependencies + */ +import { + createContext, + useState, + useEffect, + useContext, +} from '@wordpress/element'; + +/** + * Internal dependencies + */ +import history from '../../utils/history'; + +const RoutesContext = createContext(); +const HistoryContext = createContext(); + +export function useLocation() { + return useContext( RoutesContext ); +} + +export function useHistory() { + return useContext( HistoryContext ); +} + +function getLocationWithParams( location ) { + const searchParams = new URLSearchParams( location.search ); + return { + ...location, + params: Object.fromEntries( searchParams.entries() ), + }; +} + +export function Routes( { children } ) { + const [ location, setLocation ] = useState( () => + getLocationWithParams( history.location ) + ); + + useEffect( () => { + return history.listen( ( { location: updatedLocation } ) => { + setLocation( getLocationWithParams( updatedLocation ) ); + } ); + }, [] ); + + return ( + + + { children( location ) } + + + ); +} diff --git a/packages/edit-site/src/components/routes/link.js b/packages/edit-site/src/components/routes/link.js new file mode 100644 index 0000000000000..8454260b908aa --- /dev/null +++ b/packages/edit-site/src/components/routes/link.js @@ -0,0 +1,44 @@ +/** + * WordPress dependencies + */ +import { addQueryArgs } from '@wordpress/url'; + +/** + * Internal dependencies + */ +import { useHistory } from './index'; + +export function useLink( params = {}, state, shouldReplace = false ) { + const history = useHistory(); + + function onClick( event ) { + event.preventDefault(); + + if ( shouldReplace ) { + history.replace( params, state ); + } else { + history.push( params, state ); + } + } + + return { + href: addQueryArgs( window.location.href, params ), + onClick, + }; +} + +export default function Link( { + params = {}, + state, + replace: shouldReplace = false, + children, + ...props +} ) { + const { href, onClick } = useLink( params, state, shouldReplace ); + + return ( + + { children } + + ); +} diff --git a/packages/edit-site/src/components/routes/use-title.js b/packages/edit-site/src/components/routes/use-title.js new file mode 100644 index 0000000000000..23ebaf31006e7 --- /dev/null +++ b/packages/edit-site/src/components/routes/use-title.js @@ -0,0 +1,56 @@ +/** + * WordPress dependencies + */ +import { useEffect, useRef } from '@wordpress/element'; +import { useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; +import { __, sprintf } from '@wordpress/i18n'; +import { speak } from '@wordpress/a11y'; + +/** + * Internal dependencies + */ +import { useLocation } from './index'; + +export default function useTitle( title ) { + const location = useLocation(); + const siteTitle = useSelect( + ( select ) => + select( coreStore ).getEntityRecord( 'root', 'site' )?.title, + [] + ); + const isInitialLocationRef = useRef( true ); + + useEffect( () => { + isInitialLocationRef.current = false; + }, [ location ] ); + + useEffect( () => { + // Don't update or announce the title for initial page load. + if ( isInitialLocationRef.current ) { + return; + } + + if ( title && siteTitle ) { + // @see https://github.com/WordPress/wordpress-develop/blob/94849898192d271d533e09756007e176feb80697/src/wp-admin/admin-header.php#L67-L68 + const formattedTitle = sprintf( + /* translators: Admin screen title. 1: Admin screen name, 2: Network or site name. */ + __( '%1$s ‹ %2$s — WordPress' ), + title, + siteTitle + ); + + document.title = formattedTitle; + + // Announce title on route change for screen readers. + speak( + sprintf( + /* translators: The page title that is currently displaying. */ + __( 'Now displaying: %s' ), + document.title + ), + 'assertive' + ); + } + }, [ title, siteTitle, location ] ); +} diff --git a/packages/edit-site/src/components/template-details/index.js b/packages/edit-site/src/components/template-details/index.js index 78414f7936e07..5963040b7ef5b 100644 --- a/packages/edit-site/src/components/template-details/index.js +++ b/packages/edit-site/src/components/template-details/index.js @@ -12,7 +12,6 @@ import { } from '@wordpress/components'; import { useDispatch, useSelect } from '@wordpress/data'; import { store as editorStore } from '@wordpress/editor'; -import { addQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -25,6 +24,7 @@ import { import { store as editSiteStore } from '../../store'; import TemplateAreas from './template-areas'; import EditTemplateTitle from './edit-template-title'; +import { useLink } from '../routes/link'; export default function TemplateDetails( { template, onClose } ) { const { title, description } = useSelect( @@ -44,6 +44,12 @@ export default function TemplateDetails( { template, onClose } ) { ); }, [ template ] ); + const browseAllLinkProps = useLink( { + // TODO: We should update this to filter by template part's areas as well. + postType: template.type, + postId: undefined, + } ); + if ( ! template ) { return null; } @@ -95,11 +101,7 @@ export default function TemplateDetails( { template, onClose } ) {
- { template.title?.rendered || template.slug } - + { template.description }