@docusaurus/plugin-content-blog 2.0.0-beta.15 → 2.0.0-beta.16
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/authors.js +5 -4
- package/lib/blogUtils.d.ts +14 -2
- package/lib/blogUtils.js +67 -24
- package/lib/index.js +58 -68
- package/lib/pluginOptionSchema.js +3 -1
- package/lib/types.d.ts +2 -1
- package/package.json +12 -12
- package/src/authors.ts +5 -4
- package/src/blogUtils.ts +95 -28
- package/src/index.ts +75 -87
- package/src/plugin-content-blog.d.ts +7 -23
- package/src/pluginOptionSchema.ts +5 -1
- package/src/types.ts +8 -3
package/lib/authors.js
CHANGED
|
@@ -52,9 +52,9 @@ function getFrontMatterAuthorLegacy(frontMatter) {
|
|
|
52
52
|
function normalizeFrontMatterAuthors(frontMatterAuthors = []) {
|
|
53
53
|
function normalizeAuthor(authorInput) {
|
|
54
54
|
if (typeof authorInput === 'string') {
|
|
55
|
-
// Technically, we could allow users to provide an author's name here
|
|
56
|
-
//
|
|
57
|
-
//
|
|
55
|
+
// Technically, we could allow users to provide an author's name here, but
|
|
56
|
+
// we only support keys, otherwise, a typo in a key would fallback to
|
|
57
|
+
// becoming a name and may end up unnoticed
|
|
58
58
|
return { key: authorInput };
|
|
59
59
|
}
|
|
60
60
|
return authorInput;
|
|
@@ -97,7 +97,8 @@ function getBlogPostAuthors(params) {
|
|
|
97
97
|
const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
|
|
98
98
|
const authors = getFrontMatterAuthors(params);
|
|
99
99
|
if (authorLegacy) {
|
|
100
|
-
// Technically, we could allow mixing legacy/authors front matter, but do we
|
|
100
|
+
// Technically, we could allow mixing legacy/authors front matter, but do we
|
|
101
|
+
// really want to?
|
|
101
102
|
if (authors.length > 0) {
|
|
102
103
|
throw new Error(`To declare blog post authors, use the 'authors' front matter in priority.
|
|
103
104
|
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`);
|
package/lib/blogUtils.d.ts
CHANGED
|
@@ -4,12 +4,24 @@
|
|
|
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
|
-
import type { BlogPost, BlogContentPaths, BlogMarkdownLoaderOptions, BlogTags } from './types';
|
|
7
|
+
import type { BlogPost, BlogContentPaths, BlogMarkdownLoaderOptions, BlogTags, BlogPaginated } from './types';
|
|
8
8
|
import type { LoadContext } from '@docusaurus/types';
|
|
9
9
|
import type { PluginOptions } from '@docusaurus/plugin-content-blog';
|
|
10
10
|
export declare function truncate(fileString: string, truncateMarker: RegExp): string;
|
|
11
11
|
export declare function getSourceToPermalink(blogPosts: BlogPost[]): Record<string, string>;
|
|
12
|
-
export declare function
|
|
12
|
+
export declare function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }: {
|
|
13
|
+
blogPosts: BlogPost[];
|
|
14
|
+
basePageUrl: string;
|
|
15
|
+
blogTitle: string;
|
|
16
|
+
blogDescription: string;
|
|
17
|
+
postsPerPageOption: number | 'ALL';
|
|
18
|
+
}): BlogPaginated[];
|
|
19
|
+
export declare function getBlogTags({ blogPosts, ...params }: {
|
|
20
|
+
blogPosts: BlogPost[];
|
|
21
|
+
blogTitle: string;
|
|
22
|
+
blogDescription: string;
|
|
23
|
+
postsPerPageOption: number | 'ALL';
|
|
24
|
+
}): BlogTags;
|
|
13
25
|
declare type ParsedBlogFileName = {
|
|
14
26
|
date: Date | undefined;
|
|
15
27
|
text: string;
|
package/lib/blogUtils.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.getSourceToPermalink = exports.truncate = void 0;
|
|
9
|
+
exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.paginateBlogPosts = exports.getSourceToPermalink = exports.truncate = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
11
|
const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
|
|
12
12
|
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
13
13
|
const reading_time_1 = (0, tslib_1.__importDefault)(require("reading-time"));
|
|
14
|
-
const lodash_1 = require("lodash");
|
|
14
|
+
const lodash_1 = (0, tslib_1.__importDefault)(require("lodash"));
|
|
15
15
|
const utils_1 = require("@docusaurus/utils");
|
|
16
16
|
const blogFrontMatter_1 = require("./blogFrontMatter");
|
|
17
17
|
const authors_1 = require("./authors");
|
|
@@ -21,19 +21,53 @@ function truncate(fileString, truncateMarker) {
|
|
|
21
21
|
}
|
|
22
22
|
exports.truncate = truncate;
|
|
23
23
|
function getSourceToPermalink(blogPosts) {
|
|
24
|
-
return (
|
|
24
|
+
return Object.fromEntries(blogPosts.map(({ metadata: { source, permalink } }) => [source, permalink]));
|
|
25
25
|
}
|
|
26
26
|
exports.getSourceToPermalink = getSourceToPermalink;
|
|
27
|
-
function
|
|
27
|
+
function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }) {
|
|
28
|
+
const totalCount = blogPosts.length;
|
|
29
|
+
const postsPerPage = postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
|
30
|
+
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
|
31
|
+
const pages = [];
|
|
32
|
+
function permalink(page) {
|
|
33
|
+
return page > 0 ? `${basePageUrl}/page/${page + 1}` : basePageUrl;
|
|
34
|
+
}
|
|
35
|
+
for (let page = 0; page < numberOfPages; page += 1) {
|
|
36
|
+
pages.push({
|
|
37
|
+
items: blogPosts
|
|
38
|
+
.slice(page * postsPerPage, (page + 1) * postsPerPage)
|
|
39
|
+
.map((item) => item.id),
|
|
40
|
+
metadata: {
|
|
41
|
+
permalink: permalink(page),
|
|
42
|
+
page: page + 1,
|
|
43
|
+
postsPerPage,
|
|
44
|
+
totalPages: numberOfPages,
|
|
45
|
+
totalCount,
|
|
46
|
+
previousPage: page !== 0 ? permalink(page - 1) : null,
|
|
47
|
+
nextPage: page < numberOfPages - 1 ? permalink(page + 1) : null,
|
|
48
|
+
blogDescription,
|
|
49
|
+
blogTitle,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return pages;
|
|
54
|
+
}
|
|
55
|
+
exports.paginateBlogPosts = paginateBlogPosts;
|
|
56
|
+
function getBlogTags({ blogPosts, ...params }) {
|
|
28
57
|
const groups = (0, utils_1.groupTaggedItems)(blogPosts, (blogPost) => blogPost.metadata.tags);
|
|
29
|
-
return
|
|
30
|
-
name:
|
|
31
|
-
items:
|
|
32
|
-
permalink:
|
|
58
|
+
return lodash_1.default.mapValues(groups, ({ tag, items: tagBlogPosts }) => ({
|
|
59
|
+
name: tag.label,
|
|
60
|
+
items: tagBlogPosts.map((item) => item.id),
|
|
61
|
+
permalink: tag.permalink,
|
|
62
|
+
pages: paginateBlogPosts({
|
|
63
|
+
blogPosts: tagBlogPosts,
|
|
64
|
+
basePageUrl: tag.permalink,
|
|
65
|
+
...params,
|
|
66
|
+
}),
|
|
33
67
|
}));
|
|
34
68
|
}
|
|
35
69
|
exports.getBlogTags = getBlogTags;
|
|
36
|
-
const DATE_FILENAME_REGEX = /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(
|
|
70
|
+
const DATE_FILENAME_REGEX = /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
|
|
37
71
|
function parseBlogFileName(blogSourceRelative) {
|
|
38
72
|
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
|
|
39
73
|
if (dateFilenameMatch) {
|
|
@@ -44,11 +78,9 @@ function parseBlogFileName(blogSourceRelative) {
|
|
|
44
78
|
const slug = `/${slugDate}/${folder}${text}`;
|
|
45
79
|
return { date, text, slug };
|
|
46
80
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return { date: undefined, text, slug };
|
|
51
|
-
}
|
|
81
|
+
const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
|
|
82
|
+
const slug = `/${text}`;
|
|
83
|
+
return { date: undefined, text, slug };
|
|
52
84
|
}
|
|
53
85
|
exports.parseBlogFileName = parseBlogFileName;
|
|
54
86
|
function formatBlogPostDate(locale, date) {
|
|
@@ -60,8 +92,9 @@ function formatBlogPostDate(locale, date) {
|
|
|
60
92
|
timeZone: 'UTC',
|
|
61
93
|
}).format(date);
|
|
62
94
|
}
|
|
63
|
-
catch (
|
|
64
|
-
|
|
95
|
+
catch (err) {
|
|
96
|
+
logger_1.default.error `Can't format blog post date "${String(date)}"`;
|
|
97
|
+
throw err;
|
|
65
98
|
}
|
|
66
99
|
}
|
|
67
100
|
async function parseBlogPostMarkdownFile(blogSourceAbsolute) {
|
|
@@ -75,8 +108,9 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute) {
|
|
|
75
108
|
frontMatter: (0, blogFrontMatter_1.validateBlogPostFrontMatter)(result.frontMatter),
|
|
76
109
|
};
|
|
77
110
|
}
|
|
78
|
-
catch (
|
|
79
|
-
|
|
111
|
+
catch (err) {
|
|
112
|
+
logger_1.default.error `Error while parsing blog post file path=${blogSourceAbsolute}.`;
|
|
113
|
+
throw err;
|
|
80
114
|
}
|
|
81
115
|
}
|
|
82
116
|
const defaultReadingTime = ({ content, options }) => (0, reading_time_1.default)(content, options).minutes;
|
|
@@ -109,8 +143,17 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
109
143
|
else if (parsedBlogFileName.date) {
|
|
110
144
|
return parsedBlogFileName.date;
|
|
111
145
|
}
|
|
112
|
-
|
|
113
|
-
|
|
146
|
+
try {
|
|
147
|
+
const result = (0, utils_1.getFileCommitDate)(blogSourceAbsolute, {
|
|
148
|
+
age: 'oldest',
|
|
149
|
+
includeAuthor: false,
|
|
150
|
+
});
|
|
151
|
+
return result.date;
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
logger_1.default.error(err);
|
|
155
|
+
return (await fs_extra_1.default.stat(blogSourceAbsolute)).birthtime;
|
|
156
|
+
}
|
|
114
157
|
}
|
|
115
158
|
const date = await getDate();
|
|
116
159
|
const formattedDate = formatBlogPostDate(i18n.currentLocale, date);
|
|
@@ -174,7 +217,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
174
217
|
}
|
|
175
218
|
async function generateBlogPosts(contentPaths, context, options) {
|
|
176
219
|
const { include, exclude } = options;
|
|
177
|
-
if (!fs_extra_1.default.
|
|
220
|
+
if (!(await fs_extra_1.default.pathExists(contentPaths.contentPath))) {
|
|
178
221
|
return [];
|
|
179
222
|
}
|
|
180
223
|
const blogSourceFiles = await (0, utils_1.Globby)(include, {
|
|
@@ -189,9 +232,9 @@ async function generateBlogPosts(contentPaths, context, options) {
|
|
|
189
232
|
try {
|
|
190
233
|
return await processBlogSourceFile(blogSourceFile, contentPaths, context, options, authorsMap);
|
|
191
234
|
}
|
|
192
|
-
catch (
|
|
193
|
-
logger_1.default.error `Processing of blog source file
|
|
194
|
-
throw
|
|
235
|
+
catch (err) {
|
|
236
|
+
logger_1.default.error `Processing of blog source file path=${blogSourceFile} failed.`;
|
|
237
|
+
throw err;
|
|
195
238
|
}
|
|
196
239
|
}))).filter(Boolean);
|
|
197
240
|
blogPosts.sort((a, b) => b.metadata.date.getTime() - a.metadata.date.getTime());
|
package/lib/index.js
CHANGED
|
@@ -62,6 +62,7 @@ async function pluginContentBlog(context, options) {
|
|
|
62
62
|
blogListPaginated: [],
|
|
63
63
|
blogTags: {},
|
|
64
64
|
blogTagsListPath: null,
|
|
65
|
+
blogTagsPaginated: [],
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
// Colocate next and prev metadata.
|
|
@@ -81,39 +82,20 @@ async function pluginContentBlog(context, options) {
|
|
|
81
82
|
};
|
|
82
83
|
}
|
|
83
84
|
});
|
|
84
|
-
// Blog pagination routes.
|
|
85
|
-
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
|
|
86
|
-
const totalCount = blogPosts.length;
|
|
87
|
-
const postsPerPage = postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
|
88
|
-
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
|
89
85
|
const baseBlogUrl = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]);
|
|
90
|
-
const blogListPaginated =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
totalCount,
|
|
104
|
-
previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
|
|
105
|
-
nextPage: page < numberOfPages - 1
|
|
106
|
-
? blogPaginationPermalink(page + 1)
|
|
107
|
-
: null,
|
|
108
|
-
blogDescription,
|
|
109
|
-
blogTitle,
|
|
110
|
-
},
|
|
111
|
-
items: blogPosts
|
|
112
|
-
.slice(page * postsPerPage, (page + 1) * postsPerPage)
|
|
113
|
-
.map((item) => item.id),
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
const blogTags = (0, blogUtils_1.getBlogTags)(blogPosts);
|
|
86
|
+
const blogListPaginated = (0, blogUtils_1.paginateBlogPosts)({
|
|
87
|
+
blogPosts,
|
|
88
|
+
blogTitle,
|
|
89
|
+
blogDescription,
|
|
90
|
+
postsPerPageOption,
|
|
91
|
+
basePageUrl: baseBlogUrl,
|
|
92
|
+
});
|
|
93
|
+
const blogTags = (0, blogUtils_1.getBlogTags)({
|
|
94
|
+
blogPosts,
|
|
95
|
+
postsPerPageOption,
|
|
96
|
+
blogDescription,
|
|
97
|
+
blogTitle,
|
|
98
|
+
});
|
|
117
99
|
const tagsPath = (0, utils_1.normalizeUrl)([baseBlogUrl, tagsBasePath]);
|
|
118
100
|
const blogTagsListPath = Object.keys(blogTags).length > 0 ? tagsPath : null;
|
|
119
101
|
return {
|
|
@@ -128,7 +110,7 @@ async function pluginContentBlog(context, options) {
|
|
|
128
110
|
if (!blogContents) {
|
|
129
111
|
return;
|
|
130
112
|
}
|
|
131
|
-
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, routeBasePath, archiveBasePath, } = options;
|
|
113
|
+
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, } = options;
|
|
132
114
|
const { addRoute, createData } = actions;
|
|
133
115
|
const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = blogContents;
|
|
134
116
|
const blogItemsToMetadata = {};
|
|
@@ -145,7 +127,7 @@ async function pluginContentBlog(context, options) {
|
|
|
145
127
|
const archiveProp = await createData(`${(0, utils_1.docuHash)(archiveUrl)}.json`, JSON.stringify({ blogPosts }, null, 2));
|
|
146
128
|
addRoute({
|
|
147
129
|
path: archiveUrl,
|
|
148
|
-
component:
|
|
130
|
+
component: blogArchiveComponent,
|
|
149
131
|
exact: true,
|
|
150
132
|
modules: {
|
|
151
133
|
archive: aliasedSource(archiveProp),
|
|
@@ -193,7 +175,8 @@ async function pluginContentBlog(context, options) {
|
|
|
193
175
|
modules: {
|
|
194
176
|
sidebar: aliasedSource(sidebarProp),
|
|
195
177
|
items: items.map((postID) =>
|
|
196
|
-
// To tell routes.js this is an import and not a nested object
|
|
178
|
+
// To tell routes.js this is an import and not a nested object
|
|
179
|
+
// to recurse.
|
|
197
180
|
({
|
|
198
181
|
content: {
|
|
199
182
|
__import: true,
|
|
@@ -211,40 +194,46 @@ async function pluginContentBlog(context, options) {
|
|
|
211
194
|
if (blogTagsListPath === null) {
|
|
212
195
|
return;
|
|
213
196
|
}
|
|
214
|
-
const tagsModule = {
|
|
215
|
-
|
|
216
|
-
const { name, items, permalink } = blogTags[tag];
|
|
217
|
-
// Refactor all this, see docs implementation
|
|
218
|
-
tagsModule[tag] = {
|
|
197
|
+
const tagsModule = Object.fromEntries(Object.entries(blogTags).map(([tagKey, tag]) => {
|
|
198
|
+
const tagModule = {
|
|
219
199
|
allTagsPath: blogTagsListPath,
|
|
220
|
-
slug:
|
|
221
|
-
name,
|
|
222
|
-
count: items.length,
|
|
223
|
-
permalink,
|
|
200
|
+
slug: tagKey,
|
|
201
|
+
name: tag.name,
|
|
202
|
+
count: tag.items.length,
|
|
203
|
+
permalink: tag.permalink,
|
|
224
204
|
};
|
|
225
|
-
|
|
226
|
-
addRoute({
|
|
227
|
-
path: permalink,
|
|
228
|
-
component: blogTagsPostsComponent,
|
|
229
|
-
exact: true,
|
|
230
|
-
modules: {
|
|
231
|
-
sidebar: aliasedSource(sidebarProp),
|
|
232
|
-
items: items.map((postID) => {
|
|
233
|
-
const metadata = blogItemsToMetadata[postID];
|
|
234
|
-
return {
|
|
235
|
-
content: {
|
|
236
|
-
__import: true,
|
|
237
|
-
path: metadata.source,
|
|
238
|
-
query: {
|
|
239
|
-
truncated: true,
|
|
240
|
-
},
|
|
241
|
-
},
|
|
242
|
-
};
|
|
243
|
-
}),
|
|
244
|
-
metadata: aliasedSource(tagsMetadataPath),
|
|
245
|
-
},
|
|
246
|
-
});
|
|
205
|
+
return [tag.name, tagModule];
|
|
247
206
|
}));
|
|
207
|
+
async function createTagRoutes(tag) {
|
|
208
|
+
await Promise.all(tag.pages.map(async (blogPaginated) => {
|
|
209
|
+
const { metadata, items } = blogPaginated;
|
|
210
|
+
const tagsMetadataPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}.json`, JSON.stringify(tagsModule[tag.name], null, 2));
|
|
211
|
+
const listMetadataPath = await createData(`${(0, utils_1.docuHash)(metadata.permalink)}-list.json`, JSON.stringify(metadata, null, 2));
|
|
212
|
+
addRoute({
|
|
213
|
+
path: metadata.permalink,
|
|
214
|
+
component: blogTagsPostsComponent,
|
|
215
|
+
exact: true,
|
|
216
|
+
modules: {
|
|
217
|
+
sidebar: aliasedSource(sidebarProp),
|
|
218
|
+
items: items.map((postID) => {
|
|
219
|
+
const blogPostMetadata = blogItemsToMetadata[postID];
|
|
220
|
+
return {
|
|
221
|
+
content: {
|
|
222
|
+
__import: true,
|
|
223
|
+
path: blogPostMetadata.source,
|
|
224
|
+
query: {
|
|
225
|
+
truncated: true,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}),
|
|
230
|
+
metadata: aliasedSource(tagsMetadataPath),
|
|
231
|
+
listMetadata: aliasedSource(listMetadataPath),
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}));
|
|
235
|
+
}
|
|
236
|
+
await Promise.all(Object.values(blogTags).map(createTagRoutes));
|
|
248
237
|
// Only create /tags page if there are tags.
|
|
249
238
|
if (Object.keys(blogTags).length > 0) {
|
|
250
239
|
const tagsListPath = await createData(`${(0, utils_1.docuHash)(`${blogTagsListPath}-tags`)}.json`, JSON.stringify(tagsModule, null, 2));
|
|
@@ -286,7 +275,7 @@ async function pluginContentBlog(context, options) {
|
|
|
286
275
|
module: {
|
|
287
276
|
rules: [
|
|
288
277
|
{
|
|
289
|
-
test:
|
|
278
|
+
test: /\.mdx?$/i,
|
|
290
279
|
include: contentDirs
|
|
291
280
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
|
292
281
|
.map(utils_1.addTrailingPathSeparator),
|
|
@@ -311,7 +300,8 @@ async function pluginContentBlog(context, options) {
|
|
|
311
300
|
// For blog posts a title in markdown is always removed
|
|
312
301
|
// Blog posts title are rendered separately
|
|
313
302
|
removeContentTitle: true,
|
|
314
|
-
// Assets allow to convert some relative images paths to
|
|
303
|
+
// Assets allow to convert some relative images paths to
|
|
304
|
+
// require() calls
|
|
315
305
|
createAssets: ({ frontMatter, metadata, }) => ({
|
|
316
306
|
image: frontMatter.image,
|
|
317
307
|
authorsImageUrls: metadata.authors.map((author) => author.imageURL),
|
|
@@ -14,7 +14,7 @@ exports.DEFAULT_OPTIONS = {
|
|
|
14
14
|
beforeDefaultRehypePlugins: [],
|
|
15
15
|
beforeDefaultRemarkPlugins: [],
|
|
16
16
|
admonitions: {},
|
|
17
|
-
truncateMarker: /<!--\s*
|
|
17
|
+
truncateMarker: /<!--\s*truncate\s*-->/,
|
|
18
18
|
rehypePlugins: [],
|
|
19
19
|
remarkPlugins: [],
|
|
20
20
|
showReadingTime: true,
|
|
@@ -22,6 +22,7 @@ exports.DEFAULT_OPTIONS = {
|
|
|
22
22
|
blogTagsListComponent: '@theme/BlogTagsListPage',
|
|
23
23
|
blogPostComponent: '@theme/BlogPostPage',
|
|
24
24
|
blogListComponent: '@theme/BlogListPage',
|
|
25
|
+
blogArchiveComponent: '@theme/BlogArchivePage',
|
|
25
26
|
blogDescription: 'Blog',
|
|
26
27
|
blogTitle: 'Blog',
|
|
27
28
|
blogSidebarCount: 5,
|
|
@@ -57,6 +58,7 @@ exports.PluginOptionSchema = utils_validation_1.Joi.object({
|
|
|
57
58
|
blogPostComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogPostComponent),
|
|
58
59
|
blogTagsListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogTagsListComponent),
|
|
59
60
|
blogTagsPostsComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogTagsPostsComponent),
|
|
61
|
+
blogArchiveComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogArchiveComponent),
|
|
60
62
|
blogTitle: utils_validation_1.Joi.string().allow('').default(exports.DEFAULT_OPTIONS.blogTitle),
|
|
61
63
|
blogDescription: utils_validation_1.Joi.string()
|
|
62
64
|
.allow('')
|
package/lib/types.d.ts
CHANGED
|
@@ -16,12 +16,13 @@ export interface BlogContent {
|
|
|
16
16
|
blogTagsListPath: string | null;
|
|
17
17
|
}
|
|
18
18
|
export interface BlogTags {
|
|
19
|
-
[
|
|
19
|
+
[tagKey: string]: BlogTag;
|
|
20
20
|
}
|
|
21
21
|
export interface BlogTag {
|
|
22
22
|
name: string;
|
|
23
23
|
items: string[];
|
|
24
24
|
permalink: string;
|
|
25
|
+
pages: BlogPaginated[];
|
|
25
26
|
}
|
|
26
27
|
export interface BlogPost {
|
|
27
28
|
id: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-content-blog",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.16",
|
|
4
4
|
"description": "Blog plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-content-blog.d.ts",
|
|
@@ -18,24 +18,24 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@docusaurus/core": "2.0.0-beta.
|
|
22
|
-
"@docusaurus/logger": "2.0.0-beta.
|
|
23
|
-
"@docusaurus/mdx-loader": "2.0.0-beta.
|
|
24
|
-
"@docusaurus/utils": "2.0.0-beta.
|
|
25
|
-
"@docusaurus/utils-common": "2.0.0-beta.
|
|
26
|
-
"@docusaurus/utils-validation": "2.0.0-beta.
|
|
21
|
+
"@docusaurus/core": "2.0.0-beta.16",
|
|
22
|
+
"@docusaurus/logger": "2.0.0-beta.16",
|
|
23
|
+
"@docusaurus/mdx-loader": "2.0.0-beta.16",
|
|
24
|
+
"@docusaurus/utils": "2.0.0-beta.16",
|
|
25
|
+
"@docusaurus/utils-common": "2.0.0-beta.16",
|
|
26
|
+
"@docusaurus/utils-validation": "2.0.0-beta.16",
|
|
27
27
|
"cheerio": "^1.0.0-rc.10",
|
|
28
28
|
"feed": "^4.2.2",
|
|
29
|
-
"fs-extra": "^10.0.
|
|
30
|
-
"lodash": "^4.17.
|
|
29
|
+
"fs-extra": "^10.0.1",
|
|
30
|
+
"lodash": "^4.17.21",
|
|
31
31
|
"reading-time": "^1.5.0",
|
|
32
32
|
"remark-admonitions": "^1.2.1",
|
|
33
33
|
"tslib": "^2.3.1",
|
|
34
34
|
"utility-types": "^3.10.0",
|
|
35
|
-
"webpack": "^5.
|
|
35
|
+
"webpack": "^5.69.1"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@docusaurus/types": "2.0.0-beta.
|
|
38
|
+
"@docusaurus/types": "2.0.0-beta.16",
|
|
39
39
|
"escape-string-regexp": "^4.0.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=14"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "eb43c4d4f95a4fb97dc9bb9dc615413e0dc2e1e7"
|
|
49
49
|
}
|
package/src/authors.ts
CHANGED
|
@@ -83,9 +83,9 @@ function normalizeFrontMatterAuthors(
|
|
|
83
83
|
authorInput: string | BlogPostFrontMatterAuthor,
|
|
84
84
|
): BlogPostFrontMatterAuthor {
|
|
85
85
|
if (typeof authorInput === 'string') {
|
|
86
|
-
// Technically, we could allow users to provide an author's name here
|
|
87
|
-
//
|
|
88
|
-
//
|
|
86
|
+
// Technically, we could allow users to provide an author's name here, but
|
|
87
|
+
// we only support keys, otherwise, a typo in a key would fallback to
|
|
88
|
+
// becoming a name and may end up unnoticed
|
|
89
89
|
return {key: authorInput};
|
|
90
90
|
}
|
|
91
91
|
return authorInput;
|
|
@@ -137,7 +137,8 @@ export function getBlogPostAuthors(params: AuthorsParam): Author[] {
|
|
|
137
137
|
const authors = getFrontMatterAuthors(params);
|
|
138
138
|
|
|
139
139
|
if (authorLegacy) {
|
|
140
|
-
// Technically, we could allow mixing legacy/authors front matter, but do we
|
|
140
|
+
// Technically, we could allow mixing legacy/authors front matter, but do we
|
|
141
|
+
// really want to?
|
|
141
142
|
if (authors.length > 0) {
|
|
142
143
|
throw new Error(
|
|
143
144
|
`To declare blog post authors, use the 'authors' front matter in priority.
|
package/src/blogUtils.ts
CHANGED
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import readingTime from 'reading-time';
|
|
11
|
-
import
|
|
11
|
+
import _ from 'lodash';
|
|
12
12
|
import type {
|
|
13
13
|
BlogPost,
|
|
14
14
|
BlogContentPaths,
|
|
15
15
|
BlogMarkdownLoaderOptions,
|
|
16
16
|
BlogTags,
|
|
17
|
+
BlogPaginated,
|
|
17
18
|
} from './types';
|
|
18
19
|
import {
|
|
19
20
|
parseMarkdownString,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
Globby,
|
|
27
28
|
normalizeFrontMatterTags,
|
|
28
29
|
groupTaggedItems,
|
|
30
|
+
getFileCommitDate,
|
|
29
31
|
getContentPathList,
|
|
30
32
|
} from '@docusaurus/utils';
|
|
31
33
|
import type {LoadContext} from '@docusaurus/types';
|
|
@@ -44,26 +46,85 @@ export function truncate(fileString: string, truncateMarker: RegExp): string {
|
|
|
44
46
|
export function getSourceToPermalink(
|
|
45
47
|
blogPosts: BlogPost[],
|
|
46
48
|
): Record<string, string> {
|
|
47
|
-
return
|
|
48
|
-
|
|
49
|
-
(v) => v.metadata.permalink,
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]),
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
export function
|
|
54
|
+
export function paginateBlogPosts({
|
|
55
|
+
blogPosts,
|
|
56
|
+
basePageUrl,
|
|
57
|
+
blogTitle,
|
|
58
|
+
blogDescription,
|
|
59
|
+
postsPerPageOption,
|
|
60
|
+
}: {
|
|
61
|
+
blogPosts: BlogPost[];
|
|
62
|
+
basePageUrl: string;
|
|
63
|
+
blogTitle: string;
|
|
64
|
+
blogDescription: string;
|
|
65
|
+
postsPerPageOption: number | 'ALL';
|
|
66
|
+
}): BlogPaginated[] {
|
|
67
|
+
const totalCount = blogPosts.length;
|
|
68
|
+
const postsPerPage =
|
|
69
|
+
postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
|
70
|
+
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
|
71
|
+
|
|
72
|
+
const pages: BlogPaginated[] = [];
|
|
73
|
+
|
|
74
|
+
function permalink(page: number) {
|
|
75
|
+
return page > 0 ? `${basePageUrl}/page/${page + 1}` : basePageUrl;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (let page = 0; page < numberOfPages; page += 1) {
|
|
79
|
+
pages.push({
|
|
80
|
+
items: blogPosts
|
|
81
|
+
.slice(page * postsPerPage, (page + 1) * postsPerPage)
|
|
82
|
+
.map((item) => item.id),
|
|
83
|
+
metadata: {
|
|
84
|
+
permalink: permalink(page),
|
|
85
|
+
page: page + 1,
|
|
86
|
+
postsPerPage,
|
|
87
|
+
totalPages: numberOfPages,
|
|
88
|
+
totalCount,
|
|
89
|
+
previousPage: page !== 0 ? permalink(page - 1) : null,
|
|
90
|
+
nextPage: page < numberOfPages - 1 ? permalink(page + 1) : null,
|
|
91
|
+
blogDescription,
|
|
92
|
+
blogTitle,
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return pages;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function getBlogTags({
|
|
101
|
+
blogPosts,
|
|
102
|
+
...params
|
|
103
|
+
}: {
|
|
104
|
+
blogPosts: BlogPost[];
|
|
105
|
+
blogTitle: string;
|
|
106
|
+
blogDescription: string;
|
|
107
|
+
postsPerPageOption: number | 'ALL';
|
|
108
|
+
}): BlogTags {
|
|
54
109
|
const groups = groupTaggedItems(
|
|
55
110
|
blogPosts,
|
|
56
111
|
(blogPost) => blogPost.metadata.tags,
|
|
57
112
|
);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
113
|
+
|
|
114
|
+
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
|
|
115
|
+
name: tag.label,
|
|
116
|
+
items: tagBlogPosts.map((item) => item.id),
|
|
117
|
+
permalink: tag.permalink,
|
|
118
|
+
pages: paginateBlogPosts({
|
|
119
|
+
blogPosts: tagBlogPosts,
|
|
120
|
+
basePageUrl: tag.permalink,
|
|
121
|
+
...params,
|
|
122
|
+
}),
|
|
62
123
|
}));
|
|
63
124
|
}
|
|
64
125
|
|
|
65
126
|
const DATE_FILENAME_REGEX =
|
|
66
|
-
/^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(
|
|
127
|
+
/^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
|
|
67
128
|
|
|
68
129
|
type ParsedBlogFileName = {
|
|
69
130
|
date: Date | undefined;
|
|
@@ -82,11 +143,10 @@ export function parseBlogFileName(
|
|
|
82
143
|
const slugDate = dateString.replace(/-/g, '/');
|
|
83
144
|
const slug = `/${slugDate}/${folder}${text}`;
|
|
84
145
|
return {date, text, slug};
|
|
85
|
-
} else {
|
|
86
|
-
const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
|
|
87
|
-
const slug = `/${text}`;
|
|
88
|
-
return {date: undefined, text, slug};
|
|
89
146
|
}
|
|
147
|
+
const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
|
|
148
|
+
const slug = `/${text}`;
|
|
149
|
+
return {date: undefined, text, slug};
|
|
90
150
|
}
|
|
91
151
|
|
|
92
152
|
function formatBlogPostDate(locale: string, date: Date): string {
|
|
@@ -97,8 +157,9 @@ function formatBlogPostDate(locale: string, date: Date): string {
|
|
|
97
157
|
year: 'numeric',
|
|
98
158
|
timeZone: 'UTC',
|
|
99
159
|
}).format(date);
|
|
100
|
-
} catch (
|
|
101
|
-
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.error`Can't format blog post date "${String(date)}"`;
|
|
162
|
+
throw err;
|
|
102
163
|
}
|
|
103
164
|
}
|
|
104
165
|
|
|
@@ -112,12 +173,9 @@ async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
|
|
112
173
|
...result,
|
|
113
174
|
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
|
|
114
175
|
};
|
|
115
|
-
} catch (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
(e as Error).message
|
|
119
|
-
}".`,
|
|
120
|
-
);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
logger.error`Error while parsing blog post file path=${blogSourceAbsolute}.`;
|
|
178
|
+
throw err;
|
|
121
179
|
}
|
|
122
180
|
}
|
|
123
181
|
|
|
@@ -179,8 +237,17 @@ async function processBlogSourceFile(
|
|
|
179
237
|
} else if (parsedBlogFileName.date) {
|
|
180
238
|
return parsedBlogFileName.date;
|
|
181
239
|
}
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
|
|
241
|
+
try {
|
|
242
|
+
const result = getFileCommitDate(blogSourceAbsolute, {
|
|
243
|
+
age: 'oldest',
|
|
244
|
+
includeAuthor: false,
|
|
245
|
+
});
|
|
246
|
+
return result.date;
|
|
247
|
+
} catch (err) {
|
|
248
|
+
logger.error(err);
|
|
249
|
+
return (await fs.stat(blogSourceAbsolute)).birthtime;
|
|
250
|
+
}
|
|
184
251
|
}
|
|
185
252
|
|
|
186
253
|
const date = await getDate();
|
|
@@ -263,7 +330,7 @@ export async function generateBlogPosts(
|
|
|
263
330
|
): Promise<BlogPost[]> {
|
|
264
331
|
const {include, exclude} = options;
|
|
265
332
|
|
|
266
|
-
if (!fs.
|
|
333
|
+
if (!(await fs.pathExists(contentPaths.contentPath))) {
|
|
267
334
|
return [];
|
|
268
335
|
}
|
|
269
336
|
|
|
@@ -288,9 +355,9 @@ export async function generateBlogPosts(
|
|
|
288
355
|
options,
|
|
289
356
|
authorsMap,
|
|
290
357
|
);
|
|
291
|
-
} catch (
|
|
292
|
-
logger.error`Processing of blog source file
|
|
293
|
-
throw
|
|
358
|
+
} catch (err) {
|
|
359
|
+
logger.error`Processing of blog source file path=${blogSourceFile} failed.`;
|
|
360
|
+
throw err;
|
|
294
361
|
}
|
|
295
362
|
}),
|
|
296
363
|
)
|
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import {translateContent, getTranslationFiles} from './translations';
|
|
24
24
|
|
|
25
25
|
import type {
|
|
26
|
+
BlogTag,
|
|
26
27
|
BlogTags,
|
|
27
28
|
BlogContent,
|
|
28
29
|
BlogItemsToMetadata,
|
|
@@ -31,21 +32,21 @@ import type {
|
|
|
31
32
|
BlogContentPaths,
|
|
32
33
|
BlogMarkdownLoaderOptions,
|
|
33
34
|
MetaData,
|
|
35
|
+
TagModule,
|
|
34
36
|
} from './types';
|
|
35
37
|
import {PluginOptionSchema} from './pluginOptionSchema';
|
|
36
38
|
import type {
|
|
37
39
|
LoadContext,
|
|
38
|
-
ConfigureWebpackUtils,
|
|
39
40
|
Plugin,
|
|
40
41
|
HtmlTags,
|
|
41
42
|
OptionValidationContext,
|
|
42
43
|
ValidationResult,
|
|
43
44
|
} from '@docusaurus/types';
|
|
44
|
-
import type {Configuration} from 'webpack';
|
|
45
45
|
import {
|
|
46
46
|
generateBlogPosts,
|
|
47
47
|
getSourceToPermalink,
|
|
48
48
|
getBlogTags,
|
|
49
|
+
paginateBlogPosts,
|
|
49
50
|
} from './blogUtils';
|
|
50
51
|
import {createBlogFeedFiles} from './feed';
|
|
51
52
|
import type {
|
|
@@ -134,6 +135,7 @@ export default async function pluginContentBlog(
|
|
|
134
135
|
blogListPaginated: [],
|
|
135
136
|
blogTags: {},
|
|
136
137
|
blogTagsListPath: null,
|
|
138
|
+
blogTagsPaginated: [],
|
|
137
139
|
};
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -157,45 +159,22 @@ export default async function pluginContentBlog(
|
|
|
157
159
|
}
|
|
158
160
|
});
|
|
159
161
|
|
|
160
|
-
// Blog pagination routes.
|
|
161
|
-
// Example: `/blog`, `/blog/page/1`, `/blog/page/2`
|
|
162
|
-
const totalCount = blogPosts.length;
|
|
163
|
-
const postsPerPage =
|
|
164
|
-
postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
|
165
|
-
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
|
166
162
|
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
|
167
163
|
|
|
168
|
-
const blogListPaginated: BlogPaginated[] =
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
for (let page = 0; page < numberOfPages; page += 1) {
|
|
177
|
-
blogListPaginated.push({
|
|
178
|
-
metadata: {
|
|
179
|
-
permalink: blogPaginationPermalink(page),
|
|
180
|
-
page: page + 1,
|
|
181
|
-
postsPerPage,
|
|
182
|
-
totalPages: numberOfPages,
|
|
183
|
-
totalCount,
|
|
184
|
-
previousPage: page !== 0 ? blogPaginationPermalink(page - 1) : null,
|
|
185
|
-
nextPage:
|
|
186
|
-
page < numberOfPages - 1
|
|
187
|
-
? blogPaginationPermalink(page + 1)
|
|
188
|
-
: null,
|
|
189
|
-
blogDescription,
|
|
190
|
-
blogTitle,
|
|
191
|
-
},
|
|
192
|
-
items: blogPosts
|
|
193
|
-
.slice(page * postsPerPage, (page + 1) * postsPerPage)
|
|
194
|
-
.map((item) => item.id),
|
|
195
|
-
});
|
|
196
|
-
}
|
|
164
|
+
const blogListPaginated: BlogPaginated[] = paginateBlogPosts({
|
|
165
|
+
blogPosts,
|
|
166
|
+
blogTitle,
|
|
167
|
+
blogDescription,
|
|
168
|
+
postsPerPageOption,
|
|
169
|
+
basePageUrl: baseBlogUrl,
|
|
170
|
+
});
|
|
197
171
|
|
|
198
|
-
const blogTags: BlogTags = getBlogTags(
|
|
172
|
+
const blogTags: BlogTags = getBlogTags({
|
|
173
|
+
blogPosts,
|
|
174
|
+
postsPerPageOption,
|
|
175
|
+
blogDescription,
|
|
176
|
+
blogTitle,
|
|
177
|
+
});
|
|
199
178
|
|
|
200
179
|
const tagsPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
|
201
180
|
|
|
@@ -221,6 +200,7 @@ export default async function pluginContentBlog(
|
|
|
221
200
|
blogPostComponent,
|
|
222
201
|
blogTagsListComponent,
|
|
223
202
|
blogTagsPostsComponent,
|
|
203
|
+
blogArchiveComponent,
|
|
224
204
|
routeBasePath,
|
|
225
205
|
archiveBasePath,
|
|
226
206
|
} = options;
|
|
@@ -254,7 +234,7 @@ export default async function pluginContentBlog(
|
|
|
254
234
|
);
|
|
255
235
|
addRoute({
|
|
256
236
|
path: archiveUrl,
|
|
257
|
-
component:
|
|
237
|
+
component: blogArchiveComponent,
|
|
258
238
|
exact: true,
|
|
259
239
|
modules: {
|
|
260
240
|
archive: aliasedSource(archiveProp),
|
|
@@ -322,7 +302,8 @@ export default async function pluginContentBlog(
|
|
|
322
302
|
modules: {
|
|
323
303
|
sidebar: aliasedSource(sidebarProp),
|
|
324
304
|
items: items.map((postID) =>
|
|
325
|
-
// To tell routes.js this is an import and not a nested object
|
|
305
|
+
// To tell routes.js this is an import and not a nested object
|
|
306
|
+
// to recurse.
|
|
326
307
|
({
|
|
327
308
|
content: {
|
|
328
309
|
__import: true,
|
|
@@ -344,50 +325,61 @@ export default async function pluginContentBlog(
|
|
|
344
325
|
return;
|
|
345
326
|
}
|
|
346
327
|
|
|
347
|
-
const tagsModule: TagsModule =
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
Object.keys(blogTags).map(async (tag) => {
|
|
351
|
-
const {name, items, permalink} = blogTags[tag];
|
|
352
|
-
|
|
353
|
-
// Refactor all this, see docs implementation
|
|
354
|
-
tagsModule[tag] = {
|
|
328
|
+
const tagsModule: TagsModule = Object.fromEntries(
|
|
329
|
+
Object.entries(blogTags).map(([tagKey, tag]) => {
|
|
330
|
+
const tagModule: TagModule = {
|
|
355
331
|
allTagsPath: blogTagsListPath,
|
|
356
|
-
slug:
|
|
357
|
-
name,
|
|
358
|
-
count: items.length,
|
|
359
|
-
permalink,
|
|
332
|
+
slug: tagKey,
|
|
333
|
+
name: tag.name,
|
|
334
|
+
count: tag.items.length,
|
|
335
|
+
permalink: tag.permalink,
|
|
360
336
|
};
|
|
361
|
-
|
|
362
|
-
const tagsMetadataPath = await createData(
|
|
363
|
-
`${docuHash(permalink)}.json`,
|
|
364
|
-
JSON.stringify(tagsModule[tag], null, 2),
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
addRoute({
|
|
368
|
-
path: permalink,
|
|
369
|
-
component: blogTagsPostsComponent,
|
|
370
|
-
exact: true,
|
|
371
|
-
modules: {
|
|
372
|
-
sidebar: aliasedSource(sidebarProp),
|
|
373
|
-
items: items.map((postID) => {
|
|
374
|
-
const metadata = blogItemsToMetadata[postID];
|
|
375
|
-
return {
|
|
376
|
-
content: {
|
|
377
|
-
__import: true,
|
|
378
|
-
path: metadata.source,
|
|
379
|
-
query: {
|
|
380
|
-
truncated: true,
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
};
|
|
384
|
-
}),
|
|
385
|
-
metadata: aliasedSource(tagsMetadataPath),
|
|
386
|
-
},
|
|
387
|
-
});
|
|
337
|
+
return [tag.name, tagModule];
|
|
388
338
|
}),
|
|
389
339
|
);
|
|
390
340
|
|
|
341
|
+
async function createTagRoutes(tag: BlogTag): Promise<void> {
|
|
342
|
+
await Promise.all(
|
|
343
|
+
tag.pages.map(async (blogPaginated) => {
|
|
344
|
+
const {metadata, items} = blogPaginated;
|
|
345
|
+
const tagsMetadataPath = await createData(
|
|
346
|
+
`${docuHash(metadata.permalink)}.json`,
|
|
347
|
+
JSON.stringify(tagsModule[tag.name], null, 2),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
const listMetadataPath = await createData(
|
|
351
|
+
`${docuHash(metadata.permalink)}-list.json`,
|
|
352
|
+
JSON.stringify(metadata, null, 2),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
addRoute({
|
|
356
|
+
path: metadata.permalink,
|
|
357
|
+
component: blogTagsPostsComponent,
|
|
358
|
+
exact: true,
|
|
359
|
+
modules: {
|
|
360
|
+
sidebar: aliasedSource(sidebarProp),
|
|
361
|
+
items: items.map((postID) => {
|
|
362
|
+
const blogPostMetadata = blogItemsToMetadata[postID];
|
|
363
|
+
return {
|
|
364
|
+
content: {
|
|
365
|
+
__import: true,
|
|
366
|
+
path: blogPostMetadata.source,
|
|
367
|
+
query: {
|
|
368
|
+
truncated: true,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}),
|
|
373
|
+
metadata: aliasedSource(tagsMetadataPath),
|
|
374
|
+
listMetadata: aliasedSource(listMetadataPath),
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
}),
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
await Promise.all(Object.values(blogTags).map(createTagRoutes));
|
|
382
|
+
|
|
391
383
|
// Only create /tags page if there are tags.
|
|
392
384
|
if (Object.keys(blogTags).length > 0) {
|
|
393
385
|
const tagsListPath = await createData(
|
|
@@ -411,12 +403,7 @@ export default async function pluginContentBlog(
|
|
|
411
403
|
return translateContent(content, translationFiles);
|
|
412
404
|
},
|
|
413
405
|
|
|
414
|
-
configureWebpack(
|
|
415
|
-
_config: Configuration,
|
|
416
|
-
isServer: boolean,
|
|
417
|
-
{getJSLoader}: ConfigureWebpackUtils,
|
|
418
|
-
content,
|
|
419
|
-
) {
|
|
406
|
+
configureWebpack(_config, isServer, {getJSLoader}, content) {
|
|
420
407
|
const {
|
|
421
408
|
rehypePlugins,
|
|
422
409
|
remarkPlugins,
|
|
@@ -451,7 +438,7 @@ export default async function pluginContentBlog(
|
|
|
451
438
|
module: {
|
|
452
439
|
rules: [
|
|
453
440
|
{
|
|
454
|
-
test:
|
|
441
|
+
test: /\.mdx?$/i,
|
|
455
442
|
include: contentDirs
|
|
456
443
|
// Trailing slash is important, see https://github.com/facebook/docusaurus/pull/3970
|
|
457
444
|
.map(addTrailingPathSeparator),
|
|
@@ -485,7 +472,8 @@ export default async function pluginContentBlog(
|
|
|
485
472
|
// Blog posts title are rendered separately
|
|
486
473
|
removeContentTitle: true,
|
|
487
474
|
|
|
488
|
-
// Assets allow to convert some relative images paths to
|
|
475
|
+
// Assets allow to convert some relative images paths to
|
|
476
|
+
// require() calls
|
|
489
477
|
createAssets: ({
|
|
490
478
|
frontMatter,
|
|
491
479
|
metadata,
|
|
@@ -120,6 +120,7 @@ declare module '@docusaurus/plugin-content-blog' {
|
|
|
120
120
|
blogPostComponent: string;
|
|
121
121
|
blogTagsListComponent: string;
|
|
122
122
|
blogTagsPostsComponent: string;
|
|
123
|
+
blogArchiveComponent: string;
|
|
123
124
|
blogTitle: string;
|
|
124
125
|
blogDescription: string;
|
|
125
126
|
blogSidebarCount: number | 'ALL';
|
|
@@ -147,21 +148,6 @@ declare module '@docusaurus/plugin-content-blog' {
|
|
|
147
148
|
>;
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
declare module '@theme/BlogSidebar' {
|
|
151
|
-
export type BlogSidebarItem = {title: string; permalink: string};
|
|
152
|
-
export type BlogSidebar = {
|
|
153
|
-
title: string;
|
|
154
|
-
items: BlogSidebarItem[];
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
export interface Props {
|
|
158
|
-
readonly sidebar: BlogSidebar;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const BlogSidebar: (props: Props) => JSX.Element;
|
|
162
|
-
export default BlogSidebar;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
151
|
declare module '@theme/BlogPostPage' {
|
|
166
152
|
import type {BlogSidebar} from '@theme/BlogSidebar';
|
|
167
153
|
import type {TOCItem} from '@docusaurus/types';
|
|
@@ -205,8 +191,7 @@ declare module '@theme/BlogPostPage' {
|
|
|
205
191
|
readonly content: Content;
|
|
206
192
|
}
|
|
207
193
|
|
|
208
|
-
|
|
209
|
-
export default BlogPostPage;
|
|
194
|
+
export default function BlogPostPage(props: Props): JSX.Element;
|
|
210
195
|
}
|
|
211
196
|
|
|
212
197
|
declare module '@theme/BlogListPage' {
|
|
@@ -231,8 +216,7 @@ declare module '@theme/BlogListPage' {
|
|
|
231
216
|
readonly items: readonly {readonly content: Content}[];
|
|
232
217
|
}
|
|
233
218
|
|
|
234
|
-
|
|
235
|
-
export default BlogListPage;
|
|
219
|
+
export default function BlogListPage(props: Props): JSX.Element;
|
|
236
220
|
}
|
|
237
221
|
|
|
238
222
|
declare module '@theme/BlogTagsListPage' {
|
|
@@ -251,23 +235,23 @@ declare module '@theme/BlogTagsListPage' {
|
|
|
251
235
|
readonly tags: Readonly<Record<string, Tag>>;
|
|
252
236
|
}
|
|
253
237
|
|
|
254
|
-
|
|
255
|
-
export default BlogTagsListPage;
|
|
238
|
+
export default function BlogTagsListPage(props: Props): JSX.Element;
|
|
256
239
|
}
|
|
257
240
|
|
|
258
241
|
declare module '@theme/BlogTagsPostsPage' {
|
|
259
242
|
import type {BlogSidebar} from '@theme/BlogSidebar';
|
|
260
243
|
import type {Tag} from '@theme/BlogTagsListPage';
|
|
261
244
|
import type {Content} from '@theme/BlogPostPage';
|
|
245
|
+
import type {Metadata} from '@theme/BlogListPage';
|
|
262
246
|
|
|
263
247
|
export interface Props {
|
|
264
248
|
readonly sidebar: BlogSidebar;
|
|
265
249
|
readonly metadata: Tag;
|
|
250
|
+
readonly listMetadata: Metadata;
|
|
266
251
|
readonly items: readonly {readonly content: Content}[];
|
|
267
252
|
}
|
|
268
253
|
|
|
269
|
-
|
|
270
|
-
export default BlogTagsPostsPage;
|
|
254
|
+
export default function BlogTagsPostsPage(props: Props): JSX.Element;
|
|
271
255
|
}
|
|
272
256
|
|
|
273
257
|
declare module '@theme/BlogArchivePage' {
|
|
@@ -20,7 +20,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
20
20
|
beforeDefaultRehypePlugins: [],
|
|
21
21
|
beforeDefaultRemarkPlugins: [],
|
|
22
22
|
admonitions: {},
|
|
23
|
-
truncateMarker: /<!--\s*
|
|
23
|
+
truncateMarker: /<!--\s*truncate\s*-->/,
|
|
24
24
|
rehypePlugins: [],
|
|
25
25
|
remarkPlugins: [],
|
|
26
26
|
showReadingTime: true,
|
|
@@ -28,6 +28,7 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
28
28
|
blogTagsListComponent: '@theme/BlogTagsListPage',
|
|
29
29
|
blogPostComponent: '@theme/BlogPostPage',
|
|
30
30
|
blogListComponent: '@theme/BlogListPage',
|
|
31
|
+
blogArchiveComponent: '@theme/BlogArchivePage',
|
|
31
32
|
blogDescription: 'Blog',
|
|
32
33
|
blogTitle: 'Blog',
|
|
33
34
|
blogSidebarCount: 5,
|
|
@@ -68,6 +69,9 @@ export const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
68
69
|
blogTagsPostsComponent: Joi.string().default(
|
|
69
70
|
DEFAULT_OPTIONS.blogTagsPostsComponent,
|
|
70
71
|
),
|
|
72
|
+
blogArchiveComponent: Joi.string().default(
|
|
73
|
+
DEFAULT_OPTIONS.blogArchiveComponent,
|
|
74
|
+
),
|
|
71
75
|
blogTitle: Joi.string().allow('').default(DEFAULT_OPTIONS.blogTitle),
|
|
72
76
|
blogDescription: Joi.string()
|
|
73
77
|
.allow('')
|
package/src/types.ts
CHANGED
|
@@ -26,13 +26,18 @@ export interface BlogContent {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export interface BlogTags {
|
|
29
|
-
|
|
29
|
+
// TODO, the key is the tag slug/permalink
|
|
30
|
+
// This is due to legacy frontmatter: tags:
|
|
31
|
+
// [{label: "xyz", permalink: "/1"}, {label: "xyz", permalink: "/2"}]
|
|
32
|
+
// Soon we should forbid declaring permalink through frontmatter
|
|
33
|
+
[tagKey: string]: BlogTag;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export interface BlogTag {
|
|
33
37
|
name: string;
|
|
34
|
-
items: string[];
|
|
38
|
+
items: string[]; // blog post permalinks
|
|
35
39
|
permalink: string;
|
|
40
|
+
pages: BlogPaginated[];
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
export interface BlogPost {
|
|
@@ -55,7 +60,7 @@ export interface BlogPaginatedMetadata {
|
|
|
55
60
|
|
|
56
61
|
export interface BlogPaginated {
|
|
57
62
|
metadata: BlogPaginatedMetadata;
|
|
58
|
-
items: string[];
|
|
63
|
+
items: string[]; // blog post permalinks
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export interface MetaData {
|