mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-13 00:57:53 +02:00
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:
parent
c576faac73
commit
95fdfe7e15
8 changed files with 25 additions and 4 deletions
|
@ -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",
|
||||||
|
|
|
@ -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`,
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue