feat(blog): add options.createFeedItems to filter/limit/transform feed items (#8378)

Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com>
This commit is contained in:
John Reilly 2022-12-29 12:31:32 +00:00 committed by GitHub
parent 0985fa0af3
commit 022e00554e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 249 additions and 8 deletions

File diff suppressed because one or more lines are too long

View file

@ -143,4 +143,56 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
).toMatchSnapshot();
fsMock.mockClear();
});
it('filters to the first two entries', async () => {
const siteDir = path.join(__dirname, '__fixtures__', 'website');
const outDir = path.join(siteDir, 'build-snap');
const siteConfig = {
title: 'Hello',
baseUrl: '/myBaseUrl/',
url: 'https://docusaurus.io',
favicon: 'image/favicon.ico',
};
// Build is quite difficult to mock, so we built the blog beforehand and
// copied the output to the fixture...
await testGenerateFeeds(
{
siteDir,
siteConfig,
i18n: DefaultI18N,
outDir,
} as LoadContext,
{
path: 'blog',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
include: DEFAULT_OPTIONS.include,
exclude: DEFAULT_OPTIONS.exclude,
feedOptions: {
type: [feedType],
copyright: 'Copyright',
createFeedItems: async (params) => {
const {blogPosts, defaultCreateFeedItems, ...rest} = params;
const blogPostsFiltered = blogPosts.filter(
(item, index) => index < 2,
);
return defaultCreateFeedItems({
blogPosts: blogPostsFiltered,
...rest,
});
},
},
readingTime: ({content, defaultReadingTime}) =>
defaultReadingTime({content}),
truncateMarker: /<!--\s*truncate\s*-->/,
} as PluginOptions,
);
expect(
fsMock.mock.calls.map((call) => call[1] as string),
).toMatchSnapshot();
fsMock.mockClear();
});
});

View file

@ -8,7 +8,7 @@
import path from 'path';
import fs from 'fs-extra';
import logger from '@docusaurus/logger';
import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
import {Feed, type Author as FeedAuthor} from 'feed';
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
import {blogPostContainerID} from '@docusaurus/utils-common';
import {load as cheerioLoad} from 'cheerio';
@ -18,6 +18,7 @@ import type {
PluginOptions,
Author,
BlogPost,
BlogFeedItem,
} from '@docusaurus/plugin-content-blog';
async function generateBlogFeed({
@ -54,11 +55,37 @@ async function generateBlogFeed({
copyright: feedOptions.copyright,
});
const createFeedItems =
options.feedOptions.createFeedItems ?? defaultCreateFeedItems;
const feedItems = await createFeedItems({
blogPosts,
siteConfig,
outDir,
defaultCreateFeedItems,
});
feedItems.forEach(feed.addItem);
return feed;
}
async function defaultCreateFeedItems({
blogPosts,
siteConfig,
outDir,
}: {
blogPosts: BlogPost[];
siteConfig: DocusaurusConfig;
outDir: string;
}): Promise<BlogFeedItem[]> {
const {url: siteUrl} = siteConfig;
function toFeedAuthor(author: Author): FeedAuthor {
return {name: author.name, link: author.url, email: author.email};
}
await Promise.all(
return Promise.all(
blogPosts.map(async (post) => {
const {
metadata: {
@ -79,7 +106,7 @@ async function generateBlogFeed({
const $ = cheerioLoad(content);
const link = normalizeUrl([siteUrl, permalink]);
const feedItem: FeedItem = {
const feedItem: BlogFeedItem = {
title: metadataTitle,
id: link,
link,
@ -99,9 +126,7 @@ async function generateBlogFeed({
return feedItem;
}),
).then((items) => items.forEach(feed.addItem));
return feed;
);
}
async function createBlogFeedFile({

View file

@ -122,6 +122,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
.default(DEFAULT_OPTIONS.feedOptions.copyright),
}),
language: Joi.string(),
createFeedItems: Joi.function(),
}).default(DEFAULT_OPTIONS.feedOptions),
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),

View file

@ -9,12 +9,19 @@ declare module '@docusaurus/plugin-content-blog' {
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
import type {MDXOptions} from '@docusaurus/mdx-loader';
import type {FrontMatterTag, Tag} from '@docusaurus/utils';
import type {Plugin, LoadContext} from '@docusaurus/types';
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
import type {Item as FeedItem} from 'feed';
import type {Overwrite} from 'utility-types';
export type Assets = {
/**
* If `metadata.image` is a collocated image path, this entry will be the
* If `metadata.yarn workspace website typecheck
4
yarn workspace v1.22.19yarn workspace website typecheck
4
yarn workspace v1.22.19yarn workspace website typecheck
4
yarn workspace v1.22.19image` is a collocated image path, this entry will be the
* bundler-generated image path. Otherwise, it's empty, and the image URL
* should be accessed through `frontMatter.image`.
*/
@ -263,6 +270,24 @@ declare module '@docusaurus/plugin-content-blog' {
copyright: string;
/** Language of the feed. */
language?: string;
/** Allow control over the construction of BlogFeedItems */
createFeedItems?: CreateFeedItemsFn;
};
type DefaultCreateFeedItemsParams = {
blogPosts: BlogPost[];
siteConfig: DocusaurusConfig;
outDir: string;
};
type CreateFeedItemsFn = (
params: CreateFeedItemsParams,
) => Promise<BlogFeedItem[]>;
type CreateFeedItemsParams = DefaultCreateFeedItemsParams & {
defaultCreateFeedItems: (
params: DefaultCreateFeedItemsParams,
) => Promise<BlogFeedItem[]>;
};
/**
@ -451,6 +476,8 @@ declare module '@docusaurus/plugin-content-blog' {
content: string;
};
export type BlogFeedItem = FeedItem;
export type BlogPaginatedMetadata = {
/** Title of the entire blog. */
readonly blogTitle: string;