@docusaurus/plugin-content-blog 2.0.0-beta.fc64c12e4 → 2.0.0

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 (60) 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 +201 -145
  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/{blogFrontMatter.js → frontMatter.js} +31 -19
  9. package/lib/index.d.ts +4 -4
  10. package/lib/index.js +163 -194
  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} +41 -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 -110
  20. package/package.json +23 -19
  21. package/src/authors.ts +168 -0
  22. package/src/blogUtils.ts +305 -205
  23. package/src/feed.ts +171 -0
  24. package/src/frontMatter.ts +81 -0
  25. package/src/index.ts +223 -258
  26. package/src/markdownLoader.ts +11 -16
  27. package/src/{pluginOptionSchema.ts → options.ts} +54 -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 -129
  32. package/index.d.ts +0 -138
  33. package/lib/.tsbuildinfo +0 -1
  34. package/lib/blogFrontMatter.d.ts +0 -28
  35. package/lib/pluginOptionSchema.d.ts +0 -34
  36. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  37. package/src/__tests__/__fixtures__/website/blog/_partials/somePartial.md +0 -3
  38. package/src/__tests__/__fixtures__/website/blog/_partials/subfolder/somePartial.md +0 -3
  39. package/src/__tests__/__fixtures__/website/blog/_somePartial.md +0 -3
  40. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  41. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  42. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  43. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  44. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
  45. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  46. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  47. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  48. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  49. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  50. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -116
  51. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  52. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  53. package/src/__tests__/blogFrontMatter.test.ts +0 -317
  54. package/src/__tests__/generateBlogFeed.test.ts +0 -102
  55. package/src/__tests__/index.test.ts +0 -336
  56. package/src/__tests__/linkify.test.ts +0 -93
  57. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  58. package/src/blogFrontMatter.ts +0 -88
  59. package/tsconfig.json +0 -9
  60. package/types.d.ts +0 -13
package/src/blogUtils.ts CHANGED
@@ -6,20 +6,12 @@
6
6
  */
7
7
 
8
8
  import fs from 'fs-extra';
9
- import chalk from 'chalk';
10
9
  import path from 'path';
10
+ import _ from 'lodash';
11
+ import logger from '@docusaurus/logger';
11
12
  import readingTime from 'reading-time';
12
- import {Feed} from 'feed';
13
- import {keyBy, mapValues} from 'lodash';
14
13
  import {
15
- PluginOptions,
16
- BlogPost,
17
- DateLink,
18
- BlogContentPaths,
19
- BlogMarkdownLoaderOptions,
20
- } from './types';
21
- import {
22
- parseMarkdownFile,
14
+ parseMarkdownString,
23
15
  normalizeUrl,
24
16
  aliasedSitePath,
25
17
  getEditUrl,
@@ -27,252 +19,365 @@ import {
27
19
  posixPath,
28
20
  replaceMarkdownLinks,
29
21
  Globby,
22
+ normalizeFrontMatterTags,
23
+ groupTaggedItems,
24
+ getFileCommitDate,
25
+ getContentPathList,
30
26
  } from '@docusaurus/utils';
31
- import {LoadContext} from '@docusaurus/types';
32
- import {validateBlogPostFrontMatter} from './blogFrontMatter';
27
+ import {validateBlogPostFrontMatter} from './frontMatter';
28
+ import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
29
+ import type {LoadContext} from '@docusaurus/types';
30
+ import type {
31
+ PluginOptions,
32
+ ReadingTimeFunction,
33
+ BlogPost,
34
+ BlogTags,
35
+ BlogPaginated,
36
+ } from '@docusaurus/plugin-content-blog';
37
+ import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
33
38
 
34
39
  export function truncate(fileString: string, truncateMarker: RegExp): string {
35
40
  return fileString.split(truncateMarker, 1).shift()!;
36
41
  }
37
42
 
38
- export function getSourceToPermalink(
39
- blogPosts: BlogPost[],
40
- ): Record<string, string> {
41
- return mapValues(
42
- keyBy(blogPosts, (item) => item.metadata.source),
43
- (v) => v.metadata.permalink,
43
+ export function getSourceToPermalink(blogPosts: BlogPost[]): {
44
+ [aliasedPath: string]: string;
45
+ } {
46
+ return Object.fromEntries(
47
+ blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]),
44
48
  );
45
49
  }
46
50
 
47
- // YYYY-MM-DD-{name}.mdx?
48
- // Prefer named capture, but older Node versions do not support it.
49
- const DATE_FILENAME_PATTERN = /^(\d{4}-\d{1,2}-\d{1,2})-?(.*?).mdx?$/;
51
+ export function paginateBlogPosts({
52
+ blogPosts,
53
+ basePageUrl,
54
+ blogTitle,
55
+ blogDescription,
56
+ postsPerPageOption,
57
+ }: {
58
+ blogPosts: BlogPost[];
59
+ basePageUrl: string;
60
+ blogTitle: string;
61
+ blogDescription: string;
62
+ postsPerPageOption: number | 'ALL';
63
+ }): BlogPaginated[] {
64
+ const totalCount = blogPosts.length;
65
+ const postsPerPage =
66
+ postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
67
+ const numberOfPages = Math.ceil(totalCount / postsPerPage);
68
+
69
+ const pages: BlogPaginated[] = [];
70
+
71
+ function permalink(page: number) {
72
+ return page > 0
73
+ ? normalizeUrl([basePageUrl, `page/${page + 1}`])
74
+ : basePageUrl;
75
+ }
50
76
 
51
- function toUrl({date, link}: DateLink) {
52
- return `${date
53
- .toISOString()
54
- .substring(0, '2019-01-01'.length)
55
- .replace(/-/g, '/')}/${link}`;
77
+ for (let page = 0; page < numberOfPages; page += 1) {
78
+ pages.push({
79
+ items: blogPosts
80
+ .slice(page * postsPerPage, (page + 1) * postsPerPage)
81
+ .map((item) => item.id),
82
+ metadata: {
83
+ permalink: permalink(page),
84
+ page: page + 1,
85
+ postsPerPage,
86
+ totalPages: numberOfPages,
87
+ totalCount,
88
+ previousPage: page !== 0 ? permalink(page - 1) : undefined,
89
+ nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined,
90
+ blogDescription,
91
+ blogTitle,
92
+ },
93
+ });
94
+ }
95
+
96
+ return pages;
56
97
  }
57
98
 
58
- function formatBlogPostDate(locale: string, date: Date): string {
99
+ export function getBlogTags({
100
+ blogPosts,
101
+ ...params
102
+ }: {
103
+ blogPosts: BlogPost[];
104
+ blogTitle: string;
105
+ blogDescription: string;
106
+ postsPerPageOption: number | 'ALL';
107
+ }): BlogTags {
108
+ const groups = groupTaggedItems(
109
+ blogPosts,
110
+ (blogPost) => blogPost.metadata.tags,
111
+ );
112
+
113
+ return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
114
+ label: tag.label,
115
+ items: tagBlogPosts.map((item) => item.id),
116
+ permalink: tag.permalink,
117
+ pages: paginateBlogPosts({
118
+ blogPosts: tagBlogPosts,
119
+ basePageUrl: tag.permalink,
120
+ ...params,
121
+ }),
122
+ }));
123
+ }
124
+
125
+ const DATE_FILENAME_REGEX =
126
+ /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
127
+
128
+ type ParsedBlogFileName = {
129
+ date: Date | undefined;
130
+ text: string;
131
+ slug: string;
132
+ };
133
+
134
+ export function parseBlogFileName(
135
+ blogSourceRelative: string,
136
+ ): ParsedBlogFileName {
137
+ const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
138
+ if (dateFilenameMatch) {
139
+ const {folder, text, date: dateString} = dateFilenameMatch.groups!;
140
+ // Always treat dates as UTC by adding the `Z`
141
+ const date = new Date(`${dateString!}Z`);
142
+ const slugDate = dateString!.replace(/-/g, '/');
143
+ const slug = `/${slugDate}/${folder!}${text!}`;
144
+ return {date, text: text!, slug};
145
+ }
146
+ const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
147
+ const slug = `/${text}`;
148
+ return {date: undefined, text, slug};
149
+ }
150
+
151
+ function formatBlogPostDate(
152
+ locale: string,
153
+ date: Date,
154
+ calendar: string,
155
+ ): string {
59
156
  try {
60
157
  return new Intl.DateTimeFormat(locale, {
61
158
  day: 'numeric',
62
159
  month: 'long',
63
160
  year: 'numeric',
64
161
  timeZone: 'UTC',
162
+ calendar,
65
163
  }).format(date);
66
- } catch (e) {
67
- throw new Error(`Can't format blog post date "${date}"`);
164
+ } catch (err) {
165
+ logger.error`Can't format blog post date "${String(date)}"`;
166
+ throw err;
68
167
  }
69
168
  }
70
169
 
71
- export async function generateBlogFeed(
72
- contentPaths: BlogContentPaths,
73
- context: LoadContext,
74
- options: PluginOptions,
75
- ): Promise<Feed | null> {
76
- if (!options.feedOptions) {
77
- throw new Error(
78
- 'Invalid options: "feedOptions" is not expected to be null.',
79
- );
80
- }
81
- const {siteConfig} = context;
82
- const blogPosts = await generateBlogPosts(contentPaths, context, options);
83
- if (!blogPosts.length) {
84
- return null;
85
- }
86
-
87
- const {feedOptions, routeBasePath} = options;
88
- const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
89
- const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
90
-
91
- const updated =
92
- (blogPosts[0] && blogPosts[0].metadata.date) ||
93
- new Date('2015-10-25T16:29:00.000-07:00');
94
-
95
- const feed = new Feed({
96
- id: blogBaseUrl,
97
- title: feedOptions.title || `${title} Blog`,
98
- updated,
99
- language: feedOptions.language,
100
- link: blogBaseUrl,
101
- description: feedOptions.description || `${siteConfig.title} Blog`,
102
- favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
103
- copyright: feedOptions.copyright,
104
- });
105
-
106
- blogPosts.forEach((post) => {
107
- const {
108
- id,
109
- metadata: {title: metadataTitle, permalink, date, description},
110
- } = post;
111
- feed.addItem({
112
- title: metadataTitle,
113
- id,
114
- link: normalizeUrl([siteUrl, permalink]),
115
- date,
116
- description,
170
+ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
171
+ const markdownString = await fs.readFile(blogSourceAbsolute, 'utf-8');
172
+ try {
173
+ const result = parseMarkdownString(markdownString, {
174
+ removeContentTitle: true,
117
175
  });
118
- });
119
-
120
- return feed;
176
+ return {
177
+ ...result,
178
+ frontMatter: validateBlogPostFrontMatter(result.frontMatter),
179
+ };
180
+ } catch (err) {
181
+ logger.error`Error while parsing blog post file path=${blogSourceAbsolute}.`;
182
+ throw err;
183
+ }
121
184
  }
122
185
 
123
- export async function generateBlogPosts(
186
+ const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
187
+ readingTime(content, options).minutes;
188
+
189
+ async function processBlogSourceFile(
190
+ blogSourceRelative: string,
124
191
  contentPaths: BlogContentPaths,
125
- {siteConfig, siteDir, i18n}: LoadContext,
192
+ context: LoadContext,
126
193
  options: PluginOptions,
127
- ): Promise<BlogPost[]> {
194
+ authorsMap?: AuthorsMap,
195
+ ): Promise<BlogPost | undefined> {
196
+ const {
197
+ siteConfig: {baseUrl},
198
+ siteDir,
199
+ i18n,
200
+ } = context;
128
201
  const {
129
- include,
130
- exclude,
131
202
  routeBasePath,
203
+ tagsBasePath: tagsRouteBasePath,
132
204
  truncateMarker,
133
205
  showReadingTime,
134
206
  editUrl,
135
207
  } = options;
136
208
 
137
- if (!fs.existsSync(contentPaths.contentPath)) {
138
- return [];
139
- }
209
+ // Lookup in localized folder in priority
210
+ const blogDirPath = await getFolderContainingFile(
211
+ getContentPathList(contentPaths),
212
+ blogSourceRelative,
213
+ );
140
214
 
141
- const {baseUrl = ''} = siteConfig;
142
- const blogSourceFiles = await Globby(include, {
143
- cwd: contentPaths.contentPath,
144
- ignore: exclude,
145
- });
215
+ const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
146
216
 
147
- const blogPosts: BlogPost[] = [];
217
+ const {frontMatter, content, contentTitle, excerpt} =
218
+ await parseBlogPostMarkdownFile(blogSourceAbsolute);
148
219
 
149
- async function processBlogSourceFile(blogSourceFile: string) {
150
- // Lookup in localized folder in priority
151
- const blogDirPath = await getFolderContainingFile(
152
- getContentPathList(contentPaths),
153
- blogSourceFile,
154
- );
220
+ const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
155
221
 
156
- const source = path.join(blogDirPath, blogSourceFile);
157
-
158
- const {
159
- frontMatter: unsafeFrontMatter,
160
- content,
161
- contentTitle,
162
- excerpt,
163
- } = await parseMarkdownFile(source, {removeContentTitle: true});
164
- const frontMatter = validateBlogPostFrontMatter(unsafeFrontMatter);
222
+ if (frontMatter.draft && process.env.NODE_ENV === 'production') {
223
+ return undefined;
224
+ }
165
225
 
166
- const aliasedSource = aliasedSitePath(source, siteDir);
226
+ if (frontMatter.id) {
227
+ logger.warn`name=${'id'} header option is deprecated in path=${blogSourceRelative} file. Please use name=${'slug'} option instead.`;
228
+ }
167
229
 
168
- const blogFileName = path.basename(blogSourceFile);
230
+ const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
169
231
 
170
- if (frontMatter.draft && process.env.NODE_ENV === 'production') {
171
- return;
232
+ async function getDate(): Promise<Date> {
233
+ // Prefer user-defined date.
234
+ if (frontMatter.date) {
235
+ if (typeof frontMatter.date === 'string') {
236
+ // Always treat dates as UTC by adding the `Z`
237
+ return new Date(`${frontMatter.date}Z`);
238
+ }
239
+ // YAML only converts YYYY-MM-DD to dates and leaves others as strings.
240
+ return frontMatter.date;
241
+ } else if (parsedBlogFileName.date) {
242
+ return parsedBlogFileName.date;
172
243
  }
173
244
 
174
- if (frontMatter.id) {
175
- console.warn(
176
- chalk.yellow(
177
- `"id" header option is deprecated in ${blogFileName} file. Please use "slug" option instead.`,
178
- ),
179
- );
245
+ try {
246
+ const result = getFileCommitDate(blogSourceAbsolute, {
247
+ age: 'oldest',
248
+ includeAuthor: false,
249
+ });
250
+ return result.date;
251
+ } catch (err) {
252
+ logger.warn(err);
253
+ return (await fs.stat(blogSourceAbsolute)).birthtime;
180
254
  }
255
+ }
181
256
 
182
- let date: Date | undefined;
183
- // Extract date and title from filename.
184
- const dateFilenameMatch = blogFileName.match(DATE_FILENAME_PATTERN);
185
- let linkName = blogFileName.replace(/\.mdx?$/, '');
257
+ const date = await getDate();
258
+ const formattedDate = formatBlogPostDate(
259
+ i18n.currentLocale,
260
+ date,
261
+ i18n.localeConfigs[i18n.currentLocale]!.calendar,
262
+ );
186
263
 
187
- if (dateFilenameMatch) {
188
- const [, dateString, name] = dateFilenameMatch;
189
- // Always treat dates as UTC by adding the `Z`
190
- date = new Date(`${dateString}Z`);
191
- linkName = name;
192
- }
264
+ const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
265
+ const description = frontMatter.description ?? excerpt ?? '';
193
266
 
194
- // Prefer user-defined date.
195
- if (frontMatter.date) {
196
- date = new Date(frontMatter.date);
197
- }
267
+ const slug = frontMatter.slug ?? parsedBlogFileName.slug;
198
268
 
199
- // Use file create time for blog.
200
- date = date ?? (await fs.stat(source)).birthtime;
201
- const formattedDate = formatBlogPostDate(i18n.currentLocale, date);
202
-
203
- const title = frontMatter.title ?? contentTitle ?? linkName;
204
- const description = frontMatter.description ?? excerpt ?? '';
205
-
206
- const slug =
207
- frontMatter.slug ||
208
- (dateFilenameMatch ? toUrl({date, link: linkName}) : linkName);
209
-
210
- const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
211
-
212
- function getBlogEditUrl() {
213
- const blogPathRelative = path.relative(blogDirPath, path.resolve(source));
214
-
215
- if (typeof editUrl === 'function') {
216
- return editUrl({
217
- blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
218
- blogPath: posixPath(blogPathRelative),
219
- permalink,
220
- locale: i18n.currentLocale,
221
- });
222
- } else if (typeof editUrl === 'string') {
223
- const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
224
- const fileContentPath =
225
- isLocalized && options.editLocalizedFiles
226
- ? contentPaths.contentPathLocalized
227
- : contentPaths.contentPath;
228
-
229
- const contentPathEditUrl = normalizeUrl([
230
- editUrl,
231
- posixPath(path.relative(siteDir, fileContentPath)),
232
- ]);
233
-
234
- return getEditUrl(blogPathRelative, contentPathEditUrl);
235
- } else {
236
- return undefined;
237
- }
238
- }
269
+ const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
239
270
 
240
- blogPosts.push({
241
- id: frontMatter.slug ?? title,
242
- metadata: {
271
+ function getBlogEditUrl() {
272
+ const blogPathRelative = path.relative(
273
+ blogDirPath,
274
+ path.resolve(blogSourceAbsolute),
275
+ );
276
+
277
+ if (typeof editUrl === 'function') {
278
+ return editUrl({
279
+ blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
280
+ blogPath: posixPath(blogPathRelative),
243
281
  permalink,
244
- editUrl: getBlogEditUrl(),
245
- source: aliasedSource,
246
- title,
247
- description,
248
- date,
249
- formattedDate,
250
- tags: frontMatter.tags ?? [],
251
- readingTime: showReadingTime ? readingTime(content).minutes : undefined,
252
- truncated: truncateMarker?.test(content) || false,
253
- },
254
- });
282
+ locale: i18n.currentLocale,
283
+ });
284
+ } else if (typeof editUrl === 'string') {
285
+ const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
286
+ const fileContentPath =
287
+ isLocalized && options.editLocalizedFiles
288
+ ? contentPaths.contentPathLocalized
289
+ : contentPaths.contentPath;
290
+
291
+ const contentPathEditUrl = normalizeUrl([
292
+ editUrl,
293
+ posixPath(path.relative(siteDir, fileContentPath)),
294
+ ]);
295
+
296
+ return getEditUrl(blogPathRelative, contentPathEditUrl);
297
+ }
298
+ return undefined;
255
299
  }
256
300
 
257
- await Promise.all(
258
- blogSourceFiles.map(async (blogSourceFile: string) => {
259
- try {
260
- return await processBlogSourceFile(blogSourceFile);
261
- } catch (e) {
262
- console.error(
263
- chalk.red(
264
- `Processing of blog source file failed for path "${blogSourceFile}"`,
265
- ),
266
- );
267
- throw e;
268
- }
269
- }),
270
- );
301
+ const tagsBasePath = normalizeUrl([
302
+ baseUrl,
303
+ routeBasePath,
304
+ tagsRouteBasePath,
305
+ ]);
306
+ const authors = getBlogPostAuthors({authorsMap, frontMatter});
307
+
308
+ return {
309
+ id: slug,
310
+ metadata: {
311
+ permalink,
312
+ editUrl: getBlogEditUrl(),
313
+ source: aliasedSource,
314
+ title,
315
+ description,
316
+ date,
317
+ formattedDate,
318
+ tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
319
+ readingTime: showReadingTime
320
+ ? options.readingTime({
321
+ content,
322
+ frontMatter,
323
+ defaultReadingTime,
324
+ })
325
+ : undefined,
326
+ hasTruncateMarker: truncateMarker.test(content),
327
+ authors,
328
+ frontMatter,
329
+ },
330
+ content,
331
+ };
332
+ }
333
+
334
+ export async function generateBlogPosts(
335
+ contentPaths: BlogContentPaths,
336
+ context: LoadContext,
337
+ options: PluginOptions,
338
+ ): Promise<BlogPost[]> {
339
+ const {include, exclude} = options;
340
+
341
+ if (!(await fs.pathExists(contentPaths.contentPath))) {
342
+ return [];
343
+ }
344
+
345
+ const blogSourceFiles = await Globby(include, {
346
+ cwd: contentPaths.contentPath,
347
+ ignore: exclude,
348
+ });
349
+
350
+ const authorsMap = await getAuthorsMap({
351
+ contentPaths,
352
+ authorsMapPath: options.authorsMapPath,
353
+ });
354
+
355
+ const blogPosts = (
356
+ await Promise.all(
357
+ blogSourceFiles.map(async (blogSourceFile: string) => {
358
+ try {
359
+ return await processBlogSourceFile(
360
+ blogSourceFile,
361
+ contentPaths,
362
+ context,
363
+ options,
364
+ authorsMap,
365
+ );
366
+ } catch (err) {
367
+ logger.error`Processing of blog source file path=${blogSourceFile} failed.`;
368
+ throw err;
369
+ }
370
+ }),
371
+ )
372
+ ).filter(Boolean) as BlogPost[];
271
373
 
272
374
  blogPosts.sort(
273
375
  (a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
274
376
  );
275
377
 
378
+ if (options.sortPosts === 'ascending') {
379
+ return blogPosts.reverse();
380
+ }
276
381
  return blogPosts;
277
382
  }
278
383
 
@@ -304,8 +409,3 @@ export function linkify({
304
409
 
305
410
  return newContent;
306
411
  }
307
-
308
- // Order matters: we look in priority in localized folder
309
- export function getContentPathList(contentPaths: BlogContentPaths): string[] {
310
- return [contentPaths.contentPathLocalized, contentPaths.contentPath];
311
- }