feat(v2): add blog post estimated reading time (#2531)

* feat: add estimated reading time to blog posts

* docs: add showReadingTime on plugin docs

* test: update plugin-content-blog tests to cover readingTime

* Update index.js

* Update using-plugins.md

* Update index.js

Co-authored-by: Yangshun Tay <tay.yang.shun@gmail.com>
This commit is contained in:
José Renan 2020-04-05 04:08:42 -03:00 committed by GitHub
parent c576faac73
commit 95fdfe7e15
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 25 additions and 4 deletions

View file

@ -20,7 +20,8 @@
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
"globby": "^10.0.1", "globby": "^10.0.1",
"loader-utils": "^1.2.3", "loader-utils": "^1.2.3",
"lodash.kebabcase": "^4.1.1" "lodash.kebabcase": "^4.1.1",
"reading-time": "^1.2.0"
}, },
"peerDependencies": { "peerDependencies": {
"@docusaurus/core": "^2.0.0", "@docusaurus/core": "^2.0.0",

View file

@ -51,6 +51,7 @@ describe('loadBlog', () => {
...{prevItem: undefined}, ...{prevItem: undefined},
}).toEqual({ }).toEqual({
permalink: '/blog/2019/01/01/date-matter', permalink: '/blog/2019/01/01/date-matter',
readingTime: 0.02,
source: path.join('@site', pluginPath, 'date-matter.md'), source: path.join('@site', pluginPath, 'date-matter.md'),
title: 'date-matter', title: 'date-matter',
description: `date inside front matter`, description: `date inside front matter`,
@ -68,6 +69,7 @@ describe('loadBlog', () => {
.metadata, .metadata,
).toEqual({ ).toEqual({
permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash', permalink: '/blog/2018/12/14/Happy-First-Birthday-Slash',
readingTime: 0.01,
source: path.join( source: path.join(
'@site', '@site',
pluginPath, pluginPath,
@ -89,6 +91,7 @@ describe('loadBlog', () => {
...{prevItem: undefined}, ...{prevItem: undefined},
}).toEqual({ }).toEqual({
permalink: noDatePermalink, permalink: noDatePermalink,
readingTime: 0.01,
source: noDateSource, source: noDateSource,
title: 'no date', title: 'no date',
description: `no date`, description: `no date`,

View file

@ -8,6 +8,7 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import globby from 'globby'; import globby from 'globby';
import path from 'path'; import path from 'path';
import readingTime from 'reading-time';
import {Feed} from 'feed'; import {Feed} from 'feed';
import {PluginOptions, BlogPost, DateLink} from './types'; import {PluginOptions, BlogPost, DateLink} from './types';
import {parse, normalizeUrl, aliasedSitePath} from '@docusaurus/utils'; import {parse, normalizeUrl, aliasedSitePath} from '@docusaurus/utils';
@ -85,7 +86,7 @@ export async function generateBlogPosts(
{siteConfig, siteDir}: LoadContext, {siteConfig, siteDir}: LoadContext,
options: PluginOptions, options: PluginOptions,
) { ) {
const {include, routeBasePath, truncateMarker} = options; const {include, routeBasePath, truncateMarker, showReadingTime} = options;
if (!fs.existsSync(blogDir)) { if (!fs.existsSync(blogDir)) {
return []; return [];
@ -144,6 +145,9 @@ export async function generateBlogPosts(
date, date,
tags: frontMatter.tags, tags: frontMatter.tags,
title: frontMatter.title, title: frontMatter.title,
readingTime: showReadingTime
? readingTime(content).minutes
: undefined,
truncated: truncateMarker?.test(content) || false, truncated: truncateMarker?.test(content) || false,
}, },
}); });

View file

@ -40,6 +40,7 @@ const DEFAULT_OPTIONS: PluginOptions = {
blogPostComponent: '@theme/BlogPostPage', blogPostComponent: '@theme/BlogPostPage',
blogTagsListComponent: '@theme/BlogTagsListPage', blogTagsListComponent: '@theme/BlogTagsListPage',
blogTagsPostsComponent: '@theme/BlogTagsPostsPage', blogTagsPostsComponent: '@theme/BlogTagsPostsPage',
showReadingTime: true,
remarkPlugins: [], remarkPlugins: [],
rehypePlugins: [], rehypePlugins: [],
truncateMarker: /<!--\s*(truncate)\s*-->/, // Regex. truncateMarker: /<!--\s*(truncate)\s*-->/, // Regex.

View file

@ -31,6 +31,7 @@ export interface PluginOptions {
remarkPlugins: string[]; remarkPlugins: string[];
rehypePlugins: string[]; rehypePlugins: string[];
truncateMarker: RegExp; truncateMarker: RegExp;
showReadingTime: boolean;
feedOptions?: { feedOptions?: {
type: FeedType; type: FeedType;
title?: string; title?: string;
@ -77,6 +78,7 @@ export interface MetaData {
date: Date; date: Date;
tags: (Tag | string)[]; tags: (Tag | string)[];
title: string; title: string;
readingTime?: number;
prevItem?: Paginator; prevItem?: Paginator;
nextItem?: Paginator; nextItem?: Paginator;
truncated: boolean; truncated: boolean;

View file

@ -37,7 +37,7 @@ function BlogPostItem(props) {
truncated, truncated,
isBlogPostPage = false, isBlogPostPage = false,
} = props; } = props;
const {date, permalink, tags} = metadata; const {date, permalink, tags, readingTime} = metadata;
const {author, title} = frontMatter; const {author, title} = frontMatter;
const authorURL = frontMatter.author_url || frontMatter.authorURL; const authorURL = frontMatter.author_url || frontMatter.authorURL;
@ -60,7 +60,8 @@ function BlogPostItem(props) {
</TitleHeading> </TitleHeading>
<div className="margin-vert--md"> <div className="margin-vert--md">
<time dateTime={date} className={styles.blogPostDate}> <time dateTime={date} className={styles.blogPostDate}>
{month} {day}, {year} {month} {day}, {year}{' '}
{readingTime && <> · {Math.ceil(readingTime)} min read</>}
</time> </time>
</div> </div>
<div className="avatar margin-vert--md"> <div className="avatar margin-vert--md">

View file

@ -174,6 +174,10 @@ module.exports = {
* Truncate marker, can be a regex or string. * Truncate marker, can be a regex or string.
*/ */
truncateMarker: /<!--\s*(truncate)\s*-->/, truncateMarker: /<!--\s*(truncate)\s*-->/,
/**
* Show estimated reading time for the blog post.
*/
showReadingTime: true,
/** /**
* Blog feed * Blog feed
* If feedOptions is undefined, no rss feed will be generated * If feedOptions is undefined, no rss feed will be generated

View file

@ -13933,6 +13933,11 @@ readdirp@~3.3.0:
dependencies: dependencies:
picomatch "^2.0.7" picomatch "^2.0.7"
reading-time@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/reading-time/-/reading-time-1.2.0.tgz#ced71c06715762f805506328dcc1fd45d8249ac4"
integrity sha512-5b4XmKK4MEss63y0Lw0vn0Zn6G5kiHP88mUnD8UeEsyORj3sh1ghTH0/u6m1Ax9G2F4wUZrknlp6WlIsCvoXVA==
realpath-native@^1.1.0: realpath-native@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"