Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(blog): allow sorting blog posts through a options.sortPosts function hook #9840

Closed
wants to merge 16 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,32 @@ import {
getSourceToPermalink,
paginateBlogPosts,
type LinkifyParams,
sortBlogPosts,
} from '../blogUtils';
import type {BlogBrokenMarkdownLink, BlogContentPaths} from '../types';
import type {BlogPost} from '@docusaurus/plugin-content-blog';
import type {BlogPost, BlogPostMetadata} from '@docusaurus/plugin-content-blog';

const defaultValuesForOtherKeys: Omit<BlogPostMetadata, 'date'> = {
source: '',
title: '',
formattedDate: '',
permalink: '',
description: '',
hasTruncateMarker: false,
authors: [],
frontMatter: {},
tags: [],
unlisted: false,
};
const createBlogPost = (args: Partial<BlogPost>): BlogPost => ({
id: '',
metadata: {
date: new Date(),
...defaultValuesForOtherKeys,
...args.metadata,
},
content: args.content || '',
});

describe('truncate', () => {
it('truncates texts', () => {
Expand Down Expand Up @@ -214,6 +237,7 @@ describe('linkify', () => {
frontMatter: {},
authors: [],
formattedDate: '',
unlisted: false,
},
content: '',
},
Expand Down Expand Up @@ -272,3 +296,70 @@ describe('linkify', () => {
} as BlogBrokenMarkdownLink);
});
});

