@docusaurus/plugin-content-blog 2.0.0-beta.677e53d4d → 2.0.0-beta.7
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.
- package/lib/.tsbuildinfo +1 -1
- package/lib/authors.d.ts +23 -0
- package/lib/authors.js +150 -0
- package/lib/blogFrontMatter.d.ts +19 -6
- package/lib/blogFrontMatter.js +31 -19
- package/lib/blogUtils.d.ts +10 -2
- package/lib/blogUtils.js +146 -104
- package/lib/index.js +78 -77
- package/lib/markdownLoader.js +3 -3
- package/lib/pluginOptionSchema.d.ts +3 -26
- package/lib/pluginOptionSchema.js +22 -7
- package/lib/translations.d.ts +10 -0
- package/lib/translations.js +53 -0
- package/lib/types.d.ts +38 -14
- package/package.json +13 -11
- package/src/__tests__/__fixtures__/authorsMapFiles/authors.json +29 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +27 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.json +5 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad1.yml +3 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.json +3 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad2.yml +2 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.json +8 -0
- package/src/__tests__/__fixtures__/authorsMapFiles/authorsBad3.yml +3 -0
- package/src/__tests__/__fixtures__/component/Typography.tsx +6 -0
- package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathEmpty/empty +0 -0
- package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson1/authors.json +0 -0
- package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathJson2/authors.json +0 -0
- package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathNestedYml/sub/folder/authors.yml +0 -0
- package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml1/authors.yml +0 -0
- package/src/__tests__/__fixtures__/getAuthorsMapFilePath/contentPathYml2/authors.yml +0 -0
- package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
- package/src/__tests__/__fixtures__/website/blog/_partials/somePartial.md +3 -0
- package/src/__tests__/__fixtures__/website/blog/_partials/subfolder/somePartial.md +3 -0
- package/src/__tests__/__fixtures__/website/blog/_somePartial.md +3 -0
- package/src/__tests__/__fixtures__/website/blog/authors.yml +4 -0
- package/src/__tests__/__fixtures__/website/blog/mdx-blog-post.mdx +36 -0
- package/src/__tests__/__fixtures__/website/blog/simple-slug.md +4 -0
- package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +3 -0
- package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/authors.yml +5 -0
- package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +81 -3
- package/src/__tests__/__snapshots__/translations.test.ts.snap +64 -0
- package/src/__tests__/authors.test.ts +608 -0
- package/src/__tests__/blogFrontMatter.test.ts +93 -16
- package/src/__tests__/blogUtils.test.ts +94 -0
- package/src/__tests__/generateBlogFeed.test.ts +7 -1
- package/src/__tests__/index.test.ts +63 -12
- package/src/__tests__/pluginOptionSchema.test.ts +3 -3
- package/src/__tests__/translations.test.ts +92 -0
- package/src/authors.ts +202 -0
- package/src/blogFrontMatter.ts +73 -33
- package/src/blogUtils.ts +206 -131
- package/src/index.ts +98 -71
- package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
- package/src/pluginOptionSchema.ts +25 -9
- package/src/translations.ts +63 -0
- package/src/types.ts +48 -16
package/src/blogUtils.ts
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
|
-
import globby from 'globby';
|
|
10
9
|
import chalk from 'chalk';
|
|
11
10
|
import path from 'path';
|
|
12
11
|
import readingTime from 'reading-time';
|
|
13
|
-
import {Feed} from 'feed';
|
|
14
|
-
import {keyBy, mapValues} from 'lodash';
|
|
12
|
+
import {Feed, Author as FeedAuthor} from 'feed';
|
|
13
|
+
import {compact, keyBy, mapValues} from 'lodash';
|
|
15
14
|
import {
|
|
16
15
|
PluginOptions,
|
|
17
16
|
BlogPost,
|
|
18
|
-
DateLink,
|
|
19
17
|
BlogContentPaths,
|
|
20
18
|
BlogMarkdownLoaderOptions,
|
|
19
|
+
BlogTags,
|
|
20
|
+
Author,
|
|
21
21
|
} from './types';
|
|
22
22
|
import {
|
|
23
23
|
parseMarkdownFile,
|
|
@@ -26,10 +26,15 @@ import {
|
|
|
26
26
|
getEditUrl,
|
|
27
27
|
getFolderContainingFile,
|
|
28
28
|
posixPath,
|
|
29
|
+
mdxToHtml,
|
|
29
30
|
replaceMarkdownLinks,
|
|
31
|
+
Globby,
|
|
32
|
+
normalizeFrontMatterTags,
|
|
33
|
+
groupTaggedItems,
|
|
30
34
|
} from '@docusaurus/utils';
|
|
31
35
|
import {LoadContext} from '@docusaurus/types';
|
|
32
36
|
import {validateBlogPostFrontMatter} from './blogFrontMatter';
|
|
37
|
+
import {AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
33
38
|
|
|
34
39
|
export function truncate(fileString: string, truncateMarker: RegExp): string {
|
|
35
40
|
return fileString.split(truncateMarker, 1).shift()!;
|
|
@@ -44,15 +49,46 @@ export function getSourceToPermalink(
|
|
|
44
49
|
);
|
|
45
50
|
}
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
|
|
53
|
+
const groups = groupTaggedItems(
|
|
54
|
+
blogPosts,
|
|
55
|
+
(blogPost) => blogPost.metadata.tags,
|
|
56
|
+
);
|
|
57
|
+
return mapValues(groups, (group) => {
|
|
58
|
+
return {
|
|
59
|
+
name: group.tag.label,
|
|
60
|
+
items: group.items.map((item) => item.id),
|
|
61
|
+
permalink: group.tag.permalink,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
50
65
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
66
|
+
const DATE_FILENAME_REGEX =
|
|
67
|
+
/^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
|
|
68
|
+
|
|
69
|
+
type ParsedBlogFileName = {
|
|
70
|
+
date: Date | undefined;
|
|
71
|
+
text: string;
|
|
72
|
+
slug: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function parseBlogFileName(
|
|
76
|
+
blogSourceRelative: string,
|
|
77
|
+
): ParsedBlogFileName {
|
|
78
|
+
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
|
|
79
|
+
if (dateFilenameMatch) {
|
|
80
|
+
const dateString = dateFilenameMatch.groups!.date!;
|
|
81
|
+
const text = dateFilenameMatch.groups!.text!;
|
|
82
|
+
// Always treat dates as UTC by adding the `Z`
|
|
83
|
+
const date = new Date(`${dateString}Z`);
|
|
84
|
+
const slugDate = dateString.replace(/-/g, '/');
|
|
85
|
+
const slug = `/${slugDate}/${text}`;
|
|
86
|
+
return {date, text, slug};
|
|
87
|
+
} else {
|
|
88
|
+
const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
|
|
89
|
+
const slug = `/${text}`;
|
|
90
|
+
return {date: undefined, text, slug};
|
|
91
|
+
}
|
|
56
92
|
}
|
|
57
93
|
|
|
58
94
|
function formatBlogPostDate(locale: string, date: Date): string {
|
|
@@ -103,10 +139,16 @@ export async function generateBlogFeed(
|
|
|
103
139
|
copyright: feedOptions.copyright,
|
|
104
140
|
});
|
|
105
141
|
|
|
142
|
+
function toFeedAuthor(author: Author): FeedAuthor {
|
|
143
|
+
// TODO ask author emails?
|
|
144
|
+
// RSS feed requires email to render authors
|
|
145
|
+
return {name: author.name, link: author.url};
|
|
146
|
+
}
|
|
147
|
+
|
|
106
148
|
blogPosts.forEach((post) => {
|
|
107
149
|
const {
|
|
108
150
|
id,
|
|
109
|
-
metadata: {title: metadataTitle, permalink, date, description},
|
|
151
|
+
metadata: {title: metadataTitle, permalink, date, description, authors},
|
|
110
152
|
} = post;
|
|
111
153
|
feed.addItem({
|
|
112
154
|
title: metadataTitle,
|
|
@@ -114,157 +156,190 @@ export async function generateBlogFeed(
|
|
|
114
156
|
link: normalizeUrl([siteUrl, permalink]),
|
|
115
157
|
date,
|
|
116
158
|
description,
|
|
159
|
+
content: mdxToHtml(post.content),
|
|
160
|
+
author: authors.map(toFeedAuthor),
|
|
117
161
|
});
|
|
118
162
|
});
|
|
119
163
|
|
|
120
164
|
return feed;
|
|
121
165
|
}
|
|
122
166
|
|
|
123
|
-
|
|
167
|
+
async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
|
168
|
+
const result = await parseMarkdownFile(blogSourceAbsolute, {
|
|
169
|
+
removeContentTitle: true,
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
...result,
|
|
173
|
+
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function processBlogSourceFile(
|
|
178
|
+
blogSourceRelative: string,
|
|
124
179
|
contentPaths: BlogContentPaths,
|
|
125
|
-
|
|
180
|
+
context: LoadContext,
|
|
126
181
|
options: PluginOptions,
|
|
127
|
-
|
|
182
|
+
authorsMap?: AuthorsMap,
|
|
183
|
+
): Promise<BlogPost | undefined> {
|
|
184
|
+
const {
|
|
185
|
+
siteConfig: {baseUrl},
|
|
186
|
+
siteDir,
|
|
187
|
+
i18n,
|
|
188
|
+
} = context;
|
|
128
189
|
const {
|
|
129
|
-
include,
|
|
130
190
|
routeBasePath,
|
|
191
|
+
tagsBasePath: tagsRouteBasePath,
|
|
131
192
|
truncateMarker,
|
|
132
193
|
showReadingTime,
|
|
133
194
|
editUrl,
|
|
134
195
|
} = options;
|
|
135
196
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
197
|
+
// Lookup in localized folder in priority
|
|
198
|
+
const blogDirPath = await getFolderContainingFile(
|
|
199
|
+
getContentPathList(contentPaths),
|
|
200
|
+
blogSourceRelative,
|
|
201
|
+
);
|
|
139
202
|
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
203
|
+
const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
|
|
204
|
+
|
|
205
|
+
const {frontMatter, content, contentTitle, excerpt} =
|
|
206
|
+
await parseBlogPostMarkdownFile(blogSourceAbsolute);
|
|
207
|
+
|
|
208
|
+
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
|
144
209
|
|
|
145
|
-
|
|
210
|
+
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
146
213
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
214
|
+
if (frontMatter.id) {
|
|
215
|
+
console.warn(
|
|
216
|
+
chalk.yellow(
|
|
217
|
+
`"id" header option is deprecated in ${blogSourceRelative} file. Please use "slug" option instead.`,
|
|
218
|
+
),
|
|
152
219
|
);
|
|
220
|
+
}
|
|
153
221
|
|
|
154
|
-
|
|
222
|
+
const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
|
|
155
223
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
224
|
+
async function getDate(): Promise<Date> {
|
|
225
|
+
// Prefer user-defined date.
|
|
226
|
+
if (frontMatter.date) {
|
|
227
|
+
return new Date(frontMatter.date);
|
|
228
|
+
} else if (parsedBlogFileName.date) {
|
|
229
|
+
return parsedBlogFileName.date;
|
|
230
|
+
}
|
|
231
|
+
// Fallback to file create time
|
|
232
|
+
return (await fs.stat(blogSourceAbsolute)).birthtime;
|
|
233
|
+
}
|
|
163
234
|
|
|
164
|
-
|
|
235
|
+
const date = await getDate();
|
|
236
|
+
const formattedDate = formatBlogPostDate(i18n.currentLocale, date);
|
|
165
237
|
|
|
166
|
-
|
|
238
|
+
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
|
|
239
|
+
const description = frontMatter.description ?? excerpt ?? '';
|
|
167
240
|
|
|
168
|
-
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
241
|
+
const slug = frontMatter.slug || parsedBlogFileName.slug;
|
|
171
242
|
|
|
172
|
-
|
|
173
|
-
console.warn(
|
|
174
|
-
chalk.yellow(
|
|
175
|
-
`"id" header option is deprecated in ${blogFileName} file. Please use "slug" option instead.`,
|
|
176
|
-
),
|
|
177
|
-
);
|
|
178
|
-
}
|
|
243
|
+
const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
|
|
179
244
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
245
|
+
function getBlogEditUrl() {
|
|
246
|
+
const blogPathRelative = path.relative(
|
|
247
|
+
blogDirPath,
|
|
248
|
+
path.resolve(blogSourceAbsolute),
|
|
249
|
+
);
|
|
184
250
|
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
251
|
+
if (typeof editUrl === 'function') {
|
|
252
|
+
return editUrl({
|
|
253
|
+
blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
|
|
254
|
+
blogPath: posixPath(blogPathRelative),
|
|
255
|
+
permalink,
|
|
256
|
+
locale: i18n.currentLocale,
|
|
257
|
+
});
|
|
258
|
+
} else if (typeof editUrl === 'string') {
|
|
259
|
+
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
260
|
+
const fileContentPath =
|
|
261
|
+
isLocalized && options.editLocalizedFiles
|
|
262
|
+
? contentPaths.contentPathLocalized
|
|
263
|
+
: contentPaths.contentPath;
|
|
264
|
+
|
|
265
|
+
const contentPathEditUrl = normalizeUrl([
|
|
266
|
+
editUrl,
|
|
267
|
+
posixPath(path.relative(siteDir, fileContentPath)),
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
return getEditUrl(blogPathRelative, contentPathEditUrl);
|
|
190
271
|
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
191
274
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
275
|
+
const tagsBasePath = normalizeUrl([
|
|
276
|
+
baseUrl,
|
|
277
|
+
routeBasePath,
|
|
278
|
+
tagsRouteBasePath,
|
|
279
|
+
]);
|
|
280
|
+
const authors = getBlogPostAuthors({authorsMap, frontMatter});
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
id: frontMatter.slug ?? title,
|
|
284
|
+
metadata: {
|
|
285
|
+
permalink,
|
|
286
|
+
editUrl: getBlogEditUrl(),
|
|
287
|
+
source: aliasedSource,
|
|
288
|
+
title,
|
|
289
|
+
description,
|
|
290
|
+
date,
|
|
291
|
+
formattedDate,
|
|
292
|
+
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
|
|
293
|
+
readingTime: showReadingTime ? readingTime(content).minutes : undefined,
|
|
294
|
+
truncated: truncateMarker?.test(content) || false,
|
|
295
|
+
authors,
|
|
296
|
+
},
|
|
297
|
+
content,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
196
300
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const slug =
|
|
205
|
-
frontMatter.slug ||
|
|
206
|
-
(dateFilenameMatch ? toUrl({date, link: linkName}) : linkName);
|
|
207
|
-
|
|
208
|
-
const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
|
|
209
|
-
|
|
210
|
-
function getBlogEditUrl() {
|
|
211
|
-
const blogPathRelative = path.relative(blogDirPath, path.resolve(source));
|
|
212
|
-
|
|
213
|
-
if (typeof editUrl === 'function') {
|
|
214
|
-
return editUrl({
|
|
215
|
-
blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
|
|
216
|
-
blogPath: posixPath(blogPathRelative),
|
|
217
|
-
permalink,
|
|
218
|
-
locale: i18n.currentLocale,
|
|
219
|
-
});
|
|
220
|
-
} else if (typeof editUrl === 'string') {
|
|
221
|
-
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
222
|
-
const fileContentPath =
|
|
223
|
-
isLocalized && options.editLocalizedFiles
|
|
224
|
-
? contentPaths.contentPathLocalized
|
|
225
|
-
: contentPaths.contentPath;
|
|
226
|
-
|
|
227
|
-
const contentPathEditUrl = normalizeUrl([
|
|
228
|
-
editUrl,
|
|
229
|
-
posixPath(path.relative(siteDir, fileContentPath)),
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
return getEditUrl(blogPathRelative, contentPathEditUrl);
|
|
233
|
-
} else {
|
|
234
|
-
return undefined;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
301
|
+
export async function generateBlogPosts(
|
|
302
|
+
contentPaths: BlogContentPaths,
|
|
303
|
+
context: LoadContext,
|
|
304
|
+
options: PluginOptions,
|
|
305
|
+
): Promise<BlogPost[]> {
|
|
306
|
+
const {include, exclude} = options;
|
|
237
307
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
metadata: {
|
|
241
|
-
permalink,
|
|
242
|
-
editUrl: getBlogEditUrl(),
|
|
243
|
-
source: aliasedSource,
|
|
244
|
-
title,
|
|
245
|
-
description,
|
|
246
|
-
date,
|
|
247
|
-
formattedDate,
|
|
248
|
-
tags: frontMatter.tags ?? [],
|
|
249
|
-
readingTime: showReadingTime ? readingTime(content).minutes : undefined,
|
|
250
|
-
truncated: truncateMarker?.test(content) || false,
|
|
251
|
-
},
|
|
252
|
-
});
|
|
308
|
+
if (!fs.existsSync(contentPaths.contentPath)) {
|
|
309
|
+
return [];
|
|
253
310
|
}
|
|
254
311
|
|
|
255
|
-
await
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
312
|
+
const blogSourceFiles = await Globby(include, {
|
|
313
|
+
cwd: contentPaths.contentPath,
|
|
314
|
+
ignore: exclude,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const authorsMap = await getAuthorsMap({
|
|
318
|
+
contentPaths,
|
|
319
|
+
authorsMapPath: options.authorsMapPath,
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const blogPosts: BlogPost[] = compact(
|
|
323
|
+
await Promise.all(
|
|
324
|
+
blogSourceFiles.map(async (blogSourceFile: string) => {
|
|
325
|
+
try {
|
|
326
|
+
return await processBlogSourceFile(
|
|
327
|
+
blogSourceFile,
|
|
328
|
+
contentPaths,
|
|
329
|
+
context,
|
|
330
|
+
options,
|
|
331
|
+
authorsMap,
|
|
332
|
+
);
|
|
333
|
+
} catch (e) {
|
|
334
|
+
console.error(
|
|
335
|
+
chalk.red(
|
|
336
|
+
`Processing of blog source file failed for path "${blogSourceFile}"`,
|
|
337
|
+
),
|
|
338
|
+
);
|
|
339
|
+
throw e;
|
|
340
|
+
}
|
|
341
|
+
}),
|
|
342
|
+
),
|
|
268
343
|
);
|
|
269
344
|
|
|
270
345
|
blogPosts.sort(
|