@docusaurus/plugin-content-blog 2.0.0-beta.13 → 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.
Files changed (76) hide show
  1. package/lib/authors.d.ts +5 -8
  2. package/lib/authors.js +15 -53
  3. package/lib/blogFrontMatter.d.ts +1 -34
  4. package/lib/blogFrontMatter.js +3 -3
  5. package/lib/blogUtils.d.ts +3 -3
  6. package/lib/blogUtils.js +28 -22
  7. package/lib/feed.d.ts +3 -8
  8. package/lib/feed.js +46 -35
  9. package/lib/index.d.ts +4 -3
  10. package/lib/index.js +33 -28
  11. package/lib/markdownLoader.d.ts +1 -1
  12. package/lib/markdownLoader.js +1 -2
  13. package/lib/pluginOptionSchema.d.ts +1 -1
  14. package/lib/pluginOptionSchema.js +5 -3
  15. package/lib/translations.d.ts +2 -1
  16. package/lib/translations.js +3 -3
  17. package/lib/types.d.ts +2 -79
  18. package/package.json +11 -12
  19. package/src/authors.ts +22 -69
  20. package/src/blogFrontMatter.ts +5 -50
  21. package/src/blogUtils.ts +38 -34
  22. package/src/feed.ts +80 -37
  23. package/src/index.ts +48 -44
  24. package/src/markdownLoader.ts +2 -3
  25. package/src/plugin-content-blog.d.ts +147 -4
  26. package/src/pluginOptionSchema.ts +7 -5
  27. package/src/translations.ts +5 -4
  28. package/src/types.ts +5 -97
  29. package/lib/.tsbuildinfo +0 -1
  30. package/src/__tests__/__fixtures__/authorsMapFiles/authors.json +0 -29
  31. package/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +0 -27
  32. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.json +0 -5
  33. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.yml +0 -3
  34. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.json +0 -3
  35. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.yml +0 -2
  36. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.json +0 -8
  37. package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.yml +0 -3
  38. package/src/__tests__/__fixtures__/component/Typography.tsx +0 -6
  39. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathEmpty/empty +0 -0
  40. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson1/authors.json +0 -0
  41. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson2/authors.json +0 -0
  42. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathNestedYml/sub/folder/authors.yml +0 -0
  43. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml1/authors.yml +0 -0
  44. package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml2/authors.yml +0 -0
  45. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -8
  46. package/src/__tests__/__fixtures__/website/blog/_partials/somePartial.md +0 -3
  47. package/src/__tests__/__fixtures__/website/blog/_partials/subfolder/somePartial.md +0 -3
  48. package/src/__tests__/__fixtures__/website/blog/_somePartial.md +0 -3
  49. package/src/__tests__/__fixtures__/website/blog/authors.yml +0 -4
  50. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  51. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  52. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  53. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  54. package/src/__tests__/__fixtures__/website/blog/mdx-blog-post.mdx +0 -36
  55. package/src/__tests__/__fixtures__/website/blog/mdx-require-blog-post.mdx +0 -14
  56. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -11
  57. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  58. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  59. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  60. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -8
  61. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +0 -5
  62. package/src/__tests__/__fixtures__/website/static/img/docusaurus.png +0 -0
  63. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  64. package/src/__tests__/__snapshots__/feed.test.ts.snap +0 -164
  65. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  66. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  67. package/src/__tests__/__snapshots__/translations.test.ts.snap +0 -64
  68. package/src/__tests__/authors.test.ts +0 -608
  69. package/src/__tests__/blogFrontMatter.test.ts +0 -394
  70. package/src/__tests__/blogUtils.test.ts +0 -94
  71. package/src/__tests__/feed.test.ts +0 -126
  72. package/src/__tests__/index.test.ts +0 -408
  73. package/src/__tests__/linkify.test.ts +0 -93
  74. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  75. package/src/__tests__/translations.test.ts +0 -92
  76. package/tsconfig.json +0 -9
