@docusaurus/plugin-content-blog 3.1.1 → 3.2.0
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/blogUtils.d.ts +7 -1
- package/lib/blogUtils.js +15 -21
- package/lib/client/index.d.ts +8 -0
- package/lib/client/index.js +15 -0
- package/lib/feed.js +11 -5
- package/lib/frontMatter.d.ts +0 -6
- package/lib/frontMatter.js +3 -2
- package/lib/index.js +25 -5
- package/lib/options.js +10 -0
- package/package.json +26 -11
- package/src/blogUtils.ts +31 -27
- package/src/client/index.ts +20 -0
- package/src/feed.ts +22 -7
- package/src/frontMatter.ts +5 -4
- package/src/index.ts +42 -5
- package/src/options.ts +12 -0
- package/src/plugin-content-blog.d.ts +66 -8
package/lib/blogUtils.d.ts
CHANGED
|
@@ -11,12 +11,13 @@ export declare function truncate(fileString: string, truncateMarker: RegExp): st
|
|
|
11
11
|
export declare function getSourceToPermalink(blogPosts: BlogPost[]): {
|
|
12
12
|
[aliasedPath: string]: string;
|
|
13
13
|
};
|
|
14
|
-
export declare function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }: {
|
|
14
|
+
export declare function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, pageBasePath, }: {
|
|
15
15
|
blogPosts: BlogPost[];
|
|
16
16
|
basePageUrl: string;
|
|
17
17
|
blogTitle: string;
|
|
18
18
|
blogDescription: string;
|
|
19
19
|
postsPerPageOption: number | 'ALL';
|
|
20
|
+
pageBasePath: string;
|
|
20
21
|
}): BlogPaginated[];
|
|
21
22
|
export declare function shouldBeListed(blogPost: BlogPost): boolean;
|
|
22
23
|
export declare function getBlogTags({ blogPosts, ...params }: {
|
|
@@ -24,6 +25,7 @@ export declare function getBlogTags({ blogPosts, ...params }: {
|
|
|
24
25
|
blogTitle: string;
|
|
25
26
|
blogDescription: string;
|
|
26
27
|
postsPerPageOption: number | 'ALL';
|
|
28
|
+
pageBasePath: string;
|
|
27
29
|
}): BlogTags;
|
|
28
30
|
type ParsedBlogFileName = {
|
|
29
31
|
date: Date | undefined;
|
|
@@ -37,4 +39,8 @@ export type LinkifyParams = {
|
|
|
37
39
|
fileString: string;
|
|
38
40
|
} & Pick<BlogMarkdownLoaderOptions, 'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'>;
|
|
39
41
|
export declare function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }: LinkifyParams): string;
|
|
42
|
+
export declare function applyProcessBlogPosts({ blogPosts, processBlogPosts, }: {
|
|
43
|
+
blogPosts: BlogPost[];
|
|
44
|
+
processBlogPosts: PluginOptions['processBlogPosts'];
|
|
45
|
+
}): Promise<BlogPost[]>;
|
|
40
46
|
export {};
|
package/lib/blogUtils.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
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.shouldBeListed = exports.paginateBlogPosts = exports.getSourceToPermalink = exports.truncate = void 0;
|
|
9
|
+
exports.applyProcessBlogPosts = exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.shouldBeListed = exports.paginateBlogPosts = exports.getSourceToPermalink = exports.truncate = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
11
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
12
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
@@ -24,14 +24,14 @@ function getSourceToPermalink(blogPosts) {
|
|
|
24
24
|
return Object.fromEntries(blogPosts.map(({ metadata: { source, permalink } }) => [source, permalink]));
|
|
25
25
|
}
|
|
26
26
|
exports.getSourceToPermalink = getSourceToPermalink;
|
|
27
|
-
function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }) {
|
|
27
|
+
function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, pageBasePath, }) {
|
|
28
28
|
const totalCount = blogPosts.length;
|
|
29
29
|
const postsPerPage = postsPerPageOption === 'ALL' ? totalCount : postsPerPageOption;
|
|
30
30
|
const numberOfPages = Math.ceil(totalCount / postsPerPage);
|
|
31
31
|
const pages = [];
|
|
32
32
|
function permalink(page) {
|
|
33
33
|
return page > 0
|
|
34
|
-
? (0, utils_1.normalizeUrl)([basePageUrl,
|
|
34
|
+
? (0, utils_1.normalizeUrl)([basePageUrl, pageBasePath, `${page + 1}`])
|
|
35
35
|
: basePageUrl;
|
|
36
36
|
}
|
|
37
37
|
for (let page = 0; page < numberOfPages; page += 1) {
|
|
@@ -96,21 +96,6 @@ function parseBlogFileName(blogSourceRelative) {
|
|
|
96
96
|
return { date: undefined, text, slug };
|
|
97
97
|
}
|
|
98
98
|
exports.parseBlogFileName = parseBlogFileName;
|
|
99
|
-
function formatBlogPostDate(locale, date, calendar) {
|
|
100
|
-
try {
|
|
101
|
-
return new Intl.DateTimeFormat(locale, {
|
|
102
|
-
day: 'numeric',
|
|
103
|
-
month: 'long',
|
|
104
|
-
year: 'numeric',
|
|
105
|
-
timeZone: 'UTC',
|
|
106
|
-
calendar,
|
|
107
|
-
}).format(date);
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
logger_1.default.error `Can't format blog post date "${String(date)}"`;
|
|
111
|
-
throw err;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
99
|
async function parseBlogPostMarkdownFile({ filePath, parseFrontMatter, }) {
|
|
115
100
|
const fileContent = await fs_extra_1.default.readFile(filePath, 'utf-8');
|
|
116
101
|
try {
|
|
@@ -142,6 +127,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
142
127
|
parseFrontMatter,
|
|
143
128
|
});
|
|
144
129
|
const aliasedSource = (0, utils_1.aliasedSitePath)(blogSourceAbsolute, siteDir);
|
|
130
|
+
const lastUpdate = await (0, utils_1.readLastUpdateData)(blogSourceAbsolute, options, frontMatter.last_update);
|
|
145
131
|
const draft = (0, utils_1.isDraft)({ frontMatter });
|
|
146
132
|
const unlisted = (0, utils_1.isUnlisted)({ frontMatter });
|
|
147
133
|
if (draft) {
|
|
@@ -165,7 +151,7 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
165
151
|
return parsedBlogFileName.date;
|
|
166
152
|
}
|
|
167
153
|
try {
|
|
168
|
-
const result = (0, utils_1.getFileCommitDate)(blogSourceAbsolute, {
|
|
154
|
+
const result = await (0, utils_1.getFileCommitDate)(blogSourceAbsolute, {
|
|
169
155
|
age: 'oldest',
|
|
170
156
|
includeAuthor: false,
|
|
171
157
|
});
|
|
@@ -177,7 +163,6 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
177
163
|
}
|
|
178
164
|
}
|
|
179
165
|
const date = await getDate();
|
|
180
|
-
const formattedDate = formatBlogPostDate(i18n.currentLocale, date, i18n.localeConfigs[i18n.currentLocale].calendar);
|
|
181
166
|
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
|
|
182
167
|
const description = frontMatter.description ?? excerpt ?? '';
|
|
183
168
|
const slug = frontMatter.slug ?? parsedBlogFileName.slug;
|
|
@@ -220,7 +205,6 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
220
205
|
title,
|
|
221
206
|
description,
|
|
222
207
|
date,
|
|
223
|
-
formattedDate,
|
|
224
208
|
tags: (0, utils_1.normalizeFrontMatterTags)(tagsBasePath, frontMatter.tags),
|
|
225
209
|
readingTime: showReadingTime
|
|
226
210
|
? options.readingTime({
|
|
@@ -233,6 +217,8 @@ async function processBlogSourceFile(blogSourceRelative, contentPaths, context,
|
|
|
233
217
|
authors,
|
|
234
218
|
frontMatter,
|
|
235
219
|
unlisted,
|
|
220
|
+
lastUpdatedAt: lastUpdate.lastUpdatedAt,
|
|
221
|
+
lastUpdatedBy: lastUpdate.lastUpdatedBy,
|
|
236
222
|
},
|
|
237
223
|
content,
|
|
238
224
|
};
|
|
@@ -278,3 +264,11 @@ function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalin
|
|
|
278
264
|
return newContent;
|
|
279
265
|
}
|
|
280
266
|
exports.linkify = linkify;
|
|
267
|
+
async function applyProcessBlogPosts({ blogPosts, processBlogPosts, }) {
|
|
268
|
+
const processedBlogPosts = await processBlogPosts({ blogPosts });
|
|
269
|
+
if (Array.isArray(processedBlogPosts)) {
|
|
270
|
+
return processedBlogPosts;
|
|
271
|
+
}
|
|
272
|
+
return blogPosts;
|
|
273
|
+
}
|
|
274
|
+
exports.applyProcessBlogPosts = applyProcessBlogPosts;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { BlogMetadata } from '@docusaurus/plugin-content-blog';
|
|
8
|
+
export declare function useBlogMetadata(): BlogMetadata;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import useRouteContext from '@docusaurus/useRouteContext';
|
|
8
|
+
export function useBlogMetadata() {
|
|
9
|
+
const routeContext = useRouteContext();
|
|
10
|
+
const blogMetadata = routeContext?.data?.blogMetadata;
|
|
11
|
+
if (!blogMetadata) {
|
|
12
|
+
throw new Error("useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context");
|
|
13
|
+
}
|
|
14
|
+
return blogMetadata;
|
|
15
|
+
}
|
package/lib/feed.js
CHANGED
|
@@ -21,8 +21,11 @@ async function generateBlogFeed({ blogPosts, options, siteConfig, outDir, locale
|
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
23
|
const { feedOptions, routeBasePath } = options;
|
|
24
|
-
const { url: siteUrl, baseUrl, title, favicon } = siteConfig;
|
|
25
|
-
const blogBaseUrl = (0, utils_1.normalizeUrl)([siteUrl, baseUrl, routeBasePath])
|
|
24
|
+
const { url: siteUrl, baseUrl, title, favicon, trailingSlash } = siteConfig;
|
|
25
|
+
const blogBaseUrl = (0, utils_common_1.applyTrailingSlash)((0, utils_1.normalizeUrl)([siteUrl, baseUrl, routeBasePath]), {
|
|
26
|
+
trailingSlash,
|
|
27
|
+
baseUrl,
|
|
28
|
+
});
|
|
26
29
|
const blogPostsForFeed = feedOptions.limit === false || feedOptions.limit === null
|
|
27
30
|
? blogPosts
|
|
28
31
|
: blogPosts.slice(0, feedOptions.limit);
|
|
@@ -48,15 +51,18 @@ async function generateBlogFeed({ blogPosts, options, siteConfig, outDir, locale
|
|
|
48
51
|
return feed;
|
|
49
52
|
}
|
|
50
53
|
async function defaultCreateFeedItems({ blogPosts, siteConfig, outDir, }) {
|
|
51
|
-
const { url: siteUrl } = siteConfig;
|
|
54
|
+
const { url: siteUrl, baseUrl, trailingSlash } = siteConfig;
|
|
52
55
|
function toFeedAuthor(author) {
|
|
53
56
|
return { name: author.name, link: author.url, email: author.email };
|
|
54
57
|
}
|
|
55
58
|
return Promise.all(blogPosts.map(async (post) => {
|
|
56
59
|
const { metadata: { title: metadataTitle, permalink, date, description, authors, tags, }, } = post;
|
|
57
|
-
const content = await (0, utils_1.readOutputHTMLFile)(permalink.replace(
|
|
60
|
+
const content = await (0, utils_1.readOutputHTMLFile)(permalink.replace(baseUrl, ''), outDir, trailingSlash);
|
|
58
61
|
const $ = (0, cheerio_1.load)(content);
|
|
59
|
-
const blogPostAbsoluteUrl = (0, utils_1.normalizeUrl)([siteUrl, permalink])
|
|
62
|
+
const blogPostAbsoluteUrl = (0, utils_common_1.applyTrailingSlash)((0, utils_1.normalizeUrl)([siteUrl, permalink]), {
|
|
63
|
+
trailingSlash,
|
|
64
|
+
baseUrl,
|
|
65
|
+
});
|
|
60
66
|
const toAbsoluteUrl = (src) => String(new URL(src, blogPostAbsoluteUrl));
|
|
61
67
|
// Make links and image urls absolute
|
|
62
68
|
// See https://github.com/facebook/docusaurus/issues/9136
|
package/lib/frontMatter.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*/
|
|
7
1
|
import type { BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
|
|
8
2
|
export declare function validateBlogPostFrontMatter(frontMatter: {
|
|
9
3
|
[key: string]: unknown;
|
package/lib/frontMatter.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateBlogPostFrontMatter = void 0;
|
|
2
4
|
/**
|
|
3
5
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
6
|
*
|
|
5
7
|
* This source code is licensed under the MIT license found in the
|
|
6
8
|
* LICENSE file in the root directory of this source tree.
|
|
7
9
|
*/
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.validateBlogPostFrontMatter = void 0;
|
|
10
10
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
11
11
|
const BlogPostFrontMatterAuthorSchema = utils_validation_1.JoiFrontMatter.object({
|
|
12
12
|
key: utils_validation_1.JoiFrontMatter.string(),
|
|
@@ -52,6 +52,7 @@ const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
|
|
|
52
52
|
keywords: utils_validation_1.JoiFrontMatter.array().items(utils_validation_1.JoiFrontMatter.string().required()),
|
|
53
53
|
hide_table_of_contents: utils_validation_1.JoiFrontMatter.boolean(),
|
|
54
54
|
...utils_validation_1.FrontMatterTOCHeadingLevels,
|
|
55
|
+
last_update: utils_validation_1.FrontMatterLastUpdateSchema,
|
|
55
56
|
})
|
|
56
57
|
.messages({
|
|
57
58
|
'deprecate.error': '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
package/lib/index.js
CHANGED
|
@@ -47,10 +47,14 @@ async function pluginContentBlog(context, options) {
|
|
|
47
47
|
},
|
|
48
48
|
// Fetches blog contents and returns metadata for the necessary routes.
|
|
49
49
|
async loadContent() {
|
|
50
|
-
const { postsPerPage: postsPerPageOption, routeBasePath, tagsBasePath, blogDescription, blogTitle, blogSidebarTitle, } = options;
|
|
50
|
+
const { postsPerPage: postsPerPageOption, routeBasePath, tagsBasePath, blogDescription, blogTitle, blogSidebarTitle, pageBasePath, } = options;
|
|
51
51
|
const baseBlogUrl = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]);
|
|
52
52
|
const blogTagsListPath = (0, utils_1.normalizeUrl)([baseBlogUrl, tagsBasePath]);
|
|
53
|
-
|
|
53
|
+
let blogPosts = await (0, blogUtils_1.generateBlogPosts)(contentPaths, context, options);
|
|
54
|
+
blogPosts = await (0, blogUtils_1.applyProcessBlogPosts)({
|
|
55
|
+
blogPosts,
|
|
56
|
+
processBlogPosts: options.processBlogPosts,
|
|
57
|
+
});
|
|
54
58
|
const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
|
|
55
59
|
if (!blogPosts.length) {
|
|
56
60
|
return {
|
|
@@ -59,10 +63,9 @@ async function pluginContentBlog(context, options) {
|
|
|
59
63
|
blogListPaginated: [],
|
|
60
64
|
blogTags: {},
|
|
61
65
|
blogTagsListPath,
|
|
62
|
-
blogTagsPaginated: [],
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
|
-
//
|
|
68
|
+
// Collocate next and prev metadata.
|
|
66
69
|
listedBlogPosts.forEach((blogPost, index) => {
|
|
67
70
|
const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
|
|
68
71
|
if (prevItem) {
|
|
@@ -87,12 +90,14 @@ async function pluginContentBlog(context, options) {
|
|
|
87
90
|
blogDescription,
|
|
88
91
|
postsPerPageOption,
|
|
89
92
|
basePageUrl: baseBlogUrl,
|
|
93
|
+
pageBasePath,
|
|
90
94
|
});
|
|
91
95
|
const blogTags = (0, blogUtils_1.getBlogTags)({
|
|
92
96
|
blogPosts,
|
|
93
97
|
postsPerPageOption,
|
|
94
98
|
blogDescription,
|
|
95
99
|
blogTitle,
|
|
100
|
+
pageBasePath,
|
|
96
101
|
});
|
|
97
102
|
return {
|
|
98
103
|
blogSidebarTitle,
|
|
@@ -103,7 +108,7 @@ async function pluginContentBlog(context, options) {
|
|
|
103
108
|
};
|
|
104
109
|
},
|
|
105
110
|
async contentLoaded({ content: blogContents, actions }) {
|
|
106
|
-
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, } = options;
|
|
111
|
+
const { blogListComponent, blogPostComponent, blogTagsListComponent, blogTagsPostsComponent, blogArchiveComponent, routeBasePath, archiveBasePath, blogTitle, } = options;
|
|
107
112
|
const { addRoute, createData } = actions;
|
|
108
113
|
const { blogSidebarTitle, blogPosts, blogListPaginated, blogTags, blogTagsListPath, } = blogContents;
|
|
109
114
|
const listedBlogPosts = blogPosts.filter(blogUtils_1.shouldBeListed);
|
|
@@ -154,6 +159,17 @@ async function pluginContentBlog(context, options) {
|
|
|
154
159
|
unlisted: blogPost.metadata.unlisted,
|
|
155
160
|
})),
|
|
156
161
|
}, null, 2));
|
|
162
|
+
const blogMetadata = {
|
|
163
|
+
blogBasePath: (0, utils_1.normalizeUrl)([baseUrl, routeBasePath]),
|
|
164
|
+
blogTitle,
|
|
165
|
+
};
|
|
166
|
+
const blogMetadataPath = await createData(`blogMetadata-${pluginId}.json`, JSON.stringify(blogMetadata, null, 2));
|
|
167
|
+
function createBlogPostRouteMetadata(blogPostMeta) {
|
|
168
|
+
return {
|
|
169
|
+
sourceFilePath: (0, utils_1.aliasedSitePathToRelativePath)(blogPostMeta.source),
|
|
170
|
+
lastUpdatedAt: blogPostMeta.lastUpdatedAt,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
157
173
|
// Create routes for blog entries.
|
|
158
174
|
await Promise.all(blogPosts.map(async (blogPost) => {
|
|
159
175
|
const { id, metadata } = blogPost;
|
|
@@ -169,6 +185,10 @@ async function pluginContentBlog(context, options) {
|
|
|
169
185
|
sidebar: aliasedSource(sidebarProp),
|
|
170
186
|
content: metadata.source,
|
|
171
187
|
},
|
|
188
|
+
metadata: createBlogPostRouteMetadata(metadata),
|
|
189
|
+
context: {
|
|
190
|
+
blogMetadata: aliasedSource(blogMetadataPath),
|
|
191
|
+
},
|
|
172
192
|
});
|
|
173
193
|
blogItemsToMetadata[id] = metadata;
|
|
174
194
|
}));
|
package/lib/options.js
CHANGED
|
@@ -33,11 +33,15 @@ exports.DEFAULT_OPTIONS = {
|
|
|
33
33
|
routeBasePath: 'blog',
|
|
34
34
|
tagsBasePath: 'tags',
|
|
35
35
|
archiveBasePath: 'archive',
|
|
36
|
+
pageBasePath: 'page',
|
|
36
37
|
path: 'blog',
|
|
37
38
|
editLocalizedFiles: false,
|
|
38
39
|
authorsMapPath: 'authors.yml',
|
|
39
40
|
readingTime: ({ content, defaultReadingTime }) => defaultReadingTime({ content }),
|
|
40
41
|
sortPosts: 'descending',
|
|
42
|
+
showLastUpdateTime: false,
|
|
43
|
+
showLastUpdateAuthor: false,
|
|
44
|
+
processBlogPosts: async () => undefined,
|
|
41
45
|
};
|
|
42
46
|
const PluginOptionSchema = utils_validation_1.Joi.object({
|
|
43
47
|
path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
|
|
@@ -46,6 +50,7 @@ const PluginOptionSchema = utils_validation_1.Joi.object({
|
|
|
46
50
|
.allow(null),
|
|
47
51
|
routeBasePath: utils_validation_1.RouteBasePathSchema.default(exports.DEFAULT_OPTIONS.routeBasePath),
|
|
48
52
|
tagsBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.tagsBasePath),
|
|
53
|
+
pageBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.pageBasePath),
|
|
49
54
|
include: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.include),
|
|
50
55
|
exclude: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.exclude),
|
|
51
56
|
postsPerPage: utils_validation_1.Joi.alternatives()
|
|
@@ -101,6 +106,11 @@ const PluginOptionSchema = utils_validation_1.Joi.object({
|
|
|
101
106
|
sortPosts: utils_validation_1.Joi.string()
|
|
102
107
|
.valid('descending', 'ascending')
|
|
103
108
|
.default(exports.DEFAULT_OPTIONS.sortPosts),
|
|
109
|
+
showLastUpdateTime: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showLastUpdateTime),
|
|
110
|
+
showLastUpdateAuthor: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showLastUpdateAuthor),
|
|
111
|
+
processBlogPosts: utils_validation_1.Joi.function()
|
|
112
|
+
.optional()
|
|
113
|
+
.default(() => exports.DEFAULT_OPTIONS.processBlogPosts),
|
|
104
114
|
}).default(exports.DEFAULT_OPTIONS);
|
|
105
115
|
function validateOptions({ validate, options, }) {
|
|
106
116
|
const validatedOptions = validate(PluginOptionSchema, options);
|
package/package.json
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-content-blog",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Blog plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-content-blog.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./lib/*": "./lib/*",
|
|
9
|
+
"./src/*": "./src/*",
|
|
10
|
+
"./client": {
|
|
11
|
+
"type": "./lib/client/index.d.ts",
|
|
12
|
+
"default": "./lib/client/index.js"
|
|
13
|
+
},
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./src/plugin-content-blog.d.ts",
|
|
16
|
+
"default": "./lib/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
7
19
|
"scripts": {
|
|
8
|
-
"build": "tsc",
|
|
9
|
-
"watch": "tsc --watch",
|
|
20
|
+
"build": "tsc --build",
|
|
21
|
+
"watch": "tsc --build --watch",
|
|
10
22
|
"test:generate-build-snap": "yarn docusaurus build src/__tests__/__fixtures__/website --out-dir build-snap && yarn rimraf src/__tests__/__fixtures__/website/.docusaurus && yarn rimraf src/__tests__/__fixtures__/website/build-snap/assets && git add src/__tests__/__fixtures__/website/build-snap"
|
|
11
23
|
},
|
|
12
24
|
"repository": {
|
|
@@ -19,13 +31,13 @@
|
|
|
19
31
|
},
|
|
20
32
|
"license": "MIT",
|
|
21
33
|
"dependencies": {
|
|
22
|
-
"@docusaurus/core": "3.
|
|
23
|
-
"@docusaurus/logger": "3.
|
|
24
|
-
"@docusaurus/mdx-loader": "3.
|
|
25
|
-
"@docusaurus/types": "3.
|
|
26
|
-
"@docusaurus/utils": "3.
|
|
27
|
-
"@docusaurus/utils-common": "3.
|
|
28
|
-
"@docusaurus/utils-validation": "3.
|
|
34
|
+
"@docusaurus/core": "3.2.0",
|
|
35
|
+
"@docusaurus/logger": "3.2.0",
|
|
36
|
+
"@docusaurus/mdx-loader": "3.2.0",
|
|
37
|
+
"@docusaurus/types": "3.2.0",
|
|
38
|
+
"@docusaurus/utils": "3.2.0",
|
|
39
|
+
"@docusaurus/utils-common": "3.2.0",
|
|
40
|
+
"@docusaurus/utils-validation": "3.2.0",
|
|
29
41
|
"cheerio": "^1.0.0-rc.12",
|
|
30
42
|
"feed": "^4.2.2",
|
|
31
43
|
"fs-extra": "^11.1.1",
|
|
@@ -44,5 +56,8 @@
|
|
|
44
56
|
"engines": {
|
|
45
57
|
"node": ">=18.0"
|
|
46
58
|
},
|
|
47
|
-
"
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@total-typescript/shoehorn": "^0.1.2"
|
|
61
|
+
},
|
|
62
|
+
"gitHead": "5af143651b26b39761361acd96e9c5be7ba0cb25"
|
|
48
63
|
}
|
package/src/blogUtils.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
getContentPathList,
|
|
27
27
|
isUnlisted,
|
|
28
28
|
isDraft,
|
|
29
|
+
readLastUpdateData,
|
|
29
30
|
} from '@docusaurus/utils';
|
|
30
31
|
import {validateBlogPostFrontMatter} from './frontMatter';
|
|
31
32
|
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
@@ -57,12 +58,14 @@ export function paginateBlogPosts({
|
|
|
57
58
|
blogTitle,
|
|
58
59
|
blogDescription,
|
|
59
60
|
postsPerPageOption,
|
|
61
|
+
pageBasePath,
|
|
60
62
|
}: {
|
|
61
63
|
blogPosts: BlogPost[];
|
|
62
64
|
basePageUrl: string;
|
|
63
65
|
blogTitle: string;
|
|
64
66
|
blogDescription: string;
|
|
65
67
|
postsPerPageOption: number | 'ALL';
|
|
68
|
+
pageBasePath: string;
|
|
66
69
|
}): BlogPaginated[] {
|
|
67
70
|
const totalCount = blogPosts.length;
|
|
68
71
|
const postsPerPage =
|
|
@@ -73,7 +76,7 @@ export function paginateBlogPosts({
|
|
|
73
76
|
|
|
74
77
|
function permalink(page: number) {
|
|
75
78
|
return page > 0
|
|
76
|
-
? normalizeUrl([basePageUrl,
|
|
79
|
+
? normalizeUrl([basePageUrl, pageBasePath, `${page + 1}`])
|
|
77
80
|
: basePageUrl;
|
|
78
81
|
}
|
|
79
82
|
|
|
@@ -111,6 +114,7 @@ export function getBlogTags({
|
|
|
111
114
|
blogTitle: string;
|
|
112
115
|
blogDescription: string;
|
|
113
116
|
postsPerPageOption: number | 'ALL';
|
|
117
|
+
pageBasePath: string;
|
|
114
118
|
}): BlogTags {
|
|
115
119
|
const groups = groupTaggedItems(
|
|
116
120
|
blogPosts,
|
|
@@ -161,25 +165,6 @@ export function parseBlogFileName(
|
|
|
161
165
|
return {date: undefined, text, slug};
|
|
162
166
|
}
|
|
163
167
|
|
|
164
|
-
function formatBlogPostDate(
|
|
165
|
-
locale: string,
|
|
166
|
-
date: Date,
|
|
167
|
-
calendar: string,
|
|
168
|
-
): string {
|
|
169
|
-
try {
|
|
170
|
-
return new Intl.DateTimeFormat(locale, {
|
|
171
|
-
day: 'numeric',
|
|
172
|
-
month: 'long',
|
|
173
|
-
year: 'numeric',
|
|
174
|
-
timeZone: 'UTC',
|
|
175
|
-
calendar,
|
|
176
|
-
}).format(date);
|
|
177
|
-
} catch (err) {
|
|
178
|
-
logger.error`Can't format blog post date "${String(date)}"`;
|
|
179
|
-
throw err;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
168
|
async function parseBlogPostMarkdownFile({
|
|
184
169
|
filePath,
|
|
185
170
|
parseFrontMatter,
|
|
@@ -247,6 +232,12 @@ async function processBlogSourceFile(
|
|
|
247
232
|
|
|
248
233
|
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
|
249
234
|
|
|
235
|
+
const lastUpdate = await readLastUpdateData(
|
|
236
|
+
blogSourceAbsolute,
|
|
237
|
+
options,
|
|
238
|
+
frontMatter.last_update,
|
|
239
|
+
);
|
|
240
|
+
|
|
250
241
|
const draft = isDraft({frontMatter});
|
|
251
242
|
const unlisted = isUnlisted({frontMatter});
|
|
252
243
|
|
|
@@ -274,10 +265,11 @@ async function processBlogSourceFile(
|
|
|
274
265
|
}
|
|
275
266
|
|
|
276
267
|
try {
|
|
277
|
-
const result = getFileCommitDate(blogSourceAbsolute, {
|
|
268
|
+
const result = await getFileCommitDate(blogSourceAbsolute, {
|
|
278
269
|
age: 'oldest',
|
|
279
270
|
includeAuthor: false,
|
|
280
271
|
});
|
|
272
|
+
|
|
281
273
|
return result.date;
|
|
282
274
|
} catch (err) {
|
|
283
275
|
logger.warn(err);
|
|
@@ -286,11 +278,6 @@ async function processBlogSourceFile(
|
|
|
286
278
|
}
|
|
287
279
|
|
|
288
280
|
const date = await getDate();
|
|
289
|
-
const formattedDate = formatBlogPostDate(
|
|
290
|
-
i18n.currentLocale,
|
|
291
|
-
date,
|
|
292
|
-
i18n.localeConfigs[i18n.currentLocale]!.calendar,
|
|
293
|
-
);
|
|
294
281
|
|
|
295
282
|
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
|
|
296
283
|
const description = frontMatter.description ?? excerpt ?? '';
|
|
@@ -345,7 +332,6 @@ async function processBlogSourceFile(
|
|
|
345
332
|
title,
|
|
346
333
|
description,
|
|
347
334
|
date,
|
|
348
|
-
formattedDate,
|
|
349
335
|
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
|
|
350
336
|
readingTime: showReadingTime
|
|
351
337
|
? options.readingTime({
|
|
@@ -358,6 +344,8 @@ async function processBlogSourceFile(
|
|
|
358
344
|
authors,
|
|
359
345
|
frontMatter,
|
|
360
346
|
unlisted,
|
|
347
|
+
lastUpdatedAt: lastUpdate.lastUpdatedAt,
|
|
348
|
+
lastUpdatedBy: lastUpdate.lastUpdatedBy,
|
|
361
349
|
},
|
|
362
350
|
content,
|
|
363
351
|
};
|
|
@@ -443,3 +431,19 @@ export function linkify({
|
|
|
443
431
|
|
|
444
432
|
return newContent;
|
|
445
433
|
}
|
|
434
|
+
|
|
435
|
+
export async function applyProcessBlogPosts({
|
|
436
|
+
blogPosts,
|
|
437
|
+
processBlogPosts,
|
|
438
|
+
}: {
|
|
439
|
+
blogPosts: BlogPost[];
|
|
440
|
+
processBlogPosts: PluginOptions['processBlogPosts'];
|
|
441
|
+
}): Promise<BlogPost[]> {
|
|
442
|
+
const processedBlogPosts = await processBlogPosts({blogPosts});
|
|
443
|
+
|
|
444
|
+
if (Array.isArray(processedBlogPosts)) {
|
|
445
|
+
return processedBlogPosts;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return blogPosts;
|
|
449
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import useRouteContext from '@docusaurus/useRouteContext';
|
|
9
|
+
import type {BlogMetadata} from '@docusaurus/plugin-content-blog';
|
|
10
|
+
|
|
11
|
+
export function useBlogMetadata(): BlogMetadata {
|
|
12
|
+
const routeContext = useRouteContext();
|
|
13
|
+
const blogMetadata = routeContext?.data?.blogMetadata;
|
|
14
|
+
if (!blogMetadata) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"useBlogMetadata() can't be called on the current route because the blog metadata could not be found in route context",
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return blogMetadata as BlogMetadata;
|
|
20
|
+
}
|
package/src/feed.ts
CHANGED
|
@@ -11,7 +11,10 @@ import logger from '@docusaurus/logger';
|
|
|
11
11
|
import {Feed, type Author as FeedAuthor} from 'feed';
|
|
12
12
|
import * as srcset from 'srcset';
|
|
13
13
|
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
blogPostContainerID,
|
|
16
|
+
applyTrailingSlash,
|
|
17
|
+
} from '@docusaurus/utils-common';
|
|
15
18
|
import {load as cheerioLoad} from 'cheerio';
|
|
16
19
|
import type {DocusaurusConfig} from '@docusaurus/types';
|
|
17
20
|
import type {
|
|
@@ -40,8 +43,14 @@ async function generateBlogFeed({
|
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
const {feedOptions, routeBasePath} = options;
|
|
43
|
-
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
|
|
44
|
-
const blogBaseUrl =
|
|
46
|
+
const {url: siteUrl, baseUrl, title, favicon, trailingSlash} = siteConfig;
|
|
47
|
+
const blogBaseUrl = applyTrailingSlash(
|
|
48
|
+
normalizeUrl([siteUrl, baseUrl, routeBasePath]),
|
|
49
|
+
{
|
|
50
|
+
trailingSlash,
|
|
51
|
+
baseUrl,
|
|
52
|
+
},
|
|
53
|
+
);
|
|
45
54
|
|
|
46
55
|
const blogPostsForFeed =
|
|
47
56
|
feedOptions.limit === false || feedOptions.limit === null
|
|
@@ -85,7 +94,7 @@ async function defaultCreateFeedItems({
|
|
|
85
94
|
siteConfig: DocusaurusConfig;
|
|
86
95
|
outDir: string;
|
|
87
96
|
}): Promise<BlogFeedItem[]> {
|
|
88
|
-
const {url: siteUrl} = siteConfig;
|
|
97
|
+
const {url: siteUrl, baseUrl, trailingSlash} = siteConfig;
|
|
89
98
|
|
|
90
99
|
function toFeedAuthor(author: Author): FeedAuthor {
|
|
91
100
|
return {name: author.name, link: author.url, email: author.email};
|
|
@@ -105,13 +114,19 @@ async function defaultCreateFeedItems({
|
|
|
105
114
|
} = post;
|
|
106
115
|
|
|
107
116
|
const content = await readOutputHTMLFile(
|
|
108
|
-
permalink.replace(
|
|
117
|
+
permalink.replace(baseUrl, ''),
|
|
109
118
|
outDir,
|
|
110
|
-
|
|
119
|
+
trailingSlash,
|
|
111
120
|
);
|
|
112
121
|
const $ = cheerioLoad(content);
|
|
113
122
|
|
|
114
|
-
const blogPostAbsoluteUrl =
|
|
123
|
+
const blogPostAbsoluteUrl = applyTrailingSlash(
|
|
124
|
+
normalizeUrl([siteUrl, permalink]),
|
|
125
|
+
{
|
|
126
|
+
trailingSlash,
|
|
127
|
+
baseUrl,
|
|
128
|
+
},
|
|
129
|
+
);
|
|
115
130
|
|
|
116
131
|
const toAbsoluteUrl = (src: string) =>
|
|
117
132
|
String(new URL(src, blogPostAbsoluteUrl));
|
package/src/frontMatter.ts
CHANGED
|
@@ -4,14 +4,14 @@
|
|
|
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
|
-
|
|
8
7
|
import {
|
|
8
|
+
ContentVisibilitySchema,
|
|
9
|
+
FrontMatterLastUpdateSchema,
|
|
10
|
+
FrontMatterTOCHeadingLevels,
|
|
11
|
+
FrontMatterTagsSchema,
|
|
9
12
|
JoiFrontMatter as Joi, // Custom instance for front matter
|
|
10
13
|
URISchema,
|
|
11
14
|
validateFrontMatter,
|
|
12
|
-
FrontMatterTagsSchema,
|
|
13
|
-
FrontMatterTOCHeadingLevels,
|
|
14
|
-
ContentVisibilitySchema,
|
|
15
15
|
} from '@docusaurus/utils-validation';
|
|
16
16
|
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
|
17
17
|
|
|
@@ -69,6 +69,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|
|
69
69
|
hide_table_of_contents: Joi.boolean(),
|
|
70
70
|
|
|
71
71
|
...FrontMatterTOCHeadingLevels,
|
|
72
|
+
last_update: FrontMatterLastUpdateSchema,
|
|
72
73
|
})
|
|
73
74
|
.messages({
|
|
74
75
|
'deprecate.error':
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
normalizeUrl,
|
|
12
12
|
docuHash,
|
|
13
13
|
aliasedSitePath,
|
|
14
|
+
aliasedSitePathToRelativePath,
|
|
14
15
|
getPluginI18nPath,
|
|
15
16
|
posixPath,
|
|
16
17
|
addTrailingPathSeparator,
|
|
@@ -20,11 +21,12 @@ import {
|
|
|
20
21
|
DEFAULT_PLUGIN_ID,
|
|
21
22
|
} from '@docusaurus/utils';
|
|
22
23
|
import {
|
|
23
|
-
generateBlogPosts,
|
|
24
24
|
getSourceToPermalink,
|
|
25
25
|
getBlogTags,
|
|
26
26
|
paginateBlogPosts,
|
|
27
27
|
shouldBeListed,
|
|
28
|
+
applyProcessBlogPosts,
|
|
29
|
+
generateBlogPosts,
|
|
28
30
|
} from './blogUtils';
|
|
29
31
|
import footnoteIDFixer from './remark/footnoteIDFixer';
|
|
30
32
|
import {translateContent, getTranslationFiles} from './translations';
|
|
@@ -32,7 +34,12 @@ import {createBlogFeedFiles} from './feed';
|
|
|
32
34
|
|
|
33
35
|
import {toTagProp, toTagsProp} from './props';
|
|
34
36
|
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
|
35
|
-
import type {
|
|
37
|
+
import type {
|
|
38
|
+
LoadContext,
|
|
39
|
+
Plugin,
|
|
40
|
+
HtmlTags,
|
|
41
|
+
RouteMetadata,
|
|
42
|
+
} from '@docusaurus/types';
|
|
36
43
|
import type {
|
|
37
44
|
PluginOptions,
|
|
38
45
|
BlogPostFrontMatter,
|
|
@@ -42,6 +49,7 @@ import type {
|
|
|
42
49
|
BlogTags,
|
|
43
50
|
BlogContent,
|
|
44
51
|
BlogPaginated,
|
|
52
|
+
BlogMetadata,
|
|
45
53
|
} from '@docusaurus/plugin-content-blog';
|
|
46
54
|
|
|
47
55
|
export default async function pluginContentBlog(
|
|
@@ -107,11 +115,16 @@ export default async function pluginContentBlog(
|
|
|
107
115
|
blogDescription,
|
|
108
116
|
blogTitle,
|
|
109
117
|
blogSidebarTitle,
|
|
118
|
+
pageBasePath,
|
|
110
119
|
} = options;
|
|
111
120
|
|
|
112
121
|
const baseBlogUrl = normalizeUrl([baseUrl, routeBasePath]);
|
|
113
122
|
const blogTagsListPath = normalizeUrl([baseBlogUrl, tagsBasePath]);
|
|
114
|
-
|
|
123
|
+
let blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
124
|
+
blogPosts = await applyProcessBlogPosts({
|
|
125
|
+
blogPosts,
|
|
126
|
+
processBlogPosts: options.processBlogPosts,
|
|
127
|
+
});
|
|
115
128
|
const listedBlogPosts = blogPosts.filter(shouldBeListed);
|
|
116
129
|
|
|
117
130
|
if (!blogPosts.length) {
|
|
@@ -121,11 +134,10 @@ export default async function pluginContentBlog(
|
|
|
121
134
|
blogListPaginated: [],
|
|
122
135
|
blogTags: {},
|
|
123
136
|
blogTagsListPath,
|
|
124
|
-
blogTagsPaginated: [],
|
|
125
137
|
};
|
|
126
138
|
}
|
|
127
139
|
|
|
128
|
-
//
|
|
140
|
+
// Collocate next and prev metadata.
|
|
129
141
|
listedBlogPosts.forEach((blogPost, index) => {
|
|
130
142
|
const prevItem = index > 0 ? listedBlogPosts[index - 1] : null;
|
|
131
143
|
if (prevItem) {
|
|
@@ -153,6 +165,7 @@ export default async function pluginContentBlog(
|
|
|
153
165
|
blogDescription,
|
|
154
166
|
postsPerPageOption,
|
|
155
167
|
basePageUrl: baseBlogUrl,
|
|
168
|
+
pageBasePath,
|
|
156
169
|
});
|
|
157
170
|
|
|
158
171
|
const blogTags: BlogTags = getBlogTags({
|
|
@@ -160,6 +173,7 @@ export default async function pluginContentBlog(
|
|
|
160
173
|
postsPerPageOption,
|
|
161
174
|
blogDescription,
|
|
162
175
|
blogTitle,
|
|
176
|
+
pageBasePath,
|
|
163
177
|
});
|
|
164
178
|
|
|
165
179
|
return {
|
|
@@ -180,6 +194,7 @@ export default async function pluginContentBlog(
|
|
|
180
194
|
blogArchiveComponent,
|
|
181
195
|
routeBasePath,
|
|
182
196
|
archiveBasePath,
|
|
197
|
+
blogTitle,
|
|
183
198
|
} = options;
|
|
184
199
|
|
|
185
200
|
const {addRoute, createData} = actions;
|
|
@@ -255,6 +270,24 @@ export default async function pluginContentBlog(
|
|
|
255
270
|
),
|
|
256
271
|
);
|
|
257
272
|
|
|
273
|
+
const blogMetadata: BlogMetadata = {
|
|
274
|
+
blogBasePath: normalizeUrl([baseUrl, routeBasePath]),
|
|
275
|
+
blogTitle,
|
|
276
|
+
};
|
|
277
|
+
const blogMetadataPath = await createData(
|
|
278
|
+
`blogMetadata-${pluginId}.json`,
|
|
279
|
+
JSON.stringify(blogMetadata, null, 2),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
function createBlogPostRouteMetadata(
|
|
283
|
+
blogPostMeta: BlogPostMetadata,
|
|
284
|
+
): RouteMetadata {
|
|
285
|
+
return {
|
|
286
|
+
sourceFilePath: aliasedSitePathToRelativePath(blogPostMeta.source),
|
|
287
|
+
lastUpdatedAt: blogPostMeta.lastUpdatedAt,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
258
291
|
// Create routes for blog entries.
|
|
259
292
|
await Promise.all(
|
|
260
293
|
blogPosts.map(async (blogPost) => {
|
|
@@ -274,6 +307,10 @@ export default async function pluginContentBlog(
|
|
|
274
307
|
sidebar: aliasedSource(sidebarProp),
|
|
275
308
|
content: metadata.source,
|
|
276
309
|
},
|
|
310
|
+
metadata: createBlogPostRouteMetadata(metadata),
|
|
311
|
+
context: {
|
|
312
|
+
blogMetadata: aliasedSource(blogMetadataPath),
|
|
313
|
+
},
|
|
277
314
|
});
|
|
278
315
|
|
|
279
316
|
blogItemsToMetadata[id] = metadata;
|
package/src/options.ts
CHANGED
|
@@ -45,11 +45,15 @@ export const DEFAULT_OPTIONS: PluginOptions = {
|
|
|
45
45
|
routeBasePath: 'blog',
|
|
46
46
|
tagsBasePath: 'tags',
|
|
47
47
|
archiveBasePath: 'archive',
|
|
48
|
+
pageBasePath: 'page',
|
|
48
49
|
path: 'blog',
|
|
49
50
|
editLocalizedFiles: false,
|
|
50
51
|
authorsMapPath: 'authors.yml',
|
|
51
52
|
readingTime: ({content, defaultReadingTime}) => defaultReadingTime({content}),
|
|
52
53
|
sortPosts: 'descending',
|
|
54
|
+
showLastUpdateTime: false,
|
|
55
|
+
showLastUpdateAuthor: false,
|
|
56
|
+
processBlogPosts: async () => undefined,
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
@@ -59,6 +63,7 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
59
63
|
.allow(null),
|
|
60
64
|
routeBasePath: RouteBasePathSchema.default(DEFAULT_OPTIONS.routeBasePath),
|
|
61
65
|
tagsBasePath: Joi.string().default(DEFAULT_OPTIONS.tagsBasePath),
|
|
66
|
+
pageBasePath: Joi.string().default(DEFAULT_OPTIONS.pageBasePath),
|
|
62
67
|
include: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.include),
|
|
63
68
|
exclude: Joi.array().items(Joi.string()).default(DEFAULT_OPTIONS.exclude),
|
|
64
69
|
postsPerPage: Joi.alternatives()
|
|
@@ -132,6 +137,13 @@ const PluginOptionSchema = Joi.object<PluginOptions>({
|
|
|
132
137
|
sortPosts: Joi.string()
|
|
133
138
|
.valid('descending', 'ascending')
|
|
134
139
|
.default(DEFAULT_OPTIONS.sortPosts),
|
|
140
|
+
showLastUpdateTime: Joi.bool().default(DEFAULT_OPTIONS.showLastUpdateTime),
|
|
141
|
+
showLastUpdateAuthor: Joi.bool().default(
|
|
142
|
+
DEFAULT_OPTIONS.showLastUpdateAuthor,
|
|
143
|
+
),
|
|
144
|
+
processBlogPosts: Joi.function()
|
|
145
|
+
.optional()
|
|
146
|
+
.default(() => DEFAULT_OPTIONS.processBlogPosts),
|
|
135
147
|
}).default(DEFAULT_OPTIONS);
|
|
136
148
|
|
|
137
149
|
export function validateOptions({
|
|
@@ -5,10 +5,17 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
/// <reference types="@docusaurus/module-type-aliases" />
|
|
9
|
+
|
|
8
10
|
declare module '@docusaurus/plugin-content-blog' {
|
|
9
11
|
import type {LoadedMDXContent} from '@docusaurus/mdx-loader';
|
|
10
12
|
import type {MDXOptions} from '@docusaurus/mdx-loader';
|
|
11
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
FrontMatterTag,
|
|
15
|
+
Tag,
|
|
16
|
+
LastUpdateData,
|
|
17
|
+
FrontMatterLastUpdate,
|
|
18
|
+
} from '@docusaurus/utils';
|
|
12
19
|
import type {DocusaurusConfig, Plugin, LoadContext} from '@docusaurus/types';
|
|
13
20
|
import type {Item as FeedItem} from 'feed';
|
|
14
21
|
import type {Overwrite} from 'utility-types';
|
|
@@ -154,6 +161,8 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
154
161
|
toc_min_heading_level?: number;
|
|
155
162
|
/** Maximum TOC heading level. Must be between 2 and 6. */
|
|
156
163
|
toc_max_heading_level?: number;
|
|
164
|
+
/** Allows overriding the last updated author and/or date. */
|
|
165
|
+
last_update?: FrontMatterLastUpdate;
|
|
157
166
|
};
|
|
158
167
|
|
|
159
168
|
export type BlogPostFrontMatterAuthor = Author & {
|
|
@@ -178,7 +187,7 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
178
187
|
| BlogPostFrontMatterAuthor
|
|
179
188
|
| (string | BlogPostFrontMatterAuthor)[];
|
|
180
189
|
|
|
181
|
-
export type BlogPostMetadata = {
|
|
190
|
+
export type BlogPostMetadata = LastUpdateData & {
|
|
182
191
|
/** Path to the Markdown source, with `@site` alias. */
|
|
183
192
|
readonly source: string;
|
|
184
193
|
/**
|
|
@@ -190,11 +199,6 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
190
199
|
* into a string.
|
|
191
200
|
*/
|
|
192
201
|
readonly date: Date;
|
|
193
|
-
/**
|
|
194
|
-
* Publish date formatted according to the locale, so that the client can
|
|
195
|
-
* render the date regardless of the existence of `Intl.DateTimeFormat`.
|
|
196
|
-
*/
|
|
197
|
-
readonly formattedDate: string;
|
|
198
202
|
/** Full link including base URL. */
|
|
199
203
|
readonly permalink: string;
|
|
200
204
|
/**
|
|
@@ -333,6 +337,11 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
333
337
|
defaultReadingTime: ReadingTimeFunction;
|
|
334
338
|
},
|
|
335
339
|
) => number | undefined;
|
|
340
|
+
|
|
341
|
+
export type ProcessBlogPostsFn = (params: {
|
|
342
|
+
blogPosts: BlogPost[];
|
|
343
|
+
}) => Promise<void | BlogPost[]>;
|
|
344
|
+
|
|
336
345
|
/**
|
|
337
346
|
* Plugin options after normalization.
|
|
338
347
|
*/
|
|
@@ -351,9 +360,14 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
351
360
|
routeBasePath: string;
|
|
352
361
|
/**
|
|
353
362
|
* URL route for the tags section of your blog. Will be appended to
|
|
354
|
-
* `routeBasePath`.
|
|
363
|
+
* `routeBasePath`.
|
|
355
364
|
*/
|
|
356
365
|
tagsBasePath: string;
|
|
366
|
+
/**
|
|
367
|
+
* URL route for the pages section of your blog. Will be appended to
|
|
368
|
+
* `routeBasePath`.
|
|
369
|
+
*/
|
|
370
|
+
pageBasePath: string;
|
|
357
371
|
/**
|
|
358
372
|
* URL route for the archive section of your blog. Will be appended to
|
|
359
373
|
* `routeBasePath`. **DO NOT** include a trailing slash. Use `null` to
|
|
@@ -419,6 +433,14 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
419
433
|
readingTime: ReadingTimeFunctionOption;
|
|
420
434
|
/** Governs the direction of blog post sorting. */
|
|
421
435
|
sortPosts: 'ascending' | 'descending';
|
|
436
|
+
/** Whether to display the last date the doc was updated. */
|
|
437
|
+
showLastUpdateTime: boolean;
|
|
438
|
+
/** Whether to display the author who last updated the doc. */
|
|
439
|
+
showLastUpdateAuthor: boolean;
|
|
440
|
+
/** An optional function which can be used to transform blog posts
|
|
441
|
+
* (filter, modify, delete, etc...).
|
|
442
|
+
*/
|
|
443
|
+
processBlogPosts: ProcessBlogPostsFn;
|
|
422
444
|
};
|
|
423
445
|
|
|
424
446
|
/**
|
|
@@ -461,6 +483,13 @@ yarn workspace v1.22.19image` is a collocated image path, this entry will be the
|
|
|
461
483
|
blogTagsListPath: string;
|
|
462
484
|
};
|
|
463
485
|
|
|
486
|
+
export type BlogMetadata = {
|
|
487
|
+
/** the path to the base of the blog */
|
|
488
|
+
blogBasePath: string;
|
|
489
|
+
/** title of the overall blog */
|
|
490
|
+
blogTitle: string;
|
|
491
|
+
};
|
|
492
|
+
|
|
464
493
|
export type BlogTags = {
|
|
465
494
|
[permalink: string]: BlogTag;
|
|
466
495
|
};
|
|
@@ -532,6 +561,7 @@ declare module '@theme/BlogPostPage' {
|
|
|
532
561
|
BlogPostFrontMatter,
|
|
533
562
|
BlogSidebar,
|
|
534
563
|
PropBlogPostContent,
|
|
564
|
+
BlogMetadata,
|
|
535
565
|
} from '@docusaurus/plugin-content-blog';
|
|
536
566
|
|
|
537
567
|
export type FrontMatter = BlogPostFrontMatter;
|
|
@@ -543,6 +573,8 @@ declare module '@theme/BlogPostPage' {
|
|
|
543
573
|
readonly sidebar: BlogSidebar;
|
|
544
574
|
/** Content of this post as an MDX component, with useful metadata. */
|
|
545
575
|
readonly content: Content;
|
|
576
|
+
/** Metadata about the blog. */
|
|
577
|
+
readonly blogMetadata: BlogMetadata;
|
|
546
578
|
}
|
|
547
579
|
|
|
548
580
|
export default function BlogPostPage(props: Props): JSX.Element;
|
|
@@ -552,6 +584,10 @@ declare module '@theme/BlogPostPage/Metadata' {
|
|
|
552
584
|
export default function BlogPostPageMetadata(): JSX.Element;
|
|
553
585
|
}
|
|
554
586
|
|
|
587
|
+
declare module '@theme/BlogPostPage/StructuredData' {
|
|
588
|
+
export default function BlogPostStructuredData(): JSX.Element;
|
|
589
|
+
}
|
|
590
|
+
|
|
555
591
|
declare module '@theme/BlogListPage' {
|
|
556
592
|
import type {Content} from '@theme/BlogPostPage';
|
|
557
593
|
import type {
|
|
@@ -574,6 +610,28 @@ declare module '@theme/BlogListPage' {
|
|
|
574
610
|
export default function BlogListPage(props: Props): JSX.Element;
|
|
575
611
|
}
|
|
576
612
|
|
|
613
|
+
declare module '@theme/BlogListPage/StructuredData' {
|
|
614
|
+
import type {Content} from '@theme/BlogPostPage';
|
|
615
|
+
import type {
|
|
616
|
+
BlogSidebar,
|
|
617
|
+
BlogPaginatedMetadata,
|
|
618
|
+
} from '@docusaurus/plugin-content-blog';
|
|
619
|
+
|
|
620
|
+
export interface Props {
|
|
621
|
+
/** Blog sidebar. */
|
|
622
|
+
readonly sidebar: BlogSidebar;
|
|
623
|
+
/** Metadata of the current listing page. */
|
|
624
|
+
readonly metadata: BlogPaginatedMetadata;
|
|
625
|
+
/**
|
|
626
|
+
* Array of blog posts included on this page. Every post's metadata is also
|
|
627
|
+
* available.
|
|
628
|
+
*/
|
|
629
|
+
readonly items: readonly {readonly content: Content}[];
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export default function BlogListPageStructuredData(props: Props): JSX.Element;
|
|
633
|
+
}
|
|
634
|
+
|
|
577
635
|
declare module '@theme/BlogTagsListPage' {
|
|
578
636
|
import type {BlogSidebar} from '@docusaurus/plugin-content-blog';
|
|
579
637
|
import type {TagsListItem} from '@docusaurus/utils';
|