diff --git a/packages/docusaurus-plugin-content-blog/package.json b/packages/docusaurus-plugin-content-blog/package.json index e01d774d0b91..e355e56b583a 100644 --- a/packages/docusaurus-plugin-content-blog/package.json +++ b/packages/docusaurus-plugin-content-blog/package.json @@ -55,5 +55,8 @@ }, "engines": { "node": ">=18.0" + }, + "devDependencies": { + "@total-typescript/shoehorn": "^0.1.2" } } diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap index b884dddfc605..441e7e6ef590 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/index.test.ts.snap @@ -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": { diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap index 6c2eb19ac42e..2a19730b60f0 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__snapshots__/translations.test.ts.snap @@ -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", diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts index d96f1228c897..f6e34977e158 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/blogUtils.test.ts @@ -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, + ]); + }); +}); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index 7ffb8862518d..73ecb63a2567 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -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(); + }); }); diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts index 0ec7471cfa35..8de26a3fb05b 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/translations.test.ts @@ -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, diff --git a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts index e0c6de3b06fc..fd544db14f41 100644 --- a/packages/docusaurus-plugin-content-blog/src/blogUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/blogUtils.ts @@ -422,3 +422,19 @@ export function linkify({ return newContent; } + +export async function applyProcessBlogPosts({ + blogPosts, + processBlogPosts, +}: { + blogPosts: BlogPost[]; + processBlogPosts: PluginOptions['processBlogPosts']; +}): Promise { + const processedBlogPosts = await processBlogPosts({blogPosts}); + + if (Array.isArray(processedBlogPosts)) { + return processedBlogPosts; + } + + return blogPosts; +} diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index 35d6ac6b34db..eba91487779c 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -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) { diff --git a/packages/docusaurus-plugin-content-blog/src/options.ts b/packages/docusaurus-plugin-content-blog/src/options.ts index d1e6e9cc4547..62e2a36c94d9 100644 --- a/packages/docusaurus-plugin-content-blog/src/options.ts +++ b/packages/docusaurus-plugin-content-blog/src/options.ts @@ -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({ @@ -134,6 +135,9 @@ const PluginOptionSchema = Joi.object({ sortPosts: Joi.string() .valid('descending', 'ascending') .default(DEFAULT_OPTIONS.sortPosts), + processBlogPosts: Joi.function() + .optional() + .default(() => DEFAULT_OPTIONS.processBlogPosts), }).default(DEFAULT_OPTIONS); export function validateOptions({ diff --git a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts index 6506e5dc3f95..898ed8b2e92f 100644 --- a/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts +++ b/packages/docusaurus-plugin-content-blog/src/plugin-content-blog.d.ts @@ -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; + /** * 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; }; /** diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx index d970929efbfc..1d23ddafe6b1 100644 --- a/website/docs/api/plugins/plugin-content-blog.mdx +++ b/website/docs/api/plugins/plugin-content-blog.mdx @@ -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` | 'descending' \| 'ascending' | `'descending'` | Governs the direction of blog post sorting. | +| `processBlogPosts` | [ProcessBlogPostsFn](#ProcessBlogPostsFn) | `undefined` | An optional function which can be used to transform blog posts (filter, modify, delete, etc...). | ```mdx-code-block @@ -131,6 +132,14 @@ type CreateFeedItemsFn = (params: { }) => Promise; ``` +#### `ProcessBlogPostsFn` {#ProcessBlogPostsFn} + +```ts +type ProcessBlogPostsFn = (params: { + blogPosts: BlogPost[]; +}) => Promise; +``` + ### Example configuration {#ex-config} You can configure this plugin through preset options or plugin options. diff --git a/yarn.lock b/yarn.lock index d4988ed19774..3e906c4553a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"