mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-28 17:57:48 +02:00
feat(blog): allow processing blog posts through a processBlogPosts function (#9886)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com> Co-authored-by: sebastien <lorber.sebastien@gmail.com> Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
This commit is contained in:
parent
05279dc3d8
commit
2851c93d0d
12 changed files with 400 additions and 6 deletions
|
@ -55,5 +55,8 @@
|
|||
},
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@total-typescript/shoehorn": "^0.1.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,244 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`blog plugin process blog posts load content 1`] = `
|
||||
{
|
||||
"/blog/tags/tag-1": {
|
||||
"items": [
|
||||
"/simple/slug/another",
|
||||
"/another/tags",
|
||||
"/another/tags2",
|
||||
],
|
||||
"label": "tag1",
|
||||
"pages": [
|
||||
{
|
||||
"items": [
|
||||
"/simple/slug/another",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": "/blog/tags/tag-1/page/2",
|
||||
"page": 1,
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
"postsPerPage": 1,
|
||||
"previousPage": undefined,
|
||||
"totalCount": 3,
|
||||
"totalPages": 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
"/another/tags",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": "/blog/tags/tag-1/page/3",
|
||||
"page": 2,
|
||||
"permalink": "/blog/tags/tag-1/page/2",
|
||||
"postsPerPage": 1,
|
||||
"previousPage": "/blog/tags/tag-1",
|
||||
"totalCount": 3,
|
||||
"totalPages": 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
"/another/tags2",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": undefined,
|
||||
"page": 3,
|
||||
"permalink": "/blog/tags/tag-1/page/3",
|
||||
"postsPerPage": 1,
|
||||
"previousPage": "/blog/tags/tag-1/page/2",
|
||||
"totalCount": 3,
|
||||
"totalPages": 3,
|
||||
},
|
||||
},
|
||||
],
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
"unlisted": false,
|
||||
},
|
||||
"/blog/tags/tag-2": {
|
||||
"items": [
|
||||
"/another/tags",
|
||||
"/another/tags2",
|
||||
],
|
||||
"label": "tag2",
|
||||
"pages": [
|
||||
{
|
||||
"items": [
|
||||
"/another/tags",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": "/blog/tags/tag-2/page/2",
|
||||
"page": 1,
|
||||
"permalink": "/blog/tags/tag-2",
|
||||
"postsPerPage": 1,
|
||||
"previousPage": undefined,
|
||||
"totalCount": 2,
|
||||
"totalPages": 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
"items": [
|
||||
"/another/tags2",
|
||||
],
|
||||
"metadata": {
|
||||
"blogDescription": "Blog",
|
||||
"blogTitle": "Blog",
|
||||
"nextPage": undefined,
|
||||
"page": 2,
|
||||
"permalink": "/blog/tags/tag-2/page/2",
|
||||
"postsPerPage": 1,
|
||||
"previousPage": "/blog/tags/tag-2",
|
||||
"totalCount": 2,
|
||||
"totalPages": 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
"permalink": "/blog/tags/tag-2",
|
||||
"unlisted": false,
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`blog plugin process blog posts load content 2`] = `
|
||||
[
|
||||
{
|
||||
"content": "simple url slug",
|
||||
"id": "/simple/slug/another",
|
||||
"metadata": {
|
||||
"authors": [
|
||||
{
|
||||
"imageURL": undefined,
|
||||
"name": "Sébastien Lorber",
|
||||
"title": "Docusaurus maintainer",
|
||||
"url": "https://sebastienlorber.com",
|
||||
},
|
||||
],
|
||||
"date": 2020-08-15T00:00:00.000Z,
|
||||
"description": "simple url slug",
|
||||
"editUrl": "https://baseEditUrl.com/edit/blog/another-simple-slug-with-tags.md",
|
||||
"frontMatter": {
|
||||
"author": "Sébastien Lorber",
|
||||
"author_title": "Docusaurus maintainer",
|
||||
"author_url": "https://sebastienlorber.com",
|
||||
"date": 2020-08-15T00:00:00.000Z,
|
||||
"slug": "/simple/slug/another",
|
||||
"tags": [
|
||||
"tag1",
|
||||
],
|
||||
"title": "Another Simple Slug",
|
||||
},
|
||||
"hasTruncateMarker": false,
|
||||
"nextItem": {
|
||||
"permalink": "/blog/another/tags",
|
||||
"title": "Another With Tag",
|
||||
},
|
||||
"permalink": "/blog/simple/slug/another",
|
||||
"readingTime": 0.015,
|
||||
"source": "@site/blog/another-simple-slug-with-tags.md",
|
||||
"tags": [
|
||||
{
|
||||
"label": "tag1",
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
},
|
||||
],
|
||||
"title": "Another Simple Slug",
|
||||
"unlisted": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"content": "with tag",
|
||||
"id": "/another/tags",
|
||||
"metadata": {
|
||||
"authors": [],
|
||||
"date": 2020-08-15T00:00:00.000Z,
|
||||
"description": "with tag",
|
||||
"editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags.md",
|
||||
"frontMatter": {
|
||||
"date": 2020-08-15T00:00:00.000Z,
|
||||
"slug": "/another/tags",
|
||||
"tags": [
|
||||
"tag1",
|
||||
"tag2",
|
||||
],
|
||||
"title": "Another With Tag",
|
||||
},
|
||||
"hasTruncateMarker": false,
|
||||
"nextItem": {
|
||||
"permalink": "/blog/another/tags2",
|
||||
"title": "Another With Tag",
|
||||
},
|
||||
"permalink": "/blog/another/tags",
|
||||
"prevItem": {
|
||||
"permalink": "/blog/simple/slug/another",
|
||||
"title": "Another Simple Slug",
|
||||
},
|
||||
"readingTime": 0.01,
|
||||
"source": "@site/blog/another-with-tags.md",
|
||||
"tags": [
|
||||
{
|
||||
"label": "tag1",
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
},
|
||||
{
|
||||
"label": "tag2",
|
||||
"permalink": "/blog/tags/tag-2",
|
||||
},
|
||||
],
|
||||
"title": "Another With Tag",
|
||||
"unlisted": false,
|
||||
},
|
||||
},
|
||||
{
|
||||
"content": "with tag",
|
||||
"id": "/another/tags2",
|
||||
"metadata": {
|
||||
"authors": [],
|
||||
"date": 2020-08-15T00:00:00.000Z,
|
||||
"description": "with tag",
|
||||
"editUrl": "https://baseEditUrl.com/edit/blog/another-with-tags2.md",
|
||||
"frontMatter": {
|
||||
"date": 2020-08-15T00:00:00.000Z,
|
||||
"slug": "/another/tags2",
|
||||
"tags": [
|
||||
"tag1",
|
||||
"tag2",
|
||||
],
|
||||
"title": "Another With Tag",
|
||||
},
|
||||
"hasTruncateMarker": false,
|
||||
"permalink": "/blog/another/tags2",
|
||||
"prevItem": {
|
||||
"permalink": "/blog/another/tags",
|
||||
"title": "Another With Tag",
|
||||
},
|
||||
"readingTime": 0.01,
|
||||
"source": "@site/blog/another-with-tags2.md",
|
||||
"tags": [
|
||||
{
|
||||
"label": "tag1",
|
||||
"permalink": "/blog/tags/tag-1",
|
||||
},
|
||||
{
|
||||
"label": "tag2",
|
||||
"permalink": "/blog/tags/tag-2",
|
||||
},
|
||||
],
|
||||
"title": "Another With Tag",
|
||||
"unlisted": false,
|
||||
},
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`blog plugin works on blog tags without pagination 1`] = `
|
||||
{
|
||||
"/blog/tags/tag-1": {
|
||||
|
|
|
@ -50,7 +50,6 @@ exports[`translateContent falls back when translation is incomplete 1`] = `
|
|||
"authors": [],
|
||||
"date": 2021-07-19T00:00:00.000Z,
|
||||
"description": "/blog/2021/06/19/hello",
|
||||
"formattedDate": "June 19, 2021",
|
||||
"frontMatter": {},
|
||||
"hasTruncateMarker": true,
|
||||
"permalink": "/blog/2021/06/19/hello",
|
||||
|
@ -94,7 +93,6 @@ exports[`translateContent returns translated loaded 1`] = `
|
|||
"authors": [],
|
||||
"date": 2021-07-19T00:00:00.000Z,
|
||||
"description": "/blog/2021/06/19/hello",
|
||||
"formattedDate": "June 19, 2021",
|
||||
"frontMatter": {},
|
||||
"hasTruncateMarker": true,
|
||||
"permalink": "/blog/2021/06/19/hello",
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
import {jest} from '@jest/globals';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import {fromPartial} from '@total-typescript/shoehorn';
|
||||
import {
|
||||
truncate,
|
||||
parseBlogFileName,
|
||||
linkify,
|
||||
getSourceToPermalink,
|
||||
paginateBlogPosts,
|
||||
applyProcessBlogPosts,
|
||||
type LinkifyParams,
|
||||
} from '../blogUtils';
|
||||
import type {BlogBrokenMarkdownLink, BlogContentPaths} from '../types';
|
||||
|
@ -236,7 +238,7 @@ describe('linkify', () => {
|
|||
hasTruncateMarker: false,
|
||||
frontMatter: {},
|
||||
authors: [],
|
||||
formattedDate: '',
|
||||
unlisted: false,
|
||||
},
|
||||
content: '',
|
||||
},
|
||||
|
@ -295,3 +297,81 @@ describe('linkify', () => {
|
|||
} as BlogBrokenMarkdownLink);
|
||||
});
|
||||
});
|
||||
|
||||
describe('processBlogPosts', () => {
|
||||
const blogPost2022: BlogPost = fromPartial({
|
||||
metadata: {date: new Date('2022-01-01')},
|
||||
});
|
||||
const blogPost2023: BlogPost = fromPartial({
|
||||
metadata: {date: new Date('2023-01-01')},
|
||||
});
|
||||
const blogPost2024: BlogPost = fromPartial({
|
||||
metadata: {date: new Date('2024-01-01')},
|
||||
});
|
||||
|
||||
it('filter blogs only from 2024', async () => {
|
||||
const processedBlogPosts = await applyProcessBlogPosts({
|
||||
blogPosts: [blogPost2022, blogPost2023, blogPost2024],
|
||||
processBlogPosts: async ({blogPosts}: {blogPosts: BlogPost[]}) =>
|
||||
blogPosts.filter(
|
||||
(blogPost) => blogPost.metadata.date.getFullYear() === 2024,
|
||||
),
|
||||
});
|
||||
|
||||
expect(processedBlogPosts).toEqual([blogPost2024]);
|
||||
});
|
||||
|
||||
it('sort blogs by date in ascending order', async () => {
|
||||
const processedBlogPosts = await applyProcessBlogPosts({
|
||||
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
|
||||
processBlogPosts: async ({blogPosts}: {blogPosts: BlogPost[]}) =>
|
||||
blogPosts.sort(
|
||||
(a, b) => a.metadata.date.getTime() - b.metadata.date.getTime(),
|
||||
),
|
||||
});
|
||||
|
||||
expect(processedBlogPosts).toEqual([
|
||||
blogPost2022,
|
||||
blogPost2023,
|
||||
blogPost2024,
|
||||
]);
|
||||
});
|
||||
|
||||
it('sort blogs by date in descending order', async () => {
|
||||
const processedBlogPosts = await applyProcessBlogPosts({
|
||||
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
|
||||
processBlogPosts: async ({blogPosts}: {blogPosts: BlogPost[]}) =>
|
||||
blogPosts.sort(
|
||||
(a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
|
||||
),
|
||||
});
|
||||
|
||||
expect(processedBlogPosts).toEqual([
|
||||
blogPost2024,
|
||||
blogPost2023,
|
||||
blogPost2022,
|
||||
]);
|
||||
});
|
||||
|
||||
it('processBlogPosts return 2022 only', async () => {
|
||||
const processedBlogPosts = await applyProcessBlogPosts({
|
||||
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
|
||||
processBlogPosts: async () => [blogPost2022],
|
||||
});
|
||||
|
||||
expect(processedBlogPosts).toEqual([blogPost2022]);
|
||||
});
|
||||
|
||||
it('processBlogPosts return undefined', async () => {
|
||||
const processedBlogPosts = await applyProcessBlogPosts({
|
||||
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
|
||||
processBlogPosts: async () => {},
|
||||
});
|
||||
|
||||
expect(processedBlogPosts).toEqual([
|
||||
blogPost2023,
|
||||
blogPost2022,
|
||||
blogPost2024,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -498,4 +498,31 @@ describe('blog plugin', () => {
|
|||
|
||||
expect(blogTags).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('process blog posts load content', async () => {
|
||||
const siteDir = path.join(
|
||||
__dirname,
|
||||
'__fixtures__',
|
||||
'website-blog-with-tags',
|
||||
);
|
||||
const plugin = await getPlugin(
|
||||
siteDir,
|
||||
{
|
||||
postsPerPage: 1,
|
||||
processBlogPosts: async ({blogPosts}) =>
|
||||
blogPosts.filter((blog) => blog.metadata.tags[0].label === 'tag1'),
|
||||
},
|
||||
DefaultI18N,
|
||||
);
|
||||
const {blogPosts, blogTags, blogListPaginated} =
|
||||
(await plugin.loadContent!())!;
|
||||
|
||||
expect(blogListPaginated).toHaveLength(3);
|
||||
|
||||
expect(Object.keys(blogTags)).toHaveLength(2);
|
||||
expect(blogTags).toMatchSnapshot();
|
||||
|
||||
expect(blogPosts).toHaveLength(3);
|
||||
expect(blogPosts).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,7 +29,6 @@ const sampleBlogPosts: BlogPost[] = [
|
|||
source: '/blog/2021/06/19/hello',
|
||||
description: '/blog/2021/06/19/hello',
|
||||
date: new Date(2021, 6, 19),
|
||||
formattedDate: 'June 19, 2021',
|
||||
tags: [],
|
||||
title: 'Hello',
|
||||
hasTruncateMarker: true,
|
||||
|
|
|
@ -422,3 +422,19 @@ export function linkify({
|
|||
|
||||
return newContent;
|
||||
}
|
||||
|
||||
export async function applyProcessBlogPosts({
|
||||
blogPosts,
|
||||
processBlogPosts,
|
||||
}: {
|
||||
blogPosts: BlogPost[];
|
||||
processBlogPosts: PluginOptions['processBlogPosts'];
|
||||
}): Promise<BlogPost[]> {
|
||||
const processedBlogPosts = await processBlogPosts({blogPosts});
|
||||
|
||||
if (Array.isArray(processedBlogPosts)) {
|
||||
return processedBlogPosts;
|
||||
}
|
||||
|
||||
return blogPosts;
|
||||
}
|
||||
|
|
|
@ -20,11 +20,12 @@ import {
|
|||
DEFAULT_PLUGIN_ID,
|
||||
} from '@docusaurus/utils';
|
||||
import {
|
||||
generateBlogPosts,
|
||||
getSourceToPermalink,
|
||||
getBlogTags,
|
||||
paginateBlogPosts,
|
||||
shouldBeListed,
|
||||
applyProcessBlogPosts,
|
||||
generateBlogPosts,
|
||||
} from './blogUtils';
|
||||
import footnoteIDFixer from './remark/footnoteIDFixer';
|
||||
import {translateContent, getTranslationFiles} from './translations';
|
||||
|
@ -113,7 +114,11 @@ export default async function pluginContentBlog(
|
|||
|
||||
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
||||
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
||||
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
||||
let blogPosts = await generateBlogPosts(contentPaths, context, options);
|
||||
blogPosts = await applyProcessBlogPosts({
|
||||
blogPosts,
|
||||
processBlogPosts: options.processBlogPosts,
|
||||
});
|
||||
const listedBlogPosts = blogPosts.filter(shouldBeListed);
|
||||
|
||||
if (!blogPosts.length) {
|
||||
|
|
|
@ -51,6 +51,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|||
authorsMapPath: 'authors.yml',
|
||||
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
|
||||
sortPosts: 'descending',
|
||||
processBlogPosts: async () => undefined,
|
||||
};
|
||||
|
||||
const PluginOptionSchema = Joi.object<PluginOptions>({
|
||||
|
@ -134,6 +135,9 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|||
sortPosts: Joi.string()
|
||||
.valid('descending', 'ascending')
|
||||
.default(DEFAULT_OPTIONS.sortPosts),
|
||||
processBlogPosts: Joi.function()
|
||||
.optional()
|
||||
.default(() => DEFAULT_OPTIONS.processBlogPosts),
|
||||
}).default(DEFAULT_OPTIONS);
|
||||
|
||||
export function validateOptions({
|
||||
|
|
|
@ -330,6 +330,11 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|||
defaultReadingTime: ReadingTimeFunction;
|
||||
},
|
||||
) => number | undefined;
|
||||
|
||||
export type ProcessBlogPostsFn = (params: {
|
||||
blogPosts: BlogPost[];
|
||||
}) => Promise<void | BlogPost[]>;
|
||||
|
||||
/**
|
||||
* Plugin options after normalization.
|
||||
*/
|
||||
|
@ -421,6 +426,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|||
readingTime: ReadingTimeFunctionOption;
|
||||
/** Governs the direction of blog post sorting. */
|
||||
sortPosts: 'ascending' | 'descending';
|
||||
/** An optional function which can be used to transform blog posts
|
||||
* (filter, modify, delete, etc...).
|
||||
*/
|
||||
processBlogPosts: ProcessBlogPostsFn;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -75,6 +75,7 @@ Accepted fields:
|
|||
| `feedOptions.copyright` | `string` | `undefined` | Copyright message. |
|
||||
| `feedOptions.language` | `string` (See [documentation](http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes) for possible values) | `undefined` | Language metadata of the feed. |
|
||||
| `sortPosts` | <code>'descending' \| 'ascending' </code> | `'descending'` | Governs the direction of blog post sorting. |
|
||||
| `processBlogPosts` | <code>[ProcessBlogPostsFn](#ProcessBlogPostsFn)</code> | `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). |
|
||||
|
||||
```mdx-code-block
|
||||
</APITable>
|
||||
|
@ -131,6 +132,14 @@ type CreateFeedItemsFn = (params: {
|
|||
}) => Promise<BlogFeedItem[]>;
|
||||
```
|
||||
|
||||
#### `ProcessBlogPostsFn` {#ProcessBlogPostsFn}
|
||||
|
||||
```ts
|
||||
type ProcessBlogPostsFn = (params: {
|
||||
blogPosts: BlogPost[];
|
||||
}) => Promise<void | BlogPost[]>;
|
||||
```
|
||||
|
||||
### Example configuration {#ex-config}
|
||||
|
||||
You can configure this plugin through preset options or plugin options.
|
||||
|
|
|
@ -2977,6 +2977,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
|
||||
|
||||
"@total-typescript/shoehorn@^0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@total-typescript/shoehorn/-/shoehorn-0.1.2.tgz#a0c095ce8cb9b4ae3556bcff42702ddb072e9d18"
|
||||
integrity sha512-p7nNZbOZIofpDNyP0u1BctFbjxD44Qc+oO5jufgQdFdGIXJLc33QRloJpq7k5T59CTgLWfQSUxsuqLcmeurYRw==
|
||||
|
||||
"@trysound/sax@0.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
|
||||
|
|
Loading…
Add table
Reference in a new issue