@docusaurus/plugin-content-blog 2.0.0-beta.14 → 2.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/types.d.ts CHANGED
@@ -4,11 +4,9 @@
4
4
  * This source code is licensed under the MIT license found in the
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
- import type { RemarkAndRehypePluginOptions } from '@docusaurus/mdx-loader';
8
7
  import type { Tag } from '@docusaurus/utils';
9
8
  import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
10
- import { Overwrite } from 'utility-types';
11
- import { BlogPostFrontMatter } from './blogFrontMatter';
9
+ import type { BlogPostFrontMatter, Author } from '@docusaurus/plugin-content-blog';
12
10
  export declare type BlogContentPaths = ContentPaths;
13
11
  export interface BlogContent {
14
12
  blogSidebarTitle: string;
@@ -17,71 +15,6 @@ export interface BlogContent {
17
15
  blogTags: BlogTags;
18
16
  blogTagsListPath: string | null;
19
17
  }
20
- export declare type FeedType = 'rss' | 'atom' | 'json';
21
- export declare type FeedOptions = {
22
- type?: FeedType[] | null;
23
- title?: string;
24
- description?: string;
25
- copyright: string;
26
- language?: string;
27
- };
28
- export declare type UserFeedOptions = Overwrite<Partial<FeedOptions>, {
29
- type?: FeedOptions['type'] | 'all';
30
- }>;
31
- export declare type EditUrlFunction = (editUrlParams: {
32
- blogDirPath: string;
33
- blogPath: string;
34
- permalink: string;
35
- locale: string;
36
- }) => string | undefined;
37
- declare type ReadingTimeOptions = {
38
- wordsPerMinute?: number;
39
- wordBound?: (char: string) => boolean;
40
- };
41
- export declare type ReadingTimeFunction = (params: {
42
- content: string;
43
- frontMatter?: BlogPostFrontMatter & Record<string, unknown>;
44
- options?: ReadingTimeOptions;
45
- }) => number;
46
- export declare type ReadingTimeFunctionOption = (params: Required<Omit<Parameters<ReadingTimeFunction>[0], 'options'>> & {
47
- defaultReadingTime: ReadingTimeFunction;
48
- }) => number | undefined;
49
- export declare type PluginOptions = RemarkAndRehypePluginOptions & {
50
- id?: string;
51
- path: string;
52
- routeBasePath: string;
53
- tagsBasePath: string;
54
- archiveBasePath: string;
55
- include: string[];
56
- exclude: string[];
57
- postsPerPage: number | 'ALL';
58
- blogListComponent: string;
59
- blogPostComponent: string;
60
- blogTagsListComponent: string;
61
- blogTagsPostsComponent: string;
62
- blogTitle: string;
63
- blogDescription: string;
64
- blogSidebarCount: number | 'ALL';
65
- blogSidebarTitle: string;
66
- truncateMarker: RegExp;
67
- showReadingTime: boolean;
68
- feedOptions: {
69
- type?: FeedType[] | null;
70
- title?: string;
71
- description?: string;
72
- copyright: string;
73
- language?: string;
74
- };
75
- editUrl?: string | EditUrlFunction;
76
- editLocalizedFiles?: boolean;
77
- admonitions: Record<string, unknown>;
78
- authorsMapPath: string;
79
- readingTime: ReadingTimeFunctionOption;
80
- sortPosts: 'ascending' | 'descending';
81
- };
82
- export declare type UserPluginOptions = Overwrite<Partial<PluginOptions>, {
83
- feedOptions?: UserFeedOptions;
84
- }>;
85
18
  export interface BlogTags {
86
19
  [key: string]: BlogTag;
87
20
  }
@@ -110,12 +43,6 @@ export interface BlogPaginated {
110
43
  metadata: BlogPaginatedMetadata;
111
44
  items: string[];
112
45
  }
113
- export interface Author extends Record<string, unknown> {
114
- name?: string;
115
- imageURL?: string;
116
- url?: string;
117
- title?: string;
118
- }
119
46
  export interface MetaData {
120
47
  permalink: string;
121
48
  source: string;
@@ -130,10 +57,7 @@ export interface MetaData {
130
57
  truncated: boolean;
131
58
  editUrl?: string;
132
59
  authors: Author[];
133
- }
134
- export interface Assets {
135
- image?: string;
136
- authorsImageUrls: (string | undefined)[];
60
+ frontMatter: BlogPostFrontMatter & Record<string, unknown>;
137
61
  }
138
62
  export interface Paginator {
139
63
  title: string;
@@ -160,4 +84,3 @@ export declare type BlogMarkdownLoaderOptions = {
160
84
  sourceToPermalink: Record<string, string>;
161
85
  onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
162
86
  };
163
- export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "2.0.0-beta.14",
3
+ "version": "2.0.0-beta.15",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
6
  "types": "src/plugin-content-blog.d.ts",
@@ -18,17 +18,15 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "@docusaurus/core": "2.0.0-beta.14",
22
- "@docusaurus/logger": "2.0.0-beta.14",
23
- "@docusaurus/mdx-loader": "2.0.0-beta.14",
24
- "@docusaurus/utils": "2.0.0-beta.14",
25
- "@docusaurus/utils-validation": "2.0.0-beta.14",
26
- "escape-string-regexp": "^4.0.0",
21
+ "@docusaurus/core": "2.0.0-beta.15",
22
+ "@docusaurus/logger": "2.0.0-beta.15",
23
+ "@docusaurus/mdx-loader": "2.0.0-beta.15",
24
+ "@docusaurus/utils": "2.0.0-beta.15",
25
+ "@docusaurus/utils-common": "2.0.0-beta.15",
26
+ "@docusaurus/utils-validation": "2.0.0-beta.15",
27
+ "cheerio": "^1.0.0-rc.10",
27
28
  "feed": "^4.2.2",
28
29
  "fs-extra": "^10.0.0",
29
- "globby": "^11.0.2",
30
- "js-yaml": "^4.0.0",
31
- "loader-utils": "^2.0.0",
32
30
  "lodash": "^4.17.20",
33
31
  "reading-time": "^1.5.0",
34
32
  "remark-admonitions": "^1.2.1",
@@ -37,7 +35,8 @@
37
35
  "webpack": "^5.61.0"
38
36
  },
39
37
  "devDependencies": {
40
- "@docusaurus/types": "2.0.0-beta.14"
38
+ "@docusaurus/types": "2.0.0-beta.15",
39
+ "escape-string-regexp": "^4.0.0"
41
40
  },
42
41
  "peerDependencies": {
43
42
  "react": "^16.8.4 || ^17.0.0",
@@ -46,5 +45,5 @@
46
45
  "engines": {
47
46
  "node": ">=14"
48
47
  },
49
- "gitHead": "c4824a8937d8f1aa0806667749cbc74058e2b294"
48
+ "gitHead": "6cfad16436c07d8d11e5c2e1486dc59afd483e33"
50
49
  }
package/src/authors.ts CHANGED
@@ -5,92 +5,48 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import fs from 'fs-extra';
9
- import logger from '@docusaurus/logger';
10
- import path from 'path';
11
- import {Author, BlogContentPaths} from './types';
12
- import {findFolderContainingFile} from '@docusaurus/utils';
8
+ import type {BlogContentPaths} from './types';
9
+ import {getDataFileData} from '@docusaurus/utils';
13
10
  import {Joi, URISchema} from '@docusaurus/utils-validation';
14
- import {
11
+ import type {
12
+ Author,
15
13
  BlogPostFrontMatter,
16
14
  BlogPostFrontMatterAuthor,
17
15
  BlogPostFrontMatterAuthors,
18
- } from './blogFrontMatter';
19
- import {getContentPathList} from './blogUtils';
20
- import Yaml from 'js-yaml';
16
+ } from '@docusaurus/plugin-content-blog';
21
17
 
22
18
  export type AuthorsMap = Record<string, Author>;
23
19
 
24
20
  const AuthorsMapSchema = Joi.object<AuthorsMap>().pattern(
25
21
  Joi.string(),
26
22
  Joi.object({
27
- name: Joi.string().required(),
23
+ name: Joi.string(),
28
24
  url: URISchema,
29
25
  imageURL: URISchema,
30
26
  title: Joi.string(),
31
27
  })
32
28
  .rename('image_url', 'imageURL')
29
+ .or('name', 'imageURL')
33
30
  .unknown()
34
31
  .required(),
35
32
  );
36
33
 
37
- export function validateAuthorsMapFile(content: unknown): AuthorsMap {
34
+ export function validateAuthorsMap(content: unknown): AuthorsMap {
38
35
  return Joi.attempt(content, AuthorsMapSchema);
39
36
  }
40
37
 
41
- export async function readAuthorsMapFile(
42
- filePath: string,
43
- ): Promise<AuthorsMap | undefined> {
44
- if (await fs.pathExists(filePath)) {
45
- const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
46
- try {
47
- const unsafeContent = Yaml.load(contentString);
48
- return validateAuthorsMapFile(unsafeContent);
49
- } catch (e) {
50
- // TODO replace later by error cause: see https://v8.dev/features/error-cause
51
- logger.error('The author list file looks invalid!');
52
- throw e;
53
- }
54
- }
55
- return undefined;
56
- }
57
-
58
- type AuthorsMapParams = {
38
+ export async function getAuthorsMap(params: {
59
39
  authorsMapPath: string;
60
40
  contentPaths: BlogContentPaths;
61
- };
62
-
63
- export async function getAuthorsMapFilePath({
64
- authorsMapPath,
65
- contentPaths,
66
- }: AuthorsMapParams): Promise<string | undefined> {
67
- // Useful to load an eventually localize authors map
68
- const contentPath = await findFolderContainingFile(
69
- getContentPathList(contentPaths),
70
- authorsMapPath,
41
+ }): Promise<AuthorsMap | undefined> {
42
+ return getDataFileData(
43
+ {
44
+ filePath: params.authorsMapPath,
45
+ contentPaths: params.contentPaths,
46
+ fileType: 'authors map',
47
+ },
48
+ validateAuthorsMap,
71
49
  );
72
-
73
- if (contentPath) {
74
- return path.join(contentPath, authorsMapPath);
75
- }
76
-
77
- return undefined;
78
- }
79
-
80
- export async function getAuthorsMap(
81
- params: AuthorsMapParams,
82
- ): Promise<AuthorsMap | undefined> {
83
- const filePath = await getAuthorsMapFilePath(params);
84
- if (!filePath) {
85
- return undefined;
86
- }
87
- try {
88
- return await readAuthorsMapFile(filePath);
89
- } catch (e) {
90
- // TODO replace later by error cause, see https://v8.dev/features/error-cause
91
- logger.error`Couldn't read blog authors map at path=${filePath}`;
92
- throw e;
93
- }
94
50
  }
95
51
 
96
52
  type AuthorsParam = {
@@ -98,7 +54,7 @@ type AuthorsParam = {
98
54
  authorsMap: AuthorsMap | undefined;
99
55
  };
100
56
 
101
- // Legacy v1/early-v2 frontmatter fields
57
+ // Legacy v1/early-v2 front matter fields
102
58
  // We may want to deprecate those in favor of using only frontMatter.authors
103
59
  function getFrontMatterAuthorLegacy(
104
60
  frontMatter: BlogPostFrontMatter,
@@ -108,7 +64,6 @@ function getFrontMatterAuthorLegacy(
108
64
  const url = frontMatter.author_url ?? frontMatter.authorURL;
109
65
  const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
110
66
 
111
- // Shouldn't we require at least an author name?
112
67
  if (name || title || url || imageURL) {
113
68
  return {
114
69
  name,
@@ -168,7 +123,7 @@ ${Object.keys(authorsMap)
168
123
 
169
124
  function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author {
170
125
  return {
171
- // Author def from authorsMap can be locally overridden by frontmatter
126
+ // Author def from authorsMap can be locally overridden by front matter
172
127
  ...getAuthorsMapAuthor(frontMatterAuthor.key),
173
128
  ...frontMatterAuthor,
174
129
  };
@@ -182,11 +137,11 @@ export function getBlogPostAuthors(params: AuthorsParam): Author[] {
182
137
  const authors = getFrontMatterAuthors(params);
183
138
 
184
139
  if (authorLegacy) {
185
- // Technically, we could allow mixing legacy/authors frontmatter, but do we really want to?
140
+ // Technically, we could allow mixing legacy/authors front matter, but do we really want to?
186
141
  if (authors.length > 0) {
187
142
  throw new Error(
188
- `To declare blog post authors, use the 'authors' FrontMatter in priority.
189
- Don't mix 'authors' with other existing 'author_*' FrontMatter. Choose one or the other, not both at the same time.`,
143
+ `To declare blog post authors, use the 'authors' front matter in priority.
144
+ Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`,
190
145
  );
191
146
  }
192
147
  return [authorLegacy];
@@ -6,27 +6,13 @@
6
6
  */
7
7
 
8
8
  import {
9
- JoiFrontMatter as Joi, // Custom instance for frontmatter
9
+ JoiFrontMatter as Joi, // Custom instance for front matter
10
10
  URISchema,
11
11
  validateFrontMatter,
12
12
  FrontMatterTagsSchema,
13
13
  FrontMatterTOCHeadingLevels,
14
14
  } from '@docusaurus/utils-validation';
15
- import type {FrontMatterTag} from '@docusaurus/utils';
16
-
17
- export type BlogPostFrontMatterAuthor = Record<string, unknown> & {
18
- key?: string;
19
- name?: string;
20
- imageURL?: string;
21
- url?: string;
22
- title?: string;
23
- };
24
-
25
- // All the possible variants that the user can use for convenience
26
- export type BlogPostFrontMatterAuthors =
27
- | string
28
- | BlogPostFrontMatterAuthor
29
- | (string | BlogPostFrontMatterAuthor)[];
15
+ import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
30
16
 
31
17
  const BlogPostFrontMatterAuthorSchema = Joi.object({
32
18
  key: Joi.string(),
@@ -35,40 +21,9 @@ const BlogPostFrontMatterAuthorSchema = Joi.object({
35
21
  url: URISchema,
36
22
  imageURL: Joi.string(),
37
23
  })
38
- .or('key', 'name')
24
+ .or('key', 'name', 'imageURL')
39
25
  .rename('image_url', 'imageURL', {alias: true});
40
26
 
41
- export type BlogPostFrontMatter = {
42
- id?: string;
43
- title?: string;
44
- description?: string;
45
- tags?: FrontMatterTag[];
46
- slug?: string;
47
- draft?: boolean;
48
- date?: Date | string; // Yaml automagically convert some string patterns as Date, but not all
49
-
50
- authors?: BlogPostFrontMatterAuthors;
51
-
52
- // We may want to deprecate those older author frontmatter fields later:
53
- author?: string;
54
- author_title?: string;
55
- author_url?: string;
56
- author_image_url?: string;
57
-
58
- /** @deprecated */
59
- authorTitle?: string;
60
- /** @deprecated */
61
- authorURL?: string;
62
- /** @deprecated */
63
- authorImageURL?: string;
64
-
65
- image?: string;
66
- keywords?: string[];
67
- hide_table_of_contents?: boolean;
68
- toc_min_heading_level?: number;
69
- toc_max_heading_level?: number;
70
- };
71
-
72
27
  const FrontMatterAuthorErrorMessage =
73
28
  '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).';
74
29
 
@@ -80,7 +35,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
80
35
  draft: Joi.boolean(),
81
36
  date: Joi.date().raw(),
82
37
 
83
- // New multi-authors frontmatter:
38
+ // New multi-authors front matter:
84
39
  authors: Joi.alternatives()
85
40
  .try(
86
41
  Joi.string(),
@@ -95,7 +50,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
95
50
  .messages({
96
51
  'alternatives.match': FrontMatterAuthorErrorMessage,
97
52
  }),
98
- // Legacy author frontmatter
53
+ // Legacy author front matter
99
54
  author: Joi.string(),
100
55
  author_title: Joi.string(),
101
56
  author_url: URISchema,
package/src/blogUtils.ts CHANGED
@@ -9,16 +9,14 @@ import fs from 'fs-extra';
9
9
  import path from 'path';
10
10
  import readingTime from 'reading-time';
11
11
  import {keyBy, mapValues} from 'lodash';
12
- import {
13
- PluginOptions,
12
+ import type {
14
13
  BlogPost,
15
14
  BlogContentPaths,
16
15
  BlogMarkdownLoaderOptions,
17
16
  BlogTags,
18
- ReadingTimeFunction,
19
17
  } from './types';
20
18
  import {
21
- parseMarkdownFile,
19
+ parseMarkdownString,
22
20
  normalizeUrl,
23
21
  aliasedSitePath,
24
22
  getEditUrl,
@@ -28,11 +26,16 @@ import {
28
26
  Globby,
29
27
  normalizeFrontMatterTags,
30
28
  groupTaggedItems,
29
+ getContentPathList,
31
30
  } from '@docusaurus/utils';
32
- import {LoadContext} from '@docusaurus/types';
31
+ import type {LoadContext} from '@docusaurus/types';
33
32
  import {validateBlogPostFrontMatter} from './blogFrontMatter';
34
- import {AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
33
+ import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
35
34
  import logger from '@docusaurus/logger';
35
+ import type {
36
+ PluginOptions,
37
+ ReadingTimeFunction,
38
+ } from '@docusaurus/plugin-content-blog';
36
39
 
37
40
  export function truncate(fileString: string, truncateMarker: RegExp): string {
38
41
  return fileString.split(truncateMarker, 1).shift()!;
@@ -60,7 +63,7 @@ export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
60
63
  }
61
64
 
62
65
  const DATE_FILENAME_REGEX =
63
- /^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
66
+ /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
64
67
 
65
68
  type ParsedBlogFileName = {
66
69
  date: Date | undefined;
@@ -73,12 +76,11 @@ export function parseBlogFileName(
73
76
  ): ParsedBlogFileName {
74
77
  const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
75
78
  if (dateFilenameMatch) {
76
- const dateString = dateFilenameMatch.groups!.date!;
77
- const text = dateFilenameMatch.groups!.text!;
79
+ const {folder, text, date: dateString} = dateFilenameMatch.groups!;
78
80
  // Always treat dates as UTC by adding the `Z`
79
81
  const date = new Date(`${dateString}Z`);
80
82
  const slugDate = dateString.replace(/-/g, '/');
81
- const slug = `/${slugDate}/${text}`;
83
+ const slug = `/${slugDate}/${folder}${text}`;
82
84
  return {date, text, slug};
83
85
  } else {
84
86
  const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
@@ -101,13 +103,22 @@ function formatBlogPostDate(locale: string, date: Date): string {
101
103
  }
102
104
 
103
105
  async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
104
- const result = await parseMarkdownFile(blogSourceAbsolute, {
105
- removeContentTitle: true,
106
- });
107
- return {
108
- ...result,
109
- frontMatter: validateBlogPostFrontMatter(result.frontMatter),
110
- };
106
+ const markdownString = await fs.readFile(blogSourceAbsolute, 'utf-8');
107
+ try {
108
+ const result = parseMarkdownString(markdownString, {
109
+ removeContentTitle: true,
110
+ });
111
+ return {
112
+ ...result,
113
+ frontMatter: validateBlogPostFrontMatter(result.frontMatter),
114
+ };
115
+ } catch (e) {
116
+ throw new Error(
117
+ `Error while parsing blog post file ${blogSourceAbsolute}: "${
118
+ (e as Error).message
119
+ }".`,
120
+ );
121
+ }
111
122
  }
112
123
 
113
124
  const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
@@ -159,7 +170,12 @@ async function processBlogSourceFile(
159
170
  async function getDate(): Promise<Date> {
160
171
  // Prefer user-defined date.
161
172
  if (frontMatter.date) {
162
- return new Date(frontMatter.date);
173
+ if (typeof frontMatter.date === 'string') {
174
+ // Always treat dates as UTC by adding the `Z`
175
+ return new Date(`${frontMatter.date}Z`);
176
+ }
177
+ // YAML only converts YYYY-MM-DD to dates and leaves others as strings.
178
+ return frontMatter.date;
163
179
  } else if (parsedBlogFileName.date) {
164
180
  return parsedBlogFileName.date;
165
181
  }
@@ -234,6 +250,7 @@ async function processBlogSourceFile(
234
250
  : undefined,
235
251
  truncated: truncateMarker?.test(content) || false,
236
252
  authors,
253
+ frontMatter,
237
254
  },
238
255
  content,
239
256
  };
@@ -317,8 +334,3 @@ export function linkify({
317
334
 
318
335
  return newContent;
319
336
  }
320
-
321
- // Order matters: we look in priority in localized folder
322
- export function getContentPathList(contentPaths: BlogContentPaths): string[] {
323
- return [contentPaths.contentPathLocalized, contentPaths.contentPath];
324
- }
package/src/feed.ts CHANGED
@@ -5,34 +5,35 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import {Feed, Author as FeedAuthor, Item as FeedItem} from 'feed';
9
- import {PluginOptions, Author, BlogPost, FeedType} from './types';
10
- import {normalizeUrl, mdxToHtml} from '@docusaurus/utils';
11
- import {DocusaurusConfig} from '@docusaurus/types';
8
+ import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
9
+ import type {BlogPost} from './types';
10
+ import {
11
+ normalizeUrl,
12
+ posixPath,
13
+ mapAsyncSequential,
14
+ readOutputHTMLFile,
15
+ } from '@docusaurus/utils';
16
+ import cheerio from 'cheerio';
17
+ import type {DocusaurusConfig} from '@docusaurus/types';
12
18
  import path from 'path';
13
19
  import fs from 'fs-extra';
14
-
15
- // TODO this is temporary until we handle mdxToHtml better
16
- // It's hard to convert reliably JSX/require calls to an html feed content
17
- // See https://github.com/facebook/docusaurus/issues/5664
18
- function mdxToFeedContent(mdxContent: string): string | undefined {
19
- try {
20
- return mdxToHtml(mdxContent);
21
- } catch (e) {
22
- // TODO will we need a plugin option to configure how to handle such an error
23
- // Swallow the error on purpose for now, until we understand better the problem space
24
- return undefined;
25
- }
26
- }
27
-
28
- export async function generateBlogFeed({
20
+ import type {
21
+ FeedType,
22
+ PluginOptions,
23
+ Author,
24
+ } from '@docusaurus/plugin-content-blog';
25
+ import {blogPostContainerID} from '@docusaurus/utils-common';
26
+
27
+ async function generateBlogFeed({
29
28
  blogPosts,
30
29
  options,
31
30
  siteConfig,
31
+ outDir,
32
32
  }: {
33
33
  blogPosts: BlogPost[];
34
34
  options: PluginOptions;
35
35
  siteConfig: DocusaurusConfig;
36
+ outDir: string;
36
37
  }): Promise<Feed | null> {
37
38
  if (!blogPosts.length) {
38
39
  return null;
@@ -42,9 +43,7 @@ export async function generateBlogFeed({
42
43
  const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
43
44
  const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
44
45
 
45
- const updated =
46
- (blogPosts[0] && blogPosts[0].metadata.date) ||
47
- new Date('2015-10-25T16:29:00.000-07:00'); // weird legacy magic date
46
+ const updated = blogPosts[0] && blogPosts[0].metadata.date;
48
47
 
49
48
  const feed = new Feed({
50
49
  id: blogBaseUrl,
@@ -63,19 +62,35 @@ export async function generateBlogFeed({
63
62
  return {name: author.name, link: author.url};
64
63
  }
65
64
 
66
- blogPosts.forEach((post) => {
65
+ await mapAsyncSequential(blogPosts, async (post) => {
67
66
  const {
68
67
  id,
69
- metadata: {title: metadataTitle, permalink, date, description, authors},
68
+ metadata: {
69
+ title: metadataTitle,
70
+ permalink,
71
+ date,
72
+ description,
73
+ authors,
74
+ tags,
75
+ },
70
76
  } = post;
71
77
 
78
+ const content = await readOutputHTMLFile(
79
+ permalink.replace(siteConfig.baseUrl, ''),
80
+ outDir,
81
+ siteConfig.trailingSlash,
82
+ );
83
+ const $ = cheerio.load(content);
84
+
72
85
  const feedItem: FeedItem = {
73
86
  title: metadataTitle,
74
87
  id,
75
88
  link: normalizeUrl([siteUrl, permalink]),
76
89
  date,
77
90
  description,
78
- content: mdxToFeedContent(post.content),
91
+ // Atom feed demands the "term", while other feeds use "name"
92
+ category: tags.map((tag) => ({name: tag.label, term: tag.label})),
93
+ content: $(`#${blogPostContainerID}`).html()!,
79
94
  };
80
95
 
81
96
  // json1() method takes the first item of authors array
@@ -113,7 +128,10 @@ async function createBlogFeedFile({
113
128
  }
114
129
  })();
115
130
  try {
116
- await fs.outputFile(path.join(generatePath, feedPath), feedContent);
131
+ await fs.outputFile(
132
+ posixPath(path.join(generatePath, feedPath)),
133
+ feedContent,
134
+ );
117
135
  } catch (err) {
118
136
  throw new Error(`Generating ${feedType} feed failed: ${err}.`);
119
137
  }
@@ -130,7 +148,12 @@ export async function createBlogFeedFiles({
130
148
  siteConfig: DocusaurusConfig;
131
149
  outDir: string;
132
150
  }): Promise<void> {
133
- const feed = await generateBlogFeed({blogPosts, options, siteConfig});
151
+ const feed = await generateBlogFeed({
152
+ blogPosts,
153
+ options,
154
+ siteConfig,
155
+ outDir,
156
+ });
134
157
 
135
158
  const feedTypes = options.feedOptions.type;
136
159
  if (!feed || !feedTypes) {