@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/blogUtils.ts CHANGED
@@ -6,253 +6,378 @@
6
6
  */
7
7
 
8
8
  import fs from 'fs-extra';
9
- import globby from 'globby';
10
- import chalk from 'chalk';
11
9
  import path from 'path';
10
+ import _ from 'lodash';
11
+ import logger from '@docusaurus/logger';
12
12
  import readingTime from 'reading-time';
13
- import {Feed} from 'feed';
14
- import {keyBy, mapValues} from 'lodash';
15
13
  import {
16
- PluginOptions,
17
- BlogPost,
18
- DateLink,
19
- BlogContentPaths,
20
- BlogMarkdownLoaderOptions,
21
- } from './types';
22
- import {
23
- parseMarkdownFile,
14
+ parseMarkdownString,
24
15
  normalizeUrl,
25
16
  aliasedSitePath,
26
17
  getEditUrl,
27
18
  getFolderContainingFile,
28
19
  posixPath,
29
20
  replaceMarkdownLinks,
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
+ }
76
+
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;
97
+ }
98
+
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
+ );
50
112
 
51
- function toUrl({date, link}: DateLink) {
52
- return `${date
53
- .toISOString()
54
- .substring(0, '2019-01-01'.length)
55
- .replace(/-/g, '/')}/${link}`;
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
+ }));
56
123
  }
57
124
 
58
- export async function generateBlogFeed(
59
- contentPaths: BlogContentPaths,
60
- context: LoadContext,
61
- options: PluginOptions,
62
- ): Promise<Feed | null> {
63
- if (!options.feedOptions) {
64
- throw new Error(
65
- 'Invalid options - `feedOptions` is not expected to be null.',
66
- );
67
- }
68
- const {siteConfig} = context;
69
- const blogPosts = await generateBlogPosts(contentPaths, context, options);
70
- if (blogPosts == null) {
71
- return null;
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};
72
145
  }
146
+ const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
147
+ const slug = `/${text}`;
148
+ return {date: undefined, text, slug};
149
+ }
73
150
 
74
- const {feedOptions, routeBasePath} = options;
75
- const {url: siteUrl, title, favicon} = siteConfig;
76
- const blogBaseUrl = normalizeUrl([siteUrl, routeBasePath]);
77
-
78
- const updated =
79
- (blogPosts[0] && blogPosts[0].metadata.date) ||
80
- new Date('2015-10-25T16:29:00.000-07:00');
81
-
82
- const feed = new Feed({
83
- id: blogBaseUrl,
84
- title: feedOptions.title || `${title} Blog`,
85
- updated,
86
- language: feedOptions.language,
87
- link: blogBaseUrl,
88
- description: feedOptions.description || `${siteConfig.title} Blog`,
89
- favicon: normalizeUrl([siteUrl, favicon]),
90
- copyright: feedOptions.copyright,
91
- });
151
+ function formatBlogPostDate(
152
+ locale: string,
153
+ date: Date,
154
+ calendar: string,
155
+ ): string {
156
+ try {
157
+ return new Intl.DateTimeFormat(locale, {
158
+ day: 'numeric',
159
+ month: 'long',
160
+ year: 'numeric',
161
+ timeZone: 'UTC',
162
+ calendar,
163
+ }).format(date);
164
+ } catch (err) {
165
+ logger.error`Can't format blog post date "${String(date)}"`;
166
+ throw err;
167
+ }
168
+ }
92
169
 
93
- blogPosts.forEach((post) => {
94
- const {
95
- id,
96
- metadata: {title: metadataTitle, permalink, date, description},
97
- } = post;
98
- feed.addItem({
99
- title: metadataTitle,
100
- id,
101
- link: normalizeUrl([siteUrl, permalink]),
102
- date,
103
- 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,
104
175
  });
105
- });
106
-
107
- 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
+ }
108
184
  }
109
185
 
110
- export async function generateBlogPosts(
186
+ const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
187
+ readingTime(content, options).minutes;
188
+
189
+ async function processBlogSourceFile(
190
+ blogSourceRelative: string,
111
191
  contentPaths: BlogContentPaths,
112
- {siteConfig, siteDir, i18n}: LoadContext,
192
+ context: LoadContext,
113
193
  options: PluginOptions,
114
- ): Promise<BlogPost[]> {
194
+ authorsMap?: AuthorsMap,
195
+ ): Promise<BlogPost | undefined> {
196
+ const {
197
+ siteConfig: {baseUrl},
198
+ siteDir,
199
+ i18n,
200
+ } = context;
115
201
  const {
116
- include,
117
202
  routeBasePath,
203
+ tagsBasePath: tagsRouteBasePath,
118
204
  truncateMarker,
119
205
  showReadingTime,
120
206
  editUrl,
121
207
  } = options;
122
208
 
123
- if (!fs.existsSync(contentPaths.contentPath)) {
124
- return [];
125
- }
126
-
127
- const {baseUrl = ''} = siteConfig;
128
- const blogSourceFiles = await globby(include, {
129
- cwd: contentPaths.contentPath,
130
- });
209
+ // Lookup in localized folder in priority
210
+ const blogDirPath = await getFolderContainingFile(
211
+ getContentPathList(contentPaths),
212
+ blogSourceRelative,
213
+ );
131
214
 
132
- const blogPosts: BlogPost[] = [];
215
+ const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
133
216
 
134
- await Promise.all(
135
- blogSourceFiles.map(async (blogSourceFile: string) => {
136
- // Lookup in localized folder in priority
137
- const blogDirPath = await getFolderContainingFile(
138
- getContentPathList(contentPaths),
139
- blogSourceFile,
140
- );
217
+ const {frontMatter, content, contentTitle, excerpt} =
218
+ await parseBlogPostMarkdownFile(blogSourceAbsolute);
141
219
 
142
- const source = path.join(blogDirPath, blogSourceFile);
220
+ const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
143
221
 
144
- const {
145
- frontMatter: unsafeFrontMatter,
146
- content,
147
- contentTitle,
148
- excerpt,
149
- } = await parseMarkdownFile(source);
150
- const frontMatter = validateBlogPostFrontMatter(unsafeFrontMatter);
222
+ if (frontMatter.draft && process.env.NODE_ENV === 'production') {
223
+ return undefined;
224
+ }
151
225
 
152
- 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
+ }
153
229
 
154
- const blogFileName = path.basename(blogSourceFile);
230
+ const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
155
231
 
156
- if (frontMatter.draft && process.env.NODE_ENV === 'production') {
157
- 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`);
158
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;
243
+ }
244
+
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;
254
+ }
255
+ }
159
256
 
160
- if (frontMatter.id) {
161
- console.warn(
162
- chalk.yellow(
163
- `${blogFileName} - 'id' header option is deprecated. Please use 'slug' option instead.`,
164
- ),
165
- );
166
- }
257
+ const date = await getDate();
258
+ const formattedDate = formatBlogPostDate(
259
+ i18n.currentLocale,
260
+ date,
261
+ i18n.localeConfigs[i18n.currentLocale]!.calendar,
262
+ );
167
263
 
168
- let date: Date | undefined;
169
- // Extract date and title from filename.
170
- const dateFilenameMatch = blogFileName.match(DATE_FILENAME_PATTERN);
171
- let linkName = blogFileName.replace(/\.mdx?$/, '');
264
+ const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
265
+ const description = frontMatter.description ?? excerpt ?? '';
172
266
 
173
- if (dateFilenameMatch) {
174
- const [, dateString, name] = dateFilenameMatch;
175
- date = new Date(dateString);
176
- linkName = name;
177
- }
267
+ const slug = frontMatter.slug ?? parsedBlogFileName.slug;
178
268
 
179
- // Prefer user-defined date.
180
- if (frontMatter.date) {
181
- date = frontMatter.date;
182
- }
269
+ const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
183
270
 
184
- // Use file create time for blog.
185
- date = date ?? (await fs.stat(source)).birthtime;
186
- const formattedDate = new Intl.DateTimeFormat(i18n.currentLocale, {
187
- day: 'numeric',
188
- month: 'long',
189
- year: 'numeric',
190
- }).format(date);
191
-
192
- const title = frontMatter.title ?? contentTitle ?? linkName;
193
- const description = frontMatter.description ?? excerpt ?? '';
194
-
195
- const slug =
196
- frontMatter.slug ||
197
- (dateFilenameMatch ? toUrl({date, link: linkName}) : linkName);
198
-
199
- const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
200
-
201
- function getBlogEditUrl() {
202
- const blogPathRelative = path.relative(
203
- blogDirPath,
204
- path.resolve(source),
205
- );
206
-
207
- if (typeof editUrl === 'function') {
208
- return editUrl({
209
- blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
210
- blogPath: posixPath(blogPathRelative),
211
- permalink,
212
- locale: i18n.currentLocale,
213
- });
214
- } else if (typeof editUrl === 'string') {
215
- const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
216
- const fileContentPath =
217
- isLocalized && options.editLocalizedFiles
218
- ? contentPaths.contentPathLocalized
219
- : contentPaths.contentPath;
220
-
221
- const contentPathEditUrl = normalizeUrl([
222
- editUrl,
223
- posixPath(path.relative(siteDir, fileContentPath)),
224
- ]);
225
-
226
- return getEditUrl(blogPathRelative, contentPathEditUrl);
227
- } else {
228
- return undefined;
229
- }
230
- }
271
+ function getBlogEditUrl() {
272
+ const blogPathRelative = path.relative(
273
+ blogDirPath,
274
+ path.resolve(blogSourceAbsolute),
275
+ );
231
276
 
232
- blogPosts.push({
233
- id: frontMatter.slug ?? title,
234
- metadata: {
235
- permalink,
236
- editUrl: getBlogEditUrl(),
237
- source: aliasedSource,
238
- title,
239
- description,
240
- date,
241
- formattedDate,
242
- tags: frontMatter.tags ?? [],
243
- readingTime: showReadingTime
244
- ? readingTime(content).minutes
245
- : undefined,
246
- truncated: truncateMarker?.test(content) || false,
247
- },
277
+ if (typeof editUrl === 'function') {
278
+ return editUrl({
279
+ blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
280
+ blogPath: posixPath(blogPathRelative),
281
+ permalink,
282
+ locale: i18n.currentLocale,
248
283
  });
249
- }),
250
- );
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;
299
+ }
300
+
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[];
251
373
 
252
374
  blogPosts.sort(
253
375
  (a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
254
376
  );
255
377
 
378
+ if (options.sortPosts === 'ascending') {
379
+ return blogPosts.reverse();
380
+ }
256
381
  return blogPosts;
257
382
  }
258
383
 
@@ -284,8 +409,3 @@ export function linkify({
284
409
 
285
410
  return newContent;
286
411
  }
287
-
288
- // Order matters: we look in priority in localized folder
289
- export function getContentPathList(contentPaths: BlogContentPaths): string[] {
290
- return [contentPaths.contentPathLocalized, contentPaths.contentPath];
291
- }