From b39b2362ea1752274bc19a783037e83bb1faf61e Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Fri, 12 May 2023 13:19:37 +1000 Subject: [PATCH 01/19] create new page --- .../data/data-core-edit-site.md | 20 +++ .../src/components/add-new-page/index.js | 145 ++++++++++++++++++ .../edit-site/src/components/layout/index.js | 2 + .../sidebar-navigation-screen-pages/index.js | 103 +++++++------ packages/edit-site/src/store/actions.js | 13 ++ packages/edit-site/src/store/reducer.js | 16 ++ packages/edit-site/src/store/selectors.js | 11 ++ 7 files changed, 266 insertions(+), 44 deletions(-) create mode 100644 packages/edit-site/src/components/add-new-page/index.js diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 0dad03bbc8ca2..32dfdcb8b1f2c 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -131,6 +131,18 @@ _Returns_ - `Object`: Settings. +### isCreatePageModalOpened + +Returns the current opened/closed state of the create page modal. + +_Parameters_ + +- _state_ `Object`: Global application state. + +_Returns_ + +- `boolean`: True if the create page modal should be open; false if closed. + ### isFeatureActive > **Deprecated** @@ -256,6 +268,14 @@ _Returns_ > **Deprecated** +### setIsCreatePageModalOpened + +Sets whether the create new page modal is open + +_Parameters_ + +- _isOpen_ `boolean`: If true, opens the create new page modal. If false, closes it. It does not toggle the state, but sets it directly. + ### setIsInserterOpened Opens or closes the inserter. diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js new file mode 100644 index 0000000000000..686af417daf27 --- /dev/null +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -0,0 +1,145 @@ +/** + * 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, useSelect } from '@wordpress/data'; +import { useState } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { privateApis as routerPrivateApis } from '@wordpress/router'; + +/** + * Internal dependencies + */ +import { store as editSiteStore } from '../../store'; +import { unlock } from '../../private-apis'; + +const DEFAULT_TITLE = __( 'Untitled page' ); + +const { useHistory } = unlock( routerPrivateApis ); + +export default function AddNewPageModal() { + const [ isCreatingPage, setIsCreatingPage ] = useState( false ); + const [ title, setTitle ] = useState( DEFAULT_TITLE ); + + const history = useHistory(); + const { saveEntityRecord } = useDispatch( coreStore ); + const { createErrorNotice, createSuccessNotice } = + useDispatch( noticesStore ); + + const { isCreatePageModalOpen } = useSelect( ( select ) => { + const { isCreatePageModalOpened } = unlock( select( editSiteStore ) ); + + return { + isCreatePageModalOpen: isCreatePageModalOpened(), + }; + }, [] ); + + const { setIsCreatePageModalOpened } = useDispatch( editSiteStore ); + + const { setPage } = unlock( useDispatch( editSiteStore ) ); + + async function createPage( event ) { + event.preventDefault(); + + if ( isCreatingPage ) { + return; + } + setIsCreatingPage( true ); + try { + const newPage = await saveEntityRecord( + 'postType', + 'page', + { + status: 'draft', + title, + slug: kebabCase( title || DEFAULT_TITLE ), + }, + { throwOnError: true } + ); + + // Set template before navigating away to avoid initial stale value. + setPage( { + context: { postType: 'page', postId: newPage.id }, + } ); + + // Close the modal when complete + setIsCreatePageModalOpened( false ); + + // Navigate to the created template editor. + history.push( { + postId: newPage.id, + postType: newPage.type, + canvas: 'edit', + } ); + + 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 ); + } + } + + const handleClose = () => { + setIsCreatePageModalOpened( false ); + }; + + if ( ! isCreatePageModalOpen ) return null; + + return ( + +
+ + + + + + + +
+
+ ); +} diff --git a/packages/edit-site/src/components/layout/index.js b/packages/edit-site/src/components/layout/index.js index 446556719c923..1a1c1814a549b 100644 --- a/packages/edit-site/src/components/layout/index.js +++ b/packages/edit-site/src/components/layout/index.js @@ -45,6 +45,7 @@ import { unlock } from '../../private-apis'; import SavePanel from '../save-panel'; import KeyboardShortcutsRegister from '../keyboard-shortcuts/register'; import KeyboardShortcutsGlobal from '../keyboard-shortcuts/global'; +import AddNewPageModal from '../add-new-page'; const { useCommands } = unlock( coreCmmandsPrivateApis ); @@ -132,6 +133,7 @@ export default function Layout() { return ( <> { window?.__experimentalEnableCommandCenter && } + { fullResizer } diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js index 5f4958f44d251..99d858af6ec3c 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js @@ -8,14 +8,18 @@ import { import { __ } from '@wordpress/i18n'; import { useEntityRecords } from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; +import { plus } from '@wordpress/icons'; +import { useDispatch } from '@wordpress/data'; /** * Internal dependencies */ +import { store as editSiteStore } from '../../store'; 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'; const PageItem = ( { postId, ...props } ) => { const linkInfo = useLink( { @@ -31,52 +35,63 @@ export default function SidebarNavigationScreenPages() { 'page' ); + const { setIsCreatePageModalOpened } = useDispatch( editSiteStore ); + return ( - - { isLoading && ( - - { __( 'Loading pages' ) } - - ) } - { ! isLoading && ( - <> - - { __( 'Recent' ) } - + <> + setIsCreatePageModalOpened( true ) } + /> + } + content={ + <> + { isLoading && ( - { ! pages?.length && ( - { __( 'No page found' ) } - ) } - { pages?.map( ( page ) => ( - - { decodeEntities( - page.title?.rendered - ) ?? __( '(no title)' ) } - - ) ) } - { - document.location = - 'edit.php?post_type=page'; - } } - > - { __( 'Manage all pages' ) } - + { __( 'Loading pages' ) } - - ) } - - } - /> + ) } + { ! isLoading && ( + <> + + { __( 'Recent' ) } + + + { ! pages?.length && ( + { __( 'No page found' ) } + ) } + { pages?.map( ( page ) => ( + + { decodeEntities( + page.title?.rendered + ) ?? __( '(no title)' ) } + + ) ) } + { + document.location = + 'edit.php?post_type=page'; + } } + > + { __( 'Manage all pages' ) } + + + + ) } + + } + /> + ); } diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index 48ac0a343a59f..f3eed74dc6f96 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -354,6 +354,19 @@ export function setIsSaveViewOpened( isOpen ) { }; } +/** + * Sets whether the create new page modal is open + * + * @param {boolean} isOpen If true, opens the create new page modal. If false, closes it. + * It does not toggle the state, but sets it directly. + */ +export function setIsCreatePageModalOpened( isOpen ) { + return { + type: 'SET_IS_CREATE_PAGE_MODAL_OPENED', + isOpen, + }; +} + /** * Reverts a template to its original theme-provided file. * diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index a46d215f90507..6e244f443e5c8 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -157,6 +157,21 @@ function editorCanvasContainerView( state = undefined, action ) { return state; } +/** + * Reducer used to track the create new page modal which can be opened in and out of editor + * + * @param {Object} state Current state. + * @param {Object} action Dispatched action. + */ +function createPageModal( state = false, action ) { + switch ( action.type ) { + case 'SET_IS_CREATE_PAGE_MODAL_OPENED': + return action.isOpen; + } + + return state; +} + export default combineReducers( { deviceType, settings, @@ -164,6 +179,7 @@ export default combineReducers( { blockInserterPanel, listViewPanel, saveViewPanel, + createPageModal, canvasMode, editorCanvasContainerView, } ); diff --git a/packages/edit-site/src/store/selectors.js b/packages/edit-site/src/store/selectors.js index 583f37b55241b..6499fa192b90e 100644 --- a/packages/edit-site/src/store/selectors.js +++ b/packages/edit-site/src/store/selectors.js @@ -289,6 +289,17 @@ export function getEditorMode( state ) { return __unstableGetPreference( state, 'editorMode' ); } +/** + * Returns the current opened/closed state of the create page modal. + * + * @param {Object} state Global application state. + * + * @return {boolean} True if the create page modal should be open; false if closed. + */ +export function isCreatePageModalOpened( state ) { + return state.createPageModal; +} + /** * @deprecated */ From f045425e7a0409ef7160af208ac40f2e76b15527 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Fri, 12 May 2023 13:33:53 +1000 Subject: [PATCH 02/19] remove pages nesting --- .../sidebar-navigation-screen-pages/index.js | 104 +++++++++--------- 1 file changed, 51 insertions(+), 53 deletions(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js index 99d858af6ec3c..729a40881f7a9 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js @@ -38,60 +38,58 @@ export default function SidebarNavigationScreenPages() { const { setIsCreatePageModalOpened } = useDispatch( editSiteStore ); return ( - <> - setIsCreatePageModalOpened( true ) } - /> - } - content={ - <> - { isLoading && ( + setIsCreatePageModalOpened( true ) } + /> + } + content={ + <> + { isLoading && ( + + { __( 'Loading pages' ) } + + ) } + { ! isLoading && ( + <> + + { __( 'Recent' ) } + - { __( 'Loading pages' ) } - - ) } - { ! isLoading && ( - <> - - { __( 'Recent' ) } - - - { ! pages?.length && ( - { __( 'No page found' ) } - ) } - { pages?.map( ( page ) => ( - - { decodeEntities( - page.title?.rendered - ) ?? __( '(no title)' ) } - - ) ) } - { - document.location = - 'edit.php?post_type=page'; - } } + { ! pages?.length && ( + { __( 'No page found' ) } + ) } + { pages?.map( ( page ) => ( + - { __( 'Manage all pages' ) } - - - - ) } - - } - /> - + { decodeEntities( + page.title?.rendered + ) ?? __( '(no title)' ) } + + ) ) } + { + document.location = + 'edit.php?post_type=page'; + } } + > + { __( 'Manage all pages' ) } + + + + ) } + + } + /> ); } From f3e097a46bf73ed55bf03e9340fbdf15070c936d Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 15 May 2023 12:30:34 +1000 Subject: [PATCH 03/19] add redirectAfterSave to add page modal --- .../data/data-core-edit-site.md | 1 + .../src/components/add-new-page/index.js | 25 ++++++++++--------- .../sidebar-navigation-screen-pages/index.js | 8 ++++-- packages/edit-site/src/store/actions.js | 8 +++--- packages/edit-site/src/store/reducer.js | 5 +++- 5 files changed, 29 insertions(+), 18 deletions(-) diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 32dfdcb8b1f2c..1732bb15bf187 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -275,6 +275,7 @@ Sets whether the create new page modal is open _Parameters_ - _isOpen_ `boolean`: If true, opens the create new page modal. If false, closes it. It does not toggle the state, but sets it directly. +- _options_ `Object`: Options for the create new page modal. ### setIsInserterOpened diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index 686af417daf27..3baa7141a5010 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -70,21 +70,22 @@ export default function AddNewPageModal() { { throwOnError: true } ); - // Set template before navigating away to avoid initial stale value. - setPage( { - context: { postType: 'page', postId: newPage.id }, - } ); + if ( isCreatePageModalOpen.options?.redirectAfterSave ) { + // Set template before navigating away to avoid initial stale value. + setPage( { + context: { postType: 'page', postId: newPage.id }, + } ); + // Navigate to the created template editor. + history.push( { + postId: newPage.id, + postType: newPage.type, + canvas: 'edit', + } ); + } // Close the modal when complete setIsCreatePageModalOpened( false ); - // Navigate to the created template editor. - history.push( { - postId: newPage.id, - postType: newPage.type, - canvas: 'edit', - } ); - createSuccessNotice( sprintf( // translators: %s: Title of the created template e.g: "Category". @@ -113,7 +114,7 @@ export default function AddNewPageModal() { setIsCreatePageModalOpened( false ); }; - if ( ! isCreatePageModalOpen ) return null; + if ( ! isCreatePageModalOpen.isOpen ) return null; return ( diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js index 729a40881f7a9..6bf8dff320a5f 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js @@ -44,8 +44,12 @@ export default function SidebarNavigationScreenPages() { actions={ setIsCreatePageModalOpened( true ) } + label={ __( 'Draft a new page' ) } + onClick={ () => + setIsCreatePageModalOpened( true, { + redirectAfterSave: false, + } ) + } /> } content={ diff --git a/packages/edit-site/src/store/actions.js b/packages/edit-site/src/store/actions.js index f3eed74dc6f96..a13803b171f6c 100644 --- a/packages/edit-site/src/store/actions.js +++ b/packages/edit-site/src/store/actions.js @@ -357,13 +357,15 @@ export function setIsSaveViewOpened( isOpen ) { /** * Sets whether the create new page modal is open * - * @param {boolean} isOpen If true, opens the create new page modal. If false, closes it. - * It does not toggle the state, but sets it directly. + * @param {boolean} isOpen If true, opens the create new page modal. If false, closes it. + * It does not toggle the state, but sets it directly. + * @param {Object} options Options for the create new page modal. */ -export function setIsCreatePageModalOpened( isOpen ) { +export function setIsCreatePageModalOpened( isOpen, options ) { return { type: 'SET_IS_CREATE_PAGE_MODAL_OPENED', isOpen, + options, }; } diff --git a/packages/edit-site/src/store/reducer.js b/packages/edit-site/src/store/reducer.js index 6e244f443e5c8..47f60a6f10464 100644 --- a/packages/edit-site/src/store/reducer.js +++ b/packages/edit-site/src/store/reducer.js @@ -166,7 +166,10 @@ function editorCanvasContainerView( state = undefined, action ) { function createPageModal( state = false, action ) { switch ( action.type ) { case 'SET_IS_CREATE_PAGE_MODAL_OPENED': - return action.isOpen; + return { + isOpen: action.isOpen, + options: action.options, + }; } return state; From 67c222f283c892fde7e8d73da46535d568f1e977 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 15 May 2023 12:33:07 +1000 Subject: [PATCH 04/19] reset redirectAfterSave --- packages/edit-site/src/components/add-new-page/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index 3baa7141a5010..275dff4572855 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -84,7 +84,7 @@ export default function AddNewPageModal() { } // Close the modal when complete - setIsCreatePageModalOpened( false ); + setIsCreatePageModalOpened( false, { redirectAfterSave: false } ); createSuccessNotice( sprintf( From 216bb20fd298c15216a6574b0e144564cad8c955 Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Mon, 15 May 2023 12:40:50 +1000 Subject: [PATCH 05/19] adjust copy of new page to show draft --- packages/edit-site/src/components/add-new-page/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index 275dff4572855..a29a07b0f20b7 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -117,7 +117,7 @@ export default function AddNewPageModal() { if ( ! isCreatePageModalOpen.isOpen ) return null; return ( - +
From 6558f3f8468cf0ce493e1b4e074b6ebe5a7649fc Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Tue, 16 May 2023 15:00:05 +1000 Subject: [PATCH 06/19] redirect after adding page --- .../src/components/sidebar-navigation-screen-pages/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js index 6bf8dff320a5f..6bf53f9fc7f9e 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-pages/index.js @@ -47,7 +47,7 @@ export default function SidebarNavigationScreenPages() { label={ __( 'Draft a new page' ) } onClick={ () => setIsCreatePageModalOpened( true, { - redirectAfterSave: false, + redirectAfterSave: true, } ) } /> From 5fc86cfbc91e0f3963fcda23d9b11370d500c38d Mon Sep 17 00:00:00 2001 From: Saxon Fletcher Date: Wed, 17 May 2023 11:30:58 +1000 Subject: [PATCH 07/19] create page modal use local state --- .../data/data-core-edit-site.md | 21 --- .../src/components/add-new-page/index.js | 52 +------ .../edit-site/src/components/layout/index.js | 2 - .../sidebar-navigation-screen-pages/index.js | 136 +++++++++++------- packages/edit-site/src/store/actions.js | 15 -- packages/edit-site/src/store/reducer.js | 19 --- packages/edit-site/src/store/selectors.js | 11 -- 7 files changed, 86 insertions(+), 170 deletions(-) diff --git a/docs/reference-guides/data/data-core-edit-site.md b/docs/reference-guides/data/data-core-edit-site.md index 1732bb15bf187..0dad03bbc8ca2 100644 --- a/docs/reference-guides/data/data-core-edit-site.md +++ b/docs/reference-guides/data/data-core-edit-site.md @@ -131,18 +131,6 @@ _Returns_ - `Object`: Settings. -### isCreatePageModalOpened - -Returns the current opened/closed state of the create page modal. - -_Parameters_ - -- _state_ `Object`: Global application state. - -_Returns_ - -- `boolean`: True if the create page modal should be open; false if closed. - ### isFeatureActive > **Deprecated** @@ -268,15 +256,6 @@ _Returns_ > **Deprecated** -### setIsCreatePageModalOpened - -Sets whether the create new page modal is open - -_Parameters_ - -- _isOpen_ `boolean`: If true, opens the create new page modal. If false, closes it. It does not toggle the state, but sets it directly. -- _options_ `Object`: Options for the create new page modal. - ### setIsInserterOpened Opens or closes the inserter. diff --git a/packages/edit-site/src/components/add-new-page/index.js b/packages/edit-site/src/components/add-new-page/index.js index a29a07b0f20b7..354ea9d99a116 100644 --- a/packages/edit-site/src/components/add-new-page/index.js +++ b/packages/edit-site/src/components/add-new-page/index.js @@ -14,43 +14,21 @@ import { TextControl, } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; -import { useDispatch, useSelect } from '@wordpress/data'; +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'; -import { privateApis as routerPrivateApis } from '@wordpress/router'; - -/** - * Internal dependencies - */ -import { store as editSiteStore } from '../../store'; -import { unlock } from '../../private-apis'; const DEFAULT_TITLE = __( 'Untitled page' ); -const { useHistory } = unlock( routerPrivateApis ); - -export default function AddNewPageModal() { +export default function AddNewPageModal( { onSave, onCancel } ) { const [ isCreatingPage, setIsCreatingPage ] = useState( false ); const [ title, setTitle ] = useState( DEFAULT_TITLE ); - const history = useHistory(); const { saveEntityRecord } = useDispatch( coreStore ); const { createErrorNotice, createSuccessNotice } = useDispatch( noticesStore ); - const { isCreatePageModalOpen } = useSelect( ( select ) => { - const { isCreatePageModalOpened } = unlock( select( editSiteStore ) ); - - return { - isCreatePageModalOpen: isCreatePageModalOpened(), - }; - }, [] ); - - const { setIsCreatePageModalOpened } = useDispatch( editSiteStore ); - - const { setPage } = unlock( useDispatch( editSiteStore ) ); - async function createPage( event ) { event.preventDefault(); @@ -70,21 +48,7 @@ export default function AddNewPageModal() { { throwOnError: true } ); - if ( isCreatePageModalOpen.options?.redirectAfterSave ) { - // Set template before navigating away to avoid initial stale value. - setPage( { - context: { postType: 'page', postId: newPage.id }, - } ); - // Navigate to the created template editor. - history.push( { - postId: newPage.id, - postType: newPage.type, - canvas: 'edit', - } ); - } - - // Close the modal when complete - setIsCreatePageModalOpened( false, { redirectAfterSave: false } ); + onSave( newPage ); createSuccessNotice( sprintf( @@ -110,14 +74,8 @@ export default function AddNewPageModal() { } } - const handleClose = () => { - setIsCreatePageModalOpened( false ); - }; - - if ( ! isCreatePageModalOpen.isOpen ) return null; - return ( - + -