Skip to content
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

[New Block] Add post time to read block #43403

Merged
merged 47 commits into from
Feb 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5e7d7b3
Add new "Post Time To Read" block
t-hamano Sep 18, 2022
ed2b796
Add fixture
t-hamano Sep 18, 2022
cc6493c
Update npm dependencies
t-hamano Sep 18, 2022
f07c8c7
Disallow multiple insert
t-hamano Sep 18, 2022
49bff6f
Fix php lint error
t-hamano Sep 18, 2022
7e9f093
Fix npm dependencies path
t-hamano Sep 18, 2022
56a8e31
Remove unused context
t-hamano Sep 18, 2022
805a6bb
Add a missing word in translate string
t-hamano Sep 22, 2022
06eb280
Don't use @wordpress/editor package
t-hamano Oct 3, 2022
d22901f
Don't store attribute
t-hamano Oct 3, 2022
b2c90d6
Fix npm dependencies
t-hamano Oct 3, 2022
efb3580
Fix: The block is broken in the post editor
t-hamano Oct 3, 2022
094db1d
Update the block icon
t-hamano Oct 3, 2022
9c1e8d6
Add prefix and suffix
t-hamano Oct 3, 2022
088b96e
Server-side rendering implemented using dummy function
t-hamano Oct 3, 2022
9862171
Update fixtures
t-hamano Oct 3, 2022
87ed8b7
Fix phpcs lint error
t-hamano Oct 3, 2022
fa55d92
Implement word_count function
t-hamano Oct 4, 2022
2197d61
Remove some new lines on php
t-hamano Oct 4, 2022
4b6e097
Fix regexp escape
t-hamano Oct 4, 2022
72f6947
convert float zero to int zero
t-hamano Oct 4, 2022
757aa2a
Fix PHP lint error
t-hamano Oct 4, 2022
912e454
Cursor isn't displayed in prefix and suffix input area
t-hamano Oct 5, 2022
6cbca51
Calculate time only when content is changed
t-hamano Oct 5, 2022
8e065cc
Capitalize when there is no prefix
t-hamano Oct 5, 2022
46fb033
Apply inline block style to editor only
t-hamano Oct 5, 2022
faaaf9e
Add Unit Test
t-hamano Oct 11, 2022
fa9566a
Fix PHP Lint
t-hamano Oct 12, 2022
9785f99
Revert prefix/suffix
t-hamano Oct 12, 2022
4bd3ab3
Change block title and description
t-hamano Oct 12, 2022
e3f6b6e
Fix some unit tests
t-hamano Oct 12, 2022
1fa5255
Show message when there is no content
t-hamano Oct 12, 2022
b986d3a
Pass all PHP unit tests
t-hamano Oct 12, 2022
a0612ad
Add @covers to Unit Test class
t-hamano Oct 12, 2022
3909d6e
Changed prefix from gutenberg_ to wp_
t-hamano Oct 12, 2022
907945f
Use dataProvider
t-hamano Oct 12, 2022
7dc6e19
Add render_callback function unit tests
t-hamano Oct 13, 2022
c00d014
Add function existence check
t-hamano Oct 13, 2022
ca36549
Show time only
t-hamano Oct 19, 2022
a8e2884
Update PHP unit test
t-hamano Oct 19, 2022
e3417d8
Mark as experimental
t-hamano Feb 1, 2023
a27741f
Move `wp_word_count()` function to `experimental` directory
t-hamano Feb 1, 2023
39a840b
Use new function `wp_get_word_count_type()`
t-hamano Feb 11, 2023
e673723
fix lint error
t-hamano Feb 17, 2023
a0547c9
Merge branch 'trunk' into add/block-post-time-to-read
t-hamano Feb 21, 2023
b5a361a
Merge branch 'trunk' into add/block-post-time-to-read
t-hamano Feb 27, 2023
ebc1d03
Change namespace from `formatting` to `l10n`
t-hamano Feb 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/reference-guides/core-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,15 @@ Post terms. ([Source](https://github.com/WordPress/gutenberg/tree/trunk/packages
- **Supports:** anchor, color (background, gradients, link, text), spacing (margin, padding), typography (fontSize, lineHeight), ~~html~~
- **Attributes:** prefix, separator, suffix, term, textAlign

## Time To Read

Show minutes required to finish reading 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~~, ~~multiple~~
- **Attributes:** 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))
Expand Down
1 change: 1 addition & 0 deletions lib/blocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,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',
Expand Down
139 changes: 139 additions & 0 deletions lib/experimental/l10n.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php
/**
* PHP and WordPress configuration compatibility functions for the Gutenberg
* editor plugin changes related to i18n.
*
* @package gutenberg
*/

