-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the ability to create a new page in the Site Editor #50565
Changes from 17 commits
b39b236
f045425
f3e097a
67c222f
216bb20
6558f3f
5fc86cf
205a2a6
13c4eee
b4f4817
7f0140e
7ad5b2b
2148ede
e46efae
28d80f4
79d1116
604051c
a2426d5
ef1c31c
0f168d2
3edbff5
49635c0
0efe958
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/** | ||
* External dependencies | ||
*/ | ||
import { kebabCase } from 'lodash'; | ||
|
||
/** | ||
* WordPress dependencies | ||
*/ | ||
import { | ||
Button, | ||
Modal, | ||
__experimentalHStack as HStack, | ||
__experimentalVStack as VStack, | ||
TextControl, | ||
} from '@wordpress/components'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { useDispatch } from '@wordpress/data'; | ||
import { useState } from '@wordpress/element'; | ||
import { store as coreStore } from '@wordpress/core-data'; | ||
import { store as noticesStore } from '@wordpress/notices'; | ||
|
||
export default function AddNewPageModal( { onSave, onClose } ) { | ||
const [ isCreatingPage, setIsCreatingPage ] = useState( false ); | ||
const [ title, setTitle ] = useState( '' ); | ||
|
||
const { saveEntityRecord } = useDispatch( coreStore ); | ||
const { createErrorNotice, createSuccessNotice } = | ||
useDispatch( noticesStore ); | ||
|
||
async function createPage( event ) { | ||
event.preventDefault(); | ||
|
||
if ( isCreatingPage ) { | ||
return; | ||
} | ||
setIsCreatingPage( true ); | ||
try { | ||
const newPage = await saveEntityRecord( | ||
'postType', | ||
'page', | ||
{ | ||
status: 'draft', | ||
title, | ||
slug: kebabCase( title || __( 'Untitled' ) ), | ||
}, | ||
{ throwOnError: true } | ||
); | ||
|
||
onSave( newPage ); | ||
|
||
createSuccessNotice( | ||
sprintf( | ||
// translators: %s: Title of the created template e.g: "Category". | ||
__( '"%s" successfully created.' ), | ||
newPage.title?.rendered || title | ||
), | ||
{ | ||
type: 'snackbar', | ||
} | ||
); | ||
} catch ( error ) { | ||
const errorMessage = | ||
error.message && error.code !== 'unknown_error' | ||
? error.message | ||
: __( 'An error occurred while creating the page.' ); | ||
|
||
createErrorNotice( errorMessage, { | ||
type: 'snackbar', | ||
} ); | ||
} finally { | ||
setIsCreatingPage( false ); | ||
} | ||
} | ||
|
||
return ( | ||
<Modal title={ __( 'Draft a new page' ) } onRequestClose={ onClose }> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker, but I noticed that when I throttled my connection I could close the modal and click around, only to be redirected to the new page, which seemed confusing. A bit of an edge case though, unless you live in an area with bad internet speeds like me 😄 2023-06-06.12.01.53.mp4Maybe in another iteration we could look at blocking the modal close until the save callback is fired? const closeModal = () => {
if ( isCreatingPage ) {
return;
}
onClose();
}; |
||
<form onSubmit={ createPage }> | ||
<VStack spacing={ 3 }> | ||
<TextControl | ||
SaxonF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
label={ __( 'Page title' ) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Not a blocker, just asking if we're not enforcing a page title for creation, maybe add |
||
onChange={ setTitle } | ||
placeholder={ __( 'Untitled' ) } | ||
SaxonF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
value={ title } | ||
SaxonF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/> | ||
<HStack spacing={ 2 } justify="end"> | ||
<Button variant="tertiary" onClick={ onClose }> | ||
{ __( 'Cancel' ) } | ||
</Button> | ||
<Button | ||
variant="primary" | ||
type="submit" | ||
isBusy={ isCreatingPage } | ||
aria-disabled={ isCreatingPage } | ||
> | ||
{ __( 'Create draft' ) } | ||
</Button> | ||
</HStack> | ||
</VStack> | ||
</form> | ||
</Modal> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,12 @@ import { | |
__experimentalItem as Item, | ||
__experimentalTruncate as Truncate, | ||
} from '@wordpress/components'; | ||
import { useState } from '@wordpress/element'; | ||
import { __ } from '@wordpress/i18n'; | ||
import { useEntityRecords } from '@wordpress/core-data'; | ||
import { decodeEntities } from '@wordpress/html-entities'; | ||
import { plus } from '@wordpress/icons'; | ||
import { privateApis as routerPrivateApis } from '@wordpress/router'; | ||
|
||
/** | ||
* Internal dependencies | ||
|
@@ -17,6 +20,11 @@ import SidebarNavigationScreen from '../sidebar-navigation-screen'; | |
import { useLink } from '../routes/link'; | ||
import SidebarNavigationItem from '../sidebar-navigation-item'; | ||
import SidebarNavigationSubtitle from '../sidebar-navigation-subtitle'; | ||
import SidebarButton from '../sidebar-button'; | ||
import AddNewPageModal from '../add-new-page'; | ||
import { unlock } from '../../private-apis'; | ||
|
||
const { useHistory } = unlock( routerPrivateApis ); | ||
|
||
const PageItem = ( { postId, ...props } ) => { | ||
const linkInfo = useLink( { | ||
|
@@ -33,54 +41,83 @@ export default function SidebarNavigationScreenPages() { | |
{ status: 'any' } | ||
); | ||
|
||
const [ showAddPage, setShowAddPage ] = useState( false ); | ||
|
||
const history = useHistory(); | ||
|
||
const handleNewPage = ( { type, id } ) => { | ||
// Navigate to the created template editor. | ||
history.push( { | ||
SaxonF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
postId: id, | ||
postType: type, | ||
canvas: 'edit', | ||
} ); | ||
setShowAddPage( false ); | ||
}; | ||
|
||
return ( | ||
<SidebarNavigationScreen | ||
title={ __( 'Pages' ) } | ||
description={ __( 'Browse and edit pages on your site.' ) } | ||
content={ | ||
<> | ||
{ isLoading && ( | ||
<ItemGroup> | ||
<Item>{ __( 'Loading pages' ) }</Item> | ||
</ItemGroup> | ||
) } | ||
{ ! isLoading && ( | ||
<> | ||
<SidebarNavigationSubtitle> | ||
{ __( 'Recent' ) } | ||
</SidebarNavigationSubtitle> | ||
<> | ||
{ showAddPage && ( | ||
<AddNewPageModal | ||
onSave={ handleNewPage } | ||
onClose={ () => setShowAddPage( false ) } | ||
/> | ||
) } | ||
<SidebarNavigationScreen | ||
title={ __( 'Pages' ) } | ||
description={ __( 'Browse and edit pages on your site.' ) } | ||
actions={ | ||
<SidebarButton | ||
icon={ plus } | ||
label={ __( 'Draft a new page' ) } | ||
onClick={ () => setShowAddPage( true ) } | ||
/> | ||
} | ||
content={ | ||
<> | ||
{ isLoading && ( | ||
<ItemGroup> | ||
{ ! pages?.length && ( | ||
<Item>{ __( 'No page found' ) }</Item> | ||
) } | ||
{ pages?.map( ( page ) => ( | ||
<PageItem | ||
postId={ page.id } | ||
key={ page.id } | ||
withChevron | ||
> | ||
<Truncate numberOfLines={ 1 }> | ||
{ decodeEntities( | ||
page.title?.rendered | ||
) ?? __( 'Untitled' ) } | ||
</Truncate> | ||
</PageItem> | ||
) ) } | ||
<SidebarNavigationItem | ||
className="edit-site-sidebar-navigation-screen-pages__see-all" | ||
href="edit.php?post_type=page" | ||
onClick={ () => { | ||
document.location = | ||
'edit.php?post_type=page'; | ||
} } | ||
> | ||
{ __( 'Manage all pages' ) } | ||
</SidebarNavigationItem> | ||
<Item>{ __( 'Loading pages' ) }</Item> | ||
</ItemGroup> | ||
</> | ||
) } | ||
</> | ||
} | ||
/> | ||
) } | ||
{ ! isLoading && ( | ||
<> | ||
<SidebarNavigationSubtitle> | ||
{ __( 'Recent' ) } | ||
</SidebarNavigationSubtitle> | ||
<ItemGroup> | ||
{ ! pages?.length && ( | ||
<Item>{ __( 'No page found' ) }</Item> | ||
) } | ||
{ pages?.map( ( page ) => ( | ||
<PageItem | ||
postId={ page.id } | ||
key={ page.id } | ||
withChevron | ||
> | ||
<Truncate numberOfLines={ 1 }> | ||
{ decodeEntities( | ||
page.title?.rendered | ||
) ?? __( 'Untitled' ) } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be |
||
</Truncate> | ||
</PageItem> | ||
) ) } | ||
<SidebarNavigationItem | ||
className="edit-site-sidebar-navigation-screen-pages__see-all" | ||
href="edit.php?post_type=page" | ||
onClick={ () => { | ||
document.location = | ||
SaxonF marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'edit.php?post_type=page'; | ||
} } | ||
> | ||
{ __( 'Manage all pages' ) } | ||
</SidebarNavigationItem> | ||
</ItemGroup> | ||
</> | ||
) } | ||
</> | ||
} | ||
/> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); | ||
|
||
test.describe( 'Pages', () => { | ||
test.beforeAll( async ( { requestUtils } ) => { | ||
await requestUtils.activateTheme( 'emptytheme' ); | ||
} ); | ||
test( 'Create a new page', async ( { admin, page } ) => { | ||
const pageName = 'demo'; | ||
await admin.visitSiteEditor(); | ||
await page.getByRole( 'button', { name: 'Pages' } ).click(); | ||
await page.getByRole( 'button', { name: 'Draft a new page' } ).click(); | ||
// Fill the page title and submit. | ||
const newPageDialog = page.locator( | ||
'role=dialog[name="Draft a new page"i]' | ||
); | ||
const pageTitleInput = newPageDialog.locator( | ||
'role=textbox[name="Page title"i]' | ||
); | ||
await pageTitleInput.fill( pageName ); | ||
await page.keyboard.press( 'Enter' ); | ||
await expect( | ||
page.locator( | ||
`role=button[name="Dismiss this notice"i] >> text="${ pageName }" successfully created.` | ||
) | ||
).toBeVisible(); | ||
} ); | ||
} ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After the page is created, where should focus get placed? Right now it's being dropped at the hidden Back button in the closed sidebar. I agree with @carolinan that the current mode of being moved to the site editor template for this page is an odd experience. I'd be fine with it if the sidebar stayed open, in which case, handling the focus here isn't an issue since it's being placed in a sensible spot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will make more sense once the content editing PR is merged. There is also some follow up work around improving the add page experience, including selecting starter patterns.