feat(content-blog): new readingTime plugin option (#5702)

This commit is contained in:
Joshua Chen 2021-10-21 21:26:10 +08:00 committed by GitHub
parent 92002b6bd3
commit 9ad6de2b85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 179 additions and 1 deletions

View file

@ -77,6 +77,8 @@ describe('blogFeed', () => {
type: [feedType],
copyright: 'Copyright',
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
} as PluginOptions,
);
@ -111,6 +113,8 @@ describe('blogFeed', () => {
type: [feedType],
copyright: 'Copyright',
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
} as PluginOptions,
);
const feedContent =

View file

@ -16,6 +16,7 @@ import {
BlogContentPaths,
BlogMarkdownLoaderOptions,
BlogTags,
ReadingTimeFunction,
} from './types';
import {
parseMarkdownFile,
@ -111,6 +112,10 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
};
}
const defaultReadingTime: ReadingTimeFunction = ({content, options}) => {
return readingTime(content, options).minutes;
};
async function processBlogSourceFile(
blogSourceRelative: string,
contentPaths: BlogContentPaths,
@ -227,7 +232,13 @@ async function processBlogSourceFile(
date,
formattedDate,
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
readingTime: showReadingTime ? readingTime(content).minutes : undefined,
readingTime: showReadingTime
? options.readingTime({
content,
frontMatter,
defaultReadingTime,
})
: undefined,
truncated: truncateMarker?.test(content) || false,
authors,
},

View file

@ -41,6 +41,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
path: 'blog',
editLocalizedFiles: false,
authorsMapPath: 'authors.yml',
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
};
export const PluginOptionSchema = Joi.object<PluginOptions>({
@ -113,4 +114,5 @@ export const PluginOptionSchema = Joi.object<PluginOptions>({
language: Joi.string(),
}).default(DEFAULT_OPTIONS.feedOptions),
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
});

View file

@ -12,6 +12,7 @@ import type {
ContentPaths,
} from '@docusaurus/utils/lib/markdownLinks';
import {Overwrite} from 'utility-types';
import {BlogPostFrontMatter} from './blogFrontMatter';
export type BlogContentPaths = ContentPaths;
@ -46,6 +47,24 @@ export type EditUrlFunction = (editUrlParams: {
locale: string;
}) => string | undefined;
// Duplicate from ngryman/reading-time to keep stability of API
type ReadingTimeOptions = {
wordsPerMinute?: number;
wordBound?: (char: string) => boolean;
};
export type ReadingTimeFunction = (params: {
content: string;
frontMatter?: BlogPostFrontMatter & Record<string, unknown>;
options?: ReadingTimeOptions;
}) => number;
export type ReadingTimeFunctionOption = (
params: Required<Omit<Parameters<ReadingTimeFunction>[0], 'options'>> & {
defaultReadingTime: ReadingTimeFunction;
},
) => number | undefined;
export type PluginOptions = RemarkAndRehypePluginOptions & {
id?: string;
path: string;
@ -76,6 +95,7 @@ export type PluginOptions = RemarkAndRehypePluginOptions & {
editLocalizedFiles?: boolean;
admonitions: Record<string, unknown>;
authorsMapPath: string;
readingTime: ReadingTimeFunctionOption;
};
// Options, as provided in the user config (before normalization)

View file

@ -3,6 +3,7 @@ title: Blog post MDX Feed tests
authors:
- slorber
tags: [blog, docusaurus, long-long, long-long-long, long-long-long-long]
hide_reading_time: true
---
Some MDX tests, mostly to test how the RSS feed render those

View file

@ -31,6 +31,10 @@ const dogfoodingPluginInstances = [
title: 'Docusaurus Tests Blog',
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
},
readingTime: ({content, frontMatter, defaultReadingTime}) =>
frontMatter.hide_reading_time
? undefined
: defaultReadingTime({content, options: {wordsPerMinute: 5}}),
}),
],

View file

