From b3a5ecfd8024777afd42854d103c5915c1955741 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 3 Dec 2021 10:51:16 +0800 Subject: [PATCH 01/15] Add client side router --- lib/full-site-editing/edit-site-page.php | 54 ++++------------ package-lock.json | 9 +++ packages/edit-site/package.json | 1 + .../add-new-template/new-template-part.js | 9 ++- .../add-new-template/new-template.js | 14 +++-- .../components/block-editor/back-button.js | 20 ++---- .../edit-template-part-menu-button/index.js | 21 +++++-- .../edit-site/src/components/editor/index.js | 2 - .../edit-site/src/components/list/table.js | 10 +-- .../navigation-panel/index.js | 26 +++++--- .../edit-site/src/components/routes/index.js | 53 ++++++++++++++++ .../edit-site/src/components/routes/link.js | 44 +++++++++++++ .../src/components/template-details/index.js | 14 +++-- .../template-details/template-areas.js | 13 ++-- .../components/url-query-controller/index.js | 14 ++--- packages/edit-site/src/index.js | 58 ++++++++---------- packages/edit-site/src/store/actions.js | 23 ------- packages/edit-site/src/store/reducer.js | 38 ++++-------- packages/edit-site/src/store/selectors.js | 28 +-------- packages/edit-site/src/store/test/reducer.js | 61 +++++++------------ .../edit-site/src/store/test/selectors.js | 37 +---------- packages/edit-site/src/utils/history.js | 25 ++++++++ 22 files changed, 292 insertions(+), 282 deletions(-) create mode 100644 packages/edit-site/src/components/routes/index.js create mode 100644 packages/edit-site/src/components/routes/link.js create mode 100644 packages/edit-site/src/utils/history.js diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 1bd0a26ff63b5..6bd4792c46b69 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -77,14 +77,12 @@ function gutenberg_get_editor_styles() { } /** - * Initialize the Gutenberg Templates List Page. - * - * @param array $settings The editor settings. + * Get the Gutenberg Templates List Page preload paths. */ -function gutenberg_edit_site_list_init( $settings ) { - wp_enqueue_script( 'wp-edit-site' ); - wp_enqueue_style( 'wp-edit-site' ); - wp_enqueue_media(); +function gutenberg_edit_site_list_preload_paths() { + if ( ! gutenberg_is_edit_site_list_page() ) { + return array(); + } $post_type = get_post_type_object( $_GET['postType'] ); @@ -92,36 +90,11 @@ function gutenberg_edit_site_list_init( $settings ) { 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 ) - ) + return 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", ); } @@ -158,9 +131,7 @@ 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 ); - } + $list_page_preload_paths = gutenberg_edit_site_list_preload_paths(); /** * Make the WP Screen object aware that this is a block editor page. @@ -195,7 +166,8 @@ static function( $classes ) { '/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit', '/wp/v2/global-styles/' . $active_global_styles_id, '/wp/v2/global-styles/themes/' . $active_theme, - ) + ), + $list_page_preload_paths ), 'initializer_name' => 'initializeEditor', 'editor_settings' => $settings, 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..da05d7118892c 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 @@ -9,7 +9,6 @@ import { kebabCase } from 'lodash'; import { useState } from '@wordpress/element'; import { 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'; @@ -17,9 +16,11 @@ import { store as noticesStore } from '@wordpress/notices'; /** * 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 ); @@ -44,10 +45,12 @@ export default function NewTemplatePart( { postType } ) { } ); // 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' 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..02bb7edc825a8 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,15 @@ 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 +35,7 @@ const DEFAULT_TEMPLATE_SLUGS = [ ]; export default function NewTemplate( { postType } ) { + const history = useHistory(); const { templates, defaultTemplateTypes } = useSelect( ( select ) => ( { templates: select( coreStore ).getEntityRecords( @@ -65,13 +70,12 @@ export default function NewTemplate( { postType } ) { } ); // 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/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/edit-template-part-menu-button/index.js b/packages/edit-site/src/components/edit-template-part-menu-button/index.js index 8c08a8c1764be..3cb52c5c832a0 100644 --- a/packages/edit-site/src/components/edit-template-part-menu-button/index.js +++ b/packages/edit-site/src/components/edit-template-part-menu-button/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useSelect, useDispatch } from '@wordpress/data'; +import { useSelect } from '@wordpress/data'; import { store as blockEditorStore, BlockSettingsMenuControls, @@ -14,7 +14,8 @@ import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies */ -import { store as editSiteStore } from '../../store'; +import { useLocation } from '../routes'; +import { useLink } from '../routes/link'; export default function EditTemplatePartMenuButton() { return ( @@ -30,6 +31,7 @@ export default function EditTemplatePartMenuButton() { } function EditTemplatePartMenuItem( { selectedClientId, onClose } ) { + const { params } = useLocation(); const selectedTemplatePart = useSelect( ( select ) => { 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..0132a957a4c84 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -12,7 +12,6 @@ import { import { EntityProvider, store as coreStore } from '@wordpress/core-data'; import { BlockContextProvider, BlockBreadcrumb } from '@wordpress/block-editor'; import { - FullscreenMode, InterfaceSkeleton, ComplementaryArea, store as interfaceStore, @@ -207,7 +206,6 @@ function Editor( { initialSettings, onError } ) { > - 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 } ) { > - { template.title?.rendered || template.slug } - + { template.description } 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..284327a33e11c 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 @@ -19,7 +19,6 @@ 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 +28,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 ); @@ -92,32 +98,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/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 } ) { - - ) } - - } - 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 c385233242b93..45461ba6a8e05 100644 --- a/packages/edit-site/src/components/list/index.js +++ b/packages/edit-site/src/components/list/index.js @@ -21,9 +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( { templateType } ) { +export default function List() { + const { + params: { postType: templateType }, + } = useLocation(); + useRegisterShortcuts(); const { previousShortcut, nextShortcut, isNavigationOpen } = useSelect( @@ -78,12 +83,7 @@ export default function List( { templateType } ) { ...detailedRegionLabels, } } header={
} - drawer={ - - } + drawer={ } notices={ } content={
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 284327a33e11c..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,7 +15,6 @@ 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'; @@ -54,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(); @@ -76,8 +66,6 @@ const NavigationPanel = ( { activeItem = SITE_EDITOR_KEY } ) => { className={ classnames( `edit-site-navigation-panel`, { 'is-open': isNavigationOpen, } ) } - ref={ panelRef } - tabIndex="-1" onKeyDown={ closeOnEscape } >
diff --git a/packages/edit-site/src/index.js b/packages/edit-site/src/index.js index e8303011c7dd8..b92de5d8b2e47 100644 --- a/packages/edit-site/src/index.js +++ b/packages/edit-site/src/index.js @@ -8,13 +8,14 @@ import { } from '@wordpress/block-library'; import { dispatch, select } from '@wordpress/data'; import { render, unmountComponentAtNode } from '@wordpress/element'; +import { SlotFillProvider } from '@wordpress/components'; import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions, __experimentalFetchUrlData as fetchUrlData, } from '@wordpress/core-data'; import { store as editorStore } from '@wordpress/editor'; import { store as viewportStore } from '@wordpress/viewport'; -import { getQueryArg } from '@wordpress/url'; +import { getQueryArgs } from '@wordpress/url'; /** * Internal dependencies @@ -25,6 +26,11 @@ import { store as editSiteStore } from './store'; import { Routes } from './components/routes'; import Editor from './components/editor'; import List from './components/list'; +import NavigationSidebar from './components/navigation-sidebar'; + +function getIsListPage( { postId, postType } ) { + return !! ( ! postId && postType ); +} /** * Reinitializes the editor after the user chooses to reboot the editor after @@ -52,7 +58,11 @@ export function reinitializeEditor( target, settings ) { defaultTemplatePartAreas: settings.defaultTemplatePartAreas, } ); - if ( ! getQueryArg( window.location.href, 'postId' ) ) { + const isLandingOnListPage = getIsListPage( + getQueryArgs( window.location.href ) + ); + + if ( isLandingOnListPage ) { // Default the navigation panel to be opened when we're in a bigger // screen and land in the list screen. dispatch( editSiteStore ).setIsNavigationPanelOpened( @@ -62,15 +72,32 @@ export function reinitializeEditor( target, settings ) { } render( - - { ( { params: { postType, postId } } ) => { - if ( ! postId && postType ) { - return ; - } + + + { ( { params } ) => { + const isListPage = getIsListPage( params ); - return ; - } } - , + return ( + <> + { isListPage ? ( + + ) : ( + + ) } + { /* Keep the instance of the sidebar to ensure focus will not be lost + * when navigating to other pages. */ } + + + ); + } } + + , target ); } From 87c789b55c28ca4e9013cfc9eaab5606dbb027dd Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Fri, 3 Dec 2021 21:14:05 +0800 Subject: [PATCH 06/15] Announce title change --- packages/edit-site/src/components/routes/use-title.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/routes/use-title.js b/packages/edit-site/src/components/routes/use-title.js index 30eb41a1b187e..19df5b6f8d12d 100644 --- a/packages/edit-site/src/components/routes/use-title.js +++ b/packages/edit-site/src/components/routes/use-title.js @@ -5,6 +5,7 @@ import { useEffect } 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'; export default function useTitle( title ) { const siteTitle = useSelect( @@ -26,7 +27,8 @@ export default function useTitle( title ) { if ( document.title !== formattedTitle ) { document.title = formattedTitle; - // TODO: We might want to also add accessibility-related announcements here. + // Announce title on route change for screen readers. + speak( document.title ); } } }, [ title, siteTitle ] ); From 1070ca6f908a33be06ef4090069cb6636067b667 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Mon, 6 Dec 2021 10:48:04 +0800 Subject: [PATCH 07/15] Inline all api requests --- lib/full-site-editing/edit-site-page.php | 39 ++++++------------- .../components/url-query-controller/index.js | 10 ++--- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 6bd4792c46b69..c1b7b4122ec17 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -76,28 +76,6 @@ function gutenberg_get_editor_styles() { return $styles; } -/** - * Get the Gutenberg Templates List Page preload paths. - */ -function gutenberg_edit_site_list_preload_paths() { - if ( ! gutenberg_is_edit_site_list_page() ) { - return array(); - } - - $post_type = get_post_type_object( $_GET['postType'] ); - - if ( ! $post_type ) { - wp_die( __( 'Invalid post type.', 'gutenberg' ) ); - } - - return 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", - ); -} - /** * Initialize the Gutenberg Site Editor. * @@ -112,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( @@ -131,8 +117,6 @@ static function( $classes ) { '__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(), ); - $list_page_preload_paths = gutenberg_edit_site_list_preload_paths(); - /** * Make the WP Screen object aware that this is a block editor page. * Since custom blocks check whether the screen is_block_editor, @@ -154,20 +138,21 @@ 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', '/wp/v2/global-styles/' . $active_global_styles_id, '/wp/v2/global-styles/themes/' . $active_theme, ), - $list_page_preload_paths ), 'initializer_name' => 'initializeEditor', 'editor_settings' => $settings, diff --git a/packages/edit-site/src/components/url-query-controller/index.js b/packages/edit-site/src/components/url-query-controller/index.js index bd8a9195ed588..be94bed2dd3da 100644 --- a/packages/edit-site/src/components/url-query-controller/index.js +++ b/packages/edit-site/src/components/url-query-controller/index.js @@ -3,18 +3,18 @@ */ import { useEffect } from '@wordpress/element'; import { useDispatch, useSelect } from '@wordpress/data'; -import { addQueryArgs, removeQueryArgs } from '@wordpress/url'; /** * Internal dependencies */ -import { useLocation } from '../routes'; +import { useLocation, useHistory } from '../routes'; import { store as editSiteStore } from '../../store'; export default function URLQueryController() { const { setTemplate, setTemplatePart, showHomepage, setPage } = useDispatch( editSiteStore ); + const history = useHistory(); const { params: { postId, postType }, } = useLocation(); @@ -40,11 +40,7 @@ export default function URLQueryController() { // Update page URL when context changes. const pageContext = useCurrentPageContext(); useEffect( () => { - const newUrl = pageContext - ? addQueryArgs( window.location.href, pageContext ) - : removeQueryArgs( window.location.href, 'postType', 'postId' ); - - window.history.replaceState( {}, '', newUrl ); + history.replace( pageContext ); }, [ pageContext ] ); return null; From b8dfda6b7815bb263ed2397edf2302e7f722fd70 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Mon, 6 Dec 2021 11:16:44 +0800 Subject: [PATCH 08/15] Fix php lint errors --- lib/full-site-editing/edit-site-page.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index c1b7b4122ec17..e63f3697b34a2 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -152,7 +152,7 @@ static function( $classes ) { '/wp/v2/global-styles/' . $active_global_styles_id . '?context=edit', '/wp/v2/global-styles/' . $active_global_styles_id, '/wp/v2/global-styles/themes/' . $active_theme, - ), + ) ), 'initializer_name' => 'initializeEditor', 'editor_settings' => $settings, From 46c6952aa07bb376eb902ef63d85b1d518584fc9 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Mon, 6 Dec 2021 15:30:14 +0800 Subject: [PATCH 09/15] Make title announcement assertive --- packages/edit-site/src/components/routes/use-title.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/routes/use-title.js b/packages/edit-site/src/components/routes/use-title.js index 19df5b6f8d12d..0ed0659d990ed 100644 --- a/packages/edit-site/src/components/routes/use-title.js +++ b/packages/edit-site/src/components/routes/use-title.js @@ -28,7 +28,7 @@ export default function useTitle( title ) { document.title = formattedTitle; // Announce title on route change for screen readers. - speak( document.title ); + speak( document.title, 'assertive' ); } } }, [ title, siteTitle ] ); From b04aaabf8e5048c2fcb1d351cffe9e61d8961fc2 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Mon, 6 Dec 2021 19:45:03 +0800 Subject: [PATCH 10/15] Fix infinite loop in page navigation --- .../add-new-template/new-template-part.js | 6 +-- .../create-template-part-modal/index.js | 2 +- .../components/url-query-controller/index.js | 47 ++++--------------- 3 files changed, 14 insertions(+), 41 deletions(-) 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 1d566ad00e128..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 @@ -26,7 +26,7 @@ export default function NewTemplatePart( { postType } ) { const { saveEntityRecord } = useDispatch( coreStore ); const { getLastEntitySaveError } = useSelect( coreStore ); - async function createTemplatePart( { title, area }, { closeModal } ) { + async function createTemplatePart( { title, area } ) { if ( ! title ) { createErrorNotice( __( 'Title is not defined.' ), { type: 'snackbar', @@ -55,7 +55,7 @@ export default function NewTemplatePart( { postType } ) { throw lastEntitySaveError; } - closeModal(); + setIsModalOpen( false ); // Navigate to the created template part editor. history.push( { @@ -74,7 +74,7 @@ export default function NewTemplatePart( { postType } ) { createErrorNotice( errorMessage, { type: 'snackbar' } ); - closeModal(); + setIsModalOpen( false ); } } 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 622b85f3fa308..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 @@ -51,7 +51,7 @@ export default function CreateTemplatePartModal( { closeModal, onCreate } ) { return; } setIsSubmitting( true ); - await onCreate( { title, area }, { closeModal } ); + await onCreate( { title, area } ); } } > select( editSiteStore ).getHomeTemplateId(), + [] + ); // Set correct entity on page navigation. useEffect( () => { - if ( ! postId ) { - showHomepage(); - return; - } - if ( 'page' === postType || 'post' === postType ) { setPage( { context: { postType, postId } } ); // Resolves correct template based on ID. } else if ( 'wp_template' === postType ) { setTemplate( postId ); } else if ( 'wp_template_part' === postType ) { setTemplatePart( postId ); + } else if ( homeTemplateId ) { + history.replace( { + postType: 'wp_template', + postId: homeTemplateId, + } ); } else { showHomepage(); } - }, [ postId, postType ] ); - - // Update page URL when context changes. - const pageContext = useCurrentPageContext(); - useEffect( () => { - history.replace( pageContext ); - }, [ pageContext ] ); + }, [ postId, postType, homeTemplateId, history ] ); return null; } - -function useCurrentPageContext() { - return useSelect( ( select ) => { - const { getEditedPostType, getEditedPostId, getPage } = select( - editSiteStore - ); - - const page = getPage(); - let _postId = getEditedPostId(), - _postType = getEditedPostType(); - // This doesn't seem right to me, - // we shouldn't be using the "page" and the "template" in the same way. - // This need to be investigated. - if ( page?.context?.postId && page?.context?.postType ) { - _postId = page.context.postId; - _postType = page.context.postType; - } - - if ( _postId && _postType ) { - return { postId: _postId, postType: _postType }; - } - - return null; - }, [] ); -} From 5d5cc3d1afb44ac5aff862619a1e1e78a7b908a1 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 7 Dec 2021 15:39:05 +0800 Subject: [PATCH 11/15] Fix duplicate main roles --- packages/edit-site/src/components/list/index.js | 6 +----- .../edit-site/src/components/list/style.scss | 17 ++++++----------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/edit-site/src/components/list/index.js b/packages/edit-site/src/components/list/index.js index 45461ba6a8e05..2b56930142ad4 100644 --- a/packages/edit-site/src/components/list/index.js +++ b/packages/edit-site/src/components/list/index.js @@ -85,11 +85,7 @@ export default function List() { header={
} 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; + } + } } } From b006c60885cffd01f6840ff38ac789f6dae69baf Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Tue, 7 Dec 2021 17:15:39 +0800 Subject: [PATCH 12/15] Fix not announcing same titles --- .../edit-site/src/components/editor/index.js | 6 ++-- .../src/components/routes/use-title.js | 28 ++++++++++++++----- packages/edit-site/src/utils/history.js | 24 +++++++++++----- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/edit-site/src/components/editor/index.js b/packages/edit-site/src/components/editor/index.js index 730b3669570a9..fbdfcd9958b83 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -119,8 +119,6 @@ function Editor( { onError } ) { setIsEntitiesSavedStatesOpen( false ); }, [] ); - useTitle( __( 'Editor (beta)' ) ); - const blockContext = useMemo( () => ( { ...page?.context, @@ -166,6 +164,10 @@ function Editor( { 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 ( <> diff --git a/packages/edit-site/src/components/routes/use-title.js b/packages/edit-site/src/components/routes/use-title.js index 0ed0659d990ed..2fdfd5e693f0b 100644 --- a/packages/edit-site/src/components/routes/use-title.js +++ b/packages/edit-site/src/components/routes/use-title.js @@ -1,20 +1,36 @@ /** * WordPress dependencies */ -import { useEffect } from '@wordpress/element'; +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( @@ -24,12 +40,10 @@ export default function useTitle( title ) { siteTitle ); - if ( document.title !== formattedTitle ) { - document.title = formattedTitle; + document.title = formattedTitle; - // Announce title on route change for screen readers. - speak( document.title, 'assertive' ); - } + // Announce title on route change for screen readers. + speak( document.title, 'assertive' ); } - }, [ title, siteTitle ] ); + }, [ title, siteTitle, location ] ); } diff --git a/packages/edit-site/src/utils/history.js b/packages/edit-site/src/utils/history.js index 2a22fc25b8b16..54c6c49cf8d88 100644 --- a/packages/edit-site/src/utils/history.js +++ b/packages/edit-site/src/utils/history.js @@ -10,16 +10,26 @@ import { addQueryArgs } from '@wordpress/url'; const history = createBrowserHistory(); +const originalHistoryPush = history.push; +const originalHistoryReplace = history.replace; + function push( params, state ) { - history.push( addQueryArgs( window.location.href, params ), state ); + return originalHistoryPush.call( + history, + addQueryArgs( window.location.href, params ), + state + ); } function replace( params, state ) { - history.replace( addQueryArgs( window.location.href, params ), state ); + return originalHistoryReplace.call( + history, + addQueryArgs( window.location.href, params ), + state + ); } -export default { - ...history, - push, - replace, -}; +history.push = push; +history.replace = replace; + +export default history; From 3ba1da0bb7b884c884e52ed667d98137fcc1eb2c Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Wed, 8 Dec 2021 14:03:57 +0800 Subject: [PATCH 13/15] Try to fix url query controller --- .../components/url-query-controller/index.js | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/packages/edit-site/src/components/url-query-controller/index.js b/packages/edit-site/src/components/url-query-controller/index.js index 09ba748d40be2..2b0d6d88414cc 100644 --- a/packages/edit-site/src/components/url-query-controller/index.js +++ b/packages/edit-site/src/components/url-query-controller/index.js @@ -18,28 +18,48 @@ export default function URLQueryController() { const { params: { postId, postType }, } = useLocation(); - const homeTemplateId = useSelect( - ( select ) => select( editSiteStore ).getHomeTemplateId(), - [] + const { getPage, getEditedPostId, getEditedPostType } = useSelect( + editSiteStore ); // Set correct entity on page navigation. useEffect( () => { + let isMounted = true; + if ( 'page' === postType || 'post' === postType ) { setPage( { context: { postType, postId } } ); // Resolves correct template based on ID. } else if ( 'wp_template' === postType ) { setTemplate( postId ); } else if ( 'wp_template_part' === postType ) { setTemplatePart( postId ); - } else if ( homeTemplateId ) { - history.replace( { - postType: 'wp_template', - postId: homeTemplateId, - } ); } else { - showHomepage(); + showHomepage().then( () => { + if ( ! isMounted ) { + return; + } + + const page = getPage(); + const editedPostId = getEditedPostId(); + const editedPostType = getEditedPostType(); + + if ( page?.context?.postId && page?.context?.postType ) { + history.replace( { + postId: page.context.postId, + postType: page.context.postType, + } ); + } else if ( editedPostId && editedPostType ) { + history.replace( { + postId: editedPostId, + postType: editedPostType, + } ); + } + } ); } - }, [ postId, postType, homeTemplateId, history ] ); + + return () => { + isMounted = false; + }; + }, [ postId, postType ] ); return null; } From 7644e689fa20611c19198baca3700fb89df6f98d Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 9 Dec 2021 11:35:35 +0800 Subject: [PATCH 14/15] Add UnsavedWarning to list page too --- .../edit-site/src/components/app/index.js | 47 +++++++++++++++++++ .../edit-site/src/components/editor/index.js | 2 - packages/edit-site/src/index.js | 41 ++-------------- .../edit-site/src/utils/get-is-list-page.js | 11 +++++ 4 files changed, 61 insertions(+), 40 deletions(-) create mode 100644 packages/edit-site/src/components/app/index.js create mode 100644 packages/edit-site/src/utils/get-is-list-page.js 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/editor/index.js b/packages/edit-site/src/components/editor/index.js index fbdfcd9958b83..1a5e2a6dcaa32 100644 --- a/packages/edit-site/src/components/editor/index.js +++ b/packages/edit-site/src/components/editor/index.js @@ -15,7 +15,6 @@ import { EditorNotices, EditorSnackbars, EntitiesSavedStates, - UnsavedChangesWarning, } from '@wordpress/editor'; import { __ } from '@wordpress/i18n'; import { PluginArea } from '@wordpress/plugins'; @@ -183,7 +182,6 @@ function Editor( { onError } ) { - - - { ( { 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. */ } - - - ); - } } - - , - target - ); + render( , target ); } /** diff --git a/packages/edit-site/src/utils/get-is-list-page.js b/packages/edit-site/src/utils/get-is-list-page.js new file mode 100644 index 0000000000000..ef08058d00e82 --- /dev/null +++ b/packages/edit-site/src/utils/get-is-list-page.js @@ -0,0 +1,11 @@ +/** + * Returns if the params match the list page route. + * + * @param {Object} params The search params. + * @param {string} params.postId The post ID. + * @param {string} params.postType The post type. + * @return {boolean} Is list page or not. + */ +export default function getIsListPage( { postId, postType } ) { + return !! ( ! postId && postType ); +} From 6b38d674c80677571cbcc7de63414ca1caefad46 Mon Sep 17 00:00:00 2001 From: Kai Hao Date: Thu, 9 Dec 2021 13:25:06 +0800 Subject: [PATCH 15/15] Add now displaying to use-title's speak --- packages/edit-site/src/components/routes/use-title.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/routes/use-title.js b/packages/edit-site/src/components/routes/use-title.js index 2fdfd5e693f0b..23ebaf31006e7 100644 --- a/packages/edit-site/src/components/routes/use-title.js +++ b/packages/edit-site/src/components/routes/use-title.js @@ -43,7 +43,14 @@ export default function useTitle( title ) { document.title = formattedTitle; // Announce title on route change for screen readers. - speak( document.title, 'assertive' ); + speak( + sprintf( + /* translators: The page title that is currently displaying. */ + __( 'Now displaying: %s' ), + document.title + ), + 'assertive' + ); } }, [ title, siteTitle, location ] ); }