mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-10 15:47:23 +02:00
feat(content-blog): new readingTime plugin option (#5702)
This commit is contained in:
parent
92002b6bd3
commit
9ad6de2b85
8 changed files with 179 additions and 1 deletions
|
@ -77,6 +77,8 @@ describe('blogFeed', () => {
|
||||||
type: [feedType],
|
type: [feedType],
|
||||||
copyright: 'Copyright',
|
copyright: 'Copyright',
|
||||||
},
|
},
|
||||||
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
|
defaultReadingTime({content}),
|
||||||
} as PluginOptions,
|
} as PluginOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -111,6 +113,8 @@ describe('blogFeed', () => {
|
||||||
type: [feedType],
|
type: [feedType],
|
||||||
copyright: 'Copyright',
|
copyright: 'Copyright',
|
||||||
},
|
},
|
||||||
|
readingTime: ({content, defaultReadingTime}) =>
|
||||||
|
defaultReadingTime({content}),
|
||||||
} as PluginOptions,
|
} as PluginOptions,
|
||||||
);
|
);
|
||||||
const feedContent =
|
const feedContent =
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {
|
||||||
BlogContentPaths,
|
BlogContentPaths,
|
||||||
BlogMarkdownLoaderOptions,
|
BlogMarkdownLoaderOptions,
|
||||||
BlogTags,
|
BlogTags,
|
||||||
|
ReadingTimeFunction,
|
||||||
} from './types';
|
} from './types';
|
||||||
import {
|
import {
|
||||||
parseMarkdownFile,
|
parseMarkdownFile,
|
||||||
|
@ -111,6 +112,10 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultReadingTime: ReadingTimeFunction = ({content, options}) => {
|
||||||
|
return readingTime(content, options).minutes;
|
||||||
|
};
|
||||||
|
|
||||||
async function processBlogSourceFile(
|
async function processBlogSourceFile(
|
||||||
blogSourceRelative: string,
|
blogSourceRelative: string,
|
||||||
contentPaths: BlogContentPaths,
|
contentPaths: BlogContentPaths,
|
||||||
|
@ -227,7 +232,13 @@ async function processBlogSourceFile(
|
||||||
date,
|
date,
|
||||||
formattedDate,
|
formattedDate,
|
||||||
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
|
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
|
||||||
readingTime: showReadingTime ? readingTime(content).minutes : undefined,
|
readingTime: showReadingTime
|
||||||
|
? options.readingTime({
|
||||||
|
content,
|
||||||
|
frontMatter,
|
||||||
|
defaultReadingTime,
|
||||||
|
})
|
||||||
|
: undefined,
|
||||||
truncated: truncateMarker?.test(content) || false,
|
truncated: truncateMarker?.test(content) || false,
|
||||||
authors,
|
authors,
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
||||||
path: 'blog',
|
path: 'blog',
|
||||||
editLocalizedFiles: false,
|
editLocalizedFiles: false,
|
||||||
authorsMapPath: 'authors.yml',
|
authorsMapPath: 'authors.yml',
|
||||||
|
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PluginOptionSchema = Joi.object<PluginOptions>({
|
export const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||||
|
@ -113,4 +114,5 @@ export const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||||
language: Joi.string(),
|
language: Joi.string(),
|
||||||
}).default(DEFAULT_OPTIONS.feedOptions),
|
}).default(DEFAULT_OPTIONS.feedOptions),
|
||||||
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
||||||
|
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
||||||
ContentPaths,
|
ContentPaths,
|
||||||
} from '@docusaurus/utils/lib/markdownLinks';
|
} from '@docusaurus/utils/lib/markdownLinks';
|
||||||
import {Overwrite} from 'utility-types';
|
import {Overwrite} from 'utility-types';
|
||||||
|
import {BlogPostFrontMatter} from './blogFrontMatter';
|
||||||
|
|
||||||
export type BlogContentPaths = ContentPaths;
|
export type BlogContentPaths = ContentPaths;
|
||||||
|
|
||||||
|
@ -46,6 +47,24 @@ export type EditUrlFunction = (editUrlParams: {
|
||||||
locale: string;
|
locale: string;
|
||||||
}) => string | undefined;
|
}) => 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 & {
|
export type PluginOptions = RemarkAndRehypePluginOptions & {
|
||||||
id?: string;
|
id?: string;
|
||||||
path: string;
|
path: string;
|
||||||
|
@ -76,6 +95,7 @@ export type PluginOptions = RemarkAndRehypePluginOptions & {
|
||||||
editLocalizedFiles?: boolean;
|
editLocalizedFiles?: boolean;
|
||||||
admonitions: Record<string, unknown>;
|
admonitions: Record<string, unknown>;
|
||||||
authorsMapPath: string;
|
authorsMapPath: string;
|
||||||
|
readingTime: ReadingTimeFunctionOption;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Options, as provided in the user config (before normalization)
|
// Options, as provided in the user config (before normalization)
|
||||||
|
|
|
@ -3,6 +3,7 @@ title: Blog post MDX Feed tests
|
||||||
authors:
|
authors:
|
||||||
- slorber
|
- slorber
|
||||||
tags: [blog, docusaurus, long-long, long-long-long, long-long-long-long]
|
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
|
Some MDX tests, mostly to test how the RSS feed render those
|
||||||
|
|
|
@ -31,6 +31,10 @@ const dogfoodingPluginInstances = [
|
||||||
title: 'Docusaurus Tests Blog',
|
title: 'Docusaurus Tests Blog',
|
||||||
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
copyright: `Copyright © ${new Date().getFullYear()} Facebook, Inc.`,
|
||||||
},
|
},
|
||||||
|
readingTime: ({content, frontMatter, defaultReadingTime}) =>
|
||||||
|
frontMatter.hide_reading_time
|
||||||
|
? undefined
|
||||||
|
: defaultReadingTime({content, options: {wordsPerMinute: 5}}),
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ Accepted fields:
|
||||||
| `beforeDefaultRehypePlugins` | `any[]` | `[]` | Custom Rehype plugins passed to MDX before the default Docusaurus Rehype plugins. |
|
| `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. |
|
| `truncateMarker` | `string` | `/<!--\s*(truncate)\s*-->/` | Truncate marker, can be a regex or string. |
|
||||||
| `showReadingTime` | `boolean` | `true` | Show estimated reading time for the blog post. |
|
| `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. |
|
| `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` | _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. |
|
| `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;
|
permalink: string;
|
||||||
locale: string;
|
locale: string;
|
||||||
}) => string | undefined;
|
}) => 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}
|
## Example configuration {#ex-config}
|
||||||
|
|
|
@ -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}
|
## 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`.
|
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`.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue