@docusaurus/plugin-content-blog 3.4.0 → 3.5.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.
- package/assets/atom.css +75 -0
- package/assets/atom.xsl +92 -0
- package/assets/rss.css +75 -0
- package/assets/rss.xsl +86 -0
- package/lib/authors.d.ts +9 -11
- package/lib/authors.js +42 -64
- package/lib/authorsMap.d.ts +23 -0
- package/lib/authorsMap.js +116 -0
- package/lib/authorsProblems.d.ts +21 -0
- package/lib/authorsProblems.js +51 -0
- package/lib/authorsSocials.d.ts +10 -0
- package/lib/authorsSocials.js +48 -0
- package/lib/blogUtils.d.ts +6 -3
- package/lib/blogUtils.js +29 -14
- package/lib/client/contexts.d.ts +33 -0
- package/lib/client/contexts.js +54 -0
- package/lib/client/index.d.ts +3 -3
- package/lib/client/index.js +3 -9
- package/lib/client/sidebarUtils.d.ts +21 -0
- package/lib/client/sidebarUtils.js +49 -0
- package/lib/client/sidebarUtils.test.d.ts +7 -0
- package/lib/client/sidebarUtils.test.js +43 -0
- package/lib/client/structuredDataUtils.d.ts +10 -0
- package/lib/client/structuredDataUtils.js +122 -0
- package/lib/feed.d.ts +3 -2
- package/lib/feed.js +69 -21
- package/lib/frontMatter.d.ts +0 -1
- package/lib/frontMatter.js +3 -2
- package/lib/index.d.ts +0 -1
- package/lib/index.js +23 -4
- package/lib/markdownLoader.js +1 -1
- package/lib/options.d.ts +4 -1
- package/lib/options.js +98 -26
- package/lib/props.d.ts +9 -2
- package/lib/props.js +21 -3
- package/lib/remark/footnoteIDFixer.js +1 -1
- package/lib/routes.d.ts +0 -1
- package/lib/routes.js +82 -14
- package/lib/translations.d.ts +0 -1
- package/lib/translations.js +2 -3
- package/package.json +14 -11
- package/src/authors.ts +56 -93
- package/src/authorsMap.ts +171 -0
- package/src/authorsProblems.ts +72 -0
- package/src/authorsSocials.ts +64 -0
- package/src/blogUtils.ts +34 -7
- package/src/client/contexts.tsx +95 -0
- package/src/client/index.tsx +24 -0
- package/src/client/sidebarUtils.test.ts +52 -0
- package/src/client/sidebarUtils.tsx +85 -0
- package/src/client/structuredDataUtils.ts +178 -0
- package/src/feed.ts +140 -17
- package/src/frontMatter.ts +2 -0
- package/src/index.ts +31 -1
- package/src/options.ts +123 -32
- package/src/plugin-content-blog.d.ts +150 -12
- package/src/props.ts +39 -1
- package/src/routes.ts +102 -12
- package/src/client/index.ts +0 -20
package/lib/routes.js
CHANGED
|
@@ -6,22 +6,28 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
9
|
+
exports.createAllRoutes = createAllRoutes;
|
|
10
|
+
exports.buildAllRoutes = buildAllRoutes;
|
|
10
11
|
const tslib_1 = require("tslib");
|
|
11
12
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
12
13
|
const utils_1 = require("@docusaurus/utils");
|
|
13
14
|
const blogUtils_1 = require("./blogUtils");
|
|
14
15
|
const props_1 = require("./props");
|
|
16
|
+
const authors_1 = require("./authors");
|
|
15
17
|
async function createAllRoutes(param) {
|
|
16
18
|
const routes = await buildAllRoutes(param);
|
|
17
19
|
routes.forEach(param.actions.addRoute);
|
|
18
20
|
}
|
|
19
|
-
exports.createAllRoutes = createAllRoutes;
|
|
20
21
|
async function buildAllRoutes({ baseUrl, content, actions, options, aliasedSource, }) {
|
|
21
|
-
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, blogTitle, } = options;
|
|
22
|
+
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogAuthorsListComponent, blogAuthorsPostsComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, blogTitle, authorsBasePath, postsPerPage, blogDescription, } = options;
|
|
22
23
|
const pluginId = options.id;
|
|
23
24
|
const { createData } = actions;
|
|
24
|
-
const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = content;
|
|
25
|
+
const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, authorsMap, } = content;
|
|
26
|
+
const authorsListPath = (0, utils_1.normalizeUrl)([
|
|
27
|
+
baseUrl,
|
|
28
|
+
routeBasePath,
|
|
29
|
+
authorsBasePath,
|
|
30
|
+
]);
|
|
25
31
|
const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
|
|
26
32
|
const blogPostsById = lodash_1.default.keyBy(blogPosts, (post) => post.id);
|
|
27
33
|
function getBlogPostById(id) {
|
|
@@ -35,21 +41,18 @@ async function buildAllRoutes({ baseUrl, content, actions, options, aliasedSourc
|
|
|
35
41
|
? blogPosts
|
|
36
42
|
: blogPosts.slice(0, options.blogSidebarCount);
|
|
37
43
|
async function createSidebarModule() {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
unlisted: blogPost.metadata.unlisted,
|
|
44
|
-
})),
|
|
45
|
-
};
|
|
46
|
-
const modulePath = await createData(`blog-post-list-prop-${pluginId}.json`, sidebar);
|
|
44
|
+
const sidebarProp = (0, props_1.toBlogSidebarProp)({
|
|
45
|
+
blogSidebarTitle,
|
|
46
|
+
blogPosts: sidebarBlogPosts,
|
|
47
|
+
});
|
|
48
|
+
const modulePath = await createData(`blog-post-list-prop-${pluginId}.json`, sidebarProp);
|
|
47
49
|
return aliasedSource(modulePath);
|
|
48
50
|
}
|
|
49
51
|
async function createBlogMetadataModule() {
|
|
50
52
|
const blogMetadata = {
|
|
51
53
|
blogBasePath: (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]),
|
|
52
54
|
blogTitle,
|
|
55
|
+
authorsListPath,
|
|
53
56
|
};
|
|
54
57
|
const modulePath = await createData(`blogMetadata-${pluginId}.json`, blogMetadata);
|
|
55
58
|
return aliasedSource(modulePath);
|
|
@@ -172,11 +175,76 @@ async function buildAllRoutes({ baseUrl, content, actions, options, aliasedSourc
|
|
|
172
175
|
const tagsPaginatedRoutes = Object.values(blogTags).flatMap(createTagPaginatedRoutes);
|
|
173
176
|
return [tagsListRoute, ...tagsPaginatedRoutes];
|
|
174
177
|
}
|
|
178
|
+
function createAuthorsRoutes() {
|
|
179
|
+
if (authorsMap === undefined || Object.keys(authorsMap).length === 0) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
const blogPostsByAuthorKey = (0, authors_1.groupBlogPostsByAuthorKey)({
|
|
183
|
+
authorsMap,
|
|
184
|
+
blogPosts,
|
|
185
|
+
});
|
|
186
|
+
const authors = Object.values(authorsMap);
|
|
187
|
+
return [
|
|
188
|
+
createAuthorListRoute(),
|
|
189
|
+
...authors.flatMap(createAuthorPaginatedRoute),
|
|
190
|
+
];
|
|
191
|
+
function createAuthorListRoute() {
|
|
192
|
+
return {
|
|
193
|
+
path: authorsListPath,
|
|
194
|
+
component: blogAuthorsListComponent,
|
|
195
|
+
exact: true,
|
|
196
|
+
modules: {
|
|
197
|
+
sidebar: sidebarModulePath,
|
|
198
|
+
},
|
|
199
|
+
props: {
|
|
200
|
+
authors: authors.map((author) => (0, props_1.toAuthorItemProp)({
|
|
201
|
+
author,
|
|
202
|
+
count: blogPostsByAuthorKey[author.key]?.length ?? 0,
|
|
203
|
+
})),
|
|
204
|
+
},
|
|
205
|
+
context: {
|
|
206
|
+
blogMetadata: blogMetadataModulePath,
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function createAuthorPaginatedRoute(author) {
|
|
211
|
+
const authorBlogPosts = blogPostsByAuthorKey[author.key] ?? [];
|
|
212
|
+
if (!author.page) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
const pages = (0, blogUtils_1.paginateBlogPosts)({
|
|
216
|
+
blogPosts: authorBlogPosts,
|
|
217
|
+
basePageUrl: author.page.permalink,
|
|
218
|
+
blogDescription,
|
|
219
|
+
blogTitle,
|
|
220
|
+
pageBasePath: authorsBasePath,
|
|
221
|
+
postsPerPageOption: postsPerPage,
|
|
222
|
+
});
|
|
223
|
+
return pages.map(({ metadata, items }) => {
|
|
224
|
+
return {
|
|
225
|
+
path: metadata.permalink,
|
|
226
|
+
component: blogAuthorsPostsComponent,
|
|
227
|
+
exact: true,
|
|
228
|
+
modules: {
|
|
229
|
+
items: blogPostItemsModule(items),
|
|
230
|
+
sidebar: sidebarModulePath,
|
|
231
|
+
},
|
|
232
|
+
props: {
|
|
233
|
+
author: (0, props_1.toAuthorItemProp)({ author, count: authorBlogPosts.length }),
|
|
234
|
+
listMetadata: metadata,
|
|
235
|
+
},
|
|
236
|
+
context: {
|
|
237
|
+
blogMetadata: blogMetadataModulePath,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
175
243
|
return [
|
|
176
244
|
...createBlogPostRoutes(),
|
|
177
245
|
...createBlogPostsPaginatedRoutes(),
|
|
178
246
|
...createTagsRoutes(),
|
|
179
247
|
...createArchiveRoute(),
|
|
248
|
+
...createAuthorsRoutes(),
|
|
180
249
|
];
|
|
181
250
|
}
|
|
182
|
-
exports.buildAllRoutes = buildAllRoutes;
|
package/lib/translations.d.ts
CHANGED
|
@@ -4,7 +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 path="../src/plugin-content-blog.d.ts" />
|
|
8
7
|
import type { TranslationFile } from '@docusaurus/types';
|
|
9
8
|
import type { PluginOptions, BlogContent } from '@docusaurus/plugin-content-blog';
|
|
10
9
|
export declare function getTranslationFiles(options: PluginOptions): TranslationFile[];
|
package/lib/translations.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
9
|
+
exports.getTranslationFiles = getTranslationFiles;
|
|
10
|
+
exports.translateContent = translateContent;
|
|
10
11
|
function translateListPage(blogListPaginated, translations) {
|
|
11
12
|
return blogListPaginated.map((page) => {
|
|
12
13
|
const { items, metadata } = page;
|
|
@@ -41,7 +42,6 @@ function getTranslationFiles(options) {
|
|
|
41
42
|
},
|
|
42
43
|
];
|
|
43
44
|
}
|
|
44
|
-
exports.getTranslationFiles = getTranslationFiles;
|
|
45
45
|
function translateContent(content, translationFiles) {
|
|
46
46
|
const { content: optionsTranslations } = translationFiles[0];
|
|
47
47
|
return {
|
|
@@ -50,4 +50,3 @@ function translateContent(content, translationFiles) {
|
|
|
50
50
|
blogListPaginated: translateListPage(content.blogListPaginated, optionsTranslations),
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
exports.translateContent = translateContent;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-content-blog",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.1",
|
|
4
4
|
"description": "Blog plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-content-blog.d.ts",
|
|
@@ -31,14 +31,15 @@
|
|
|
31
31
|
},
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@docusaurus/core": "3.
|
|
35
|
-
"@docusaurus/logger": "3.
|
|
36
|
-
"@docusaurus/mdx-loader": "3.
|
|
37
|
-
"@docusaurus/
|
|
38
|
-
"@docusaurus/
|
|
39
|
-
"@docusaurus/utils
|
|
40
|
-
"@docusaurus/utils-
|
|
41
|
-
"
|
|
34
|
+
"@docusaurus/core": "3.5.1",
|
|
35
|
+
"@docusaurus/logger": "3.5.1",
|
|
36
|
+
"@docusaurus/mdx-loader": "3.5.1",
|
|
37
|
+
"@docusaurus/theme-common": "3.5.1",
|
|
38
|
+
"@docusaurus/types": "3.5.1",
|
|
39
|
+
"@docusaurus/utils": "3.5.1",
|
|
40
|
+
"@docusaurus/utils-common": "3.5.1",
|
|
41
|
+
"@docusaurus/utils-validation": "3.5.1",
|
|
42
|
+
"cheerio": "1.0.0-rc.12",
|
|
42
43
|
"feed": "^4.2.2",
|
|
43
44
|
"fs-extra": "^11.1.1",
|
|
44
45
|
"lodash": "^4.17.21",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"webpack": "^5.88.1"
|
|
51
52
|
},
|
|
52
53
|
"peerDependencies": {
|
|
54
|
+
"@docusaurus/plugin-content-docs": "*",
|
|
53
55
|
"react": "^18.0.0",
|
|
54
56
|
"react-dom": "^18.0.0"
|
|
55
57
|
},
|
|
@@ -57,7 +59,8 @@
|
|
|
57
59
|
"node": ">=18.0"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
|
-
"@total-typescript/shoehorn": "^0.1.2"
|
|
62
|
+
"@total-typescript/shoehorn": "^0.1.2",
|
|
63
|
+
"tree-node-cli": "^1.6.0"
|
|
61
64
|
},
|
|
62
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "5acbc57bd62f3f808ef72007cd67d4c410d13829"
|
|
63
66
|
}
|
package/src/authors.ts
CHANGED
|
@@ -5,66 +5,16 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import type {BlogContentPaths} from './types';
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
import {normalizeUrl} from '@docusaurus/utils';
|
|
11
10
|
import type {
|
|
12
11
|
Author,
|
|
12
|
+
AuthorsMap,
|
|
13
|
+
BlogPost,
|
|
13
14
|
BlogPostFrontMatter,
|
|
14
15
|
BlogPostFrontMatterAuthor,
|
|
15
|
-
BlogPostFrontMatterAuthors,
|
|
16
16
|
} from '@docusaurus/plugin-content-blog';
|
|
17
17
|
|
|
18
|
-
export type AuthorsMap = {[authorKey: string]: Author};
|
|
19
|
-
|
|
20
|
-
const AuthorsMapSchema = Joi.object<AuthorsMap>()
|
|
21
|
-
.pattern(
|
|
22
|
-
Joi.string(),
|
|
23
|
-
Joi.object({
|
|
24
|
-
name: Joi.string(),
|
|
25
|
-
url: URISchema,
|
|
26
|
-
imageURL: URISchema,
|
|
27
|
-
title: Joi.string(),
|
|
28
|
-
email: Joi.string(),
|
|
29
|
-
})
|
|
30
|
-
.rename('image_url', 'imageURL')
|
|
31
|
-
.or('name', 'imageURL')
|
|
32
|
-
.unknown()
|
|
33
|
-
.required()
|
|
34
|
-
.messages({
|
|
35
|
-
'object.base':
|
|
36
|
-
'{#label} should be an author object containing properties like name, title, and imageURL.',
|
|
37
|
-
'any.required':
|
|
38
|
-
'{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.',
|
|
39
|
-
}),
|
|
40
|
-
)
|
|
41
|
-
.messages({
|
|
42
|
-
'object.base':
|
|
43
|
-
"The authors map file should contain an object where each entry contains an author key and the corresponding author's data.",
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
export function validateAuthorsMap(content: unknown): AuthorsMap {
|
|
47
|
-
const {error, value} = AuthorsMapSchema.validate(content);
|
|
48
|
-
if (error) {
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
return value;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function getAuthorsMap(params: {
|
|
55
|
-
authorsMapPath: string;
|
|
56
|
-
contentPaths: BlogContentPaths;
|
|
57
|
-
}): Promise<AuthorsMap | undefined> {
|
|
58
|
-
return getDataFileData(
|
|
59
|
-
{
|
|
60
|
-
filePath: params.authorsMapPath,
|
|
61
|
-
contentPaths: params.contentPaths,
|
|
62
|
-
fileType: 'authors map',
|
|
63
|
-
},
|
|
64
|
-
validateAuthorsMap,
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
18
|
type AuthorsParam = {
|
|
69
19
|
frontMatter: BlogPostFrontMatter;
|
|
70
20
|
authorsMap: AuthorsMap | undefined;
|
|
@@ -85,6 +35,7 @@ function normalizeImageUrl({
|
|
|
85
35
|
|
|
86
36
|
// Legacy v1/early-v2 front matter fields
|
|
87
37
|
// We may want to deprecate those in favor of using only frontMatter.authors
|
|
38
|
+
// TODO Docusaurus v4: remove this legacy front matter
|
|
88
39
|
function getFrontMatterAuthorLegacy({
|
|
89
40
|
baseUrl,
|
|
90
41
|
frontMatter,
|
|
@@ -106,37 +57,40 @@ function getFrontMatterAuthorLegacy({
|
|
|
106
57
|
title,
|
|
107
58
|
url,
|
|
108
59
|
imageURL,
|
|
60
|
+
// legacy front matter authors do not have an author key/page
|
|
61
|
+
key: null,
|
|
62
|
+
page: null,
|
|
109
63
|
};
|
|
110
64
|
}
|
|
111
65
|
|
|
112
66
|
return undefined;
|
|
113
67
|
}
|
|
114
68
|
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
// Technically, we could allow users to provide an author's name here, but
|
|
123
|
-
// we only support keys, otherwise, a typo in a key would fallback to
|
|
124
|
-
// becoming a name and may end up unnoticed
|
|
125
|
-
return {key: authorInput};
|
|
69
|
+
function getFrontMatterAuthors(params: AuthorsParam): Author[] {
|
|
70
|
+
const {authorsMap, frontMatter, baseUrl} = params;
|
|
71
|
+
return normalizeFrontMatterAuthors().map(toAuthor);
|
|
72
|
+
|
|
73
|
+
function normalizeFrontMatterAuthors(): BlogPostFrontMatterAuthor[] {
|
|
74
|
+
if (frontMatter.authors === undefined) {
|
|
75
|
+
return [];
|
|
126
76
|
}
|
|
127
|
-
return authorInput;
|
|
128
|
-
}
|
|
129
77
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
:
|
|
133
|
-
|
|
78
|
+
function normalizeAuthor(
|
|
79
|
+
authorInput: string | BlogPostFrontMatterAuthor,
|
|
80
|
+
): BlogPostFrontMatterAuthor {
|
|
81
|
+
if (typeof authorInput === 'string') {
|
|
82
|
+
// We could allow users to provide an author's name here, but we only
|
|
83
|
+
// support keys, otherwise, a typo in a key would fall back to
|
|
84
|
+
// becoming a name and may end up unnoticed
|
|
85
|
+
return {key: authorInput};
|
|
86
|
+
}
|
|
87
|
+
return authorInput;
|
|
88
|
+
}
|
|
134
89
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
);
|
|
90
|
+
return Array.isArray(frontMatter.authors)
|
|
91
|
+
? frontMatter.authors.map(normalizeAuthor)
|
|
92
|
+
: [normalizeAuthor(frontMatter.authors)];
|
|
93
|
+
}
|
|
140
94
|
|
|
141
95
|
function getAuthorsMapAuthor(key: string | undefined): Author | undefined {
|
|
142
96
|
if (key) {
|
|
@@ -158,36 +112,29 @@ ${Object.keys(authorsMap)
|
|
|
158
112
|
}
|
|
159
113
|
|
|
160
114
|
function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author {
|
|
161
|
-
|
|
115
|
+
const author = {
|
|
162
116
|
// Author def from authorsMap can be locally overridden by front matter
|
|
163
117
|
...getAuthorsMapAuthor(frontMatterAuthor.key),
|
|
164
118
|
...frontMatterAuthor,
|
|
165
119
|
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
return frontMatterAuthors.map(toAuthor);
|
|
169
|
-
}
|
|
170
120
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}));
|
|
121
|
+
return {
|
|
122
|
+
...author,
|
|
123
|
+
key: author.key ?? null,
|
|
124
|
+
page: author.page ?? null,
|
|
125
|
+
imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
179
128
|
}
|
|
180
129
|
|
|
181
130
|
export function getBlogPostAuthors(params: AuthorsParam): Author[] {
|
|
182
131
|
const authorLegacy = getFrontMatterAuthorLegacy(params);
|
|
183
132
|
const authors = getFrontMatterAuthors(params);
|
|
184
133
|
|
|
185
|
-
const updatedAuthors = fixAuthorImageBaseURL(authors, params);
|
|
186
|
-
|
|
187
134
|
if (authorLegacy) {
|
|
188
135
|
// Technically, we could allow mixing legacy/authors front matter, but do we
|
|
189
136
|
// really want to?
|
|
190
|
-
if (
|
|
137
|
+
if (authors.length > 0) {
|
|
191
138
|
throw new Error(
|
|
192
139
|
`To declare blog post authors, use the 'authors' front matter in priority.
|
|
193
140
|
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`,
|
|
@@ -196,5 +143,21 @@ Don't mix 'authors' with other existing 'author_*' front matter. Choose one or t
|
|
|
196
143
|
return [authorLegacy];
|
|
197
144
|
}
|
|
198
145
|
|
|
199
|
-
return
|
|
146
|
+
return authors;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Group blog posts by author key
|
|
151
|
+
* Blog posts with only inline authors are ignored
|
|
152
|
+
*/
|
|
153
|
+
export function groupBlogPostsByAuthorKey({
|
|
154
|
+
blogPosts,
|
|
155
|
+
authorsMap,
|
|
156
|
+
}: {
|
|
157
|
+
blogPosts: BlogPost[];
|
|
158
|
+
authorsMap: AuthorsMap | undefined;
|
|
159
|
+
}): Record<string, BlogPost[]> {
|
|
160
|
+
return _.mapValues(authorsMap, (author, key) =>
|
|
161
|
+
blogPosts.filter((p) => p.metadata.authors.some((a) => a.key === key)),
|
|
162
|
+
);
|
|
200
163
|
}
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
import {readDataFile, normalizeUrl} from '@docusaurus/utils';
|
|
10
|
+
import {Joi, URISchema} from '@docusaurus/utils-validation';
|
|
11
|
+
import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials';
|
|
12
|
+
import type {BlogContentPaths} from './types';
|
|
13
|
+
import type {
|
|
14
|
+
Author,
|
|
15
|
+
AuthorAttributes,
|
|
16
|
+
AuthorPage,
|
|
17
|
+
AuthorsMap,
|
|
18
|
+
} from '@docusaurus/plugin-content-blog';
|
|
19
|
+
|
|
20
|
+
type AuthorInput = AuthorAttributes & {
|
|
21
|
+
page?: boolean | AuthorPage;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AuthorsMapInput = {[authorKey: string]: AuthorInput};
|
|
25
|
+
|
|
26
|
+
const AuthorPageSchema = Joi.object<AuthorPage>({
|
|
27
|
+
permalink: Joi.string().required(),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const AuthorsMapInputSchema = Joi.object<AuthorsMapInput>()
|
|
31
|
+
.pattern(
|
|
32
|
+
Joi.string(),
|
|
33
|
+
Joi.object({
|
|
34
|
+
name: Joi.string(),
|
|
35
|
+
url: URISchema,
|
|
36
|
+
imageURL: URISchema,
|
|
37
|
+
title: Joi.string(),
|
|
38
|
+
email: Joi.string(),
|
|
39
|
+
page: Joi.alternatives(Joi.bool(), AuthorPageSchema),
|
|
40
|
+
socials: AuthorSocialsSchema,
|
|
41
|
+
description: Joi.string(),
|
|
42
|
+
})
|
|
43
|
+
.rename('image_url', 'imageURL')
|
|
44
|
+
.or('name', 'imageURL')
|
|
45
|
+
.unknown()
|
|
46
|
+
.required()
|
|
47
|
+
.messages({
|
|
48
|
+
'object.base':
|
|
49
|
+
'{#label} should be an author object containing properties like name, title, and imageURL.',
|
|
50
|
+
'any.required':
|
|
51
|
+
'{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.',
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
54
|
+
.messages({
|
|
55
|
+
'object.base':
|
|
56
|
+
"The authors map file should contain an object where each entry contains an author key and the corresponding author's data.",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export function checkAuthorsMapPermalinkCollisions(
|
|
60
|
+
authorsMap: AuthorsMap | undefined,
|
|
61
|
+
): void {
|
|
62
|
+
if (!authorsMap) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const permalinkCounts = _(authorsMap)
|
|
67
|
+
// Filter to keep only authors with a page
|
|
68
|
+
.pickBy((author) => !!author.page)
|
|
69
|
+
// Group authors by their permalink
|
|
70
|
+
.groupBy((author) => author.page?.permalink)
|
|
71
|
+
// Filter to keep only permalinks with more than one author
|
|
72
|
+
.pickBy((authors) => authors.length > 1)
|
|
73
|
+
// Transform the object into an array of [permalink, authors] pairs
|
|
74
|
+
.toPairs()
|
|
75
|
+
.value();
|
|
76
|
+
|
|
77
|
+
if (permalinkCounts.length > 0) {
|
|
78
|
+
const errorMessage = permalinkCounts
|
|
79
|
+
.map(
|
|
80
|
+
([permalink, authors]) =>
|
|
81
|
+
`Permalink: ${permalink}\nAuthors: ${authors
|
|
82
|
+
.map((author) => author.name || 'Unknown')
|
|
83
|
+
.join(', ')}`,
|
|
84
|
+
)
|
|
85
|
+
.join('\n');
|
|
86
|
+
|
|
87
|
+
throw new Error(
|
|
88
|
+
`The following permalinks are duplicated:\n${errorMessage}`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizeAuthor({
|
|
94
|
+
authorsBaseRoutePath,
|
|
95
|
+
authorKey,
|
|
96
|
+
author,
|
|
97
|
+
}: {
|
|
98
|
+
authorsBaseRoutePath: string;
|
|
99
|
+
authorKey: string;
|
|
100
|
+
author: AuthorInput;
|
|
101
|
+
}): Author & {key: string} {
|
|
102
|
+
function getAuthorPage(): AuthorPage | null {
|
|
103
|
+
if (!author.page) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
const slug =
|
|
107
|
+
author.page === true ? _.kebabCase(authorKey) : author.page.permalink;
|
|
108
|
+
return {
|
|
109
|
+
permalink: normalizeUrl([authorsBaseRoutePath, slug]),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...author,
|
|
115
|
+
key: authorKey,
|
|
116
|
+
page: getAuthorPage(),
|
|
117
|
+
socials: author.socials ? normalizeSocials(author.socials) : undefined,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeAuthorsMap({
|
|
122
|
+
authorsBaseRoutePath,
|
|
123
|
+
authorsMapInput,
|
|
124
|
+
}: {
|
|
125
|
+
authorsBaseRoutePath: string;
|
|
126
|
+
authorsMapInput: AuthorsMapInput;
|
|
127
|
+
}): AuthorsMap {
|
|
128
|
+
return _.mapValues(authorsMapInput, (author, authorKey) => {
|
|
129
|
+
return normalizeAuthor({authorsBaseRoutePath, authorKey, author});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function validateAuthorsMapInput(content: unknown): AuthorsMapInput {
|
|
134
|
+
const {error, value} = AuthorsMapInputSchema.validate(content);
|
|
135
|
+
if (error) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function getAuthorsMapInput(params: {
|
|
142
|
+
authorsMapPath: string;
|
|
143
|
+
contentPaths: BlogContentPaths;
|
|
144
|
+
}): Promise<AuthorsMapInput | undefined> {
|
|
145
|
+
const content = await readDataFile({
|
|
146
|
+
filePath: params.authorsMapPath,
|
|
147
|
+
contentPaths: params.contentPaths,
|
|
148
|
+
});
|
|
149
|
+
return content ? validateAuthorsMapInput(content) : undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function getAuthorsMap(params: {
|
|
153
|
+
authorsMapPath: string;
|
|
154
|
+
authorsBaseRoutePath: string;
|
|
155
|
+
contentPaths: BlogContentPaths;
|
|
156
|
+
}): Promise<AuthorsMap | undefined> {
|
|
157
|
+
const authorsMapInput = await getAuthorsMapInput(params);
|
|
158
|
+
if (!authorsMapInput) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
const authorsMap = normalizeAuthorsMap({authorsMapInput, ...params});
|
|
162
|
+
return authorsMap;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function validateAuthorsMap(content: unknown): AuthorsMapInput {
|
|
166
|
+
const {error, value} = AuthorsMapInputSchema.validate(content);
|
|
167
|
+
if (error) {
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
import logger from '@docusaurus/logger';
|
|
10
|
+
import type {Author, PluginOptions} from '@docusaurus/plugin-content-blog';
|
|
11
|
+
|
|
12
|
+
export function reportAuthorsProblems(params: {
|
|
13
|
+
authors: Author[];
|
|
14
|
+
blogSourceRelative: string;
|
|
15
|
+
options: Pick<PluginOptions, 'onInlineAuthors' | 'authorsMapPath'>;
|
|
16
|
+
}): void {
|
|
17
|
+
reportInlineAuthors(params);
|
|
18
|
+
reportDuplicateAuthors(params);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function reportInlineAuthors({
|
|
22
|
+
authors,
|
|
23
|
+
blogSourceRelative,
|
|
24
|
+
options: {onInlineAuthors, authorsMapPath},
|
|
25
|
+
}: {
|
|
26
|
+
authors: Author[];
|
|
27
|
+
blogSourceRelative: string;
|
|
28
|
+
options: Pick<PluginOptions, 'onInlineAuthors' | 'authorsMapPath'>;
|
|
29
|
+
}): void {
|
|
30
|
+
if (onInlineAuthors === 'ignore') {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const inlineAuthors = authors.filter((author) => !author.key);
|
|
34
|
+
if (inlineAuthors.length > 0) {
|
|
35
|
+
logger.report(onInlineAuthors)(
|
|
36
|
+
logger.interpolate`Some blog authors used in path=${blogSourceRelative} are not defined in path=${authorsMapPath}:
|
|
37
|
+
- ${inlineAuthors.map(authorToString).join('\n- ')}
|
|
38
|
+
|
|
39
|
+
Note that we recommend to declare authors once in a path=${authorsMapPath} file and reference them by key in blog posts front matter to avoid author info duplication.
|
|
40
|
+
But if you want to allow inline blog authors, you can disable this message by setting onInlineAuthors: 'ignore' in your blog plugin options.
|
|
41
|
+
More info at url=${'https://docusaurus.io/docs/blog'}
|
|
42
|
+
`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function reportDuplicateAuthors({
|
|
48
|
+
authors,
|
|
49
|
+
blogSourceRelative,
|
|
50
|
+
}: {
|
|
51
|
+
authors: Author[];
|
|
52
|
+
blogSourceRelative: string;
|
|
53
|
+
}): void {
|
|
54
|
+
const duplicateAuthors = _(authors)
|
|
55
|
+
// for now we only check for predefined authors duplicates
|
|
56
|
+
.filter((author) => !!author.key)
|
|
57
|
+
.groupBy((author) => author.key)
|
|
58
|
+
.pickBy((authorsByKey) => authorsByKey.length > 1)
|
|
59
|
+
// We only keep the "test" of all the duplicate groups
|
|
60
|
+
// The first author of a group is not really a duplicate...
|
|
61
|
+
.flatMap(([, ...rest]) => rest)
|
|
62
|
+
.value();
|
|
63
|
+
|
|
64
|
+
if (duplicateAuthors.length > 0) {
|
|
65
|
+
throw new Error(logger.interpolate`Duplicate blog post authors were found in blog post path=${blogSourceRelative} front matter:
|
|
66
|
+
- ${duplicateAuthors.map(authorToString).join('\n- ')}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function authorToString(author: Author) {
|
|
71
|
+
return JSON.stringify(author);
|
|
72
|
+
}
|