package/lib/authors.d.ts CHANGED
@@ -4,17 +4,14 @@
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 { Author, BlogContentPaths } from './types';
8
- import { BlogPostFrontMatter } from './blogFrontMatter';
7
+ import type { BlogContentPaths } from './types';
8
+ import type { Author, BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
9
9
  export declare type AuthorsMap = Record<string, Author>;
10
- export declare function validateAuthorsMapFile(content: unknown): AuthorsMap;
11
- export declare function readAuthorsMapFile(filePath: string): Promise<AuthorsMap | undefined>;
12
- declare type AuthorsMapParams = {
10
+ export declare function validateAuthorsMap(content: unknown): AuthorsMap;
11
+ export declare function getAuthorsMap(params: {
13
12
  authorsMapPath: string;
14
13
  contentPaths: BlogContentPaths;
15
- };
16
- export declare function getAuthorsMapFilePath({ authorsMapPath, contentPaths, }: AuthorsMapParams): Promise<string | undefined>;
17
- export declare function getAuthorsMap(params: AuthorsMapParams): Promise<AuthorsMap | undefined>;
14
+ }): Promise<AuthorsMap | undefined>;
18
15
  declare type AuthorsParam = {
19
16
  frontMatter: BlogPostFrontMatter;
20
17
  authorsMap: AuthorsMap | undefined;
package/lib/authors.js CHANGED
@@ -6,69 +6,32 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getBlogPostAuthors = exports.getAuthorsMap = exports.getAuthorsMapFilePath = exports.readAuthorsMapFile = exports.validateAuthorsMapFile = void 0;
10
- const tslib_1 = require("tslib");
11
- const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
12
- const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
13
- const path_1 = (0, tslib_1.__importDefault)(require("path"));
9
+ exports.getBlogPostAuthors = exports.getAuthorsMap = exports.validateAuthorsMap = void 0;
14
10
  const utils_1 = require("@docusaurus/utils");
15
11
  const utils_validation_1 = require("@docusaurus/utils-validation");
16
- const blogUtils_1 = require("./blogUtils");
17
- const js_yaml_1 = (0, tslib_1.__importDefault)(require("js-yaml"));
18
12
  const AuthorsMapSchema = utils_validation_1.Joi.object().pattern(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
19
- name: utils_validation_1.Joi.string().required(),
13
+ name: utils_validation_1.Joi.string(),
20
14
  url: utils_validation_1.URISchema,
21
15
  imageURL: utils_validation_1.URISchema,
22
16
  title: utils_validation_1.Joi.string(),
23
17
  })
24
18
  .rename('image_url', 'imageURL')
19
+ .or('name', 'imageURL')
25
20
  .unknown()
26
21
  .required());
27
- function validateAuthorsMapFile(content) {
22
+ function validateAuthorsMap(content) {
28
23
  return utils_validation_1.Joi.attempt(content, AuthorsMapSchema);
29
24
  }
30
- exports.validateAuthorsMapFile = validateAuthorsMapFile;
31
- async function readAuthorsMapFile(filePath) {
32
- if (await fs_extra_1.default.pathExists(filePath)) {
33
- const contentString = await fs_extra_1.default.readFile(filePath, { encoding: 'utf8' });
34
- try {
35
- const unsafeContent = js_yaml_1.default.load(contentString);
36
- return validateAuthorsMapFile(unsafeContent);
37
- }
38
- catch (e) {
39
- // TODO replace later by error cause: see https://v8.dev/features/error-cause
40
- console.error(chalk_1.default.red('The author list file looks invalid!'));
41
- throw e;
42
- }
43
- }
44
- return undefined;
45
- }
46
- exports.readAuthorsMapFile = readAuthorsMapFile;
47
- async function getAuthorsMapFilePath({ authorsMapPath, contentPaths, }) {
48
- // Useful to load an eventually localize authors map
49
- const contentPath = await (0, utils_1.findFolderContainingFile)((0, blogUtils_1.getContentPathList)(contentPaths), authorsMapPath);
50
- if (contentPath) {
51
- return path_1.default.join(contentPath, authorsMapPath);
52
- }
53
- return undefined;
54
- }
55
- exports.getAuthorsMapFilePath = getAuthorsMapFilePath;
25
+ exports.validateAuthorsMap = validateAuthorsMap;
56
26
  async function getAuthorsMap(params) {
57
- const filePath = await getAuthorsMapFilePath(params);
58
- if (!filePath) {
59
- return undefined;
60
- }
61
- try {
62
- return await readAuthorsMapFile(filePath);
63
- }
64
- catch (e) {
65
- // TODO replace later by error cause, see https://v8.dev/features/error-cause
66
- console.error(chalk_1.default.red(`Couldn't read blog authors map at path ${filePath}`));
67
- throw e;
68
- }
27
+ return (0, utils_1.getDataFileData)({
28
+ filePath: params.authorsMapPath,
29
+ contentPaths: params.contentPaths,
30
+ fileType: 'authors map',
31
+ }, validateAuthorsMap);
69
32
  }
70
33
  exports.getAuthorsMap = getAuthorsMap;
71
- // Legacy v1/early-v2 frontmatter fields
34
+ // Legacy v1/early-v2 front matter fields
72
35
  // We may want to deprecate those in favor of using only frontMatter.authors
73
36
  function getFrontMatterAuthorLegacy(frontMatter) {
74
37
  var _a, _b, _c;
@@ -76,7 +39,6 @@ function getFrontMatterAuthorLegacy(frontMatter) {
76
39
  const title = (_a = frontMatter.author_title) !== null && _a !== void 0 ? _a : frontMatter.authorTitle;
77
40
  const url = (_b = frontMatter.author_url) !== null && _b !== void 0 ? _b : frontMatter.authorURL;
78
41
  const imageURL = (_c = frontMatter.author_image_url) !== null && _c !== void 0 ? _c : frontMatter.authorImageURL;
79
- // Shouldn't we require at least an author name?
80
42
  if (name || title || url || imageURL) {
81
43
  return {
82
44
  name,
@@ -124,7 +86,7 @@ ${Object.keys(authorsMap)
124
86
  }
125
87
  function toAuthor(frontMatterAuthor) {
126
88
  return {
127
- // Author def from authorsMap can be locally overridden by frontmatter
89
+ // Author def from authorsMap can be locally overridden by front matter
128
90
  ...getAuthorsMapAuthor(frontMatterAuthor.key),
129
91
  ...frontMatterAuthor,
130
92
  };
@@ -135,10 +97,10 @@ function getBlogPostAuthors(params) {
135
97
  const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
136
98
  const authors = getFrontMatterAuthors(params);
137
99
  if (authorLegacy) {
138
- // Technically, we could allow mixing legacy/authors frontmatter, but do we really want to?
100
+ // Technically, we could allow mixing legacy/authors front matter, but do we really want to?
139
101
  if (authors.length > 0) {
140
- throw new Error(`To declare blog post authors, use the 'authors' FrontMatter in priority.
141
- Don't mix 'authors' with other existing 'author_*' FrontMatter. Choose one or the other, not both at the same time.`);
102
+ throw new Error(`To declare blog post authors, use the 'authors' front matter in priority.
103
+ Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`);
142
104
  }
143
105
  return [authorLegacy];
144
106
  }
@@ -4,38 +4,5 @@
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 { FrontMatterTag } from '@docusaurus/utils';
8
- export declare type BlogPostFrontMatterAuthor = Record<string, unknown> & {
9
- key?: string;
10
- name?: string;
11
- imageURL?: string;
12
- url?: string;
13
- title?: string;
14
- };
15
- export declare type BlogPostFrontMatterAuthors = string | BlogPostFrontMatterAuthor | (string | BlogPostFrontMatterAuthor)[];
16
- export declare type BlogPostFrontMatter = {
17
- id?: string;
18
- title?: string;
19
- description?: string;
20
- tags?: FrontMatterTag[];
21
- slug?: string;
22
- draft?: boolean;
23
- date?: Date | string;
24
- authors?: BlogPostFrontMatterAuthors;
25
- author?: string;
26
- author_title?: string;
27
- author_url?: string;
28
- author_image_url?: string;
29
- /** @deprecated */
30
- authorTitle?: string;
31
- /** @deprecated */
32
- authorURL?: string;
33
- /** @deprecated */
34
- authorImageURL?: string;
35
- image?: string;
36
- keywords?: string[];
37
- hide_table_of_contents?: boolean;
38
- toc_min_heading_level?: number;
39
- toc_max_heading_level?: number;
40
- };
7
+ import type { BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
41
8
  export declare function validateBlogPostFrontMatter(frontMatter: Record<string, unknown>): BlogPostFrontMatter;
@@ -15,7 +15,7 @@ const BlogPostFrontMatterAuthorSchema = utils_validation_1.JoiFrontMatter.object
15
15
  url: utils_validation_1.URISchema,
16
16
  imageURL: utils_validation_1.JoiFrontMatter.string(),
17
17
  })
18
- .or('key', 'name')
18
+ .or('key', 'name', 'imageURL')
19
19
  .rename('image_url', 'imageURL', { alias: true });
20
20
  const FrontMatterAuthorErrorMessage = '{{#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).';
21
21
  const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
@@ -25,7 +25,7 @@ const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
25
25
  tags: utils_validation_1.FrontMatterTagsSchema,
26
26
  draft: utils_validation_1.JoiFrontMatter.boolean(),
27
27
  date: utils_validation_1.JoiFrontMatter.date().raw(),
28
- // New multi-authors frontmatter:
28
+ // New multi-authors front matter:
29
29
  authors: utils_validation_1.JoiFrontMatter.alternatives()
30
30
  .try(utils_validation_1.JoiFrontMatter.string(), BlogPostFrontMatterAuthorSchema, utils_validation_1.JoiFrontMatter.array()
31
31
  .items(utils_validation_1.JoiFrontMatter.string(), BlogPostFrontMatterAuthorSchema)
@@ -36,7 +36,7 @@ const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
36
36
  .messages({
37
37
  'alternatives.match': FrontMatterAuthorErrorMessage,
38
38
  }),
39
- // Legacy author frontmatter
39
+ // Legacy author front matter
40
40
  author: utils_validation_1.JoiFrontMatter.string(),
41
41
  author_title: utils_validation_1.JoiFrontMatter.string(),
42
42
  author_url: utils_validation_1.URISchema,
@@ -4,8 +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 { PluginOptions, BlogPost, BlogContentPaths, BlogMarkdownLoaderOptions, BlogTags } from './types';
8
- import { LoadContext } from '@docusaurus/types';
7
+ import type { BlogPost, BlogContentPaths, BlogMarkdownLoaderOptions, BlogTags } from './types';
8
+ import type { LoadContext } from '@docusaurus/types';
9
+ import type { PluginOptions } from '@docusaurus/plugin-content-blog';
9
10
  export declare function truncate(fileString: string, truncateMarker: RegExp): string;
10
11
  export declare function getSourceToPermalink(blogPosts: BlogPost[]): Record<string, string>;
11
12
  export declare function getBlogTags(blogPosts: BlogPost[]): BlogTags;
@@ -21,5 +22,4 @@ export declare type LinkifyParams = {
21
22
  fileString: string;
22
23
  } & Pick<BlogMarkdownLoaderOptions, 'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'>;
23
24
  export declare function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }: LinkifyParams): string;
24
- export declare function getContentPathList(contentPaths: BlogContentPaths): string[];
25
25
  export {};
package/lib/blogUtils.js CHANGED
@@ -6,16 +6,16 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getContentPathList = exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.getSourceToPermalink = exports.truncate = void 0;
9
+ exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.getSourceToPermalink = exports.truncate = void 0;
10
10
  const tslib_1 = require("tslib");
11
11
  const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
12
- const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
13
12
  const path_1 = (0, tslib_1.__importDefault)(require("path"));
14
13
  const reading_time_1 = (0, tslib_1.__importDefault)(require("reading-time"));
15
14
  const lodash_1 = require("lodash");
16
15
  const utils_1 = require("@docusaurus/utils");
17
16
  const blogFrontMatter_1 = require("./blogFrontMatter");
18
17
  const authors_1 = require("./authors");
18
+ const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
19
19
  function truncate(fileString, truncateMarker) {
20
20
  return fileString.split(truncateMarker, 1).shift();
21
21
  }
@@ -33,16 +33,15 @@ function getBlogTags(blogPosts) {
33
33
  }));
34
34
  }
35
35
  exports.getBlogTags = getBlogTags;
36
- const DATE_FILENAME_REGEX = /^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
36
+ const DATE_FILENAME_REGEX = /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
37
37
  function parseBlogFileName(blogSourceRelative) {
38
38
  const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
39
39
  if (dateFilenameMatch) {
40
- const dateString = dateFilenameMatch.groups.date;
41
- const text = dateFilenameMatch.groups.text;
40
+ const { folder, text, date: dateString } = dateFilenameMatch.groups;
42
41
  // Always treat dates as UTC by adding the `Z`
43
42
  const date = new Date(`${dateString}Z`);
44
43
  const slugDate = dateString.replace(/-/g, '/');
45
- const slug = `/${slugDate}/${text}`;
44
+ const slug = `/${slugDate}/${folder}${text}`;
46
45
  return { date, text, slug };
47
46
  }
48
47
  else {
@@ -66,13 +65,19 @@ function formatBlogPostDate(locale, date) {
66
65
  }
67
66
  }
68
67
  async function parseBlogPostMarkdownFile(blogSourceAbsolute) {
69
- const result = await (0, utils_1.parseMarkdownFile)(blogSourceAbsolute, {
70
- removeContentTitle: true,
71
- });
72
- return {
73
- ...result,
74
- frontMatter: (0, blogFrontMatter_1.validateBlogPostFrontMatter)(result.frontMatter),
75
- };
68
+ const markdownString = await fs_extra_1.default.readFile(blogSourceAbsolute, 'utf-8');
69
+ try {
70
+ const result = (0, utils_1.parseMarkdownString)(markdownString, {
71
+ removeContentTitle: true,
72
+ });
73
+ return {
74
+ ...result,
75
+ frontMatter: (0, blogFrontMatter_1.validateBlogPostFrontMatter)(result.frontMatter),
76
+ };
77
+ }
78
+ catch (e) {
79
+ throw new Error(`Error while parsing blog post file ${blogSourceAbsolute}: "${e.message}".`);
80
+ }
76
81
  }
77
82
  const defaultReadingTime = ({ content, options }) => (0, reading_time_1.default)(content, options).minutes;
78
83
  async function processBlogSourceFile(blogSourceRelative, contentPaths, context, options, authorsMap) {
@@ -80,7 +85,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
80
85
  const { siteConfig: { baseUrl }, siteDir, i18n, } = context;
81
86
  const { routeBasePath, tagsBasePath: tagsRouteBasePath, truncateMarker, showReadingTime, editUrl, } = options;
82
87
  // Lookup in localized folder in priority
83
- const blogDirPath = await (0, utils_1.getFolderContainingFile)(getContentPathList(contentPaths), blogSourceRelative);
88
+ const blogDirPath = await (0, utils_1.getFolderContainingFile)((0, utils_1.getContentPathList)(contentPaths), blogSourceRelative);
84
89
  const blogSourceAbsolute = path_1.default.join(blogDirPath, blogSourceRelative);
85
90
  const { frontMatter, content, contentTitle, excerpt } = await parseBlogPostMarkdownFile(blogSourceAbsolute);
86
91
  const aliasedSource = (0, utils_1.aliasedSitePath)(blogSourceAbsolute, siteDir);
@@ -88,13 +93,18 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
88
93
  return undefined;
89
94
  }
90
95
  if (frontMatter.id) {
91
- console.warn(chalk_1.default.yellow(`"id" header option is deprecated in ${blogSourceRelative} file. Please use "slug" option instead.`));
96
+ logger_1.default.warn `name=${'id'} header option is deprecated in path=${blogSourceRelative} file. Please use name=${'slug'} option instead.`;
92
97
  }
93
98
  const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
94
99
  async function getDate() {
95
100
  // Prefer user-defined date.
96
101
  if (frontMatter.date) {
97
- return new Date(frontMatter.date);
102
+ if (typeof frontMatter.date === 'string') {
103
+ // Always treat dates as UTC by adding the `Z`
104
+ return new Date(`${frontMatter.date}Z`);
105
+ }
106
+ // YAML only converts YYYY-MM-DD to dates and leaves others as strings.
107
+ return frontMatter.date;
98
108
  }
99
109
  else if (parsedBlogFileName.date) {
100
110
  return parsedBlogFileName.date;
@@ -157,6 +167,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
157
167
  : undefined,
158
168
  truncated: (truncateMarker === null || truncateMarker === void 0 ? void 0 : truncateMarker.test(content)) || false,
159
169
  authors,
170
+ frontMatter,
160
171
  },
161
172
  content,
162
173
  };
@@ -179,7 +190,7 @@ async function generateBlogPosts(contentPaths, context, options) {
179
190
  return await processBlogSourceFile(blogSourceFile, contentPaths, context, options, authorsMap);
180
191
  }
181
192
  catch (e) {
182
- console.error(chalk_1.default.red(`Processing of blog source file failed for path "${blogSourceFile}"`));
193
+ logger_1.default.error `Processing of blog source file failed for path path=${blogSourceFile}.`;
183
194
  throw e;
184
195
  }
185
196
  }))).filter(Boolean);
@@ -202,8 +213,3 @@ function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalin
202
213
  return newContent;
203
214
  }
204
215
  exports.linkify = linkify;
205
- // Order matters: we look in priority in localized folder
206
- function getContentPathList(contentPaths) {
207
- return [contentPaths.contentPathLocalized, contentPaths.contentPath];
208
- }
209
- exports.getContentPathList = getContentPathList;
package/lib/feed.d.ts CHANGED
@@ -4,14 +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 { Feed } from 'feed';
8
- import { PluginOptions, BlogPost } from './types';
9
- import { DocusaurusConfig } from '@docusaurus/types';
10
- export declare function generateBlogFeed({ blogPosts, options, siteConfig, }: {
11
- blogPosts: BlogPost[];
12
- options: PluginOptions;
13
- siteConfig: DocusaurusConfig;
14
- }): Promise<Feed | null>;
7
+ import type { BlogPost } from './types';
8
+ import type { DocusaurusConfig } from '@docusaurus/types';
9
+ import type { PluginOptions } from '@docusaurus/plugin-content-blog';
15
10
  export declare function createBlogFeedFiles({ blogPosts, options, siteConfig, outDir, }: {
16
11
  blogPosts: BlogPost[];
17
12
  options: PluginOptions;
package/lib/feed.js CHANGED
@@ -6,34 +6,22 @@
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.createBlogFeedFiles = exports.generateBlogFeed = void 0;
9
+ exports.createBlogFeedFiles = void 0;
10
10
  const tslib_1 = require("tslib");
11
11
  const feed_1 = require("feed");
12
12
  const utils_1 = require("@docusaurus/utils");
13
+ const cheerio_1 = (0, tslib_1.__importDefault)(require("cheerio"));
13
14
  const path_1 = (0, tslib_1.__importDefault)(require("path"));
14
15
  const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
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) {
19
- try {
20
- return (0, utils_1.mdxToHtml)(mdxContent);
21
- }
22
- catch (e) {
23
- // TODO will we need a plugin option to configure how to handle such an error
24
- // Swallow the error on purpose for now, until we understand better the problem space
25
- return undefined;
26
- }
27
- }
28
- async function generateBlogFeed({ blogPosts, options, siteConfig, }) {
16
+ const utils_common_1 = require("@docusaurus/utils-common");
17
+ async function generateBlogFeed({ blogPosts, options, siteConfig, outDir, }) {
29
18
  if (!blogPosts.length) {
30
19
  return null;
31
20
  }
32
21
  const { feedOptions, routeBasePath } = options;
33
22
  const { url: siteUrl, baseUrl, title, favicon } = siteConfig;
34
23
  const blogBaseUrl = (0, utils_1.normalizeUrl)([siteUrl, baseUrl, routeBasePath]);
35
- const updated = (blogPosts[0] && blogPosts[0].metadata.date) ||
36
- new Date('2015-10-25T16:29:00.000-07:00'); // weird legacy magic date
24
+ const updated = blogPosts[0] && blogPosts[0].metadata.date;
37
25
  const feed = new feed_1.Feed({
38
26
  id: blogBaseUrl,
39
27
  title: feedOptions.title || `${title} Blog`,
@@ -49,42 +37,65 @@ async function generateBlogFeed({ blogPosts, options, siteConfig, }) {
49
37
  // RSS feed requires email to render authors
50
38
  return { name: author.name, link: author.url };
51
39
  }
52
- blogPosts.forEach((post) => {
53
- const { id, metadata: { title: metadataTitle, permalink, date, description, authors }, } = post;
54
- feed.addItem({
40
+ await (0, utils_1.mapAsyncSequential)(blogPosts, async (post) => {
41
+ const { id, metadata: { title: metadataTitle, permalink, date, description, authors, tags, }, } = post;
42
+ const content = await (0, utils_1.readOutputHTMLFile)(permalink.replace(siteConfig.baseUrl, ''), outDir, siteConfig.trailingSlash);
43
+ const $ = cheerio_1.default.load(content);
44
+ const feedItem = {
55
45
  title: metadataTitle,
56
46
  id,
57
47
  link: (0, utils_1.normalizeUrl)([siteUrl, permalink]),
58
48
  date,
59
49
  description,
60
- content: mdxToFeedContent(post.content),
61
- author: authors.map(toFeedAuthor),
62
- });
50
+ // Atom feed demands the "term", while other feeds use "name"
51
+ category: tags.map((tag) => ({ name: tag.label, term: tag.label })),
52
+ content: $(`#${utils_common_1.blogPostContainerID}`).html(),
53
+ };
54
+ // json1() method takes the first item of authors array
55
+ // it causes an error when authors array is empty
56
+ const feedItemAuthors = authors.map(toFeedAuthor);
57
+ if (feedItemAuthors.length > 0) {
58
+ feedItem.author = feedItemAuthors;
59
+ }
60
+ feed.addItem(feedItem);
63
61
  });
64
62
  return feed;
65
63
  }
66
- exports.generateBlogFeed = generateBlogFeed;
67
- async function createBlogFeedFile({ feed, feedType, filePath, }) {
68
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
64
+ async function createBlogFeedFile({ feed, feedType, generatePath, }) {
65
+ const [feedContent, feedPath] = (() => {
66
+ switch (feedType) {
67
+ case 'rss':
68
+ return [feed.rss2(), 'rss.xml'];
69
+ case 'json':
70
+ return [feed.json1(), 'feed.json'];
71
+ case 'atom':
72
+ return [feed.atom1(), 'atom.xml'];
73
+ default:
74
+ throw new Error(`Feed type ${feedType} not supported.`);
75
+ }
76
+ })();
69
77
  try {
70
- await fs_extra_1.default.outputFile(filePath, feedContent);
78
+ await fs_extra_1.default.outputFile((0, utils_1.posixPath)(path_1.default.join(generatePath, feedPath)), feedContent);
71
79
  }
72
80
  catch (err) {
73
81
  throw new Error(`Generating ${feedType} feed failed: ${err}.`);
74
82
  }
75
83
  }
76
84
  async function createBlogFeedFiles({ blogPosts, options, siteConfig, outDir, }) {
77
- const feed = await generateBlogFeed({ blogPosts, options, siteConfig });
85
+ const feed = await generateBlogFeed({
86
+ blogPosts,
87
+ options,
88
+ siteConfig,
89
+ outDir,
90
+ });
78
91
  const feedTypes = options.feedOptions.type;
79
92
  if (!feed || !feedTypes) {
80
93
  return;
81
94
  }
82
- await Promise.all(feedTypes.map(async (feedType) => {
83
- await createBlogFeedFile({
84
- feed,
85
- feedType,
86
- filePath: path_1.default.join(outDir, options.routeBasePath, `${feedType}.xml`),
87
- });
88
- }));
95
+ await Promise.all(feedTypes.map((feedType) => createBlogFeedFile({
96
+ feed,
97
+ feedType,
98
+ generatePath: path_1.default.join(outDir, options.routeBasePath),
99
+ })));
89
100
  }
90
101
  exports.createBlogFeedFiles = createBlogFeedFiles;
package/lib/index.d.ts CHANGED
@@ -4,7 +4,8 @@
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 { PluginOptions, BlogContent } from './types';
8
- import { LoadContext, Plugin, OptionValidationContext, ValidationResult } from '@docusaurus/types';
9
- export default function pluginContentBlog(context: LoadContext, options: PluginOptions): Plugin<BlogContent>;
7
+ import type { BlogContent } from './types';
8
+ import type { LoadContext, Plugin, OptionValidationContext, ValidationResult } from '@docusaurus/types';
9
+ import type { PluginOptions } from '@docusaurus/plugin-content-blog';
10
+ export default function pluginContentBlog(context: LoadContext, options: PluginOptions): Promise<Plugin<BlogContent>>;
10
11
  export declare function validateOptions({ validate, options, }: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions>;
package/lib/index.js CHANGED
@@ -15,7 +15,7 @@ const translations_1 = require("./translations");
15
15
  const pluginOptionSchema_1 = require("./pluginOptionSchema");
16
16
  const blogUtils_1 = require("./blogUtils");
17
17
  const feed_1 = require("./feed");
18
- function pluginContentBlog(context, options) {
18
+ async function pluginContentBlog(context, options) {
19
19
  var _a;
20
20
  if (options.admonitions) {
21
21
  options.remarkPlugins = options.remarkPlugins.concat([
@@ -37,16 +37,16 @@ function pluginContentBlog(context, options) {
37
37
  const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-blog');
38
38
  const dataDir = path_1.default.join(pluginDataDirRoot, pluginId);
39
39
  const aliasedSource = (source) => `~blog/${(0, utils_1.posixPath)(path_1.default.relative(pluginDataDirRoot, source))}`;
40
+ const authorsMapFilePath = await (0, utils_1.getDataFilePath)({
41
+ filePath: options.authorsMapPath,
42
+ contentPaths,
43
+ });
40
44
  return {
41
45
  name: 'docusaurus-plugin-content-blog',
42
46
  getPathsToWatch() {
43
- const { include, authorsMapPath } = options;
44
- const contentMarkdownGlobs = (0, blogUtils_1.getContentPathList)(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`));
45
- // TODO: we should read this path in plugin! but plugins do not support async init for now :'(
46
- // const authorsMapFilePath = await getAuthorsMapFilePath({authorsMapPath,contentPaths,});
47
- // simplified impl, better than nothing for now:
48
- const authorsMapFilePath = path_1.default.join(contentPaths.contentPath, authorsMapPath);
49
- return [authorsMapFilePath, ...contentMarkdownGlobs];
47
+ const { include } = options;
48
+ const contentMarkdownGlobs = (0, utils_1.getContentPathList)(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`));
49
+ return [authorsMapFilePath, ...contentMarkdownGlobs].filter(Boolean);
50
50
  },
51
51
  async getTranslationFiles() {
52
52
  return (0, translations_1.getTranslationFiles)(options);
@@ -135,21 +135,23 @@ function pluginContentBlog(context, options) {
135
135
  const sidebarBlogPosts = options.blogSidebarCount === 'ALL'
136
136
  ? blogPosts
137
137
  : blogPosts.slice(0, options.blogSidebarCount);
138
- const archiveUrl = (0, utils_1.normalizeUrl)([
139
- baseUrl,
140
- routeBasePath,
141
- archiveBasePath,
142
- ]);
143
- // creates a blog archive route
144
- const archiveProp = await createData(`${(0, utils_1.docuHash)(archiveUrl)}.json`, JSON.stringify({ blogPosts }, null, 2));
145
- addRoute({
146
- path: archiveUrl,
147
- component: '@theme/BlogArchivePage',
148
- exact: true,
149
- modules: {
150
- archive: aliasedSource(archiveProp),
151
- },
152
- });
138
+ if (archiveBasePath) {
139
+ const archiveUrl = (0, utils_1.normalizeUrl)([
140
+ baseUrl,
141
+ routeBasePath,
142
+ archiveBasePath,
143
+ ]);
144
+ // creates a blog archive route
145
+ const archiveProp = await createData(`${(0, utils_1.docuHash)(archiveUrl)}.json`, JSON.stringify({ blogPosts }, null, 2));
146
+ addRoute({
147
+ path: archiveUrl,
148
+ component: '@theme/BlogArchivePage',
149
+ exact: true,
150
+ modules: {
151
+ archive: aliasedSource(archiveProp),
152
+ },
153
+ });
154
+ }
153
155
  // This prop is useful to provide the blog list sidebar
154
156
  const sidebarProp = await createData(
155
157
  // Note that this created data path must be in sync with
@@ -274,7 +276,7 @@ function pluginContentBlog(context, options) {
274
276
  (0, utils_1.reportMessage)(`Blog markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath}`, onBrokenMarkdownLinks);
275
277
  },
276
278
  };
277
- const contentDirs = (0, blogUtils_1.getContentPathList)(contentPaths);
279
+ const contentDirs = (0, utils_1.getContentPathList)(contentPaths);
278
280
  return {
279
281
  resolve: {
280
282
  alias: {
@@ -326,13 +328,11 @@ function pluginContentBlog(context, options) {
326
328
  },
327
329
  };
328
330
  },
329
- async postBuild({ outDir }) {
331
+ async postBuild({ outDir, content }) {
330
332
  if (!options.feedOptions.type) {
331
333
  return;
332
334
  }
333
- // TODO: we shouldn't need to re-read the posts here!
334
- // postBuild should receive loadedContent
335
- const blogPosts = await (0, blogUtils_1.generateBlogPosts)(contentPaths, context, options);
335
+ const { blogPosts } = content;
336
336
  if (!blogPosts.length) {
337
337
  return;
338
338
  }
@@ -364,6 +364,11 @@ function pluginContentBlog(context, options) {
364
364
  path: 'atom.xml',
365
365
  title: `${feedTitle} Atom Feed`,
366
366
  },
367
+ json: {
368
+ type: 'application/json',
369
+ path: 'feed.json',
370
+ title: `${feedTitle} JSON Feed`,
371
+ },
367
372
  };
368
373
  const headTags = [];
369
374
  feedTypes.forEach((feedType) => {
@@ -4,6 +4,6 @@
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 { BlogMarkdownLoaderOptions } from './types';
7
+ import type { BlogMarkdownLoaderOptions } from './types';
8
8
  import type { LoaderContext } from 'webpack';
9
9
  export default function markdownLoader(this: LoaderContext<BlogMarkdownLoaderOptions>, source: string): void;
@@ -7,7 +7,6 @@
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  const blogUtils_1 = require("./blogUtils");
10
- const loader_utils_1 = require("loader-utils");
11
10
  function markdownLoader(source) {
12
11
  const filePath = this.resourcePath;
13
12
  const fileString = source;
@@ -21,7 +20,7 @@ function markdownLoader(source) {
21
20
  });
22
21
  // Truncate content if requested (e.g: file.md?truncated=true).
23
22
  const truncated = this.resourceQuery
24
- ? !!(0, loader_utils_1.parseQuery)(this.resourceQuery).truncated
23
+ ? !!new URLSearchParams(this.resourceQuery.slice(1)).get('truncated')
25
24
  : undefined;
26
25
  if (truncated) {
27
26
  finalContent = (0, blogUtils_1.truncate)(finalContent, markdownLoaderOptions.truncateMarker);
@@ -5,6 +5,6 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import { Joi } from '@docusaurus/utils-validation';
8
- import { PluginOptions } from './types';
8
+ import type { PluginOptions } from '@docusaurus/plugin-content-blog';
9
9
  export declare const DEFAULT_OPTIONS: PluginOptions;
10
10
  export declare const PluginOptionSchema: Joi.ObjectSchema<PluginOptions>;