diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js index bcf81a7aa7d75..69971d1ad413a 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/index.js @@ -1,7 +1,11 @@ /** * WordPress dependencies */ -import { PanelBody } from '@wordpress/components'; +import { + PanelBody, + __experimentalText as Text, + __experimentalVStack as VStack, +} from '@wordpress/components'; import { page as pageIcon } from '@wordpress/icons'; import { __, sprintf } from '@wordpress/i18n'; import { humanTimeDiff } from '@wordpress/date'; @@ -15,25 +19,32 @@ import { decodeEntities } from '@wordpress/html-entities'; import { store as editSiteStore } from '../../../store'; import SidebarCard from '../sidebar-card'; import PageContent from './page-content'; +import PageSummary from './page-summary'; import EditTemplate from './edit-template'; export default function PagePanels() { - const { hasResolved, title, modified } = useSelect( ( select ) => { - const { getEditedPostContext } = select( editSiteStore ); - const { getEditedEntityRecord, hasFinishedResolution } = - select( coreStore ); - const context = getEditedPostContext(); - const queryArgs = [ 'postType', context.postType, context.postId ]; - const page = getEditedEntityRecord( ...queryArgs ); - return { - hasResolved: hasFinishedResolution( - 'getEditedEntityRecord', - queryArgs - ), - title: page?.title, - modified: page?.modified, - }; - }, [] ); + const { id, type, hasResolved, status, date, password, title, modified } = + useSelect( ( select ) => { + const { getEditedPostContext } = select( editSiteStore ); + const { getEditedEntityRecord, hasFinishedResolution } = + select( coreStore ); + const context = getEditedPostContext(); + const queryArgs = [ 'postType', context.postType, context.postId ]; + const page = getEditedEntityRecord( ...queryArgs ); + return { + hasResolved: hasFinishedResolution( + 'getEditedEntityRecord', + queryArgs + ), + title: page?.title, + id: page?.id, + type: page?.type, + status: page?.status, + date: page?.date, + password: page?.password, + modified: page?.modified, + }; + }, [] ); if ( ! hasResolved ) { return null; @@ -45,11 +56,26 @@ export default function PagePanels() { + + { sprintf( + // translators: %s: Human-readable time difference, e.g. "2 days ago". + __( 'Last edited %s' ), + humanTimeDiff( modified ) + ) } + + + } + /> + + + diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js new file mode 100644 index 0000000000000..8910cb1968c3e --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-status.js @@ -0,0 +1,233 @@ +/** + * WordPress dependencies + */ +import { + Button, + BaseControl, + ToggleControl, + Dropdown, + __experimentalText as Text, + __experimentalHStack as HStack, + __experimentalVStack as VStack, + TextControl, + RadioControl, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { useState, useMemo } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { __experimentalInspectorPopoverHeader as InspectorPopoverHeader } from '@wordpress/block-editor'; +/** + * Internal dependencies + */ +import StatusLabel from '../../sidebar-navigation-screen-page/status-label'; + +const STATUS_OPTIONS = [ + { + label: ( + <> + { __( 'Draft' ) } + { __( 'Not ready to publish.' ) } + + ), + value: 'draft', + }, + { + label: ( + <> + { __( 'Pending' ) } + + { __( 'Waiting for review before publishing.' ) } + + + ), + value: 'pending', + }, + { + label: ( + <> + { __( 'Private' ) } + + { __( 'Only visible to site admins and editors.' ) } + + + ), + value: 'private', + }, + { + label: ( + <> + { __( 'Scheduled' ) } + + { __( 'Publish automatically on a chosen date.' ) } + + + ), + value: 'future', + }, + { + label: ( + <> + { __( 'Published' ) } + { __( 'Visible to everyone.' ) } + + ), + value: 'publish', + }, +]; + +export default function PageStatus( { + postType, + postId, + status, + password, + date, +} ) { + const [ showPassword, setShowPassword ] = useState( !! password ); + + const { editEntityRecord } = useDispatch( coreStore ); + const { createErrorNotice } = useDispatch( noticesStore ); + + const [ popoverAnchor, setPopoverAnchor ] = useState( null ); + // Memoize popoverProps to avoid returning a new object every time. + const popoverProps = useMemo( + () => ( { + // Anchor the popover to the middle of the entire row so that it doesn't + // move around when the label changes. + anchor: popoverAnchor, + 'aria-label': __( 'Change status' ), + placement: 'bottom-end', + } ), + [ popoverAnchor ] + ); + + const saveStatus = async ( { + status: newStatus = status, + password: newPassword = password, + date: newDate = date, + } ) => { + try { + await editEntityRecord( 'postType', postType, postId, { + status: newStatus, + date: newDate, + password: newPassword, + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while updating the status' ); + + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }; + + const handleTogglePassword = ( value ) => { + setShowPassword( value ); + if ( ! value ) { + saveStatus( { password: '' } ); + } + }; + + const handleStatus = ( value ) => { + let newDate = date; + let newPassword = password; + if ( value === 'publish' ) { + if ( new Date( date ) > new Date() ) { + newDate = null; + } + } else if ( value === 'future' ) { + if ( ! date || new Date( date ) < new Date() ) { + newDate = new Date(); + newDate.setDate( newDate.getDate() + 7 ); + } + } else if ( value === 'private' && password ) { + setShowPassword( false ); + newPassword = ''; + } + saveStatus( { + status: value, + date: newDate, + password: newPassword, + } ); + }; + + return ( + + + { __( 'Status' ) } + + ( + + ) } + renderContent={ ( { onClose } ) => ( + <> + +
+ + + { status !== 'private' && ( + + + { showPassword && ( + + saveStatus( { + password: value, + } ) + } + value={ password } + /* eslint-disable jsx-a11y/no-autofocus */ + autoFocus={ ! password } + /* eslint-enable jsx-a11y/no-autofocus */ + placeholder={ __( + 'Enter a secure password' + ) } + type="password" + /> + ) } + + ) } + +
+ + ) } + /> +
+ ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js new file mode 100644 index 0000000000000..3dce743b298d4 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/page-summary.js @@ -0,0 +1,35 @@ +/** + * WordPress dependencies + */ +import { __experimentalVStack as VStack } from '@wordpress/components'; +/** + * Internal dependencies + */ +import PageStatus from './page-status'; +import PublishDate from './publish-date'; + +export default function PageSummary( { + status, + date, + password, + postId, + postType, +} ) { + return ( + + + + + ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/publish-date.js b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/publish-date.js new file mode 100644 index 0000000000000..d000394f6816b --- /dev/null +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/publish-date.js @@ -0,0 +1,94 @@ +/** + * WordPress dependencies + */ +import { + Button, + Dropdown, + __experimentalText as Text, + __experimentalHStack as HStack, +} from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { useState, useMemo } from '@wordpress/element'; +import { store as coreStore } from '@wordpress/core-data'; +import { store as noticesStore } from '@wordpress/notices'; +import { __experimentalPublishDateTimePicker as PublishDateTimePicker } from '@wordpress/block-editor'; +import { humanTimeDiff } from '@wordpress/date'; + +export default function ChangeStatus( { postType, postId, status, date } ) { + const { editEntityRecord } = useDispatch( coreStore ); + const { createErrorNotice } = useDispatch( noticesStore ); + + const [ popoverAnchor, setPopoverAnchor ] = useState( null ); + // Memoize popoverProps to avoid returning a new object every time. + const popoverProps = useMemo( + () => ( { + // Anchor the popover to the middle of the entire row so that it doesn't + // move around when the label changes. + anchor: popoverAnchor, + 'aria-label': __( 'Change publish date' ), + placement: 'bottom-end', + } ), + [ popoverAnchor ] + ); + + const saveDate = async ( newDate ) => { + try { + let newStatus = status; + if ( status === 'future' && new Date( newDate ) < new Date() ) { + newStatus = 'publish'; + } else if ( + status === 'publish' && + new Date( newDate ) > new Date() + ) { + newStatus = 'future'; + } + await editEntityRecord( 'postType', postType, postId, { + status: newStatus, + date: newDate, + } ); + } catch ( error ) { + const errorMessage = + error.message && error.code !== 'unknown_error' + ? error.message + : __( 'An error occurred while updating the status' ); + + createErrorNotice( errorMessage, { + type: 'snackbar', + } ); + } + }; + + const relateToNow = date ? humanTimeDiff( date ) : __( 'Immediately' ); + + return ( + + + { __( 'Publish' ) } + + ( + + ) } + renderContent={ ( { onClose } ) => ( + + ) } + /> + + ); +} diff --git a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss index 58178303e48e5..0a618d9ff53d3 100644 --- a/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss +++ b/packages/edit-site/src/components/sidebar-edit-mode/page-panels/style.scss @@ -8,3 +8,34 @@ .edit-site-page-panels__edit-template-button { justify-content: center; } + +.edit-site-change-status__content { + .components-popover__content { + min-width: 320px; + padding: $grid-unit-20; + } + .edit-site-change-status__options { + .components-base-control__field > .components-v-stack { + gap: $grid-unit-10; + } + label { + .components-text { + display: block; + margin-left: $radio-input-size + 6; + } + } + } +} + +.edit-site-summary-field { + .components-dropdown { + flex-grow: 1; + } + .edit-site-summary-field__trigger { + width: 100%; + } + .edit-site-summary-field__label { + width: 30%; + } +} + diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-page/page-details.js b/packages/edit-site/src/components/sidebar-navigation-screen-page/page-details.js index 0b8b459e482e6..289bb25e9a071 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-page/page-details.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-page/page-details.js @@ -36,6 +36,7 @@ function getPageDetails( page ) { ), }, diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-page/status-label.js b/packages/edit-site/src/components/sidebar-navigation-screen-page/status-label.js index 208d8adc59ab9..13aba13aacbec 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-page/status-label.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-page/status-label.js @@ -7,7 +7,7 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { dateI18n, getDate, getSettings, humanTimeDiff } from '@wordpress/date'; +import { dateI18n, getDate, humanTimeDiff } from '@wordpress/date'; import { createInterpolateElement } from '@wordpress/element'; import { Path, SVG } from '@wordpress/primitives'; @@ -41,35 +41,39 @@ const pendingIcon = ( ); -export default function StatusLabel( { status, date } ) { +export default function StatusLabel( { status, date, short } ) { const relateToNow = humanTimeDiff( date ); let statusLabel = ''; let statusIcon = pendingIcon; switch ( status ) { case 'publish': - statusLabel = createInterpolateElement( - sprintf( - /* translators: %s: is the relative time when the post was published. */ - __( 'Published ' ), - relateToNow - ), - { time: