diff --git a/package-lock.json b/package-lock.json index 9a9358f11a53f..4401d6b669b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17760,6 +17760,7 @@ "@wordpress/dom": "file:packages/dom", "@wordpress/editor": "file:packages/editor", "@wordpress/element": "file:packages/element", + "@wordpress/escape-html": "file:packages/escape-html", "@wordpress/hooks": "file:packages/hooks", "@wordpress/html-entities": "file:packages/html-entities", "@wordpress/i18n": "file:packages/i18n", @@ -17771,6 +17772,7 @@ "@wordpress/notices": "file:packages/notices", "@wordpress/plugins": "file:packages/plugins", "@wordpress/preferences": "file:packages/preferences", + "@wordpress/primitives": "file:packages/primitives", "@wordpress/private-apis": "file:packages/private-apis", "@wordpress/reusable-blocks": "file:packages/reusable-blocks", "@wordpress/router": "file:packages/router", @@ -17778,6 +17780,7 @@ "@wordpress/url": "file:packages/url", "@wordpress/viewport": "file:packages/viewport", "@wordpress/widgets": "file:packages/widgets", + "@wordpress/wordcount": "file:packages/wordcount", "classnames": "^2.3.1", "colord": "^2.9.2", "downloadjs": "^1.4.7", @@ -25879,7 +25882,7 @@ "array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true }, "array-includes": { @@ -31048,7 +31051,7 @@ "debuglog": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", "dev": true }, "decache": { @@ -35890,7 +35893,7 @@ "git-remote-origin-url": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "integrity": "sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==", "dev": true, "requires": { "gitconfiglocal": "^1.0.0", @@ -35937,7 +35940,7 @@ "gitconfiglocal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "integrity": "sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==", "dev": true, "requires": { "ini": "^1.3.2" @@ -37211,7 +37214,7 @@ "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", "dev": true, "requires": { "ms": "^2.0.0" @@ -38227,7 +38230,7 @@ "is-text-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "integrity": "sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==", "dev": true, "requires": { "text-extensions": "^1.0.0" @@ -40000,7 +40003,7 @@ "jsonparse": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "dev": true }, "jsprim": { @@ -41100,7 +41103,7 @@ "lodash.ismatch": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "integrity": "sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==", "dev": true }, "lodash.isplainobject": { @@ -48603,7 +48606,7 @@ "promzard": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", + "integrity": "sha512-JZeYqd7UAcHCwI+sTOeUDYkvEU+1bQ7iE0UT1MgB/tERkAPkesW46MrpIySzODi+owTjZtiF8Ay5j9m60KmMBw==", "dev": true, "requires": { "read": "1" @@ -48637,7 +48640,7 @@ "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, "protocols": { @@ -50194,7 +50197,7 @@ "read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", + "integrity": "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ==", "dev": true, "requires": { "mute-stream": "~0.0.4" @@ -55551,7 +55554,7 @@ "temp-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=", + "integrity": "sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==", "dev": true }, "terminal-link": { diff --git a/packages/date/src/test/index.js b/packages/date/src/test/index.js index ff82748e02f23..3c52aae1fbd0d 100644 --- a/packages/date/src/test/index.js +++ b/packages/date/src/test/index.js @@ -623,7 +623,7 @@ describe( 'Moment.js Localization', () => { } ); describe( 'humanTimeDiff', () => { - it( 'should return human readable time differences', () => { + it( 'should return human readable time differences in the past', () => { expect( humanTimeDiff( '2023-04-28T11:00:00.000Z', @@ -643,5 +643,20 @@ describe( 'Moment.js Localization', () => { ) ).toBe( '2 days ago' ); } ); + + it( 'should return human readable time differences in the future', () => { + // Future. + const now = new Date(); + const twoHoursLater = new Date( + now.getTime() + 2 * 60 * 60 * 1000 + ); + expect( humanTimeDiff( twoHoursLater ) ).toBe( 'in 2 hours' ); + + const twoDaysLater = new Date( + now.getTime() + 2 * 24 * 60 * 60 * 1000 + ); // Adding 2 days in milliseconds + + expect( humanTimeDiff( twoDaysLater ) ).toBe( 'in 2 days' ); + } ); } ); } ); diff --git a/packages/edit-site/package.json b/packages/edit-site/package.json index 8eba789d5713e..361c11ab0d560 100644 --- a/packages/edit-site/package.json +++ b/packages/edit-site/package.json @@ -43,6 +43,7 @@ "@wordpress/dom": "file:../dom", "@wordpress/editor": "file:../editor", "@wordpress/element": "file:../element", + "@wordpress/escape-html": "file:../escape-html", "@wordpress/hooks": "file:../hooks", "@wordpress/html-entities": "file:../html-entities", "@wordpress/i18n": "file:../i18n", @@ -54,6 +55,7 @@ "@wordpress/notices": "file:../notices", "@wordpress/plugins": "file:../plugins", "@wordpress/preferences": "file:../preferences", + "@wordpress/primitives": "file:../primitives", "@wordpress/private-apis": "file:../private-apis", "@wordpress/reusable-blocks": "file:../reusable-blocks", "@wordpress/router": "file:../router", @@ -61,6 +63,7 @@ "@wordpress/url": "file:../url", "@wordpress/viewport": "file:../viewport", "@wordpress/widgets": "file:../widgets", + "@wordpress/wordcount": "file:../wordcount", "classnames": "^2.3.1", "colord": "^2.9.2", "downloadjs": "^1.4.7", diff --git a/packages/edit-site/src/components/sidebar-navigation-data-list/data-list-item.js b/packages/edit-site/src/components/sidebar-navigation-data-list/data-list-item.js new file mode 100644 index 0000000000000..beeff0b6367b5 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-data-list/data-list-item.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { + __experimentalHStack as HStack, + __experimentalText as Text, +} from '@wordpress/components'; + +export default function DataListItem( { label, value } ) { + return ( + + + { label } + + + { value } + + + ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-data-list/index.js b/packages/edit-site/src/components/sidebar-navigation-data-list/index.js new file mode 100644 index 0000000000000..aeff422026591 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-data-list/index.js @@ -0,0 +1,25 @@ +/** + * WordPress dependencies + */ +import { __experimentalVStack as VStack } from '@wordpress/components'; +/** + * Internal dependencies + */ +import DataListItem from './data-list-item'; + +export default function SidebarDetails( { details } ) { + return ( + + { details.map( ( detail ) => ( + + ) ) } + + ); +} diff --git a/packages/edit-site/src/components/sidebar-navigation-data-list/style.scss b/packages/edit-site/src/components/sidebar-navigation-data-list/style.scss new file mode 100644 index 0000000000000..3a19bba8ed924 --- /dev/null +++ b/packages/edit-site/src/components/sidebar-navigation-data-list/style.scss @@ -0,0 +1,12 @@ + + +.edit-site-sidebar-navigation_data-list__item { + .edit-site-sidebar-navigation_data-list__label { + color: $gray-600; + width: 100px; + } + + .edit-site-sidebar-navigation_data-list__value.edit-site-sidebar-navigation_data-list__value { + color: $gray-200; + } +} diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss index e594fa0384e1d..6ecf61df710a4 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss +++ b/packages/edit-site/src/components/sidebar-navigation-screen-navigation-menus/style.scss @@ -1,5 +1,5 @@ .edit-site-sidebar-navigation-screen__description { - margin: 0 0 $grid-unit-40 $grid-unit-20; + margin: 0 0 $grid-unit-40 0; } .edit-site-sidebar-navigation-screen-navigation-menus__content { diff --git a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js index 5acfc98ffe52c..332c6ba62bfd4 100644 --- a/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js +++ b/packages/edit-site/src/components/sidebar-navigation-screen-page/index.js @@ -1,15 +1,32 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + /** * WordPress dependencies */ -import { __ } from '@wordpress/i18n'; -import { useDispatch } from '@wordpress/data'; +import { __, _x, sprintf } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; import { __experimentalUseNavigator as useNavigator, + __experimentalVStack as VStack, ExternalLink, + __experimentalTruncate as Truncate, } from '@wordpress/components'; -import { useEntityRecord } from '@wordpress/core-data'; +import { + store as coreStore, + useEntityRecord, + useEntityRecords, +} from '@wordpress/core-data'; import { decodeEntities } from '@wordpress/html-entities'; import { pencil } from '@wordpress/icons'; +import { humanTimeDiff } from '@wordpress/date'; +import { count as wordCount } from '@wordpress/wordcount'; +import { createInterpolateElement } from '@wordpress/element'; +import { privateApis as privateEditorApis } from '@wordpress/editor'; +import { __unstableStripHTML as stripHTML } from '@wordpress/dom'; +import { escapeAttribute } from '@wordpress/escape-html'; /** * Internal dependencies @@ -18,17 +35,153 @@ import SidebarNavigationScreen from '../sidebar-navigation-screen'; import { unlock } from '../../private-apis'; import { store as editSiteStore } from '../../store'; import SidebarButton from '../sidebar-button'; +import SidebarNavigationSubtitle from '../sidebar-navigation-subtitle'; +import SidebarDetails from '../sidebar-navigation-data-list'; +import DataListItem from '../sidebar-navigation-data-list/data-list-item'; +import StatusLabel from './status-label'; + +// Taken from packages/editor/src/components/time-to-read/index.js. +const AVERAGE_READING_RATE = 189; + +function getPageDetails( page ) { + if ( ! page ) { + return []; + } + + const details = [ + { + label: __( 'Status' ), + value: ( + + ), + }, + { + label: __( 'Slug' ), + value: { page.slug }, + }, + ]; + + if ( page?.templateTitle ) { + details.push( { + label: __( 'Template' ), + value: page.templateTitle, + } ); + } + + details.push( { + label: __( 'Parent' ), + value: page?.parentTitle, + } ); + + /* + * translators: If your word count is based on single characters (e.g. East Asian characters), + * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. + * Do not translate into your own language. + */ + const wordCountType = _x( 'words', 'Word count type. Do not translate!' ); + const wordsCounted = page?.content?.rendered + ? wordCount( page.content.rendered, wordCountType ) + : 0; + const readingTime = Math.round( wordsCounted / AVERAGE_READING_RATE ); + + if ( wordsCounted ) { + details.push( + { + label: __( 'Words' ), + value: wordsCounted.toLocaleString() || __( 'Unknown' ), + }, + { + label: __( 'Time to read' ), + value: + readingTime > 1 + ? sprintf( + /* translators: %s: is the number of minutes. */ + __( '%s mins' ), + readingTime.toLocaleString() + ) + : __( '< 1 min' ), + } + ); + } + return details; +} export default function SidebarNavigationScreenPage() { const { setCanvasMode } = unlock( useDispatch( editSiteStore ) ); + const { getFeaturedMediaDetails } = unlock( privateEditorApis ); const { params: { postId }, } = useNavigator(); const { record } = useEntityRecord( 'postType', 'page', postId ); - return ( + const { + parentTitle, + featuredMediaDetails: { mediaSourceUrl, altText }, + templateSlug, + } = useSelect( + ( select ) => { + const { getEditedPostContext } = unlock( select( editSiteStore ) ); + + // Featured image. + const attachedMedia = record?.featured_media + ? select( coreStore ).getEntityRecord( + 'postType', + 'attachment', + record?.featured_media + ) + : null; + + // Parent page title. + let _parentTitle = record?.parent + ? select( coreStore ).getEntityRecord( + 'postType', + 'page', + record.parent, + { _fields: [ 'title' ] } + ) + : null; + + if ( _parentTitle?.title ) { + _parentTitle = _parentTitle.title?.rendered + ? decodeEntities( _parentTitle.title.rendered ) + : __( 'Untitled' ); + } else { + _parentTitle = __( 'Top level' ); + } + + return { + parentTitle: _parentTitle, + templateSlug: getEditedPostContext()?.templateSlug, + featuredMediaDetails: { + ...getFeaturedMediaDetails( attachedMedia, postId ), + altText: escapeAttribute( + attachedMedia?.alt_text || + attachedMedia?.description?.raw || + '' + ), + }, + }; + }, + [ record, postId ] + ); + + // Match template slug to template title. + const { records: templates, isResolving: areTemplatesLoading } = + useEntityRecords( 'postType', 'wp_template', { + per_page: -1, + } ); + const templateTitle = + ! areTemplatesLoading && templateSlug + ? templates?.find( ( template ) => template?.slug === templateSlug ) + ?.title?.rendered || templateSlug + : ''; + + return record ? ( setCanvasMode( 'edit' ) } @@ -36,24 +189,81 @@ export default function SidebarNavigationScreenPage() { icon={ pencil } /> } - description={ __( - 'Pages are static and are not listed by date. Pages do not use tags or categories.' - ) } + meta={ + + { record.link.replace( /^(https?:\/\/)?/, '' ) } + + } content={ <> - { record?.link ? ( - +
+ { !! mediaSourceUrl && ( + { + ) } + { ! record?.featured_media && ( +

{ __( 'No featured image' ) }

+ ) } +
+ + { !! record?.excerpt?.rendered && ( + - { record.link } -
- ) : null } - { record - ? decodeEntities( record?.description?.rendered ) - : null } + { stripHTML( record.excerpt.rendered ) } + + ) } + + Details + + } + footer={ + %s' ), + humanTimeDiff( record.modified ) + ), + { + time: