@docusaurus/plugin-content-blog 2.0.0-beta.ff31de0ff → 2.0.1

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 (58) hide show
  1. package/lib/authors.d.ts +22 -0
  2. package/lib/authors.js +122 -0
  3. package/lib/blogUtils.d.ts +27 -7
  4. package/lib/blogUtils.js +214 -141
  5. package/lib/feed.d.ts +15 -0
  6. package/lib/feed.js +102 -0
  7. package/lib/frontMatter.d.ts +10 -0
  8. package/lib/frontMatter.js +62 -0
  9. package/lib/index.d.ts +4 -4
  10. package/lib/index.js +179 -205
  11. package/lib/markdownLoader.d.ts +3 -6
  12. package/lib/markdownLoader.js +6 -7
  13. package/lib/options.d.ts +10 -0
  14. package/lib/{pluginOptionSchema.js → options.js} +44 -14
  15. package/lib/remark/footnoteIDFixer.d.ts +14 -0
  16. package/lib/remark/footnoteIDFixer.js +29 -0
  17. package/lib/translations.d.ts +10 -0
  18. package/lib/translations.js +53 -0
  19. package/lib/types.d.ts +4 -109
  20. package/package.json +23 -18
  21. package/src/authors.ts +168 -0
  22. package/src/blogUtils.ts +316 -196
  23. package/src/feed.ts +171 -0
  24. package/src/frontMatter.ts +81 -0
  25. package/src/index.ts +246 -268
  26. package/src/markdownLoader.ts +11 -16
  27. package/src/{pluginOptionSchema.ts → options.ts} +57 -16
  28. package/src/plugin-content-blog.d.ts +587 -0
  29. package/src/remark/footnoteIDFixer.ts +29 -0
  30. package/src/translations.ts +69 -0
  31. package/src/types.ts +2 -128
  32. package/index.d.ts +0 -138
  33. package/lib/.tsbuildinfo +0 -4415
  34. package/lib/blogFrontMatter.d.ts +0 -28
  35. package/lib/blogFrontMatter.js +0 -50
  36. package/lib/pluginOptionSchema.d.ts +0 -33
  37. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  38. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  39. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  40. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  41. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  42. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
  43. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  44. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  45. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  46. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  47. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  48. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -101
  49. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  50. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  51. package/src/__tests__/blogFrontMatter.test.ts +0 -265
  52. package/src/__tests__/generateBlogFeed.test.ts +0 -100
  53. package/src/__tests__/index.test.ts +0 -336
  54. package/src/__tests__/linkify.test.ts +0 -93
  55. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  56. package/src/blogFrontMatter.ts +0 -87
  57. package/tsconfig.json +0 -9
  58. package/types.d.ts +0 -13
package/src/index.ts CHANGED
@@ -5,75 +5,62 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import fs from 'fs-extra';
9
8
  import path from 'path';
10
- import admonitions from 'remark-admonitions';
9
+ import logger from '@docusaurus/logger';
11
10
  import {
12
11
  normalizeUrl,
13
12
  docuHash,
14
13
  aliasedSitePath,
15
14
  getPluginI18nPath,
16
- reportMessage,
17
15
  posixPath,
18
16
  addTrailingPathSeparator,
17
+ createAbsoluteFilePathMatcher,
18
+ getContentPathList,
19
+ getDataFilePath,
20
+ DEFAULT_PLUGIN_ID,
21
+ type TagsListItem,
22
+ type TagModule,
19
23
  } from '@docusaurus/utils';
20
24
  import {
21
- STATIC_DIR_NAME,
22
- DEFAULT_PLUGIN_ID,
23
- } from '@docusaurus/core/lib/constants';
24
- import {flatten, take, kebabCase} from 'lodash';
25
+ generateBlogPosts,
26
+ getSourceToPermalink,
27
+ getBlogTags,
28
+ paginateBlogPosts,
29
+ } from './blogUtils';
30
+ import footnoteIDFixer from './remark/footnoteIDFixer';
31
+ import {translateContent, getTranslationFiles} from './translations';
32
+ import {createBlogFeedFiles} from './feed';
25
33
 
