diff --git a/docs/reference-guides/core-blocks.md b/docs/reference-guides/core-blocks.md
index 87d57f14f08724..ba0a8eceafa26d 100644
--- a/docs/reference-guides/core-blocks.md
+++ b/docs/reference-guides/core-blocks.md
@@ -575,6 +575,15 @@ Post terms. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages
- **Supports:** color (background, gradients, link, text), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** prefix, separator, suffix, term, textAlign
+## Post Time To Read
+
+Time to read the post. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-time-to-read))
+
+- **Name:** core/post-time-to-read
+- **Category:** theme
+- **Supports:** ~~html~~
+- **Attributes:** minutesToRead, textAlign
+
## Post Title
Displays the title of a post, page, or any other content-type. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-title))
diff --git a/lib/blocks.php b/lib/blocks.php
index 842ff6a6ec093e..8aa5dbe712487d 100644
--- a/lib/blocks.php
+++ b/lib/blocks.php
@@ -89,6 +89,7 @@ function gutenberg_reregister_core_block_types() {
'post-featured-image.php' => 'core/post-featured-image',
'post-navigation-link.php' => 'core/post-navigation-link',
'post-terms.php' => 'core/post-terms',
+ 'post-time-to-read.php' => 'core/post-time-to-read',
'post-title.php' => 'core/post-title',
'query.php' => 'core/query',
'post-template.php' => 'core/post-template',
diff --git a/packages/block-library/package.json b/packages/block-library/package.json
index a09436c8993829..504888b4a7b6db 100644
--- a/packages/block-library/package.json
+++ b/packages/block-library/package.json
@@ -44,6 +44,7 @@
"@wordpress/date": "file:../date",
"@wordpress/deprecated": "file:../deprecated",
"@wordpress/dom": "file:../dom",
+ "@wordpress/editor": "file:../editor",
"@wordpress/element": "file:../element",
"@wordpress/hooks": "file:../hooks",
"@wordpress/html-entities": "file:../html-entities",
@@ -57,6 +58,7 @@
"@wordpress/server-side-render": "file:../server-side-render",
"@wordpress/url": "file:../url",
"@wordpress/viewport": "file:../viewport",
+ "@wordpress/wordcount": "file:../wordcount",
"change-case": "^4.1.2",
"classnames": "^2.3.1",
"colord": "^2.7.0",
diff --git a/packages/block-library/src/index.js b/packages/block-library/src/index.js
index 88d2c26f02403a..a505e66d1e8418 100644
--- a/packages/block-library/src/index.js
+++ b/packages/block-library/src/index.js
@@ -82,6 +82,7 @@ import * as postFeaturedImage from './post-featured-image';
import * as postNavigationLink from './post-navigation-link';
import * as postTemplate from './post-template';
import * as postTerms from './post-terms';
+import * as postTimeToRead from './post-time-to-read';
import * as postTitle from './post-title';
import * as preformatted from './preformatted';
import * as pullquote from './pullquote';
@@ -195,6 +196,7 @@ const getAllBlocks = () =>
postTerms,
postNavigationLink,
postTemplate,
+ postTimeToRead,
queryPagination,
queryPaginationNext,
queryPaginationNumbers,
diff --git a/packages/block-library/src/post-time-to-read/block.json b/packages/block-library/src/post-time-to-read/block.json
new file mode 100644
index 00000000000000..d5f9f657b67f94
--- /dev/null
+++ b/packages/block-library/src/post-time-to-read/block.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 2,
+ "name": "core/post-time-to-read",
+ "title": "Post Time To Read",
+ "category": "theme",
+ "description": "Time to read the post.",
+ "textdomain": "default",
+ "usesContext": [ "postType", "postId" ],
+ "attributes": {
+ "textAlign": {
+ "type": "string"
+ },
+ "minutesToRead": {
+ "type": "number"
+ }
+ },
+ "supports": {
+ "html": false
+ }
+}
diff --git a/packages/block-library/src/post-time-to-read/edit.js b/packages/block-library/src/post-time-to-read/edit.js
new file mode 100644
index 00000000000000..14f95452cf798e
--- /dev/null
+++ b/packages/block-library/src/post-time-to-read/edit.js
@@ -0,0 +1,96 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+
+/**
+ * WordPress dependencies
+ */
+import {
+ AlignmentControl,
+ BlockControls,
+ store as blockEditorStore,
+ useBlockProps,
+} from '@wordpress/block-editor';
+import { store as editorStore } from '@wordpress/editor';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { useEffect } from '@wordpress/element';
+import { _x, _n, __, sprintf } from '@wordpress/i18n';
+import { count as wordCount } from '@wordpress/wordcount';
+
+/**
+ * Average reading rate - based on average taken from
+ * https://irisreading.com/average-reading-speed-in-various-languages/
+ * (Characters/minute used for Chinese rather than words).
+ */
+const AVERAGE_READING_RATE = 189;
+
+function PostTimeToReadEdit( { attributes, setAttributes } ) {
+ const { textAlign, minutesToRead } = attributes;
+
+ const content = useSelect(
+ ( select ) => select( editorStore ).getEditedPostAttribute( 'content' ),
+ []
+ );
+
+ const { __unstableMarkNextChangeAsNotPersistent } =
+ useDispatch( blockEditorStore );
+
+ /*
+ * 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!' );
+
+ useEffect( () => {
+ const newMinutesToRead = Math.round(
+ wordCount( content, wordCountType ) / AVERAGE_READING_RATE
+ );
+ // This is required to keep undo working and not create 2 undo steps
+ // for the content change.
+ __unstableMarkNextChangeAsNotPersistent();
+ setAttributes( {
+ minutesToRead: content ? newMinutesToRead : undefined,
+ } );
+ }, [ content ] );
+
+ let minutesToReadString = __( 'There is no content.' );
+
+ if ( minutesToRead !== undefined ) {
+ minutesToReadString =
+ minutesToRead !== 0
+ ? sprintf(
+ /* translators: %d is the number of minutes the post will take to read. */
+ _n(
+ 'You can read this post in %d minute.',
+ 'You can read this post in %d minutes.',
+ minutesToRead
+ ),
+ minutesToRead
+ )
+ : __( 'You can read this post less than a minute.' );
+ }
+
+ const blockProps = useBlockProps( {
+ className: classnames( {
+ [ `has-text-align-${ textAlign }` ]: textAlign,
+ } ),
+ } );
+
+ return (
+ <>
+
{ minutesToReadString }
+ > + ); +} + +export default PostTimeToReadEdit; diff --git a/packages/block-library/src/post-time-to-read/icon.js b/packages/block-library/src/post-time-to-read/icon.js new file mode 100644 index 00000000000000..24c5dc2fae5169 --- /dev/null +++ b/packages/block-library/src/post-time-to-read/icon.js @@ -0,0 +1,15 @@ +/** + * WordPress dependencies + */ +import { SVG, Path } from '@wordpress/components'; + +export default ( + +); diff --git a/packages/block-library/src/post-time-to-read/index.js b/packages/block-library/src/post-time-to-read/index.js new file mode 100644 index 00000000000000..95b379f55f0b3f --- /dev/null +++ b/packages/block-library/src/post-time-to-read/index.js @@ -0,0 +1,17 @@ +/** + * Internal dependencies + */ +import initBlock from '../utils/init-block'; +import metadata from './block.json'; +import edit from './edit'; +import icon from './icon'; + +const { name } = metadata; +export { metadata, name }; + +export const settings = { + icon, + edit, +}; + +export const init = () => initBlock( { name, metadata, settings } ); diff --git a/packages/block-library/src/post-time-to-read/index.php b/packages/block-library/src/post-time-to-read/index.php new file mode 100644 index 00000000000000..006db16efb8b76 --- /dev/null +++ b/packages/block-library/src/post-time-to-read/index.php @@ -0,0 +1,57 @@ +context['postId'] ) ) { + return ''; + } + + $minutes_to_read = ! empty( $attributes['minutesToRead'] ) ? (int) $attributes['minutesToRead'] : 0; + + $minutes_to_read_string = $minutes_to_read !== 0 + ? sprintf( + /* translators: %d is the number of minutes the post will take to read. */ + _n( + 'You can read this post in %d minute.', + 'You can read this post in %d minutes.', + $minutes_to_read + ), + $minutes_to_read + ) + : __( 'You can read this post less than a minute.' ); + + $align_class_name = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}"; + + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $align_class_name ) ); + + return sprintf( + '%2$s
', + $wrapper_attributes, + $minutes_to_read_string + ); +} + +/** + * Registers the `core/post-time-to-read` block on the server. + */ +function register_block_core_post_time_to_read() { + register_block_type_from_metadata( + __DIR__ . '/post-time-to-read', + array( + 'render_callback' => 'render_block_core_post_time_to_read', + ) + ); +} +add_action( 'init', 'register_block_core_post_time_to_read' );