/**
* Override core's wp_get_word_count_type() introduced in WordPress 6.2.
* Originally, get_word_count_type() method of the WP_Locale class is executed,
* but the process is simulated here.
*
* This function should not be backported to core.
*/
if ( ! function_exists( 'wp_get_word_count_type' ) ) {
/**
* Retrieves the word count type based on the locale.
*
* @return string Locale-specific word count type.
*/
function wp_get_word_count_type() {
$word_count_type = _x( 'words', 'Word count type. Do not translate!', 'gutenberg' );

// Check for valid types.
if ( 'characters_excluding_spaces' !== $word_count_type && 'characters_including_spaces' !== $word_count_type ) {
// Defaults to 'words'.
$word_count_type = 'words';
}
return $word_count_type;
}
}

if ( ! function_exists( 'wp_word_count' ) ) {
/**
* Count words or characters in a provided text string.
*
* @param string $text Text to count elements in.
* @param string $type The type of count. Accepts 'words', 'characters_excluding_spaces', or 'characters_including_spaces'.
* @param array $settings {
* Optional. Array of arguments used to overrides for settings.
*
* @type string $html_regexp Optional. Regular expression to find HTML elements.
* @type string $html_comment_regexp Optional. Regular expression to find HTML comments.
* @type string $space_regexp Optional. Regular expression to find irregular space
* characters.
* @type string $html_entity_regexp Optional. Regular expression to find HTML entities.
* @type string $connector_regexp Optional. Regular expression to find connectors that
* split words.
* @type string $remove_regexp Optional. Regular expression to find remove unwanted
* characters to reduce false-positives.
* @type string $astral_regexp Optional. Regular expression to find unwanted
* characters when searching for non-words.
* @type string $words_regexp Optional. Regular expression to find words by spaces.
* @type string $characters_excluding_spaces_regexp Optional. Regular expression to find characters which
* are non-spaces.
* @type string $characters_including_spaces_regexp Optional. Regular expression to find characters
* including spaces.
* @type array $shortcodes Optional. Array of shortcodes that should be removed
* from the text.
* }
* @return int The word or character count.
*/
function wp_word_count( $text, $type, $settings = array() ) {
$defaults = array(
'html_regexp' => '/<\/?[a-z][^>]*?>/i',
'html_comment_regexp' => '/<!--[\s\S]*?-->/',
'space_regexp' => '/&nbsp;|&#160;/i',
'html_entity_regexp' => '/&\S+?;/',
'connector_regexp' => "/--|\x{2014}/u",
'remove_regexp' => "/[\x{0021}-\x{0040}\x{005B}-\x{0060}\x{007B}-\x{007E}\x{0080}-\x{00BF}\x{00D7}\x{00F7}\x{2000}-\x{2BFF}\x{2E00}-\x{2E7F}]/u",
'astral_regexp' => "/[\x{010000}-\x{10FFFF}]/u",
'words_regexp' => '/\S\s+/u',
'characters_excluding_spaces_regexp' => '/\S/u',
'characters_including_spaces_regexp' => "/[^\f\n\r\t\v\x{00AD}\x{2028}\x{2029}]/u",
'shortcodes' => array(),
);

$count = 0;

if ( ! $text ) {
return $count;
}

$settings = wp_parse_args( $settings, $defaults );

// If there are any shortcodes, add this as a shortcode regular expression.
if ( is_array( $settings['shortcodes'] ) && ! empty( $settings['shortcodes'] ) ) {
$settings['shortcodes_regexp'] = '/\\[\\/?(?:' . implode( '|', $settings['shortcodes'] ) . ')[^\\]]*?\\]/';
}

// Sanitize type to one of three possibilities: 'words', 'characters_excluding_spaces' or 'characters_including_spaces'.
if ( 'characters_excluding_spaces' !== $type && 'characters_including_spaces' !== $type ) {
$type = 'words';
}

$text .= "\n";

// Replace all HTML with a new-line.
$text = preg_replace( $settings['html_regexp'], "\n", $text );

// Remove all HTML comments.
$text = preg_replace( $settings['html_comment_regexp'], '', $text );

// If a shortcode regular expression has been provided use it to remove shortcodes.
if ( ! empty( $settings['shortcodes_regexp'] ) ) {
$text = preg_replace( $settings['shortcodes_regexp'], "\n", $text );
}

// Normalize non-breaking space to a normal space.
$text = preg_replace( $settings['space_regexp'], ' ', $text );

if ( 'words' === $type ) {
// Remove HTML Entities.
$text = preg_replace( $settings['html_entity_regexp'], '', $text );

// Convert connectors to spaces to count attached text as words.
$text = preg_replace( $settings['connector_regexp'], ' ', $text );

// Remove unwanted characters.
$text = preg_replace( $settings['remove_regexp'], '', $text );
} else {
// Convert HTML Entities to "a".
$text = preg_replace( $settings['html_entity_regexp'], 'a', $text );

// Remove surrogate points.
$text = preg_replace( $settings['astral_regexp'], 'a', $text );
}

// Match with the selected type regular expression to count the items.
preg_match_all( $settings[ $type . '_regexp' ], $text, $matches );

if ( $matches ) {
return count( $matches[0] );
}

return $count;
}
}
1 change: 1 addition & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ function gutenberg_is_experiment_enabled( $name ) {
require __DIR__ . '/experimental/blocks.php';
require __DIR__ . '/experimental/navigation-theme-opt-in.php';
require __DIR__ . '/experimental/kses.php';
require __DIR__ . '/experimental/l10n.php';

// Fonts API.
if ( ! class_exists( 'WP_Fonts' ) ) {
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/block-library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,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",
Expand Down
2 changes: 2 additions & 0 deletions packages/block-library/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,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';
Expand Down Expand Up @@ -197,6 +198,7 @@ const getAllBlocks = () =>
postTerms,
postNavigationLink,
postTemplate,
postTimeToRead,
queryPagination,
queryPaginationNext,
queryPaginationNumbers,
Expand Down
20 changes: 20 additions & 0 deletions packages/block-library/src/post-time-to-read/block.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"$schema": "https://schemas.wp.org/trunk/block.json",
"apiVersion": 2,
"__experimental": true,
"name": "core/post-time-to-read",
"title": "Time To Read",
"category": "theme",
"description": "Show minutes required to finish reading the post.",
"textdomain": "default",
"usesContext": [ "postId", "postType" ],
"attributes": {
"textAlign": {
"type": "string"
}
},
"supports": {
"html": false,
"multiple": false
}
Comment on lines +16 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a follow-up PR once this is merged, it'd be good to consider what design tools this needs. There are some tracking issues for design tool consistency - https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22%5BFeature%5D+Design+Tools%22+label%3A%22%5BType%5D+Tracking+Issue%22.

}
101 changes: 101 additions & 0 deletions packages/block-library/src/post-time-to-read/edit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* External dependencies
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { _x, _n, sprintf } from '@wordpress/i18n';
import { useMemo } from '@wordpress/element';
import {
AlignmentControl,
BlockControls,
useBlockProps,
} from '@wordpress/block-editor';
import { __unstableSerializeAndClean } from '@wordpress/blocks';
import { useEntityProp, useEntityBlockEditor } from '@wordpress/core-data';
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, context } ) {
const { textAlign } = attributes;
const { postId, postType } = context;

const [ contentStructure ] = useEntityProp(
'postType',
postType,
'content',
postId
);

const [ blocks ] = useEntityBlockEditor( 'postType', postType, {
id: postId,
} );

const minutesToReadString = useMemo( () => {
// Replicates the logic found in getEditedPostContent().
let content;
if ( contentStructure instanceof Function ) {
content = contentStructure( { blocks } );
} else if ( blocks ) {
// If we have parsed blocks already, they should be our source of truth.
// Parsing applies block deprecations and legacy block conversions that
// unparsed content will not have.
content = __unstableSerializeAndClean( blocks );
} else {
content = contentStructure;
}

/*
* 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 minutesToRead = Math.max(
1,
Math.round(
wordCount( content, wordCountType ) / AVERAGE_READING_RATE
)
);

return sprintf(
/* translators: %d is the number of minutes the post will take to read. */
_n( '%d minute', '%d minutes', minutesToRead ),
minutesToRead
);
}, [ contentStructure, blocks ] );