26
- import {
34
+ import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
35
+ import type {LoadContext, Plugin, HtmlTags} from '@docusaurus/types';
36
+ import type {
27
37
  PluginOptions,
38
+ BlogPostFrontMatter,
39
+ BlogPostMetadata,
40
+ Assets,
41
+ BlogTag,
28
42
  BlogTags,
29
43
  BlogContent,
30
- BlogItemsToMetadata,
31
- TagsModule,
32
44
  BlogPaginated,
33
- BlogPost,
34
- BlogContentPaths,
35
- BlogMarkdownLoaderOptions,
36
- } from './types';
37
- import {PluginOptionSchema} from './pluginOptionSchema';
38
- import {
39
- LoadContext,
40
- ConfigureWebpackUtils,
41
- Props,
42
- Plugin,
43
- HtmlTags,
44
- OptionValidationContext,
45
- ValidationResult,
46
- } from '@docusaurus/types';
47
- import {Configuration} from 'webpack';
48
- import {
49
- generateBlogFeed,
50
- generateBlogPosts,
51
- getContentPathList,
52
- getSourceToPermalink,
53
- } from './blogUtils';
45
+ } from '@docusaurus/plugin-content-blog';
54
46
 
55
- export default function pluginContentBlog(
47
+ export default async function pluginContentBlog(
56
48
  context: LoadContext,
57
49
  options: PluginOptions,
58
- ): Plugin<BlogContent | null> {
59
- if (options.admonitions) {
60
- options.remarkPlugins = options.remarkPlugins.concat([
61
- [admonitions, options.admonitions],
62
- ]);
63
- }
64
-
50
+ ): Promise<Plugin<BlogContent>> {
65
51
  const {
66
52
  siteDir,
67
- siteConfig: {onBrokenMarkdownLinks},
53
+ siteConfig,
68
54
  generatedFilesDir,
55
+ localizationDir,
69
56
  i18n: {currentLocale},
70
57
  } = context;
58
+ const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
71
59
 
72
60
  const contentPaths: BlogContentPaths = {
73
61
  contentPath: path.resolve(siteDir, options.path),
74
62
  contentPathLocalized: getPluginI18nPath({
75
- siteDir,
76
- locale: currentLocale,
63
+ localizationDir,
77
64
  pluginName: 'docusaurus-plugin-content-blog',
78
65
  pluginId: options.id,
79
66
  }),
@@ -88,37 +75,53 @@ export default function pluginContentBlog(
88
75
  const aliasedSource = (source: string) =>
89
76
  `~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
90
77
 
91
- let blogPosts: BlogPost[] = [];
78
+ const authorsMapFilePath = await getDataFilePath({
79
+ filePath: options.authorsMapPath,
80
+ contentPaths,
81
+ });
92
82
 
93
83
  return {
94
84
  name: 'docusaurus-plugin-content-blog',
95
85
 
96
86
  getPathsToWatch() {
97
- const {include = []} = options;
98
- return flatten(
99
- getContentPathList(contentPaths).map((contentPath) => {
100
- return include.map((pattern) => `${contentPath}/${pattern}`);
101
- }),
87
+ const {include} = options;
88
+ const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap(
89
+ (contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
102
90
  );
103
- },
104
-
105
- getClientModules() {
106
- const modules = [];
107
91
 
108
- if (options.admonitions) {
109
- modules.push(require.resolve('remark-admonitions/styles/infima.css'));
110
- }
92
+ return [authorsMapFilePath, ...contentMarkdownGlobs].filter(
93
+ Boolean,
94
+ ) as string[];
95
+ },
111
96
 
112
- return modules;
97
+ getTranslationFiles() {
98
+ return getTranslationFiles(options);
113
99
  },
114
100
 
115
101
  // Fetches blog contents and returns metadata for the necessary routes.
116
102
  async loadContent() {
117
- const {postsPerPage, routeBasePath} = options;
103
+ const {
104
+ postsPerPage: postsPerPageOption,
105
+ routeBasePath,
106
+ tagsBasePath,
107
+ blogDescription,
108
+ blogTitle,
109
+ blogSidebarTitle,
110
+ } = options;
111
+
112
+ const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
113
+ const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
114
+ const blogPosts = await generateBlogPosts(contentPaths, context, options);
118
115
 
119
- blogPosts = await generateBlogPosts(contentPaths, context, options);
120
116
  if (!blogPosts.length) {
121
- return null;
117
+ return {
118
+ blogSidebarTitle,
119
+ blogPosts: [],
120
+ blogListPaginated: [],
121
+ blogTags: {},
122
+ blogTagsListPath,
123
+ blogTagsPaginated: [],
124
+ };
122
125
  }
123
126
 
124
127
  // Colocate next and prev metadata.
@@ -141,85 +144,23 @@ export default function pluginContentBlog(
141
144
  }
142
145
  });
143
146
 
144
- // Blog pagination routes.
145
- // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
146
- const totalCount = blogPosts.length;
147
- const numberOfPages = Math.ceil(totalCount / postsPerPage);
148
- const {
149
- siteConfig: {baseUrl = ''},
150
- } = context;
151
- const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
152
-
153
- const blogListPaginated: BlogPaginated[] = [];
154
-
155
- function blogPaginationPermalink(page: number) {
156
- return page > 0
157
- ? normalizeUrl([basePageUrl, `page/${page + 1}`])
158
- : basePageUrl;
159
- }
160
-
161
- for (let page = 0; page < numberOfPages; page += 1) {
162
- blogListPaginated.push({
163
- metadata: {
164
- permalink: blogPaginationPermalink(page),
165
- page: page + 1,
166
- postsPerPage,
167
- totalPages: numberOfPages,
168
- totalCount,
169
- previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
170
- nextPage:
171
- page < numberOfPages - 1
172
- ? blogPaginationPermalink(page + 1)
173
- : null,
174
- blogDescription: options.blogDescription,
175
- blogTitle: options.blogTitle,
176
- },
177
- items: blogPosts
178
- .slice(page * postsPerPage, (page + 1) * postsPerPage)
179
- .map((item) => item.id),
180
- });
181
- }
182
-
183
- const blogTags: BlogTags = {};
184
- const tagsPath = normalizeUrl([basePageUrl, 'tags']);
185
- blogPosts.forEach((blogPost) => {
186
- const {tags} = blogPost.metadata;
187
- if (!tags || tags.length === 0) {
188
- // TODO: Extract tags out into a separate plugin.
189
- // eslint-disable-next-line no-param-reassign
190
- blogPost.metadata.tags = [];
191
- return;
192
- }
193
-
194
- // eslint-disable-next-line no-param-reassign
195
- blogPost.metadata.tags = tags.map((tag) => {
196
- if (typeof tag === 'string') {
197
- const normalizedTag = kebabCase(tag);
198
- const permalink = normalizeUrl([tagsPath, normalizedTag]);
199
- if (!blogTags[normalizedTag]) {
200
- blogTags[normalizedTag] = {
201
- // Will only use the name of the first occurrence of the tag.
202
- name: tag.toLowerCase(),
203
- items: [],
204
- permalink,
205
- };
206
- }
207
-
208
- blogTags[normalizedTag].items.push(blogPost.id);
209
-
210
- return {
211
- label: tag,
212
- permalink,
213
- };
214
- }
215
- return tag;
216
- });
147
+ const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
148
+ blogPosts,
149
+ blogTitle,
150
+ blogDescription,
151
+ postsPerPageOption,
152
+ basePageUrl: baseBlogUrl,
217
153
  });
218
154
 
219
- const blogTagsListPath =
220
- Object.keys(blogTags).length > 0 ? tagsPath : null;
155
+ const blogTags: BlogTags = getBlogTags({
156
+ blogPosts,
157
+ postsPerPageOption,
158
+ blogDescription,
159
+ blogTitle,
160
+ });
221
161
 
222
162
  return {
163
+ blogSidebarTitle,
223
164
  blogPosts,
224
165
  blogListPaginated,
225
166
  blogTags,
@@ -228,31 +169,67 @@ export default function pluginContentBlog(
228
169
  },
229
170
 
230
171
  async contentLoaded({content: blogContents, actions}) {
231
- if (!blogContents) {
232
- return;
233
- }
234
-
235
172
  const {
236
173
  blogListComponent,
237
174
  blogPostComponent,
238
175
  blogTagsListComponent,
239
176
  blogTagsPostsComponent,
177
+ blogArchiveComponent,
178
+ routeBasePath,
179
+ archiveBasePath,
240
180
  } = options;
241
181
 
242
182
  const {addRoute, createData} = actions;
243
183
  const {
244
- blogPosts: loadedBlogPosts,
184
+ blogSidebarTitle,
185
+ blogPosts,
245
186
  blogListPaginated,
246
187
  blogTags,
247
188
  blogTagsListPath,
248
189
  } = blogContents;
249
190
 
250
- const blogItemsToMetadata: BlogItemsToMetadata = {};
191
+ const blogItemsToMetadata: {[postId: string]: BlogPostMetadata} = {};
251
192
 
252
193
  const sidebarBlogPosts =
253
194
  options.blogSidebarCount === 'ALL'
254
195
  ? blogPosts
255
- : take(blogPosts, options.blogSidebarCount);
196
+ : blogPosts.slice(0, options.blogSidebarCount);
197
+
198
+ function blogPostItemsModule(items: string[]) {
199
+ return items.map((postId) => {
200
+ const blogPostMetadata = blogItemsToMetadata[postId]!;
201
+ return {
202
+ content: {
203
+ __import: true,
204
+ path: blogPostMetadata.source,
205
+ query: {
206
+ truncated: true,
207
+ },
208
+ },
209
+ };
210
+ });
211
+ }
212
+
213
+ if (archiveBasePath && blogPosts.length) {
214
+ const archiveUrl = normalizeUrl([
215
+ baseUrl,
216
+ routeBasePath,
217
+ archiveBasePath,
218
+ ]);
219
+ // Create a blog archive route
220
+ const archiveProp = await createData(
221
+ `${docuHash(archiveUrl)}.json`,
222
+ JSON.stringify({blogPosts}, null, 2),
223
+ );
224
+ addRoute({
225
+ path: archiveUrl,
226
+ component: blogArchiveComponent,
227
+ exact: true,
228
+ modules: {
229
+ archive: aliasedSource(archiveProp),
230
+ },
231
+ });
232
+ }
256
233
 
257
234
  // This prop is useful to provide the blog list sidebar
258
235
  const sidebarProp = await createData(
@@ -261,7 +238,7 @@ export default function pluginContentBlog(
261
238
  `blog-post-list-prop-${pluginId}.json`,
262
239
  JSON.stringify(
263
240
  {
264
- title: options.blogSidebarTitle,
241
+ title: blogSidebarTitle,
265
242
  items: sidebarBlogPosts.map((blogPost) => ({
266
243
  title: blogPost.metadata.title,
267
244
  permalink: blogPost.metadata.permalink,
@@ -274,7 +251,7 @@ export default function pluginContentBlog(
274
251
 
275
252
  // Create routes for blog entries.
276
253
  await Promise.all(
277
- loadedBlogPosts.map(async (blogPost) => {
254
+ blogPosts.map(async (blogPost) => {
278
255
  const {id, metadata} = blogPost;
279
256
  await createData(
280
257
  // Note that this created data path must be in sync with
@@ -288,7 +265,7 @@ export default function pluginContentBlog(
288
265
  component: blogPostComponent,
289
266
  exact: true,
290
267
  modules: {
291
- sidebar: sidebarProp,
268
+ sidebar: aliasedSource(sidebarProp),
292
269
  content: metadata.source,
293
270
  },
294
271
  });
@@ -312,78 +289,29 @@ export default function pluginContentBlog(
312
289
  component: blogListComponent,
313
290
  exact: true,
314
291
  modules: {
315
- sidebar: sidebarProp,
316
- items: items.map((postID) => {
317
- // To tell routes.js this is an import and not a nested object to recurse.
318
- return {
319
- content: {
320
- __import: true,
321
- path: blogItemsToMetadata[postID].source,
322
- query: {
323
- truncated: true,
324
- },
325
- },
326
- };
327
- }),
292
+ sidebar: aliasedSource(sidebarProp),
293
+ items: blogPostItemsModule(items),
328
294
  metadata: aliasedSource(pageMetadataPath),
329
295
  },
330
296
  });
331
297
  }),
332
298
  );
333
299
 
334
- // Tags.
335
- if (blogTagsListPath === null) {
300
+ // Tags. This is the last part so we early-return if there are no tags.
301
+ if (Object.keys(blogTags).length === 0) {
336
302
  return;
337
303
  }
338
304
 
339
- const tagsModule: TagsModule = {};
340
-
341
- await Promise.all(
342
- Object.keys(blogTags).map(async (tag) => {
343
- const {name, items, permalink} = blogTags[tag];
344
-
345
- tagsModule[tag] = {
346
- allTagsPath: blogTagsListPath,
347
- slug: tag,
348
- name,
349
- count: items.length,
350
- permalink,
351
- };
352
-
353
- const tagsMetadataPath = await createData(
354
- `${docuHash(permalink)}.json`,
355
- JSON.stringify(tagsModule[tag], null, 2),
356
- );
357
-
358
- addRoute({
359
- path: permalink,
360
- component: blogTagsPostsComponent,
361
- exact: true,
362
- modules: {
363
- sidebar: sidebarProp,
364
- items: items.map((postID) => {
365
- const metadata = blogItemsToMetadata[postID];
366
- return {
367
- content: {
368
- __import: true,
369
- path: metadata.source,
370
- query: {
371
- truncated: true,
372
- },
373
- },
374
- };
375
- }),
376
- metadata: aliasedSource(tagsMetadataPath),
377
- },
378
- });
379
- }),
380
- );
305
+ async function createTagsListPage() {
306
+ const tagsProp: TagsListItem[] = Object.values(blogTags).map((tag) => ({
307
+ label: tag.label,
308
+ permalink: tag.permalink,
309
+ count: tag.items.length,
310
+ }));
381
311
 
382
- // Only create /tags page if there are tags.
383
- if (Object.keys(blogTags).length > 0) {
384
- const tagsListPath = await createData(
312
+ const tagsPropPath = await createData(
385
313
  `${docuHash(`${blogTagsListPath}-tags`)}.json`,
386
- JSON.stringify(tagsModule, null, 2),
314
+ JSON.stringify(tagsProp, null, 2),
387
315
  );
388
316
 
389
317
  addRoute({
@@ -391,19 +319,58 @@ export default function pluginContentBlog(
391
319
  component: blogTagsListComponent,
392
320
  exact: true,
393
321
  modules: {
394
- sidebar: sidebarProp,
395
- tags: aliasedSource(tagsListPath),
322
+ sidebar: aliasedSource(sidebarProp),
323
+ tags: aliasedSource(tagsPropPath),
396
324
  },
397
325
  });
398
326
  }
327
+
328
+ async function createTagPostsListPage(tag: BlogTag): Promise<void> {
329
+ await Promise.all(
330
+ tag.pages.map(async (blogPaginated) => {
331
+ const {metadata, items} = blogPaginated;
332
+ const tagProp: TagModule = {
333
+ label: tag.label,
334
+ permalink: tag.permalink,
335
+ allTagsPath: blogTagsListPath,
336
+ count: tag.items.length,
337
+ };
338
+ const tagPropPath = await createData(
339
+ `${docuHash(metadata.permalink)}.json`,
340
+ JSON.stringify(tagProp, null, 2),
341
+ );
342
+
343
+ const listMetadataPath = await createData(
344
+ `${docuHash(metadata.permalink)}-list.json`,
345
+ JSON.stringify(metadata, null, 2),
346
+ );
347
+
348
+ addRoute({
349
+ path: metadata.permalink,
350
+ component: blogTagsPostsComponent,
351
+ exact: true,
352
+ modules: {
353
+ sidebar: aliasedSource(sidebarProp),
354
+ items: blogPostItemsModule(items),
355
+ tag: aliasedSource(tagPropPath),
356
+ listMetadata: aliasedSource(listMetadataPath),
357
+ },
358
+ });
359
+ }),
360
+ );
361
+ }
362
+
363
+ await createTagsListPage();
364
+ await Promise.all(Object.values(blogTags).map(createTagPostsListPage));
365
+ },
366
+
367
+ translateContent({content, translationFiles}) {
368
+ return translateContent(content, translationFiles);
399
369
  },
400
370
 
401
- configureWebpack(
402
- _config: Configuration,
403
- isServer: boolean,
404
- {getJSLoader}: ConfigureWebpackUtils,
405
- ) {
371
+ configureWebpack(_config, isServer, {getJSLoader}, content) {
406
372
  const {
373
+ admonitions,
407
374
  rehypePlugins,
408
375
  remarkPlugins,
409
376
  truncateMarker,
@@ -415,18 +382,18 @@ export default function pluginContentBlog(
415
382
  siteDir,
416
383
  contentPaths,
417
384
  truncateMarker,
418
- sourceToPermalink: getSourceToPermalink(blogPosts),
385
+ sourceToPermalink: getSourceToPermalink(content.blogPosts),
419
386
  onBrokenMarkdownLink: (brokenMarkdownLink) => {
420
387
  if (onBrokenMarkdownLinks === 'ignore') {
421
388
  return;
422
389
  }
423
- reportMessage(
424
- `Blog markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath}`,
390
+ logger.report(
425
391
  onBrokenMarkdownLinks,
426
- );
392
+ )`Blog markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath}`;
427
393
  },
428
394
  };
429
395
 
396
+ const contentDirs = getContentPathList(contentPaths);
430
397
  return {
431
398
  resolve: {
432
399
  alias: {
@@ -436,8 +403,8 @@ export default function pluginContentBlog(
436
403
  module: {
437
404
  rules: [
438
405
  {
439
- test: /(\.mdx?)$/,
440
- include: getContentPathList(contentPaths)
406
+ test: /\.mdx?$/i,
407
+ include: contentDirs
441
408
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
442
409
  .map(addTrailingPathSeparator),
443
410
  use: [
@@ -445,20 +412,49 @@ export default function pluginContentBlog(
445
412
  {
446
413
  loader: require.resolve('@docusaurus/mdx-loader'),
447
414
  options: {
415
+ admonitions,
448
416
  remarkPlugins,
449
417
  rehypePlugins,
450
- beforeDefaultRemarkPlugins,
418
+ beforeDefaultRemarkPlugins: [
419
+ footnoteIDFixer,
420
+ ...beforeDefaultRemarkPlugins,
421
+ ],
451
422
  beforeDefaultRehypePlugins,
452
- staticDir: path.join(siteDir, STATIC_DIR_NAME),
453
- // Note that metadataPath must be the same/in-sync as
454
- // the path from createData for each MDX.
423
+ staticDirs: siteConfig.staticDirectories.map((dir) =>
424
+ path.resolve(siteDir, dir),
425
+ ),
426
+ siteDir,
427
+ isMDXPartial: createAbsoluteFilePathMatcher(
428
+ options.exclude,
429
+ contentDirs,
430
+ ),
455
431
  metadataPath: (mdxPath: string) => {
432
+ // Note that metadataPath must be the same/in-sync as
433
+ // the path from createData for each MDX.
456
434
  const aliasedPath = aliasedSitePath(mdxPath, siteDir);
457
435
  return path.join(
458
436
  dataDir,
459
437
  `${docuHash(aliasedPath)}.json`,
460
438
  );
461
439
  },
440
+ // For blog posts a title in markdown is always removed
441
+ // Blog posts title are rendered separately
442
+ removeContentTitle: true,
443
+
444
+ // Assets allow to convert some relative images paths to
445
+ // require() calls
446
+ createAssets: ({
447
+ frontMatter,
448
+ metadata,
449
+ }: {
450
+ frontMatter: BlogPostFrontMatter;
451
+ metadata: BlogPostMetadata;
452
+ }): Assets => ({
453
+ image: frontMatter.image,
454
+ authorsImageUrls: metadata.authors.map(
455
+ (author) => author.imageURL,
456
+ ),
457
+ }),
462
458
  },
463
459
  },
464
460
  {
@@ -472,67 +468,55 @@ export default function pluginContentBlog(
472
468
  };
473
469
  },
474
470
 
475
- async postBuild({outDir}: Props) {
476
- if (!options.feedOptions?.type) {
471
+ async postBuild({outDir, content}) {
472
+ if (!options.feedOptions.type) {
477
473
  return;
478
474
  }
479
-
480
- const feed = await generateBlogFeed(contentPaths, context, options);
481
-
482
- if (!feed) {
475
+ const {blogPosts} = content;
476
+ if (!blogPosts.length) {
483
477
  return;
484
478
  }
485
-
486
- const feedTypes = options.feedOptions.type;
487
-
488
- await Promise.all(
489
- feedTypes.map(async (feedType) => {
490
- const feedPath = path.join(
491
- outDir,
492
- options.routeBasePath,
493
- `${feedType}.xml`,
494
- );
495
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
496
- try {
497
- await fs.outputFile(feedPath, feedContent);
498
- } catch (err) {
499
- throw new Error(`Generating ${feedType} feed failed: ${err}`);
500
- }
501
- }),
502
- );
479
+ await createBlogFeedFiles({
480
+ blogPosts,
481
+ options,
482
+ outDir,
483
+ siteConfig,
484
+ locale: currentLocale,
485
+ });
503
486
  },
504
487
 
505
- injectHtmlTags() {
506
- if (!options.feedOptions?.type) {
488
+ injectHtmlTags({content}) {
489
+ if (!content.blogPosts.length || !options.feedOptions.type) {
507
490
  return {};
508
491
  }
492
+
509
493
  const feedTypes = options.feedOptions.type;
510
- const {
511
- siteConfig: {title},
512
- baseUrl,
513
- } = context;
494
+ const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
514
495
  const feedsConfig = {
515
496
  rss: {
516
497
  type: 'application/rss+xml',
517
498
  path: 'rss.xml',
518
- title: `${title} Blog RSS Feed`,
499
+ title: `${feedTitle} RSS Feed`,
519
500
  },
520
501
  atom: {
521
502
  type: 'application/atom+xml',
522
503
  path: 'atom.xml',
523
- title: `${title} Blog Atom Feed`,
504
+ title: `${feedTitle} Atom Feed`,
505
+ },
506
+ json: {
507
+ type: 'application/json',
508
+ path: 'feed.json',
509
+ title: `${feedTitle} JSON Feed`,
524
510
  },
525
511
  };
526
512
  const headTags: HtmlTags = [];
527
513
 
528
514
  feedTypes.forEach((feedType) => {
529
- const feedConfig = feedsConfig[feedType] || {};
530
-
531
- if (!feedsConfig) {
532
- return;
533
- }
534
-
535
- const {type, path: feedConfigPath, title: feedConfigTitle} = feedConfig;
515
+ const {
516
+ type,
517
+ path: feedConfigPath,
518
+ title: feedConfigTitle,
519
+ } = feedsConfig[feedType];
536
520
 
537
521
  headTags.push({
538
522
  tagName: 'link',
@@ -556,10 +540,4 @@ export default function pluginContentBlog(
556
540
  };
557
541
  }
558
542
 
559
- export function validateOptions({
560
- validate,
561
- options,
562
- }: OptionValidationContext<PluginOptions>): ValidationResult<PluginOptions> {
563
- const validatedOptions = validate(PluginOptionSchema, options);
564
- return validatedOptions;
565
- }
543
+ export {validateOptions} from './options';