@docusaurus/plugin-content-blog 2.0.0-beta.8bda3b2db → 2.0.0-beta.9
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 -4
- package/lib/blogUtils.js +142 -136
- package/lib/feed.d.ts +20 -0
- package/lib/feed.js +90 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +98 -98
- package/lib/markdownLoader.d.ts +3 -6
- package/lib/markdownLoader.js +5 -5
- package/lib/pluginOptionSchema.d.ts +3 -26
- package/lib/pluginOptionSchema.js +24 -7
- package/lib/translations.d.ts +10 -0
- package/lib/translations.js +53 -0
- package/lib/types.d.ts +53 -14
- package/package.json +15 -13
- 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/mdx-require-blog-post.mdx +14 -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__/__fixtures__/website/static/img/docusaurus.png +0 -0
- package/src/__tests__/__snapshots__/feed.test.ts.snap +164 -0
- 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 → feed.test.ts} +35 -9
- package/src/__tests__/index.test.ts +73 -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 +202 -179
- package/src/feed.ts +129 -0
- package/src/index.ts +124 -103
- package/src/markdownLoader.ts +8 -12
- package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
- package/src/pluginOptionSchema.ts +27 -9
- package/src/translations.ts +63 -0
- package/src/types.ts +68 -16
- package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -76
package/src/index.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import fs from 'fs-extra';
|
|
9
8
|
import path from 'path';
|
|
10
9
|
import admonitions from 'remark-admonitions';
|
|
11
10
|
import {
|
|
@@ -16,12 +15,13 @@ import {
|
|
|
16
15
|
reportMessage,
|
|
17
16
|
posixPath,
|
|
18
17
|
addTrailingPathSeparator,
|
|
18
|
+
createAbsoluteFilePathMatcher,
|
|
19
19
|
} from '@docusaurus/utils';
|
|
20
20
|
import {
|
|
21
21
|
STATIC_DIR_NAME,
|
|
22
22
|
DEFAULT_PLUGIN_ID,
|
|
23
23
|
} from '@docusaurus/core/lib/constants';
|
|
24
|
-
import {
|
|
24
|
+
import {translateContent, getTranslationFiles} from './translations';
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
PluginOptions,
|
|
@@ -30,9 +30,10 @@ import {
|
|
|
30
30
|
BlogItemsToMetadata,
|
|
31
31
|
TagsModule,
|
|
32
32
|
BlogPaginated,
|
|
33
|
-
BlogPost,
|
|
34
33
|
BlogContentPaths,
|
|
35
34
|
BlogMarkdownLoaderOptions,
|
|
35
|
+
MetaData,
|
|
36
|
+
Assets,
|
|
36
37
|
} from './types';
|
|
37
38
|
import {PluginOptionSchema} from './pluginOptionSchema';
|
|
38
39
|
import {
|
|
@@ -46,16 +47,18 @@ import {
|
|
|
46
47
|
} from '@docusaurus/types';
|
|
47
48
|
import {Configuration} from 'webpack';
|
|
48
49
|
import {
|
|
49
|
-
generateBlogFeed,
|
|
50
50
|
generateBlogPosts,
|
|
51
51
|
getContentPathList,
|
|
52
52
|
getSourceToPermalink,
|
|
53
|
+
getBlogTags,
|
|
53
54
|
} from './blogUtils';
|
|
55
|
+
import {BlogPostFrontMatter} from './blogFrontMatter';
|
|
56
|
+
import {createBlogFeedFiles} from './feed';
|
|
54
57
|
|
|
55
58
|
export default function pluginContentBlog(
|
|
56
59
|
context: LoadContext,
|
|
57
60
|
options: PluginOptions,
|
|
58
|
-
): Plugin<BlogContent
|
|
61
|
+
): Plugin<BlogContent> {
|
|
59
62
|
if (options.admonitions) {
|
|
60
63
|
options.remarkPlugins = options.remarkPlugins.concat([
|
|
61
64
|
[admonitions, options.admonitions],
|
|
@@ -64,10 +67,11 @@ export default function pluginContentBlog(
|
|
|
64
67
|
|
|
65
68
|
const {
|
|
66
69
|
siteDir,
|
|
67
|
-
siteConfig
|
|
70
|
+
siteConfig,
|
|
68
71
|
generatedFilesDir,
|
|
69
72
|
i18n: {currentLocale},
|
|
70
73
|
} = context;
|
|
74
|
+
const {onBrokenMarkdownLinks, baseUrl} = siteConfig;
|
|
71
75
|
|
|
72
76
|
const contentPaths: BlogContentPaths = {
|
|
73
77
|
contentPath: path.resolve(siteDir, options.path),
|
|
@@ -88,38 +92,51 @@ export default function pluginContentBlog(
|
|
|
88
92
|
const aliasedSource = (source: string) =>
|
|
89
93
|
`~blog/${posixPath(path.relative(pluginDataDirRoot, source))}`;
|
|
90
94
|
|
|
91
|
-
let blogPosts: BlogPost[] = [];
|
|
92
|
-
|
|
93
95
|
return {
|
|
94
96
|
name: 'docusaurus-plugin-content-blog',
|
|
95
97
|
|
|
96
98
|
getPathsToWatch() {
|
|
97
|
-
const {include
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return include.map((pattern) => `${contentPath}/${pattern}`);
|
|
101
|
-
}),
|
|
99
|
+
const {include, authorsMapPath} = options;
|
|
100
|
+
const contentMarkdownGlobs = getContentPathList(contentPaths).flatMap(
|
|
101
|
+
(contentPath) => include.map((pattern) => `${contentPath}/${pattern}`),
|
|
102
102
|
);
|
|
103
|
-
},
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
const
|
|
104
|
+
// TODO: we should read this path in plugin! but plugins do not support async init for now :'(
|
|
105
|
+
// const authorsMapFilePath = await getAuthorsMapFilePath({authorsMapPath,contentPaths,});
|
|
106
|
+
// simplified impl, better than nothing for now:
|
|
107
|
+
const authorsMapFilePath = path.join(
|
|
108
|
+
contentPaths.contentPath,
|
|
109
|
+
authorsMapPath,
|
|
110
|
+
);
|
|
107
111
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
112
|
+
return [authorsMapFilePath, ...contentMarkdownGlobs];
|
|
113
|
+
},
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
async getTranslationFiles() {
|
|
116
|
+
return getTranslationFiles(options);
|
|
113
117
|
},
|
|
114
118
|
|
|
115
119
|
// Fetches blog contents and returns metadata for the necessary routes.
|
|
116
120
|
async loadContent() {
|
|
117
|
-
const {
|
|
121
|
+
const {
|
|
122
|
+
postsPerPage: postsPerPageOption,
|
|
123
|
+
routeBasePath,
|
|
124
|
+
tagsBasePath,
|
|
125
|
+
blogDescription,
|
|
126
|
+
blogTitle,
|
|
127
|
+
blogSidebarTitle,
|
|
128
|
+
} = options;
|
|
118
129
|
|
|
119
|
-
blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
130
|
+
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
120
131
|
|
|
121
132
|
if (!blogPosts.length) {
|
|
122
|
-
return
|
|
133
|
+
return {
|
|
134
|
+
blogSidebarTitle,
|
|
135
|
+
blogPosts: [],
|
|
136
|
+
blogListPaginated: [],
|
|
137
|
+
blogTags: {},
|
|
138
|
+
blogTagsListPath: null,
|
|
139
|
+
};
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
// Colocate next and prev metadata.
|
|
@@ -145,18 +162,17 @@ export default function pluginContentBlog(
|
|
|
145
162
|
// Blog pagination routes.
|
|
146
163
|
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
|
|
147
164
|
const totalCount = blogPosts.length;
|
|
165
|
+
const postsPerPage =
|
|
166
|
+
postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
|
148
167
|
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
|
149
|
-
const
|
|
150
|
-
siteConfig: {baseUrl = ''},
|
|
151
|
-
} = context;
|
|
152
|
-
const basePageUrl = normalizeUrl([baseUrl, routeBasePath]);
|
|
168
|
+
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
|
153
169
|
|
|
154
170
|
const blogListPaginated: BlogPaginated[] = [];
|
|
155
171
|
|
|
156
172
|
function blogPaginationPermalink(page: number) {
|
|
157
173
|
return page > 0
|
|
158
|
-
? normalizeUrl([
|
|
159
|
-
:
|
|
174
|
+
? normalizeUrl([baseBlogUrl, `page/${page + 1}`])
|
|
175
|
+
: baseBlogUrl;
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
for (let page = 0; page < numberOfPages; page += 1) {
|
|
@@ -172,8 +188,8 @@ export default function pluginContentBlog(
|
|
|
172
188
|
page < numberOfPages - 1
|
|
173
189
|
? blogPaginationPermalink(page + 1)
|
|
174
190
|
: null,
|
|
175
|
-
blogDescription
|
|
176
|
-
blogTitle
|
|
191
|
+
blogDescription,
|
|
192
|
+
blogTitle,
|
|
177
193
|
},
|
|
178
194
|
items: blogPosts
|
|
179
195
|
.slice(page * postsPerPage, (page + 1) * postsPerPage)
|
|
@@ -181,46 +197,15 @@ export default function pluginContentBlog(
|
|
|
181
197
|
});
|
|
182
198
|
}
|
|
183
199
|
|
|
184
|
-
const blogTags: BlogTags =
|
|
185
|
-
const tagsPath = normalizeUrl([basePageUrl, 'tags']);
|
|
186
|
-
blogPosts.forEach((blogPost) => {
|
|
187
|
-
const {tags} = blogPost.metadata;
|
|
188
|
-
if (!tags || tags.length === 0) {
|
|
189
|
-
// TODO: Extract tags out into a separate plugin.
|
|
190
|
-
// eslint-disable-next-line no-param-reassign
|
|
191
|
-
blogPost.metadata.tags = [];
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
200
|
+
const blogTags: BlogTags = getBlogTags(blogPosts);
|
|
194
201
|
|
|
195
|
-
|
|
196
|
-
blogPost.metadata.tags = tags.map((tag) => {
|
|
197
|
-
if (typeof tag === 'string') {
|
|
198
|
-
const normalizedTag = kebabCase(tag);
|
|
199
|
-
const permalink = normalizeUrl([tagsPath, normalizedTag]);
|
|
200
|
-
if (!blogTags[normalizedTag]) {
|
|
201
|
-
blogTags[normalizedTag] = {
|
|
202
|
-
// Will only use the name of the first occurrence of the tag.
|
|
203
|
-
name: tag.toLowerCase(),
|
|
204
|
-
items: [],
|
|
205
|
-
permalink,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
blogTags[normalizedTag].items.push(blogPost.id);
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
label: tag,
|
|
213
|
-
permalink,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
return tag;
|
|
217
|
-
});
|
|
218
|
-
});
|
|
202
|
+
const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
|
219
203
|
|
|
220
204
|
const blogTagsListPath =
|
|
221
205
|
Object.keys(blogTags).length > 0 ? tagsPath : null;
|
|
222
206
|
|
|
223
207
|
return {
|
|
208
|
+
blogSidebarTitle,
|
|
224
209
|
blogPosts,
|
|
225
210
|
blogListPaginated,
|
|
226
211
|
blogTags,
|
|
@@ -238,11 +223,14 @@ export default function pluginContentBlog(
|
|
|
238
223
|
blogPostComponent,
|
|
239
224
|
blogTagsListComponent,
|
|
240
225
|
blogTagsPostsComponent,
|
|
226
|
+
routeBasePath,
|
|
227
|
+
archiveBasePath,
|
|
241
228
|
} = options;
|
|
242
229
|
|
|
243
230
|
const {addRoute, createData} = actions;
|
|
244
231
|
const {
|
|
245
|
-
|
|
232
|
+
blogSidebarTitle,
|
|
233
|
+
blogPosts,
|
|
246
234
|
blogListPaginated,
|
|
247
235
|
blogTags,
|
|
248
236
|
blogTagsListPath,
|
|
@@ -253,7 +241,27 @@ export default function pluginContentBlog(
|
|
|
253
241
|
const sidebarBlogPosts =
|
|
254
242
|
options.blogSidebarCount === 'ALL'
|
|
255
243
|
? blogPosts
|
|
256
|
-
:
|
|
244
|
+
: blogPosts.slice(0, options.blogSidebarCount);
|
|
245
|
+
|
|
246
|
+
const archiveUrl = normalizeUrl([
|
|
247
|
+
baseUrl,
|
|
248
|
+
routeBasePath,
|
|
249
|
+
archiveBasePath,
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
// creates a blog archive route
|
|
253
|
+
const archiveProp = await createData(
|
|
254
|
+
`${docuHash(archiveUrl)}.json`,
|
|
255
|
+
JSON.stringify({blogPosts}, null, 2),
|
|
256
|
+
);
|
|
257
|
+
addRoute({
|
|
258
|
+
path: archiveUrl,
|
|
259
|
+
component: '@theme/BlogArchivePage',
|
|
260
|
+
exact: true,
|
|
261
|
+
modules: {
|
|
262
|
+
archive: aliasedSource(archiveProp),
|
|
263
|
+
},
|
|
264
|
+
});
|
|
257
265
|
|
|
258
266
|
// This prop is useful to provide the blog list sidebar
|
|
259
267
|
const sidebarProp = await createData(
|
|
@@ -262,7 +270,7 @@ export default function pluginContentBlog(
|
|
|
262
270
|
`blog-post-list-prop-${pluginId}.json`,
|
|
263
271
|
JSON.stringify(
|
|
264
272
|
{
|
|
265
|
-
title:
|
|
273
|
+
title: blogSidebarTitle,
|
|
266
274
|
items: sidebarBlogPosts.map((blogPost) => ({
|
|
267
275
|
title: blogPost.metadata.title,
|
|
268
276
|
permalink: blogPost.metadata.permalink,
|
|
@@ -275,7 +283,7 @@ export default function pluginContentBlog(
|
|
|
275
283
|
|
|
276
284
|
// Create routes for blog entries.
|
|
277
285
|
await Promise.all(
|
|
278
|
-
|
|
286
|
+
blogPosts.map(async (blogPost) => {
|
|
279
287
|
const {id, metadata} = blogPost;
|
|
280
288
|
await createData(
|
|
281
289
|
// Note that this created data path must be in sync with
|
|
@@ -343,6 +351,7 @@ export default function pluginContentBlog(
|
|
|
343
351
|
Object.keys(blogTags).map(async (tag) => {
|
|
344
352
|
const {name, items, permalink} = blogTags[tag];
|
|
345
353
|
|
|
354
|
+
// Refactor all this, see docs implementation
|
|
346
355
|
tagsModule[tag] = {
|
|
347
356
|
allTagsPath: blogTagsListPath,
|
|
348
357
|
slug: tag,
|
|
@@ -399,10 +408,15 @@ export default function pluginContentBlog(
|
|
|
399
408
|
}
|
|
400
409
|
},
|
|
401
410
|
|
|
411
|
+
translateContent({content, translationFiles}) {
|
|
412
|
+
return translateContent(content, translationFiles);
|
|
413
|
+
},
|
|
414
|
+
|
|
402
415
|
configureWebpack(
|
|
403
416
|
_config: Configuration,
|
|
404
417
|
isServer: boolean,
|
|
405
418
|
{getJSLoader}: ConfigureWebpackUtils,
|
|
419
|
+
content,
|
|
406
420
|
) {
|
|
407
421
|
const {
|
|
408
422
|
rehypePlugins,
|
|
@@ -416,7 +430,7 @@ export default function pluginContentBlog(
|
|
|
416
430
|
siteDir,
|
|
417
431
|
contentPaths,
|
|
418
432
|
truncateMarker,
|
|
419
|
-
sourceToPermalink: getSourceToPermalink(blogPosts),
|
|
433
|
+
sourceToPermalink: getSourceToPermalink(content.blogPosts),
|
|
420
434
|
onBrokenMarkdownLink: (brokenMarkdownLink) => {
|
|
421
435
|
if (onBrokenMarkdownLinks === 'ignore') {
|
|
422
436
|
return;
|
|
@@ -428,6 +442,7 @@ export default function pluginContentBlog(
|
|
|
428
442
|
},
|
|
429
443
|
};
|
|
430
444
|
|
|
445
|
+
const contentDirs = getContentPathList(contentPaths);
|
|
431
446
|
return {
|
|
432
447
|
resolve: {
|
|
433
448
|
alias: {
|
|
@@ -438,7 +453,7 @@ export default function pluginContentBlog(
|
|
|
438
453
|
rules: [
|
|
439
454
|
{
|
|
440
455
|
test: /(\.mdx?)$/,
|
|
441
|
-
include:
|
|
456
|
+
include: contentDirs
|
|
442
457
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
|
443
458
|
.map(addTrailingPathSeparator),
|
|
444
459
|
use: [
|
|
@@ -451,9 +466,13 @@ export default function pluginContentBlog(
|
|
|
451
466
|
beforeDefaultRemarkPlugins,
|
|
452
467
|
beforeDefaultRehypePlugins,
|
|
453
468
|
staticDir: path.join(siteDir, STATIC_DIR_NAME),
|
|
454
|
-
|
|
455
|
-
|
|
469
|
+
isMDXPartial: createAbsoluteFilePathMatcher(
|
|
470
|
+
options.exclude,
|
|
471
|
+
contentDirs,
|
|
472
|
+
),
|
|
456
473
|
metadataPath: (mdxPath: string) => {
|
|
474
|
+
// Note that metadataPath must be the same/in-sync as
|
|
475
|
+
// the path from createData for each MDX.
|
|
457
476
|
const aliasedPath = aliasedSitePath(mdxPath, siteDir);
|
|
458
477
|
return path.join(
|
|
459
478
|
dataDir,
|
|
@@ -463,6 +482,22 @@ export default function pluginContentBlog(
|
|
|
463
482
|
// For blog posts a title in markdown is always removed
|
|
464
483
|
// Blog posts title are rendered separately
|
|
465
484
|
removeContentTitle: true,
|
|
485
|
+
|
|
486
|
+
// Assets allow to convert some relative images paths to require() calls
|
|
487
|
+
createAssets: ({
|
|
488
|
+
frontMatter,
|
|
489
|
+
metadata,
|
|
490
|
+
}: {
|
|
491
|
+
frontMatter: BlogPostFrontMatter;
|
|
492
|
+
metadata: MetaData;
|
|
493
|
+
}): Assets => {
|
|
494
|
+
return {
|
|
495
|
+
image: frontMatter.image,
|
|
496
|
+
authorsImageUrls: metadata.authors.map(
|
|
497
|
+
(author) => author.imageURL,
|
|
498
|
+
),
|
|
499
|
+
};
|
|
500
|
+
},
|
|
466
501
|
},
|
|
467
502
|
},
|
|
468
503
|
{
|
|
@@ -477,37 +512,26 @@ export default function pluginContentBlog(
|
|
|
477
512
|
},
|
|
478
513
|
|
|
479
514
|
async postBuild({outDir}: Props) {
|
|
480
|
-
if (!options.feedOptions
|
|
515
|
+
if (!options.feedOptions.type) {
|
|
481
516
|
return;
|
|
482
517
|
}
|
|
483
518
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
519
|
+
// TODO: we shouldn't need to re-read the posts here!
|
|
520
|
+
// postBuild should receive loadedContent
|
|
521
|
+
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
522
|
+
if (!blogPosts.length) {
|
|
487
523
|
return;
|
|
488
524
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
outDir,
|
|
496
|
-
options.routeBasePath,
|
|
497
|
-
`${feedType}.xml`,
|
|
498
|
-
);
|
|
499
|
-
const feedContent = feedType === 'rss' ? feed.rss2() : feed.atom1();
|
|
500
|
-
try {
|
|
501
|
-
await fs.outputFile(feedPath, feedContent);
|
|
502
|
-
} catch (err) {
|
|
503
|
-
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
|
|
504
|
-
}
|
|
505
|
-
}),
|
|
506
|
-
);
|
|
525
|
+
await createBlogFeedFiles({
|
|
526
|
+
blogPosts,
|
|
527
|
+
options,
|
|
528
|
+
outDir,
|
|
529
|
+
siteConfig,
|
|
530
|
+
});
|
|
507
531
|
},
|
|
508
532
|
|
|
509
|
-
injectHtmlTags() {
|
|
510
|
-
if (!blogPosts.length) {
|
|
533
|
+
injectHtmlTags({content}) {
|
|
534
|
+
if (!content.blogPosts.length) {
|
|
511
535
|
return {};
|
|
512
536
|
}
|
|
513
537
|
|
|
@@ -516,20 +540,17 @@ export default function pluginContentBlog(
|
|
|
516
540
|
}
|
|
517
541
|
|
|
518
542
|
const feedTypes = options.feedOptions.type;
|
|
519
|
-
const
|
|
520
|
-
siteConfig: {title},
|
|
521
|
-
baseUrl,
|
|
522
|
-
} = context;
|
|
543
|
+
const feedTitle = options.feedOptions.title ?? context.siteConfig.title;
|
|
523
544
|
const feedsConfig = {
|
|
524
545
|
rss: {
|
|
525
546
|
type: 'application/rss+xml',
|
|
526
547
|
path: 'rss.xml',
|
|
527
|
-
title: `${
|
|
548
|
+
title: `${feedTitle} RSS Feed`,
|
|
528
549
|
},
|
|
529
550
|
atom: {
|
|
530
551
|
type: 'application/atom+xml',
|
|
531
552
|
path: 'atom.xml',
|
|
532
|
-
title: `${
|
|
553
|
+
title: `${feedTitle} Atom Feed`,
|
|
533
554
|
},
|
|
534
555
|
};
|
|
535
556
|
const headTags: HtmlTags = [];
|
package/src/markdownLoader.ts
CHANGED
|
@@ -8,18 +8,16 @@
|
|
|
8
8
|
import {truncate, linkify} from './blogUtils';
|
|
9
9
|
import {parseQuery} from 'loader-utils';
|
|
10
10
|
import {BlogMarkdownLoaderOptions} from './types';
|
|
11
|
+
import type {LoaderContext} from 'webpack';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const markdownLoader: Loader = function (source) {
|
|
13
|
+
export default function markdownLoader(
|
|
14
|
+
this: LoaderContext<BlogMarkdownLoaderOptions>,
|
|
15
|
+
source: string,
|
|
16
|
+
): void {
|
|
19
17
|
const filePath = this.resourcePath;
|
|
20
|
-
const fileString = source
|
|
18
|
+
const fileString = source;
|
|
21
19
|
const callback = this.async();
|
|
22
|
-
const markdownLoaderOptions = this.getOptions()
|
|
20
|
+
const markdownLoaderOptions = this.getOptions();
|
|
23
21
|
|
|
24
22
|
// Linkify blog posts
|
|
25
23
|
let finalContent = linkify({
|
|
@@ -38,6 +36,4 @@ const markdownLoader: Loader = function (source) {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
return callback && callback(null, finalContent);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default markdownLoader;
|
|
39
|
+
}
|
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
declare module '@docusaurus/plugin-content-blog' {
|
|
9
|
+
export type Options = Partial<import('./types').UserPluginOptions>;
|
|
10
|
+
}
|
|
10
11
|
|
|
11
12
|
declare module '@theme/BlogSidebar' {
|
|
12
13
|
export type BlogSidebarItem = {title: string; permalink: string};
|
|
@@ -15,32 +16,20 @@ declare module '@theme/BlogSidebar' {
|
|
|
15
16
|
items: BlogSidebarItem[];
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
export
|
|
19
|
+
export interface Props {
|
|
19
20
|
readonly sidebar: BlogSidebar;
|
|
20
|
-
}
|
|
21
|
+
}
|
|
21
22
|
|
|
22
23
|
const BlogSidebar: (props: Props) => JSX.Element;
|
|
23
24
|
export default BlogSidebar;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
declare module '@theme/BlogPostPage' {
|
|
27
|
-
import type {TOCItem} from '@docusaurus/types';
|
|
28
28
|
import type {BlogSidebar} from '@theme/BlogSidebar';
|
|
29
|
+
import type {TOCItem} from '@docusaurus/types';
|
|
29
30
|
|
|
30
|
-
export type FrontMatter =
|
|
31
|
-
|
|
32
|
-
readonly author?: string;
|
|
33
|
-
readonly image?: string;
|
|
34
|
-
readonly tags?: readonly string[];
|
|
35
|
-
readonly keywords?: readonly string[];
|
|
36
|
-
readonly author_url?: string;
|
|
37
|
-
readonly authorURL?: string;
|
|
38
|
-
readonly author_title?: string;
|
|
39
|
-
readonly authorTitle?: string;
|
|
40
|
-
readonly author_image_url?: string;
|
|
41
|
-
readonly authorImageURL?: string;
|
|
42
|
-
readonly hide_table_of_contents?: boolean;
|
|
43
|
-
};
|
|
31
|
+
export type FrontMatter = import('./blogFrontMatter').BlogPostFrontMatter;
|
|
32
|
+
export type Assets = import('./types').Assets;
|
|
44
33
|
|
|
45
34
|
export type Metadata = {
|
|
46
35
|
readonly title: string;
|
|
@@ -53,6 +42,7 @@ declare module '@theme/BlogPostPage' {
|
|
|
53
42
|
readonly truncated?: string;
|
|
54
43
|
readonly nextItem?: {readonly title: string; readonly permalink: string};
|
|
55
44
|
readonly prevItem?: {readonly title: string; readonly permalink: string};
|
|
45
|
+
readonly authors: import('./types').Author[];
|
|
56
46
|
readonly tags: readonly {
|
|
57
47
|
readonly label: string;
|
|
58
48
|
readonly permalink: string;
|
|
@@ -61,15 +51,16 @@ declare module '@theme/BlogPostPage' {
|
|
|
61
51
|
|
|
62
52
|
export type Content = {
|
|
63
53
|
readonly frontMatter: FrontMatter;
|
|
54
|
+
readonly assets: Assets;
|
|
64
55
|
readonly metadata: Metadata;
|
|
65
56
|
readonly toc: readonly TOCItem[];
|
|
66
57
|
(): JSX.Element;
|
|
67
58
|
};
|
|
68
59
|
|
|
69
|
-
export
|
|
60
|
+
export interface Props {
|
|
70
61
|
readonly sidebar: BlogSidebar;
|
|
71
62
|
readonly content: Content;
|
|
72
|
-
}
|
|
63
|
+
}
|
|
73
64
|
|
|
74
65
|
const BlogPostPage: (props: Props) => JSX.Element;
|
|
75
66
|
export default BlogPostPage;
|
|
@@ -79,10 +70,6 @@ declare module '@theme/BlogListPage' {
|
|
|
79
70
|
import type {Content} from '@theme/BlogPostPage';
|
|
80
71
|
import type {BlogSidebar} from '@theme/BlogSidebar';
|
|
81
72
|
|
|
82
|
-
export type Item = {
|
|
83
|
-
readonly content: () => JSX.Element;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
73
|
export type Metadata = {
|
|
87
74
|
readonly blogTitle: string;
|
|
88
75
|
readonly blogDescription: string;
|
|
@@ -95,11 +82,11 @@ declare module '@theme/BlogListPage' {
|
|
|
95
82
|
readonly totalPages: number;
|
|
96
83
|
};
|
|
97
84
|
|
|
98
|
-
export
|
|
85
|
+
export interface Props {
|
|
99
86
|
readonly sidebar: BlogSidebar;
|
|
100
87
|
readonly metadata: Metadata;
|
|
101
88
|
readonly items: readonly {readonly content: Content}[];
|
|
102
|
-
}
|
|
89
|
+
}
|
|
103
90
|
|
|
104
91
|
const BlogListPage: (props: Props) => JSX.Element;
|
|
105
92
|
export default BlogListPage;
|
|
@@ -116,10 +103,10 @@ declare module '@theme/BlogTagsListPage' {
|
|
|
116
103
|
slug: string;
|
|
117
104
|
};
|
|
118
105
|
|
|
119
|
-
export
|
|
106
|
+
export interface Props {
|
|
120
107
|
readonly sidebar: BlogSidebar;
|
|
121
108
|
readonly tags: Readonly<Record<string, Tag>>;
|
|
122
|
-
}
|
|
109
|
+
}
|
|
123
110
|
|
|
124
111
|
const BlogTagsListPage: (props: Props) => JSX.Element;
|
|
125
112
|
export default BlogTagsListPage;
|
|
@@ -130,9 +117,26 @@ declare module '@theme/BlogTagsPostsPage' {
|
|
|
130
117
|
import type {Tag} from '@theme/BlogTagsListPage';
|
|
131
118
|
import type {Content} from '@theme/BlogPostPage';
|
|
132
119
|
|
|
133
|
-
export
|
|
120
|
+
export interface Props {
|
|
134
121
|
readonly sidebar: BlogSidebar;
|
|
135
122
|
readonly metadata: Tag;
|
|
136
123
|
readonly items: readonly {readonly content: Content}[];
|
|
137
|
-
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const BlogTagsPostsPage: (props: Props) => JSX.Element;
|
|
127
|
+
export default BlogTagsPostsPage;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
declare module '@theme/BlogArchivePage' {
|
|
131
|
+
import type {Content} from '@theme/BlogPostPage';
|
|
132
|
+
|
|
133
|
+
export type ArchiveBlogPost = Content;
|
|
134
|
+
|
|
135
|
+
export interface Props {
|
|
136
|
+
readonly archive: {
|
|
137
|
+
readonly blogPosts: readonly ArchiveBlogPost[];
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default function BlogArchivePage(props: Props): JSX.Element;
|
|
138
142
|
}
|
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
AdmonitionsSchema,
|
|
13
13
|
URISchema,
|
|
14
14
|
} from '@docusaurus/utils-validation';
|
|
15
|
+
import {GlobExcludeDefault} from '@docusaurus/utils';
|
|
16
|
+
import {PluginOptions} from './types';
|
|
15
17
|
|
|
16
|
-
export const DEFAULT_OPTIONS = {
|
|
17
|
-
feedOptions: {type: ['rss', 'atom']},
|
|
18
|
+
export const DEFAULT_OPTIONS: PluginOptions = {
|
|
19
|
+
feedOptions: {type: ['rss', 'atom'], copyright: ''},
|
|
18
20
|
beforeDefaultRehypePlugins: [],
|
|
19
21
|
beforeDefaultRemarkPlugins: [],
|
|
20
22
|
admonitions: {},
|
|
@@ -31,22 +33,29 @@ export const DEFAULT_OPTIONS = {
|
|
|
31
33
|
blogSidebarCount: 5,
|
|
32
34
|
blogSidebarTitle: 'Recent posts',
|
|
33
35
|
postsPerPage: 10,
|
|
34
|
-
include: ['
|
|
36
|
+
include: ['**/*.{md,mdx}'],
|
|
37
|
+
exclude: GlobExcludeDefault,
|
|
35
38
|
routeBasePath: 'blog',
|
|
39
|
+
tagsBasePath: 'tags',
|
|
40
|
+
archiveBasePath: 'archive',
|
|
36
41
|
path: 'blog',
|
|
37
42
|
editLocalizedFiles: false,
|
|
43
|
+
authorsMapPath: 'authors.yml',
|
|
44
|
+
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
|
|
38
45
|
};
|
|
39
46
|
|
|
40
|
-
export const PluginOptionSchema = Joi.object({
|
|
47
|
+
export const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
41
48
|
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
|
49
|
+
archiveBasePath: Joi.string().default(DEFAULT_OPTIONS.archiveBasePath),
|
|
42
50
|
routeBasePath: Joi.string()
|
|
43
51
|
// '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
|
|
44
52
|
// .allow('')
|
|
45
53
|
.default(DEFAULT_OPTIONS.routeBasePath),
|
|
54
|
+
tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
|
|
46
55
|
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.min(1)
|
|
56
|
+
exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
|
|
57
|
+
postsPerPage: Joi.alternatives()
|
|
58
|
+
.try(Joi.equal('ALL').required(), Joi.number().integer().min(1).required())
|
|
50
59
|
.default(DEFAULT_OPTIONS.postsPerPage),
|
|
51
60
|
blogListComponent: Joi.string().default(DEFAULT_OPTIONS.blogListComponent),
|
|
52
61
|
blogPostComponent: Joi.string().default(DEFAULT_OPTIONS.blogPostComponent),
|
|
@@ -61,7 +70,7 @@ export const PluginOptionSchema = Joi.object({
|
|
|
61
70
|
.allow('')
|
|
62
71
|
.default(DEFAULT_OPTIONS.blogDescription),
|
|
63
72
|
blogSidebarCount: Joi.alternatives()
|
|
64
|
-
.try(Joi.equal('ALL').required(), Joi.number().required())
|
|
73
|
+
.try(Joi.equal('ALL').required(), Joi.number().integer().min(0).required())
|
|
65
74
|
.default(DEFAULT_OPTIONS.blogSidebarCount),
|
|
66
75
|
blogSidebarTitle: Joi.string().default(DEFAULT_OPTIONS.blogSidebarTitle),
|
|
67
76
|
showReadingTime: Joi.bool().default(DEFAULT_OPTIONS.showReadingTime),
|
|
@@ -94,7 +103,16 @@ export const PluginOptionSchema = Joi.object({
|
|
|
94
103
|
.default(DEFAULT_OPTIONS.feedOptions.type),
|
|
95
104
|
title: Joi.string().allow(''),
|
|
96
105
|
description: Joi.string().allow(''),
|
|
97
|
-
|
|
106
|
+
// only add default value when user actually wants a feed (type is not null)
|
|
107
|
+
copyright: Joi.when('type', {
|
|
108
|
+
is: Joi.any().valid(null),
|
|
109
|
+
then: Joi.string().optional(),
|
|
110
|
+
otherwise: Joi.string()
|
|
111
|
+
.allow('')
|
|
112
|
+
.default(DEFAULT_OPTIONS.feedOptions.copyright),
|
|
113
|
+
}),
|
|
98
114
|
language: Joi.string(),
|
|
99
115
|
}).default(DEFAULT_OPTIONS.feedOptions),
|
|
116
|
+
authorsMapPath: Joi.string().default(DEFAULT_OPTIONS.authorsMapPath),
|
|
117
|
+
readingTime: Joi.function().default(() => DEFAULT_OPTIONS.readingTime),
|
|
100
118
|
});
|