const blockProps = useBlockProps( {
className: classnames( {
[ `has-text-align-${ textAlign }` ]: textAlign,
} ),
} );

return (
<>
<BlockControls group="block">
<AlignmentControl
value={ textAlign }
onChange={ ( nextAlign ) => {
setAttributes( { textAlign: nextAlign } );
} }
/>
</BlockControls>
<p { ...blockProps }>{ minutesToReadString }</p>
</>
);
}

export default PostTimeToReadEdit;
15 changes: 15 additions & 0 deletions packages/block-library/src/post-time-to-read/icon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* WordPress dependencies
*/
import { SVG, Path } from '@wordpress/components';

export default (
<SVG
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<Path d="M12 3c-5 0-9 4-9 9s4 9 9 9 9-4 9-9-4-9-9-9zm0 16.5c-4.1 0-7.5-3.4-7.5-7.5S7.9 4.5 12 4.5s7.5 3.4 7.5 7.5-3.4 7.5-7.5 7.5zM12 7l-1 5c0 .3.2.6.4.8l4.2 2.8-2.7-4.1L12 7z" />
</SVG>
);
17 changes: 17 additions & 0 deletions packages/block-library/src/post-time-to-read/index.js
Original file line number Diff line number Diff line change
@@ -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 } );
Loading