@docusaurus/plugin-content-blog 0.0.0-6012 → 0.0.0-6016
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 -10
- package/lib/authors.js +41 -78
- package/lib/authorsMap.d.ts +23 -0
- package/lib/authorsMap.js +116 -0
- package/lib/blogUtils.d.ts +2 -2
- package/lib/blogUtils.js +2 -6
- package/lib/feed.d.ts +3 -1
- package/lib/feed.js +67 -18
- package/lib/index.js +16 -2
- package/lib/options.d.ts +4 -0
- package/lib/options.js +87 -25
- package/lib/props.d.ts +5 -1
- package/lib/props.js +7 -0
- package/lib/routes.js +75 -2
- package/package.json +12 -11
- package/src/authors.ts +57 -111
- package/src/authorsMap.ts +171 -0
- package/src/blogUtils.ts +4 -7
- package/src/feed.ts +140 -17
- package/src/index.ts +24 -1
- package/src/options.ts +112 -32
- package/src/plugin-content-blog.d.ts +116 -12
- package/src/props.ts +15 -0
- package/src/routes.ts +97 -2
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {translateContent, getTranslationFiles} from './translations';
|
|
|
34
34
|
import {createBlogFeedFiles, createFeedHtmlHeadTags} from './feed';
|
|
35
35
|
|
|
36
36
|
import {createAllRoutes} from './routes';
|
|
37
|
+
import {checkAuthorsMapPermalinkCollisions, getAuthorsMap} from './authorsMap';
|
|
37
38
|
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
|
38
39
|
import type {LoadContext, Plugin} from '@docusaurus/types';
|
|
39
40
|
import type {
|
|
@@ -160,11 +161,30 @@ export default async function pluginContentBlog(
|
|
|
160
161
|
blogTitle,
|
|
161
162
|
blogSidebarTitle,
|
|
162
163
|
pageBasePath,
|
|
164
|
+
authorsBasePath,
|
|
165
|
+
authorsMapPath,
|
|
163
166
|
} = options;
|
|
164
167
|
|
|
165
168
|
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
|
166
169
|
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
|
167
|
-
|
|
170
|
+
|
|
171
|
+
const authorsMap = await getAuthorsMap({
|
|
172
|
+
contentPaths,
|
|
173
|
+
authorsMapPath,
|
|
174
|
+
authorsBaseRoutePath: normalizeUrl([
|
|
175
|
+
baseUrl,
|
|
176
|
+
routeBasePath,
|
|
177
|
+
authorsBasePath,
|
|
178
|
+
]),
|
|
179
|
+
});
|
|
180
|
+
checkAuthorsMapPermalinkCollisions(authorsMap);
|
|
181
|
+
|
|
182
|
+
let blogPosts = await generateBlogPosts(
|
|
183
|
+
contentPaths,
|
|
184
|
+
context,
|
|
185
|
+
options,
|
|
186
|
+
authorsMap,
|
|
187
|
+
);
|
|
168
188
|
blogPosts = await applyProcessBlogPosts({
|
|
169
189
|
blogPosts,
|
|
170
190
|
processBlogPosts: options.processBlogPosts,
|
|
@@ -178,6 +198,7 @@ export default async function pluginContentBlog(
|
|
|
178
198
|
blogListPaginated: [],
|
|
179
199
|
blogTags: {},
|
|
180
200
|
blogTagsListPath,
|
|
201
|
+
authorsMap,
|
|
181
202
|
};
|
|
182
203
|
}
|
|
183
204
|
|
|
@@ -226,6 +247,7 @@ export default async function pluginContentBlog(
|
|
|
226
247
|
blogListPaginated,
|
|
227
248
|
blogTags,
|
|
228
249
|
blogTagsListPath,
|
|
250
|
+
authorsMap,
|
|
229
251
|
};
|
|
230
252
|
},
|
|
231
253
|
|
|
@@ -366,6 +388,7 @@ export default async function pluginContentBlog(
|
|
|
366
388
|
outDir,
|
|
367
389
|
siteConfig,
|
|
368
390
|
locale: currentLocale,
|
|
391
|
+
contentPaths,
|
|
369
392
|
});
|
|
370
393
|
},
|
|
371
394
|
|
package/src/options.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import path from 'path';
|
|
8
9
|
import {
|
|
9
10
|
Joi,
|
|
10
11
|
RemarkPluginsSchema,
|
|
@@ -19,11 +20,20 @@ import type {
|
|
|
19
20
|
PluginOptions,
|
|
20
21
|
Options,
|
|
21
22
|
FeedType,
|
|
23
|
+
FeedXSLTOptions,
|
|
22
24
|
} from '@docusaurus/plugin-content-blog';
|
|
23
25
|
import type {OptionValidationContext} from '@docusaurus/types';
|
|
24
26
|
|
|
25
27
|
export const DEFAULT_OPTIONS: PluginOptions = {
|
|
26
|
-
feedOptions: {
|
|
28
|
+
feedOptions: {
|
|
29
|
+
type: ['rss', 'atom'],
|
|
30
|
+
copyright: '',
|
|
31
|
+
limit: 20,
|
|
32
|
+
xslt: {
|
|
33
|
+
rss: null,
|
|
34
|
+
atom: null,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
27
37
|
beforeDefaultRehypePlugins: [],
|
|
28
38
|
beforeDefaultRemarkPlugins: [],
|
|
29
39
|
admonitions: true,
|
|
@@ -34,6 +44,8 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
34
44
|
showReadingTime: true,
|
|
35
45
|
blogTagsPostsComponent: '@theme/BlogTagsPostsPage',
|
|
36
46
|
blogTagsListComponent: '@theme/BlogTagsListPage',
|
|
47
|
+
blogAuthorsPostsComponent: '@theme/Blog/Pages/BlogAuthorsPostsPage',
|
|
48
|
+
blogAuthorsListComponent: '@theme/Blog/Pages/BlogAuthorsListPage',
|
|
37
49
|
blogPostComponent: '@theme/BlogPostPage',
|
|
38
50
|
blogListComponent: '@theme/BlogListPage',
|
|
39
51
|
blogArchiveComponent: '@theme/BlogArchivePage',
|
|
@@ -58,9 +70,98 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
58
70
|
processBlogPosts: async () => undefined,
|
|
59
71
|
onInlineTags: 'warn',
|
|
60
72
|
tags: undefined,
|
|
73
|
+
authorsBasePath: 'authors',
|
|
61
74
|
onInlineAuthors: 'warn',
|
|
62
75
|
};
|
|
63
76
|
|
|
77
|
+
export const XSLTBuiltInPaths = {
|
|
78
|
+
rss: path.resolve(__dirname, '..', 'assets', 'rss.xsl'),
|
|
79
|
+
atom: path.resolve(__dirname, '..', 'assets', 'atom.xsl'),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function normalizeXsltOption(
|
|
83
|
+
option: string | null | boolean,
|
|
84
|
+
type: 'rss' | 'atom',
|
|
85
|
+
): string | null {
|
|
86
|
+
if (typeof option === 'string') {
|
|
87
|
+
return option;
|
|
88
|
+
}
|
|
89
|
+
if (option === true) {
|
|
90
|
+
return XSLTBuiltInPaths[type];
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createXSLTFilePathSchema(type: 'atom' | 'rss') {
|
|
96
|
+
return Joi.alternatives()
|
|
97
|
+
.try(
|
|
98
|
+
Joi.string().required(),
|
|
99
|
+
Joi.boolean()
|
|
100
|
+
.allow(null, () => undefined)
|
|
101
|
+
.custom((val) => normalizeXsltOption(val, type)),
|
|
102
|
+
)
|
|
103
|
+
.optional()
|
|
104
|
+
.default(null);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const FeedXSLTOptionsSchema = Joi.alternatives()
|
|
108
|
+
.try(
|
|
109
|
+
Joi.object<FeedXSLTOptions>({
|
|
110
|
+
rss: createXSLTFilePathSchema('rss'),
|
|
111
|
+
atom: createXSLTFilePathSchema('atom'),
|
|
112
|
+
}).required(),
|
|
113
|
+
Joi.boolean()
|
|
114
|
+
.allow(null, () => undefined)
|
|
115
|
+
.custom((val) => ({
|
|
116
|
+
rss: normalizeXsltOption(val, 'rss'),
|
|
117
|
+
atom: normalizeXsltOption(val, 'atom'),
|
|
118
|
+
})),
|
|
119
|
+
)
|
|
120
|
+
.optional()
|
|
121
|
+
.custom((val) => {
|
|
122
|
+
if (val === null) {
|
|
123
|
+
return {
|
|
124
|
+
rss: null,
|
|
125
|
+
atom: null,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return val;
|
|
129
|
+
})
|
|
130
|
+
.default(DEFAULT_OPTIONS.feedOptions.xslt);
|
|
131
|
+
|
|
132
|
+
const FeedOptionsSchema = Joi.object({
|
|
133
|
+
type: Joi.alternatives()
|
|
134
|
+
.try(
|
|
135
|
+
Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
|
|
136
|
+
Joi.alternatives().conditional(
|
|
137
|
+
Joi.string().equal('all', 'rss', 'atom', 'json'),
|
|
138
|
+
{
|
|
139
|
+
then: Joi.custom((val: FeedType | 'all') =>
|
|
140
|
+
val === 'all' ? ['rss', 'atom', 'json'] : [val],
|
|
141
|
+
),
|
|
142
|
+
},
|
|
143
|
+
),
|
|
144
|
+
)
|
|
145
|
+
.allow(null)
|
|
146
|
+
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
147
|
+
xslt: FeedXSLTOptionsSchema,
|
|
148
|
+
title: Joi.string().allow(''),
|
|
149
|
+
description: Joi.string().allow(''),
|
|
150
|
+
// Only add default value when user actually wants a feed (type is not null)
|
|
151
|
+
copyright: Joi.when('type', {
|
|
152
|
+
is: Joi.any().valid(null),
|
|
153
|
+
then: Joi.string().optional(),
|
|
154
|
+
otherwise: Joi.string()
|
|
155
|
+
.allow('')
|
|
156
|
+
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
157
|
+
}),
|
|
158
|
+
language: Joi.string(),
|
|
159
|
+
createFeedItems: Joi.function(),
|
|
160
|
+
limit: Joi.alternatives()
|
|
161
|
+
.try(Joi.number(), Joi.valid(null), Joi.valid(false))
|
|
162
|
+
.default(DEFAULT_OPTIONS.feedOptions.limit),
|
|
163
|
+
}).default(DEFAULT_OPTIONS.feedOptions);
|
|
164
|
+
|
|
64
165
|
const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
65
166
|
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
|
66
167
|
archiveBasePath: Joi.string()
|
|
@@ -82,6 +183,12 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
82
183
|
blogTagsPostsComponent: Joi.string().default(
|
|
83
184
|
DEFAULT_OPTIONS.blogTagsPostsComponent,
|
|
84
185
|
),
|
|
186
|
+
blogAuthorsPostsComponent: Joi.string().default(
|
|
187
|
+
DEFAULT_OPTIONS.blogAuthorsPostsComponent,
|
|
188
|
+
),
|
|
189
|
+
blogAuthorsListComponent: Joi.string().default(
|
|
190
|
+
DEFAULT_OPTIONS.blogAuthorsListComponent,
|
|
191
|
+
),
|
|
85
192
|
blogArchiveComponent: Joi.string().default(
|
|
86
193
|
DEFAULT_OPTIONS.blogArchiveComponent,
|
|
87
194
|
),
|
|
@@ -107,37 +214,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
107
214
|
beforeDefaultRehypePlugins: RehypePluginsSchema.default(
|
|
108
215
|
DEFAULT_OPTIONS.beforeDefaultRehypePlugins,
|
|
109
216
|
),
|
|
110
|
-
feedOptions:
|
|
111
|
-
type: Joi.alternatives()
|
|
112
|
-
.try(
|
|
113
|
-
Joi.array().items(Joi.string().equal('rss', 'atom', 'json')),
|
|
114
|
-
Joi.alternatives().conditional(
|
|
115
|
-
Joi.string().equal('all', 'rss', 'atom', 'json'),
|
|
116
|
-
{
|
|
117
|
-
then: Joi.custom((val: FeedType | 'all') =>
|
|
118
|
-
val === 'all' ? ['rss', 'atom', 'json'] : [val],
|
|
119
|
-
),
|
|
120
|
-
},
|
|
121
|
-
),
|
|
122
|
-
)
|
|
123
|
-
.allow(null)
|
|
124
|
-
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
125
|
-
title: Joi.string().allow(''),
|
|
126
|
-
description: Joi.string().allow(''),
|
|
127
|
-
// Only add default value when user actually wants a feed (type is not null)
|
|
128
|
-
copyright: Joi.when('type', {
|
|
129
|
-
is: Joi.any().valid(null),
|
|
130
|
-
then: Joi.string().optional(),
|
|
131
|
-
otherwise: Joi.string()
|
|
132
|
-
.allow('')
|
|
133
|
-
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
134
|
-
}),
|
|
135
|
-
language: Joi.string(),
|
|
136
|
-
createFeedItems: Joi.function(),
|
|
137
|
-
limit: Joi.alternatives()
|
|
138
|
-
.try(Joi.number(), Joi.valid(null), Joi.valid(false))
|
|
139
|
-
.default(DEFAULT_OPTIONS.feedOptions.limit),
|
|
140
|
-
}).default(DEFAULT_OPTIONS.feedOptions),
|
|
217
|
+
feedOptions: FeedOptionsSchema,
|
|
141
218
|
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
|
142
219
|
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
|
143
220
|
sortPosts: Joi.string()
|
|
@@ -157,6 +234,9 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
157
234
|
.disallow('')
|
|
158
235
|
.allow(null, false)
|
|
159
236
|
.default(() => DEFAULT_OPTIONS.tags),
|
|
237
|
+
authorsBasePath: Joi.string()
|
|
238
|
+
.default(DEFAULT_OPTIONS.authorsBasePath)
|
|
239
|
+
.disallow(''),
|
|
160
240
|
onInlineAuthors: Joi.string()
|
|
161
241
|
.equal('ignore', 'log', 'warn', 'throw')
|
|
162
242
|
.default(DEFAULT_OPTIONS.onInlineAuthors),
|
|
@@ -22,13 +22,7 @@ declare module '@docusaurus/plugin-content-blog' {
|
|
|
22
22
|
|
|
23
23
|
export type Assets = {
|
|
24
24
|
/**
|
|
25
|
-
* If `metadata.
|
|
26
|
-
4
|
|
27
|
-
yarn workspace v1.22.19yarn workspace website typecheck
|
|
28
|
-
4
|
|
29
|
-
yarn workspace v1.22.19yarn workspace website typecheck
|
|
30
|
-
4
|
|
31
|
-
yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
25
|
+
* If `metadata.image` is a collocated image path, this entry will be the
|
|
32
26
|
* bundler-generated image path. Otherwise, it's empty, and the image URL
|
|
33
27
|
* should be accessed through `frontMatter.image`.
|
|
34
28
|
*/
|
|
@@ -66,9 +60,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
66
60
|
[customAuthorSocialPlatform: string]: string;
|
|
67
61
|
};
|
|
68
62
|
|
|
69
|
-
export type
|
|
70
|
-
key?: string; // TODO temporary, need refactor
|
|
71
|
-
|
|
63
|
+
export type AuthorAttributes = {
|
|
72
64
|
/**
|
|
73
65
|
* If `name` doesn't exist, an `imageURL` is expected.
|
|
74
66
|
*/
|
|
@@ -98,11 +90,45 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
98
90
|
*/
|
|
99
91
|
socials?: AuthorSocials;
|
|
100
92
|
/**
|
|
101
|
-
*
|
|
93
|
+
* Description of the author.
|
|
94
|
+
*/
|
|
95
|
+
description?: string;
|
|
96
|
+
/**
|
|
97
|
+
* Unknown keys are allowed, so that we can pass custom fields to authors.
|
|
102
98
|
*/
|
|
103
99
|
[customAuthorAttribute: string]: unknown;
|
|
104
100
|
};
|
|
105
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Metadata of the author's page, if it exists.
|
|
104
|
+
*/
|
|
105
|
+
export type AuthorPage = {permalink: string};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Normalized author metadata.
|
|
109
|
+
*/
|
|
110
|
+
export type Author = AuthorAttributes & {
|
|
111
|
+
/**
|
|
112
|
+
* Author key, if the author was loaded from the authors map.
|
|
113
|
+
* `null` means the author was declared inline.
|
|
114
|
+
*/
|
|
115
|
+
key: string | null;
|
|
116
|
+
/**
|
|
117
|
+
* Metadata of the author's page.
|
|
118
|
+
* `null` means the author doesn't have a dedicated author page.
|
|
119
|
+
*/
|
|
120
|
+
page: AuthorPage | null;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/** Authors coming from the AuthorsMap always have a key */
|
|
124
|
+
export type AuthorWithKey = Author & {key: string};
|
|
125
|
+
|
|
126
|
+
/** What the authors list page should know about each author. */
|
|
127
|
+
export type AuthorItemProp = AuthorWithKey & {
|
|
128
|
+
/** Number of blog posts with this author. */
|
|
129
|
+
count: number;
|
|
130
|
+
};
|
|
131
|
+
|
|
106
132
|
/**
|
|
107
133
|
* Everything is partial/unnormalized, because front matter is always
|
|
108
134
|
* preserved as-is. Default values will be applied when generating metadata
|
|
@@ -194,7 +220,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
194
220
|
last_update?: FrontMatterLastUpdate;
|
|
195
221
|
};
|
|
196
222
|
|
|
197
|
-
export type BlogPostFrontMatterAuthor =
|
|
223
|
+
export type BlogPostFrontMatterAuthor = AuthorAttributes & {
|
|
198
224
|
/**
|
|
199
225
|
* Will be normalized into the `imageURL` prop.
|
|
200
226
|
*/
|
|
@@ -289,10 +315,26 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
289
315
|
}) => string | undefined;
|
|
290
316
|
|
|
291
317
|
export type FeedType = 'rss' | 'atom' | 'json';
|
|
318
|
+
|
|
319
|
+
export type FeedXSLTOptions = {
|
|
320
|
+
/**
|
|
321
|
+
* RSS XSLT file path, relative to the blog content folder.
|
|
322
|
+
* If null, no XSLT file is used and the feed will be displayed as raw XML.
|
|
323
|
+
*/
|
|
324
|
+
rss: string | null;
|
|
325
|
+
/**
|
|
326
|
+
* Atom XSLT file path, relative to the blog content folder.
|
|
327
|
+
* If null, no XSLT file is used and the feed will be displayed as raw XML.
|
|
328
|
+
*/
|
|
329
|
+
atom: string | null;
|
|
330
|
+
};
|
|
331
|
+
|
|
292
332
|
/**
|
|
293
333
|
* Normalized feed options used within code.
|
|
294
334
|
*/
|
|
295
335
|
export type FeedOptions = {
|
|
336
|
+
/** Enable feeds xslt stylesheets */
|
|
337
|
+
xslt: FeedXSLTOptions;
|
|
296
338
|
/** If `null`, no feed is generated. */
|
|
297
339
|
type?: FeedType[] | null;
|
|
298
340
|
/** Title of generated feed. */
|
|
@@ -427,6 +469,10 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
427
469
|
blogTagsListComponent: string;
|
|
428
470
|
/** Root component of the "posts containing tag" page. */
|
|
429
471
|
blogTagsPostsComponent: string;
|
|
472
|
+
/** Root component of the authors list page. */
|
|
473
|
+
blogAuthorsListComponent: string;
|
|
474
|
+
/** Root component of the "posts containing author" page. */
|
|
475
|
+
blogAuthorsPostsComponent: string;
|
|
430
476
|
/** Root component of the blog archive page. */
|
|
431
477
|
blogArchiveComponent: string;
|
|
432
478
|
/** Blog page title for better SEO. */
|
|
@@ -471,10 +517,20 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
471
517
|
* (filter, modify, delete, etc...).
|
|
472
518
|
*/
|
|
473
519
|
processBlogPosts: ProcessBlogPostsFn;
|
|
520
|
+
/* Base path for the authors page */
|
|
521
|
+
authorsBasePath: string;
|
|
474
522
|
/** The behavior of Docusaurus when it finds inline authors. */
|
|
475
523
|
onInlineAuthors: 'ignore' | 'log' | 'warn' | 'throw';
|
|
476
524
|
};
|
|
477
525
|
|
|
526
|
+
export type UserFeedXSLTOptions =
|
|
527
|
+
| boolean
|
|
528
|
+
| null
|
|
529
|
+
| {
|
|
530
|
+
rss?: string | boolean | null;
|
|
531
|
+
atom?: string | boolean | null;
|
|
532
|
+
};
|
|
533
|
+
|
|
478
534
|
/**
|
|
479
535
|
* Feed options, as provided by user config. `type` accepts `all` as shortcut
|
|
480
536
|
*/
|
|
@@ -483,6 +539,8 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
483
539
|
{
|
|
484
540
|
/** Type of feed to be generated. Use `null` to disable generation. */
|
|
485
541
|
type?: FeedOptions['type'] | 'all' | FeedType;
|
|
542
|
+
/** User-provided XSLT config for feeds, un-normalized */
|
|
543
|
+
xslt?: UserFeedXSLTOptions;
|
|
486
544
|
}
|
|
487
545
|
>;
|
|
488
546
|
/**
|
|
@@ -508,17 +566,22 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
508
566
|
items: BlogSidebarItem[];
|
|
509
567
|
};
|
|
510
568
|
|
|
569
|
+
export type AuthorsMap = {[authorKey: string]: AuthorWithKey};
|
|
570
|
+
|
|
511
571
|
export type BlogContent = {
|
|
512
572
|
blogSidebarTitle: string;
|
|
513
573
|
blogPosts: BlogPost[];
|
|
514
574
|
blogListPaginated: BlogPaginated[];
|
|
515
575
|
blogTags: BlogTags;
|
|
516
576
|
blogTagsListPath: string;
|
|
577
|
+
authorsMap?: AuthorsMap;
|
|
517
578
|
};
|
|
518
579
|
|
|
519
580
|
export type BlogMetadata = {
|
|
520
581
|
/** the path to the base of the blog */
|
|
521
582
|
blogBasePath: string;
|
|
583
|
+
/** the path to the authors list page */
|
|
584
|
+
authorsListPath: string;
|
|
522
585
|
/** title of the overall blog */
|
|
523
586
|
blogTitle: string;
|
|
524
587
|
};
|
|
@@ -679,6 +742,47 @@ declare module '@theme/BlogTagsListPage' {
|
|
|
679
742
|
export default function BlogTagsListPage(props: Props): JSX.Element;
|
|
680
743
|
}
|
|
681
744
|
|
|
745
|
+
declare module '@theme/Blog/Pages/BlogAuthorsListPage' {
|
|
746
|
+
import type {
|
|
747
|
+
AuthorItemProp,
|
|
748
|
+
BlogSidebar,
|
|
749
|
+
} from '@docusaurus/plugin-content-blog';
|
|
750
|
+
|
|
751
|
+
export interface Props {
|
|
752
|
+
/** Blog sidebar. */
|
|
753
|
+
readonly sidebar: BlogSidebar;
|
|
754
|
+
/** All authors declared in this blog. */
|
|
755
|
+
readonly authors: AuthorItemProp[];
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
export default function BlogAuthorsListPage(props: Props): JSX.Element;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
declare module '@theme/Blog/Pages/BlogAuthorsPostsPage' {
|
|
762
|
+
import type {Content} from '@theme/BlogPostPage';
|
|
763
|
+
import type {
|
|
764
|
+
AuthorItemProp,
|
|
765
|
+
BlogSidebar,
|
|
766
|
+
BlogPaginatedMetadata,
|
|
767
|
+
} from '@docusaurus/plugin-content-blog';
|
|
768
|
+
|
|
769
|
+
export interface Props {
|
|
770
|
+
/** Blog sidebar. */
|
|
771
|
+
readonly sidebar: BlogSidebar;
|
|
772
|
+
/** Metadata of this author. */
|
|
773
|
+
readonly author: AuthorItemProp;
|
|
774
|
+
/** Looks exactly the same as the posts list page */
|
|
775
|
+
readonly listMetadata: BlogPaginatedMetadata;
|
|
776
|
+
/**
|
|
777
|
+
* Array of blog posts included on this page. Every post's metadata is also
|
|
778
|
+
* available.
|
|
779
|
+
*/
|
|
780
|
+
readonly items: readonly {readonly content: Content}[];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export default function BlogAuthorsPostsPage(props: Props): JSX.Element;
|
|
784
|
+
}
|
|
785
|
+
|
|
682
786
|
declare module '@theme/BlogTagsPostsPage' {
|
|
683
787
|
import type {Content} from '@theme/BlogPostPage';
|
|
684
788
|
import type {
|
package/src/props.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type {TagsListItem, TagModule} from '@docusaurus/utils';
|
|
8
8
|
import type {
|
|
9
|
+
AuthorItemProp,
|
|
10
|
+
AuthorWithKey,
|
|
9
11
|
BlogPost,
|
|
10
12
|
BlogSidebar,
|
|
11
13
|
BlogTag,
|
|
@@ -40,6 +42,19 @@ export function toTagProp({
|
|
|
40
42
|
};
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
export function toAuthorItemProp({
|
|
46
|
+
author,
|
|
47
|
+
count,
|
|
48
|
+
}: {
|
|
49
|
+
author: AuthorWithKey;
|
|
50
|
+
count: number;
|
|
51
|
+
}): AuthorItemProp {
|
|
52
|
+
return {
|
|
53
|
+
...author,
|
|
54
|
+
count,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
43
58
|
export function toBlogSidebarProp({
|
|
44
59
|
blogSidebarTitle,
|
|
45
60
|
blogPosts,
|
package/src/routes.ts
CHANGED
|
@@ -11,9 +11,15 @@ import {
|
|
|
11
11
|
docuHash,
|
|
12
12
|
aliasedSitePathToRelativePath,
|
|
13
13
|
} from '@docusaurus/utils';
|
|
14
|
-
import {shouldBeListed} from './blogUtils';
|
|
14
|
+
import {paginateBlogPosts, shouldBeListed} from './blogUtils';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
toAuthorItemProp,
|
|
18
|
+
toBlogSidebarProp,
|
|
19
|
+
toTagProp,
|
|
20
|
+
toTagsProp,
|
|
21
|
+
} from './props';
|
|
22
|
+
import {groupBlogPostsByAuthorKey} from './authors';
|
|
17
23
|
import type {
|
|
18
24
|
PluginContentLoadedActions,
|
|
19
25
|
RouteConfig,
|
|
@@ -26,6 +32,7 @@ import type {
|
|
|
26
32
|
BlogContent,
|
|
27
33
|
PluginOptions,
|
|
28
34
|
BlogPost,
|
|
35
|
+
AuthorWithKey,
|
|
29
36
|
} from '@docusaurus/plugin-content-blog';
|
|
30
37
|
|
|
31
38
|
type CreateAllRoutesParam = {
|
|
@@ -54,11 +61,16 @@ export async function buildAllRoutes({
|
|
|
54
61
|
blogListComponent,
|
|
55
62
|
blogPostComponent,
|
|
56
63
|
blogTagsListComponent,
|
|
64
|
+
blogAuthorsListComponent,
|
|
65
|
+
blogAuthorsPostsComponent,
|
|
57
66
|
blogTagsPostsComponent,
|
|
58
67
|
blogArchiveComponent,
|
|
59
68
|
routeBasePath,
|
|
60
69
|
archiveBasePath,
|
|
61
70
|
blogTitle,
|
|
71
|
+
authorsBasePath,
|
|
72
|
+
postsPerPage,
|
|
73
|
+
blogDescription,
|
|
62
74
|
} = options;
|
|
63
75
|
const pluginId = options.id!;
|
|
64
76
|
const {createData} = actions;
|
|
@@ -68,8 +80,15 @@ export async function buildAllRoutes({
|
|
|
68
80
|
blogListPaginated,
|
|
69
81
|
blogTags,
|
|
70
82
|
blogTagsListPath,
|
|
83
|
+
authorsMap,
|
|
71
84
|
} = content;
|
|
72
85
|
|
|
86
|
+
const authorsListPath = normalizeUrl([
|
|
87
|
+
baseUrl,
|
|
88
|
+
routeBasePath,
|
|
89
|
+
authorsBasePath,
|
|
90
|
+
]);
|
|
91
|
+
|
|
73
92
|
const listedBlogPosts = blogPosts.filter(shouldBeListed);
|
|
74
93
|
|
|
75
94
|
const blogPostsById = _.keyBy(blogPosts, (post) => post.id);
|
|
@@ -102,6 +121,7 @@ export async function buildAllRoutes({
|
|
|
102
121
|
const blogMetadata: BlogMetadata = {
|
|
103
122
|
blogBasePath: normalizeUrl([baseUrl, routeBasePath]),
|
|
104
123
|
blogTitle,
|
|
124
|
+
authorsListPath,
|
|
105
125
|
};
|
|
106
126
|
const modulePath = await createData(
|
|
107
127
|
`blogMetadata-${pluginId}.json`,
|
|
@@ -249,10 +269,85 @@ export async function buildAllRoutes({
|
|
|
249
269
|
return [tagsListRoute, ...tagsPaginatedRoutes];
|
|
250
270
|
}
|
|
251
271
|
|
|
272
|
+
function createAuthorsRoutes(): RouteConfig[] {
|
|
273
|
+
if (authorsMap === undefined || Object.keys(authorsMap).length === 0) {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const blogPostsByAuthorKey = groupBlogPostsByAuthorKey({
|
|
278
|
+
authorsMap,
|
|
279
|
+
blogPosts,
|
|
280
|
+
});
|
|
281
|
+
const authors = Object.values(authorsMap);
|
|
282
|
+
|
|
283
|
+
return [
|
|
284
|
+
createAuthorListRoute(),
|
|
285
|
+
...authors.flatMap(createAuthorPaginatedRoute),
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
function createAuthorListRoute(): RouteConfig {
|
|
289
|
+
return {
|
|
290
|
+
path: authorsListPath,
|
|
291
|
+
component: blogAuthorsListComponent,
|
|
292
|
+
exact: true,
|
|
293
|
+
modules: {
|
|
294
|
+
sidebar: sidebarModulePath,
|
|
295
|
+
},
|
|
296
|
+
props: {
|
|
297
|
+
authors: authors.map((author) =>
|
|
298
|
+
toAuthorItemProp({
|
|
299
|
+
author,
|
|
300
|
+
count: blogPostsByAuthorKey[author.key]?.length ?? 0,
|
|
301
|
+
}),
|
|
302
|
+
),
|
|
303
|
+
},
|
|
304
|
+
context: {
|
|
305
|
+
blogMetadata: blogMetadataModulePath,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function createAuthorPaginatedRoute(author: AuthorWithKey): RouteConfig[] {
|
|
311
|
+
const authorBlogPosts = blogPostsByAuthorKey[author.key] ?? [];
|
|
312
|
+
if (!author.page) {
|
|
313
|
+
return [];
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const pages = paginateBlogPosts({
|
|
317
|
+
blogPosts: authorBlogPosts,
|
|
318
|
+
basePageUrl: author.page.permalink,
|
|
319
|
+
blogDescription,
|
|
320
|
+
blogTitle,
|
|
321
|
+
pageBasePath: authorsBasePath,
|
|
322
|
+
postsPerPageOption: postsPerPage,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return pages.map(({metadata, items}) => {
|
|
326
|
+
return {
|
|
327
|
+
path: metadata.permalink,
|
|
328
|
+
component: blogAuthorsPostsComponent,
|
|
329
|
+
exact: true,
|
|
330
|
+
modules: {
|
|
331
|
+
items: blogPostItemsModule(items),
|
|
332
|
+
sidebar: sidebarModulePath,
|
|
333
|
+
},
|
|
334
|
+
props: {
|
|
335
|
+
author: toAuthorItemProp({author, count: authorBlogPosts.length}),
|
|
336
|
+
listMetadata: metadata,
|
|
337
|
+
},
|
|
338
|
+
context: {
|
|
339
|
+
blogMetadata: blogMetadataModulePath,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
252
346
|
return [
|
|
253
347
|
...createBlogPostRoutes(),
|
|
254
348
|
...createBlogPostsPaginatedRoutes(),
|
|
255
349
|
...createTagsRoutes(),
|
|
256
350
|
...createArchiveRoute(),
|
|
351
|
+
...createAuthorsRoutes(),
|
|
257
352
|
];
|
|
258
353
|
}
|