@docusaurus/plugin-content-blog 2.0.0-beta.ff31de0ff → 2.0.0-rc.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/lib/index.js CHANGED
@@ -8,57 +8,57 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.validateOptions = void 0;
10
10
  const tslib_1 = require("tslib");
11
- const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
12
11
  const path_1 = tslib_1.__importDefault(require("path"));
13
- const remark_admonitions_1 = tslib_1.__importDefault(require("remark-admonitions"));
12
+ const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
14
13
  const utils_1 = require("@docusaurus/utils");
15
- const constants_1 = require("@docusaurus/core/lib/constants");
16
- const lodash_1 = require("lodash");
17
- const pluginOptionSchema_1 = require("./pluginOptionSchema");
18
14
  const blogUtils_1 = require("./blogUtils");
19
- function pluginContentBlog(context, options) {
20
- var _a;
21
- if (options.admonitions) {
22
- options.remarkPlugins = options.remarkPlugins.concat([
23
- [remark_admonitions_1.default, options.admonitions],
24
- ]);
25
- }
26
- const { siteDir, siteConfig: { onBrokenMarkdownLinks }, generatedFilesDir, i18n: { currentLocale }, } = context;
15
+ const footnoteIDFixer_1 = tslib_1.__importDefault(require("./remark/footnoteIDFixer"));
16
+ const translations_1 = require("./translations");
17
+ const feed_1 = require("./feed");
18
+ async function pluginContentBlog(context, options) {
19
+ const { siteDir, siteConfig, generatedFilesDir, localizationDir, i18n: { currentLocale }, } = context;
20
+ const { onBrokenMarkdownLinks, baseUrl } = siteConfig;
27
21
  const contentPaths = {
28
22
  contentPath: path_1.default.resolve(siteDir, options.path),
29
- contentPathLocalized: utils_1.getPluginI18nPath({
30
- siteDir,
31
- locale: currentLocale,
23
+ contentPathLocalized: (0, utils_1.getPluginI18nPath)({
24
+ localizationDir,
32
25
  pluginName: 'docusaurus-plugin-content-blog',
33
26
  pluginId: options.id,
34
27
  }),
35
28
  };
36
- const pluginId = (_a = options.id) !== null && _a !== void 0 ? _a : constants_1.DEFAULT_PLUGIN_ID;
29
+ const pluginId = options.id ?? utils_1.DEFAULT_PLUGIN_ID;
37
30
  const pluginDataDirRoot = path_1.default.join(generatedFilesDir, 'docusaurus-plugin-content-blog');
38
31
  const dataDir = path_1.default.join(pluginDataDirRoot, pluginId);
39
- const aliasedSource = (source) => `~blog/${utils_1.posixPath(path_1.default.relative(pluginDataDirRoot, source))}`;
40
- let blogPosts = [];
32
+ const aliasedSource = (source) => `~blog/${(0, utils_1.posixPath)(path_1.default.relative(pluginDataDirRoot, source))}`;
33
+ const authorsMapFilePath = await (0, utils_1.getDataFilePath)({
34
+ filePath: options.authorsMapPath,
35
+ contentPaths,
36
+ });
41
37
  return {
42
38
  name: 'docusaurus-plugin-content-blog',
43
39
  getPathsToWatch() {
44
- const { include = [] } = options;
45
- return lodash_1.flatten(blogUtils_1.getContentPathList(contentPaths).map((contentPath) => {
46
- return include.map((pattern) => `${contentPath}/${pattern}`);
47
- }));
40
+ const { include } = options;
41
+ const contentMarkdownGlobs = (0, utils_1.getContentPathList)(contentPaths).flatMap((contentPath) => include.map((pattern) => `${contentPath}/${pattern}`));
42
+ return [authorsMapFilePath, ...contentMarkdownGlobs].filter(Boolean);
48
43
  },
49
- getClientModules() {
50
- const modules = [];
51
- if (options.admonitions) {
52
- modules.push(require.resolve('remark-admonitions/styles/infima.css'));
53
- }
54
- return modules;
44
+ getTranslationFiles() {
45
+ return (0, translations_1.getTranslationFiles)(options);
55
46
  },
56
47
  // Fetches blog contents and returns metadata for the necessary routes.
57
48
  async loadContent() {
58
- const { postsPerPage, routeBasePath } = options;
59
- blogPosts = await blogUtils_1.generateBlogPosts(contentPaths, context, options);
49
+ const { postsPerPage: postsPerPageOption, routeBasePath, tagsBasePath, blogDescription, blogTitle, blogSidebarTitle, } = options;
50
+ const baseBlogUrl = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]);
51
+ const blogTagsListPath = (0, utils_1.normalizeUrl)([baseBlogUrl, tagsBasePath]);
52
+ const blogPosts = await (0, blogUtils_1.generateBlogPosts)(contentPaths, context, options);
60
53
  if (!blogPosts.length) {
61
- return null;
54
+ return {
55
+ blogSidebarTitle,
56
+ blogPosts: [],
57
+ blogListPaginated: [],
58
+ blogTags: {},
59
+ blogTagsListPath,
60
+ blogTagsPaginated: [],
61
+ };
62
62
  }
63
63
  // Colocate next and prev metadata.
64
64
  blogPosts.forEach((blogPost, index) => {
@@ -77,72 +77,21 @@ function pluginContentBlog(context, options) {
77
77
  };
78
78
  }
79
79
  });
80
- // Blog pagination routes.
81
- // Example: `/blog`, `/blog/page/1`, `/blog/page/2`
82
- const totalCount = blogPosts.length;
83
- const numberOfPages = Math.ceil(totalCount / postsPerPage);
84
- const { siteConfig: { baseUrl = '' }, } = context;
85
- const basePageUrl = utils_1.normalizeUrl([baseUrl, routeBasePath]);
86
- const blogListPaginated = [];
87
- function blogPaginationPermalink(page) {
88
- return page > 0
89
- ? utils_1.normalizeUrl([basePageUrl, `page/${page + 1}`])
90
- : basePageUrl;
91
- }
92
- for (let page = 0; page < numberOfPages; page += 1) {
93
- blogListPaginated.push({
94
- metadata: {
95
- permalink: blogPaginationPermalink(page),
96
- page: page + 1,
97
- postsPerPage,
98
- totalPages: numberOfPages,
99
- totalCount,
100
- previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
101
- nextPage: page < numberOfPages - 1
102
- ? blogPaginationPermalink(page + 1)
103
- : null,
104
- blogDescription: options.blogDescription,
105
- blogTitle: options.blogTitle,
106
- },
107
- items: blogPosts
108
- .slice(page * postsPerPage, (page + 1) * postsPerPage)
109
- .map((item) => item.id),
110
- });
111
- }
112
- const blogTags = {};
113
- const tagsPath = utils_1.normalizeUrl([basePageUrl, 'tags']);
114
- blogPosts.forEach((blogPost) => {
115
- const { tags } = blogPost.metadata;
116
- if (!tags || tags.length === 0) {
117
- // TODO: Extract tags out into a separate plugin.
118
- // eslint-disable-next-line no-param-reassign
119
- blogPost.metadata.tags = [];
120
- return;
121
- }
122
- // eslint-disable-next-line no-param-reassign
123
- blogPost.metadata.tags = tags.map((tag) => {
124
- if (typeof tag === 'string') {
125
- const normalizedTag = lodash_1.kebabCase(tag);
126
- const permalink = utils_1.normalizeUrl([tagsPath, normalizedTag]);
127
- if (!blogTags[normalizedTag]) {
128
- blogTags[normalizedTag] = {
129
- // Will only use the name of the first occurrence of the tag.
130
- name: tag.toLowerCase(),
131
- items: [],
132
- permalink,
133
- };
134
- }
135
- blogTags[normalizedTag].items.push(blogPost.id);
136
- return {
137
- label: tag,
138
- permalink,
139
- };
140
- }
141
- return tag;
142
- });
80
+ const blogListPaginated = (0, blogUtils_1.paginateBlogPosts)({
81
+ blogPosts,
82
+ blogTitle,
83
+ blogDescription,
84
+ postsPerPageOption,
85
+ basePageUrl: baseBlogUrl,
86
+ });
87
+ const blogTags = (0, blogUtils_1.getBlogTags)({
88
+ blogPosts,
89
+ postsPerPageOption,
90
+ blogDescription,
91
+ blogTitle,
143
92
  });
144
- const blogTagsListPath = Object.keys(blogTags).length > 0 ? tagsPath : null;
145
93
  return {
94
+ blogSidebarTitle,
146
95
  blogPosts,
147
96
  blogListPaginated,
148
97
  blogTags,
@@ -150,40 +99,68 @@ function pluginContentBlog(context, options) {
150
99
  };
151
100
  },
152
101
  async contentLoaded({ content: blogContents, actions }) {
153
- if (!blogContents) {
154
- return;
155
- }
156
- const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, } = options;
102
+ const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, } = options;
157
103
  const { addRoute, createData } = actions;
158
- const { blogPosts: loadedBlogPosts, blogListPaginated, blogTags, blogTagsListPath, } = blogContents;
104
+ const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = blogContents;
159
105
  const blogItemsToMetadata = {};
160
106
  const sidebarBlogPosts = options.blogSidebarCount === 'ALL'
161
107
  ? blogPosts
162
- : lodash_1.take(blogPosts, options.blogSidebarCount);
108
+ : blogPosts.slice(0, options.blogSidebarCount);
109
+ function blogPostItemsModule(items) {
110
+ return items.map((postId) => {
111
+ const blogPostMetadata = blogItemsToMetadata[postId];
112
+ return {
113
+ content: {
114
+ __import: true,
115
+ path: blogPostMetadata.source,
116
+ query: {
117
+ truncated: true,
118
+ },
119
+ },
120
+ };
121
+ });
122
+ }
123
+ if (archiveBasePath && blogPosts.length) {
124
+ const archiveUrl = (0, utils_1.normalizeUrl)([
125
+ baseUrl,
126
+ routeBasePath,
127
+ archiveBasePath,
128
+ ]);
129
+ // Create a blog archive route
130
+ const archiveProp = await createData(`${(0, utils_1.docuHash)(archiveUrl)}.json`, JSON.stringify({ blogPosts }, null, 2));
131
+ addRoute({
132
+ path: archiveUrl,
133
+ component: blogArchiveComponent,
134
+ exact: true,
135
+ modules: {
136
+ archive: aliasedSource(archiveProp),
137
+ },
138
+ });
139
+ }
163
140
  // This prop is useful to provide the blog list sidebar
164
141
  const sidebarProp = await createData(
165
142
  // Note that this created data path must be in sync with
166
143
  // metadataPath provided to mdx-loader.
167
144
  `blog-post-list-prop-${pluginId}.json`, JSON.stringify({
168
- title: options.blogSidebarTitle,
145
+ title: blogSidebarTitle,
169
146
  items: sidebarBlogPosts.map((blogPost) => ({
170
147
  title: blogPost.metadata.title,
171
148
  permalink: blogPost.metadata.permalink,
172
149
  })),
173
150
  }, null, 2));
174
151
  // Create routes for blog entries.
175
- await Promise.all(loadedBlogPosts.map(async (blogPost) => {
152
+ await Promise.all(blogPosts.map(async (blogPost) => {
176
153
  const { id, metadata } = blogPost;
177
154
  await createData(
178
155
  // Note that this created data path must be in sync with
179
156
  // metadataPath provided to mdx-loader.
180
- `${utils_1.docuHash(metadata.source)}.json`, JSON.stringify(metadata, null, 2));
157
+ `${(0, utils_1.docuHash)(metadata.source)}.json`, JSON.stringify(metadata, null, 2));
181
158
  addRoute({
182
159
  path: metadata.permalink,
183
160
  component: blogPostComponent,
184
161
  exact: true,
185
162
  modules: {
186
- sidebar: sidebarProp,
163
+ sidebar: aliasedSource(sidebarProp),
187
164
  content: metadata.source,
188
165
  },
189
166
  });
@@ -193,94 +170,84 @@ function pluginContentBlog(context, options) {
193
170
  await Promise.all(blogListPaginated.map(async (listPage) => {
194
171
  const { metadata, items } = listPage;
195
172
  const { permalink } = metadata;
196
- const pageMetadataPath = await createData(`${utils_1.docuHash(permalink)}.json`, JSON.stringify(metadata, null, 2));
173
+ const pageMetadataPath = await createData(`${(0, utils_1.docuHash)(permalink)}.json`, JSON.stringify(metadata, null, 2));
197
174
  addRoute({
198
175
  path: permalink,
199
176
  component: blogListComponent,
200
177
  exact: true,
201
178
  modules: {
202
- sidebar: sidebarProp,
203
- items: items.map((postID) => {
204
- // To tell routes.js this is an import and not a nested object to recurse.
205
- return {
206
- content: {
207
- __import: true,
208
- path: blogItemsToMetadata[postID].source,
209
- query: {
210
- truncated: true,
211
- },
212
- },
213
- };
214
- }),
179
+ sidebar: aliasedSource(sidebarProp),
180
+ items: blogPostItemsModule(items),
215
181
  metadata: aliasedSource(pageMetadataPath),
216
182
  },
217
183
  });
218
184
  }));
219
- // Tags.
220
- if (blogTagsListPath === null) {
185
+ // Tags. This is the last part so we early-return if there are no tags.
186
+ if (Object.keys(blogTags).length === 0) {
221
187
  return;
222
188
  }
223
- const tagsModule = {};
224
- await Promise.all(Object.keys(blogTags).map(async (tag) => {
225
- const { name, items, permalink } = blogTags[tag];
226
- tagsModule[tag] = {
227
- allTagsPath: blogTagsListPath,
228
- slug: tag,
229
- name,
230
- count: items.length,
231
- permalink,
232
- };
233
- const tagsMetadataPath = await createData(`${utils_1.docuHash(permalink)}.json`, JSON.stringify(tagsModule[tag], null, 2));
234
- addRoute({
235
- path: permalink,
236
- component: blogTagsPostsComponent,
237
- exact: true,
238
- modules: {
239
- sidebar: sidebarProp,
240
- items: items.map((postID) => {
241
- const metadata = blogItemsToMetadata[postID];
242
- return {
243
- content: {
244
- __import: true,
245
- path: metadata.source,
246
- query: {
247
- truncated: true,
248
- },
249
- },
250
- };
251
- }),
252
- metadata: aliasedSource(tagsMetadataPath),
253
- },
254
- });
255
- }));
256
- // Only create /tags page if there are tags.
257
- if (Object.keys(blogTags).length > 0) {
258
- const tagsListPath = await createData(`${utils_1.docuHash(`${blogTagsListPath}-tags`)}.json`, JSON.stringify(tagsModule, null, 2));
189
+ async function createTagsListPage() {
190
+ const tagsProp = Object.values(blogTags).map((tag) => ({
191
+ label: tag.label,
192
+ permalink: tag.permalink,
193
+ count: tag.items.length,
194
+ }));
195
+ const tagsPropPath = await createData(`${(0, utils_1.docuHash)(`${blogTagsListPath}-tags`)}.json`, JSON.stringify(tagsProp, null, 2));
259
196
  addRoute({
260
197
  path: blogTagsListPath,
261
198
  component: blogTagsListComponent,
262
199
  exact: true,
263
200
  modules: {
264
- sidebar: sidebarProp,
265
- tags: aliasedSource(tagsListPath),
201
+ sidebar: aliasedSource(sidebarProp),
202
+ tags: aliasedSource(tagsPropPath),
266
203
  },
267
204
  });
268
205
  }
206
+ async function createTagPostsListPage(tag) {
207
+ await Promise.all(tag.pages.map(async (blogPaginated) => {
208
+ const { metadata, items } = blogPaginated;
209
+ const tagProp = {
210
+ label: tag.label,
211
+ permalink: tag.permalink,
212
+ allTagsPath: blogTagsListPath,
213
+ count: tag.items.length,
214
+ };
215
+ const tagPropPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}.json`, JSON.stringify(tagProp, null, 2));
216
+ const listMetadataPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}-list.json`, JSON.stringify(metadata, null, 2));
217
+ addRoute({
218
+ path: metadata.permalink,
219
+ component: blogTagsPostsComponent,
220
+ exact: true,
221
+ modules: {
222
+ sidebar: aliasedSource(sidebarProp),
223
+ items: blogPostItemsModule(items),
224
+ tag: aliasedSource(tagPropPath),
225
+ listMetadata: aliasedSource(listMetadataPath),
226
+ },
227
+ });
228
+ }));
229
+ }
230
+ await createTagsListPage();
231
+ await Promise.all(Object.values(blogTags).map(createTagPostsListPage));
232
+ },
233
+ translateContent({ content, translationFiles }) {
234
+ return (0, translations_1.translateContent)(content, translationFiles);
269
235
  },
270
- configureWebpack(_config, isServer, { getJSLoader }) {
271
- const { rehypePlugins, remarkPlugins, truncateMarker, beforeDefaultRemarkPlugins, beforeDefaultRehypePlugins, } = options;
236
+ configureWebpack(_config, isServer, { getJSLoader }, content) {
237
+ const { admonitions, rehypePlugins, remarkPlugins, truncateMarker, beforeDefaultRemarkPlugins, beforeDefaultRehypePlugins, } = options;
272
238
  const markdownLoaderOptions = {
273
239
  siteDir,
274
240
  contentPaths,
275
241
  truncateMarker,
276
- sourceToPermalink: blogUtils_1.getSourceToPermalink(blogPosts),
242
+ sourceToPermalink: (0, blogUtils_1.getSourceToPermalink)(content.blogPosts),
277
243
  onBrokenMarkdownLink: (brokenMarkdownLink) => {
278
244
  if (onBrokenMarkdownLinks === 'ignore') {
279
245
  return;
280
246
  }
281
- utils_1.reportMessage(`Blog markdown link couldn't be resolved: (${brokenMarkdownLink.link}) in ${brokenMarkdownLink.filePath}`, onBrokenMarkdownLinks);
247
+ logger_1.default.report(onBrokenMarkdownLinks) `Blog markdown link couldn't be resolved: (url=${brokenMarkdownLink.link}) in path=${brokenMarkdownLink.filePath}`;
282
248
  },
283
249
  };
250
+ const contentDirs = (0, utils_1.getContentPathList)(contentPaths);
284
251
  return {
285
252
  resolve: {
286
253
  alias: {
@@ -290,8 +257,8 @@ function pluginContentBlog(context, options) {
290
257
  module: {
291
258
  rules: [
292
259
  {
293
- test: /(\.mdx?)$/,
294
- include: blogUtils_1.getContentPathList(contentPaths)
260
+ test: /\.mdx?$/i,
261
+ include: contentDirs
295
262
  // Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
296
263
  .map(utils_1.addTrailingPathSeparator),
297
264
  use: [
@@ -299,17 +266,32 @@ function pluginContentBlog(context, options) {
299
266
  {
300
267
  loader: require.resolve('@docusaurus/mdx-loader'),
301
268
  options: {
269
+ admonitions,
302
270
  remarkPlugins,
303
271
  rehypePlugins,
304
- beforeDefaultRemarkPlugins,
272
+ beforeDefaultRemarkPlugins: [
273
+ footnoteIDFixer_1.default,
274
+ ...beforeDefaultRemarkPlugins,
275
+ ],
305
276
  beforeDefaultRehypePlugins,
306
- staticDir: path_1.default.join(siteDir, constants_1.STATIC_DIR_NAME),
307
- // Note that metadataPath must be the same/in-sync as
308
- // the path from createData for each MDX.
277
+ staticDirs: siteConfig.staticDirectories.map((dir) => path_1.default.resolve(siteDir, dir)),
278
+ siteDir,
279
+ isMDXPartial: (0, utils_1.createAbsoluteFilePathMatcher)(options.exclude, contentDirs),
309
280
  metadataPath: (mdxPath) => {
310
- const aliasedPath = utils_1.aliasedSitePath(mdxPath, siteDir);
311
- return path_1.default.join(dataDir, `${utils_1.docuHash(aliasedPath)}.json`);
281
+ // Note that metadataPath must be the same/in-sync as
282
+ // the path from createData for each MDX.
283
+ const aliasedPath = (0, utils_1.aliasedSitePath)(mdxPath, siteDir);
284
+ return path_1.default.join(dataDir, `${(0, utils_1.docuHash)(aliasedPath)}.json`);
312
285
  },
286
+ // For blog posts a title in markdown is always removed
287
+ // Blog posts title are rendered separately
288
+ removeContentTitle: true,
289
+ // Assets allow to convert some relative images paths to
290
+ // require() calls
291
+ createAssets: ({ frontMatter, metadata, }) => ({
292
+ image: frontMatter.image,
293
+ authorsImageUrls: metadata.authors.map((author) => author.imageURL),
294
+ }),
313
295
  },
314
296
  },
315
297
  {
@@ -322,59 +304,54 @@ function pluginContentBlog(context, options) {
322
304
  },
323
305
  };
324
306
  },
325
- async postBuild({ outDir }) {
326
- var _a;
327
- if (!((_a = options.feedOptions) === null || _a === void 0 ? void 0 : _a.type)) {
307
+ async postBuild({ outDir, content }) {
308
+ if (!options.feedOptions.type) {
328
309
  return;
329
310
  }
330
- const feed = await blogUtils_1.generateBlogFeed(contentPaths, context, options);
331
- if (!feed) {
311
+ const { blogPosts } = content;
312
+ if (!blogPosts.length) {
332
313
  return;
333
314
  }
334
- const feedTypes = options.feedOptions.type;
335
- await Promise.all(feedTypes.map(async (feedType) => {
336
- const feedPath = path_1.default.join(outDir, options.routeBasePath, `${feedType}.xml`);
337
- const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
338
- try {
339
- await fs_extra_1.default.outputFile(feedPath, feedContent);
340
- }
341
- catch (err) {
342
- throw new Error(`Generating ${feedType} feed failed: ${err}`);
343
- }
344
- }));
315
+ await (0, feed_1.createBlogFeedFiles)({
316
+ blogPosts,
317
+ options,
318
+ outDir,
319
+ siteConfig,
320
+ locale: currentLocale,
321
+ });
345
322
  },
346
- injectHtmlTags() {
347
- var _a;
348
- if (!((_a = options.feedOptions) === null || _a === void 0 ? void 0 : _a.type)) {
323
+ injectHtmlTags({ content }) {
324
+ if (!content.blogPosts.length || !options.feedOptions.type) {
349
325
  return {};
350
326
  }
351
327
  const feedTypes = options.feedOptions.type;
352
- const { siteConfig: { title }, baseUrl, } = context;
328
+ const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
353
329
  const feedsConfig = {
354
330
  rss: {
355
331
  type: 'application/rss+xml',
356
332
  path: 'rss.xml',
357
- title: `${title} Blog RSS Feed`,
333
+ title: `${feedTitle} RSS Feed`,
358
334
  },
359
335
  atom: {
360
336
  type: 'application/atom+xml',
361
337
  path: 'atom.xml',
362
- title: `${title} Blog Atom Feed`,
338
+ title: `${feedTitle} Atom Feed`,
339
+ },
340
+ json: {
341
+ type: 'application/json',
342
+ path: 'feed.json',
343
+ title: `${feedTitle} JSON Feed`,
363
344
  },
364
345
  };
365
346
  const headTags = [];
366
347
  feedTypes.forEach((feedType) => {
367
- const feedConfig = feedsConfig[feedType] || {};
368
- if (!feedsConfig) {
369
- return;
370
- }
371
- const { type, path: feedConfigPath, title: feedConfigTitle } = feedConfig;
348
+ const { type, path: feedConfigPath, title: feedConfigTitle, } = feedsConfig[feedType];
372
349
  headTags.push({
373
350
  tagName: 'link',
374
351
  attributes: {
375
352
  rel: 'alternate',
376
353
  type,
377
- href: utils_1.normalizeUrl([
354
+ href: (0, utils_1.normalizeUrl)([
378
355
  baseUrl,
379
356
  options.routeBasePath,
380
357
  feedConfigPath,
@@ -390,8 +367,5 @@ function pluginContentBlog(context, options) {
390
367
  };
391
368
  }
392
369
  exports.default = pluginContentBlog;
393
- function validateOptions({ validate, options, }) {
394
- const validatedOptions = validate(pluginOptionSchema_1.PluginOptionSchema, options);
395
- return validatedOptions;
396
- }
397
- exports.validateOptions = validateOptions;
370
+ var options_1 = require("./options");
371
+ Object.defineProperty(exports, "validateOptions", { enumerable: true, get: function () { return options_1.validateOptions; } });
@@ -4,9 +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
- /// <reference types="node" />
8
- interface Loader extends Function {
9
- (this: any, source: string): string | Buffer | void | undefined;
10
- }
11
- declare const markdownLoader: Loader;
12
- export default markdownLoader;
7
+ import type { BlogMarkdownLoaderOptions } from './types';
8
+ import type { LoaderContext } from 'webpack';
9
+ export default function markdownLoader(this: LoaderContext<BlogMarkdownLoaderOptions>, source: string): void;
@@ -7,25 +7,24 @@
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
- const markdownLoader = function (source) {
10
+ function markdownLoader(source) {
12
11
  const filePath = this.resourcePath;
13
12
  const fileString = source;
14
13
  const callback = this.async();
15
14
  const markdownLoaderOptions = this.getOptions();
16
15
  // Linkify blog posts
17
- let finalContent = blogUtils_1.linkify({
16
+ let finalContent = (0, blogUtils_1.linkify)({
18
17
  fileString,
19
18
  filePath,
20
19
  ...markdownLoaderOptions,
21
20
  });
22
21
  // Truncate content if requested (e.g: file.md?truncated=true).
23
22
  const truncated = this.resourceQuery
24
- ? !!loader_utils_1.parseQuery(this.resourceQuery).truncated
23
+ ? !!new URLSearchParams(this.resourceQuery.slice(1)).get('truncated')
25
24
  : undefined;
26
25
  if (truncated) {
27
- finalContent = blogUtils_1.truncate(finalContent, markdownLoaderOptions.truncateMarker);
26
+ finalContent = (0, blogUtils_1.truncate)(finalContent, markdownLoaderOptions.truncateMarker);
28
27
  }
29
- return callback && callback(null, finalContent);
30
- };
28
+ return callback(null, finalContent);
29
+ }
31
30
  exports.default = markdownLoader;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import type { PluginOptions, Options } from '@docusaurus/plugin-content-blog';
8
+ import type { OptionValidationContext } from '@docusaurus/types';
9
+ export declare const DEFAULT_OPTIONS: PluginOptions;
10
+ export declare function validateOptions({ validate, options, }: OptionValidationContext<Options | undefined, PluginOptions>): PluginOptions;