@ -51,6 +51,7 @@ Accepted fields:
| `beforeDefaultRehypePlugins` | `any[]` | `[]` | Custom Rehype plugins passed to MDX before the default Docusaurus Rehype plugins. |
| `truncateMarker` | `string` | `/<!--\s*(truncate)\s*-->/` | Truncate marker, can be a regex or string. |
| `showReadingTime` | `boolean` | `true` | Show estimated reading time for the blog post. |
| `readingTime` | `ReadingTimeFunctionOption` | The default reading time | A callback to customize the reading time number displayed. |
| `authorsMapPath` | `string` | `'authors.yml'` | Path to the authors map file, relative to the blog content directory specified with `path`. Can also be a `json` file. |
| `feedOptions` | _See below_ | `{type: ['rss', 'atom']}` | Blog feed. If undefined, no rss feed will be generated. |
| `feedOptions.type` | <code>'rss' \| 'atom' \| 'all'</code> (or array of multiple options) | **Required** | Type of feed to be generated. |
@ -68,6 +69,23 @@ type EditUrlFunction = (params: {
permalink: string;
locale: string;
}) => string | undefined;
type ReadingTimeOptions = {
wordsPerMinute: number;
wordBound: (char: string) => boolean;
};
type ReadingTimeFunction = (params: {
content: string;
frontMatter?: BlogPostFrontMatter & Record<string, unknown>;
options?: ReadingTimeOptions;
}) => number;
type ReadingTimeFunctionOption = (params: {
content: string;
frontMatter: BlogPostFrontMatter & Record<string, unknown>;
defaultReadingTime: ReadingTimeFunction;
}) => number | undefined;
```
## Example configuration {#ex-config}

View file

@ -335,6 +335,124 @@ website/i18n/<locale>/docusaurus-plugin-content-blog/authors.yml
:::
## Reading time {#reading-time}
Docusaurus generates a reading time estimation for each blog post based on word count. We provide an option to customize this.
```js title="docusaurus.config.js"
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
blog: {
// highlight-start
showReadingTime: true, // When set to false, the "x min read" won't be shown
readingTime: ({content, frontMatter, defaultReadingTime}) =>
defaultReadingTime({content, options: {wordsPerMinute: 300}}),
// highlight-end
},
},
],
],
};
```
The `readingTime` callback receives three parameters: the blog content text as a string, front matter as a record of string keys and their values, and the default reading time function. It returns a number (reading time in minutes) or `undefined` (disable reading time for this page).
The default reading time is able to accept additional options: `wordsPerMinute` as a number (default: 300), and `wordBound` as a function from string to boolean. If the string passed to `wordBound` should be a word bound (spaces, tabs, and line breaks by default), the function should return `true`.
:::tip
Use the callback for all your customization needs:
````mdx-code-block
<Tabs>
<TabItem value="disable-per-post" label="Per-post disabling">
**Disable reading time on one page:**
```js title="docusaurus.config.js"
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
blog: {
showReadingTime: true,
// highlight-start
readingTime: ({content, frontMatter, defaultReadingTime}) =>
frontMatter.hide_reading_time ? undefined : defaultReadingTime({content}),
// highlight-end
},
},
],
],
};
```
Usage:
```yml "my-blog-post.md"
---
hide_reading_time: true
---
This page will no longer display the reading time stats!
```
</TabItem>
<TabItem value="passing-options" label="Passing options">
**Pass options to the default reading time function:**
```js title="docusaurus.config.js"
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
blog: {
// highlight-start
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content, options: {wordsPerMinute: 100}}),
// highlight-end
},
},
],
],
};
```
</TabItem>
<TabItem value="using-custom-algo" label="Using custom algorithms">
**Use a custom implementation of reading time:**
```js title="docusaurus.config.js"
const myReadingTime = require('./myReadingTime');
module.exports = {
presets: [
[
'@docusaurus/preset-classic',
{
blog: {
// highlight-next-line
readingTime: ({content}) => myReadingTime(content),
},
},
],
],
};
```
</TabItem>
</Tabs>
````
:::
## Feed {#feed}
You can generate RSS/Atom feed by passing feedOptions. By default, RSS and Atom feeds are generated. To disable feed generation, set `feedOptions.type` to `null`.