describe('blog sort', () => {
const blogPost2022 = createBlogPost({
metadata: {date: new Date('2022-12-14'), ...defaultValuesForOtherKeys},
});

const blogPost2023 = createBlogPost({
metadata: {date: new Date('2023-12-14'), ...defaultValuesForOtherKeys},
});
const blogPost2024 = createBlogPost({
metadata: {date: new Date('2024-12-14'), ...defaultValuesForOtherKeys},
});

it('sort blog posts by descending date no return', () => {
const sortedBlogPosts = sortBlogPosts({
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
sortPosts: 'descending',
});
expect(sortedBlogPosts).toEqual([blogPost2024, blogPost2023, blogPost2022]);
});

it('sort blog posts by ascending date no return', () => {
const sortedBlogPosts = sortBlogPosts({
blogPosts: [blogPost2023, blogPost2022, blogPost2024],
sortPosts: 'ascending',
});
expect(sortedBlogPosts).toEqual([blogPost2022, blogPost2023, blogPost2024]);
});

// it('sort blog posts by descending date with function return', () => {
// const sortedBlogPosts = sortBlogPosts({
// blogPosts: [blogPost2023, blogPost2022, blogPost2024],
// sortPosts: ({blogPosts}) =>
// [...blogPosts].sort(
// (a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
// ),
// });
// expect(sortedBlogPosts).toEqual([blogPost2024, blogPost2023, blogPost2022]);
// });

// it('sort blog posts by ascending date with function return', () => {
// const sortedBlogPosts = sortBlogPosts({
// blogPosts: [blogPost2023, blogPost2022, blogPost2024],
// sortPosts: ({blogPosts}) =>
// [...blogPosts].sort(
// (b, a) => b.metadata.date.getTime() - a.metadata.date.getTime(),
// ),
// });
// expect(sortedBlogPosts).toEqual([blogPost2022, blogPost2023, blogPost2024]);
// });

// it('sort blog posts with empty function', () => {
// const sortedBlogPosts = sortBlogPosts({
// blogPosts: [blogPost2023, blogPost2022, blogPost2024],
// sortPosts: () => {},
// });
// expect(sortedBlogPosts).toEqual([blogPost2023, blogPost2022, blogPost2024]);
// });

// it('sort blog posts with function return empty array', () => {
// const sortedBlogPosts = sortBlogPosts({
// blogPosts: [blogPost2023, blogPost2022, blogPost2024],
// sortPosts: () => [],
// });
// expect(sortedBlogPosts).toEqual([blogPost2023, blogPost2022, blogPost2024]);
// });
});
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
} as LoadContext,
{
path: 'invalid-blog-path',
sortPosts: 'descending',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
Expand Down Expand Up @@ -128,6 +129,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
} as LoadContext,
{
path: 'blog',
sortPosts: 'descending',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
Expand Down Expand Up @@ -171,6 +173,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
} as LoadContext,
{
path: 'blog',
sortPosts: 'descending',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
Expand Down Expand Up @@ -224,6 +227,7 @@ describe.each(['atom', 'rss', 'json'])('%s', (feedType) => {
} as LoadContext,
{
path: 'blog',
sortPosts: 'descending',
routeBasePath: 'blog',
tagsBasePath: 'tags',
authorsMapPath: 'authors.yml',
Expand Down
55 changes: 48 additions & 7 deletions packages/docusaurus-plugin-content-blog/src/blogUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ import type {
BlogPost,
BlogTags,
BlogPaginated,
Options,
SortPresets,
SortBlogPostsPreset,
} from '@docusaurus/plugin-content-blog';
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';

Expand Down Expand Up @@ -363,6 +366,46 @@ async function processBlogSourceFile(
};
}

const sortPresets: SortPresets = {
descending: ({blogPosts}) =>
blogPosts.sort(
(a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
),
ascending: ({blogPosts}) =>
blogPosts.sort(
(a, b) => a.metadata.date.getTime() - b.metadata.date.getTime(),
),
};

interface SortBlogPostsOptions {
blogPosts: BlogPost[];
sortPosts: SortBlogPostsPreset;
}

function getSortFunction(sortPosts: Options['sortPosts']) {
if (sortPosts === undefined) {
return sortPresets.descending;
}
const presetFn = sortPresets[sortPosts];
if (!presetFn) {
throw new Error(
`sortPosts preset ${sortPosts} does not exist, valid presets are: ${Object.keys(
sortPresets,
).join(', ')}`,
);
}
return presetFn;
}

export function sortBlogPosts({
blogPosts,
sortPosts,
}: SortBlogPostsOptions): BlogPost[] {
getSortFunction(sortPosts);

return blogPosts;
}

export async function generateBlogPosts(
contentPaths: BlogContentPaths,
context: LoadContext,
Expand Down Expand Up @@ -405,14 +448,12 @@ export async function generateBlogPosts(
await Promise.all(blogSourceFiles.map(doProcessBlogSourceFile))
).filter(Boolean) as BlogPost[];

blogPosts.sort(
(a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
);
const sortedPosts = sortBlogPosts({
blogPosts,
sortPosts: options.sortPosts,
});

if (options.sortPosts === 'ascending') {
return blogPosts.reverse();
}
return blogPosts;
return sortedPosts;
slorber marked this conversation as resolved.
Show resolved Hide resolved
}

export type LinkifyParams = {
Expand Down
11 changes: 10 additions & 1 deletion packages/docusaurus-plugin-content-blog/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,20 @@ export default async function pluginContentBlog(
blogDescription,
blogTitle,
blogSidebarTitle,
processPosts,
OzakIOne marked this conversation as resolved.
Show resolved Hide resolved
} = options;

const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
const blogPosts = await generateBlogPosts(contentPaths, context, options);
let blogPosts = await generateBlogPosts(contentPaths, context, options);
const processedBlogPosts = processPosts({
blogPosts,
});

if (processedBlogPosts !== undefined) {
blogPosts = processedBlogPosts;
}

const listedBlogPosts = blogPosts.filter(shouldBeListed);

if (!blogPosts.length) {
Expand Down
8 changes: 6 additions & 2 deletions packages/docusaurus-plugin-content-blog/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
authorsMapPath: 'authors.yml',
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
sortPosts: 'descending',
processPosts: () => undefined,
};

const PluginOptionSchema = Joi.object<PluginOptions>({
Expand Down Expand Up @@ -129,9 +130,12 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
}).default(DEFAULT_OPTIONS.feedOptions),
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
sortPosts: Joi.string()
.valid('descending', 'ascending')
sortPosts: Joi.alternatives()
.try(Joi.string().valid('descending', 'ascending'))
OzakIOne marked this conversation as resolved.
Show resolved Hide resolved
.default(DEFAULT_OPTIONS.sortPosts),
processPosts: Joi.function()
.optional()
.default(() => DEFAULT_OPTIONS.processPosts),
}).default(DEFAULT_OPTIONS);

export function validateOptions({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,16 @@ 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[];
}) => void | BlogPost[];

export type SortBlogPostsPreset = 'ascending' | 'descending';

// eslint-disable-next-line @typescript-eslint/consistent-indexed-object-style
export type SortPresets = Record<SortBlogPostsPreset, ProcessBlogPostsFn>;

/**
* Plugin options after normalization.
*/
Expand Down Expand Up @@ -418,7 +428,9 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
/** A callback to customize the reading time number displayed. */
readingTime: ReadingTimeFunctionOption;
/** Governs the direction of blog post sorting. */
sortPosts: 'ascending' | 'descending';
sortPosts: SortBlogPostsPreset;
/** TODO process blog posts. */
processPosts: ProcessBlogPostsFn;
};

/**
Expand Down
11 changes: 10 additions & 1 deletion website/docs/api/plugins/plugin-content-blog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ Accepted fields:
| `feedOptions.description` | `string` | <code>\`$\{siteConfig.title} Blog\`</code> | Description of the feed. |
| `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. |
| `sortPosts` | <code>'descending' \| 'ascending' \| </code> | `'descending'` | Governs the direction of blog post sorting. |
| `processPosts` | <code><a href="#ProcessBlogPostsFn">ProcessBlogPostsFn</a> </code> | `undefined` | Function which process blog posts (filter, modify, delete, etc...). |

```mdx-code-block
</APITable>
Expand Down Expand Up @@ -130,6 +131,14 @@ type CreateFeedItemsFn = (params: {
}) => Promise<BlogFeedItem[]>;
```

#### `ProcessBlogPostsFn` {#ProcessBlogPostsFn}

```ts
type ProcessBlogPostsFn = (params: {
blogPosts: BlogPost[];
}) => void | BlogPost[];
```

### Example configuration {#ex-config}

You can configure this plugin through preset options or plugin options.
Expand Down