@docusaurus/plugin-content-blog 2.0.0-beta.ff31de0ff → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/authors.d.ts +22 -0
- package/lib/authors.js +122 -0
- package/lib/blogUtils.d.ts +27 -7
- package/lib/blogUtils.js +214 -141
- package/lib/feed.d.ts +15 -0
- package/lib/feed.js +102 -0
- package/lib/frontMatter.d.ts +10 -0
- package/lib/frontMatter.js +62 -0
- package/lib/index.d.ts +4 -4
- package/lib/index.js +179 -205
- package/lib/markdownLoader.d.ts +3 -6
- package/lib/markdownLoader.js +6 -7
- package/lib/options.d.ts +10 -0
- package/lib/{pluginOptionSchema.js → options.js} +44 -14
- package/lib/remark/footnoteIDFixer.d.ts +14 -0
- package/lib/remark/footnoteIDFixer.js +29 -0
- package/lib/translations.d.ts +10 -0
- package/lib/translations.js +53 -0
- package/lib/types.d.ts +4 -109
- package/package.json +23 -18
- package/src/authors.ts +168 -0
- package/src/blogUtils.ts +316 -196
- package/src/feed.ts +171 -0
- package/src/frontMatter.ts +81 -0
- package/src/index.ts +246 -268
- package/src/markdownLoader.ts +11 -16
- package/src/{pluginOptionSchema.ts → options.ts} +57 -16
- package/src/plugin-content-blog.d.ts +587 -0
- package/src/remark/footnoteIDFixer.ts +29 -0
- package/src/translations.ts +69 -0
- package/src/types.ts +2 -128
- package/index.d.ts +0 -138
- package/lib/.tsbuildinfo +0 -4415
- package/lib/blogFrontMatter.d.ts +0 -28
- package/lib/blogFrontMatter.js +0 -50
- package/lib/pluginOptionSchema.d.ts +0 -33
- package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
- package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
- package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
- package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
- package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
- package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
- package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
- package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
- package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
- package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
- package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
- package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -101
- package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
- package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
- package/src/__tests__/blogFrontMatter.test.ts +0 -265
- package/src/__tests__/generateBlogFeed.test.ts +0 -100
- package/src/__tests__/index.test.ts +0 -336
- package/src/__tests__/linkify.test.ts +0 -93
- package/src/__tests__/pluginOptionSchema.test.ts +0 -150
- package/src/blogFrontMatter.ts +0 -87
- package/tsconfig.json +0 -9
- package/types.d.ts +0 -13
package/lib/authors.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
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 { BlogContentPaths } from './types';
|
|
8
|
+
import type { Author, BlogPostFrontMatter } from '@docusaurus/plugin-content-blog';
|
|
9
|
+
export declare type AuthorsMap = {
|
|
10
|
+
[authorKey: string]: Author;
|
|
11
|
+
};
|
|
12
|
+
export declare function validateAuthorsMap(content: unknown): AuthorsMap;
|
|
13
|
+
export declare function getAuthorsMap(params: {
|
|
14
|
+
authorsMapPath: string;
|
|
15
|
+
contentPaths: BlogContentPaths;
|
|
16
|
+
}): Promise<AuthorsMap | undefined>;
|
|
17
|
+
declare type AuthorsParam = {
|
|
18
|
+
frontMatter: BlogPostFrontMatter;
|
|
19
|
+
authorsMap: AuthorsMap | undefined;
|
|
20
|
+
};
|
|
21
|
+
export declare function getBlogPostAuthors(params: AuthorsParam): Author[];
|
|
22
|
+
export {};
|
package/lib/authors.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.getBlogPostAuthors = exports.getAuthorsMap = exports.validateAuthorsMap = void 0;
|
|
10
|
+
const utils_1 = require("@docusaurus/utils");
|
|
11
|
+
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
12
|
+
const AuthorsMapSchema = utils_validation_1.Joi.object()
|
|
13
|
+
.pattern(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
|
|
14
|
+
name: utils_validation_1.Joi.string(),
|
|
15
|
+
url: utils_validation_1.URISchema,
|
|
16
|
+
imageURL: utils_validation_1.URISchema,
|
|
17
|
+
title: utils_validation_1.Joi.string(),
|
|
18
|
+
email: utils_validation_1.Joi.string(),
|
|
19
|
+
})
|
|
20
|
+
.rename('image_url', 'imageURL')
|
|
21
|
+
.or('name', 'imageURL')
|
|
22
|
+
.unknown()
|
|
23
|
+
.required()
|
|
24
|
+
.messages({
|
|
25
|
+
'object.base': '{#label} should be an author object containing properties like name, title, and imageURL.',
|
|
26
|
+
'any.required': '{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.',
|
|
27
|
+
}))
|
|
28
|
+
.messages({
|
|
29
|
+
'object.base': "The authors map file should contain an object where each entry contains an author key and the corresponding author's data.",
|
|
30
|
+
});
|
|
31
|
+
function validateAuthorsMap(content) {
|
|
32
|
+
const { error, value } = AuthorsMapSchema.validate(content);
|
|
33
|
+
if (error) {
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
exports.validateAuthorsMap = validateAuthorsMap;
|
|
39
|
+
async function getAuthorsMap(params) {
|
|
40
|
+
return (0, utils_1.getDataFileData)({
|
|
41
|
+
filePath: params.authorsMapPath,
|
|
42
|
+
contentPaths: params.contentPaths,
|
|
43
|
+
fileType: 'authors map',
|
|
44
|
+
}, validateAuthorsMap);
|
|
45
|
+
}
|
|
46
|
+
exports.getAuthorsMap = getAuthorsMap;
|
|
47
|
+
// Legacy v1/early-v2 front matter fields
|
|
48
|
+
// We may want to deprecate those in favor of using only frontMatter.authors
|
|
49
|
+
function getFrontMatterAuthorLegacy(frontMatter) {
|
|
50
|
+
const name = frontMatter.author;
|
|
51
|
+
const title = frontMatter.author_title ?? frontMatter.authorTitle;
|
|
52
|
+
const url = frontMatter.author_url ?? frontMatter.authorURL;
|
|
53
|
+
const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
|
|
54
|
+
if (name || title || url || imageURL) {
|
|
55
|
+
return {
|
|
56
|
+
name,
|
|
57
|
+
title,
|
|
58
|
+
url,
|
|
59
|
+
imageURL,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
function normalizeFrontMatterAuthors(frontMatterAuthors = []) {
|
|
65
|
+
function normalizeAuthor(authorInput) {
|
|
66
|
+
if (typeof authorInput === 'string') {
|
|
67
|
+
// Technically, we could allow users to provide an author's name here, but
|
|
68
|
+
// we only support keys, otherwise, a typo in a key would fallback to
|
|
69
|
+
// becoming a name and may end up unnoticed
|
|
70
|
+
return { key: authorInput };
|
|
71
|
+
}
|
|
72
|
+
return authorInput;
|
|
73
|
+
}
|
|
74
|
+
return Array.isArray(frontMatterAuthors)
|
|
75
|
+
? frontMatterAuthors.map(normalizeAuthor)
|
|
76
|
+
: [normalizeAuthor(frontMatterAuthors)];
|
|
77
|
+
}
|
|
78
|
+
function getFrontMatterAuthors(params) {
|
|
79
|
+
const { authorsMap } = params;
|
|
80
|
+
const frontMatterAuthors = normalizeFrontMatterAuthors(params.frontMatter.authors);
|
|
81
|
+
function getAuthorsMapAuthor(key) {
|
|
82
|
+
if (key) {
|
|
83
|
+
if (!authorsMap || Object.keys(authorsMap).length === 0) {
|
|
84
|
+
throw new Error(`Can't reference blog post authors by a key (such as '${key}') because no authors map file could be loaded.
|
|
85
|
+
Please double-check your blog plugin config (in particular 'authorsMapPath'), ensure the file exists at the configured path, is not empty, and is valid!`);
|
|
86
|
+
}
|
|
87
|
+
const author = authorsMap[key];
|
|
88
|
+
if (!author) {
|
|
89
|
+
throw Error(`Blog author with key "${key}" not found in the authors map file.
|
|
90
|
+
Valid author keys are:
|
|
91
|
+
${Object.keys(authorsMap)
|
|
92
|
+
.map((validKey) => `- ${validKey}`)
|
|
93
|
+
.join('\n')}`);
|
|
94
|
+
}
|
|
95
|
+
return author;
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
function toAuthor(frontMatterAuthor) {
|
|
100
|
+
return {
|
|
101
|
+
// Author def from authorsMap can be locally overridden by front matter
|
|
102
|
+
...getAuthorsMapAuthor(frontMatterAuthor.key),
|
|
103
|
+
...frontMatterAuthor,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return frontMatterAuthors.map(toAuthor);
|
|
107
|
+
}
|
|
108
|
+
function getBlogPostAuthors(params) {
|
|
109
|
+
const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
|
|
110
|
+
const authors = getFrontMatterAuthors(params);
|
|
111
|
+
if (authorLegacy) {
|
|
112
|
+
// Technically, we could allow mixing legacy/authors front matter, but do we
|
|
113
|
+
// really want to?
|
|
114
|
+
if (authors.length > 0) {
|
|
115
|
+
throw new Error(`To declare blog post authors, use the 'authors' front matter in priority.
|
|
116
|
+
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`);
|
|
117
|
+
}
|
|
118
|
+
return [authorLegacy];
|
|
119
|
+
}
|
|
120
|
+
return authors;
|
|
121
|
+
}
|
|
122
|
+
exports.getBlogPostAuthors = getBlogPostAuthors;
|
package/lib/blogUtils.d.ts
CHANGED
|
@@ -4,16 +4,36 @@
|
|
|
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 {
|
|
8
|
-
import { PluginOptions, BlogPost,
|
|
9
|
-
import {
|
|
7
|
+
import type { LoadContext } from '@docusaurus/types';
|
|
8
|
+
import type { PluginOptions, BlogPost, BlogTags, BlogPaginated } from '@docusaurus/plugin-content-blog';
|
|
9
|
+
import type { BlogContentPaths, BlogMarkdownLoaderOptions } from './types';
|
|
10
10
|
export declare function truncate(fileString: string, truncateMarker: RegExp): string;
|
|
11
|
-
export declare function getSourceToPermalink(blogPosts: BlogPost[]):
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export declare function getSourceToPermalink(blogPosts: BlogPost[]): {
|
|
12
|
+
[aliasedPath: string]: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function paginateBlogPosts({ blogPosts, basePageUrl, blogTitle, blogDescription, postsPerPageOption, }: {
|
|
15
|
+
blogPosts: BlogPost[];
|
|
16
|
+
basePageUrl: string;
|
|
17
|
+
blogTitle: string;
|
|
18
|
+
blogDescription: string;
|
|
19
|
+
postsPerPageOption: number | 'ALL';
|
|
20
|
+
}): BlogPaginated[];
|
|
21
|
+
export declare function getBlogTags({ blogPosts, ...params }: {
|
|
22
|
+
blogPosts: BlogPost[];
|
|
23
|
+
blogTitle: string;
|
|
24
|
+
blogDescription: string;
|
|
25
|
+
postsPerPageOption: number | 'ALL';
|
|
26
|
+
}): BlogTags;
|
|
27
|
+
declare type ParsedBlogFileName = {
|
|
28
|
+
date: Date | undefined;
|
|
29
|
+
text: string;
|
|
30
|
+
slug: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function parseBlogFileName(blogSourceRelative: string): ParsedBlogFileName;
|
|
33
|
+
export declare function generateBlogPosts(contentPaths: BlogContentPaths, context: LoadContext, options: PluginOptions): Promise<BlogPost[]>;
|
|
14
34
|
export declare type LinkifyParams = {
|
|
15
35
|
filePath: string;
|
|
16
36
|
fileString: string;
|
|
17
37
|
} & Pick<BlogMarkdownLoaderOptions, 'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'>;
|
|
18
38
|
export declare function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }: LinkifyParams): string;
|
|
19
|
-
export
|
|
39
|
+
export {};
|
package/lib/blogUtils.js
CHANGED
|
@@ -6,170 +6,248 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
9
|
+
exports.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 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
|
-
const globby_1 = tslib_1.__importDefault(require("globby"));
|
|
13
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
14
12
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
13
|
+
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
14
|
+
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
15
15
|
const reading_time_1 = tslib_1.__importDefault(require("reading-time"));
|
|
16
|
-
const feed_1 = require("feed");
|
|
17
|
-
const lodash_1 = require("lodash");
|
|
18
16
|
const utils_1 = require("@docusaurus/utils");
|
|
19
|
-
const
|
|
17
|
+
const frontMatter_1 = require("./frontMatter");
|
|
18
|
+
const authors_1 = require("./authors");
|
|
20
19
|
function truncate(fileString, truncateMarker) {
|
|
21
20
|
return fileString.split(truncateMarker, 1).shift();
|
|
22
21
|
}
|
|
23
22
|
exports.truncate = truncate;
|
|
24
23
|
function getSourceToPermalink(blogPosts) {
|
|
25
|
-
return
|
|
24
|
+
return Object.fromEntries(blogPosts.map(({ metadata: { source, permalink } }) => [source, permalink]));
|
|
26
25
|
}
|
|
27
26
|
exports.getSourceToPermalink = getSourceToPermalink;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
async function generateBlogFeed(contentPaths, context, options) {
|
|
38
|
-
if (!options.feedOptions) {
|
|
39
|
-
throw new Error('Invalid options - `feedOptions` is not expected to be null.');
|
|
40
|
-
}
|
|
41
|
-
const { siteConfig } = context;
|
|
42
|
-
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
43
|
-
if (blogPosts == null) {
|
|
44
|
-
return null;
|
|
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
|
|
34
|
+
? (0, utils_1.normalizeUrl)([basePageUrl, `page/${page + 1}`])
|
|
35
|
+
: basePageUrl;
|
|
45
36
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const { id, metadata: { title: metadataTitle, permalink, date, description }, } = post;
|
|
63
|
-
feed.addItem({
|
|
64
|
-
title: metadataTitle,
|
|
65
|
-
id,
|
|
66
|
-
link: utils_1.normalizeUrl([siteUrl, permalink]),
|
|
67
|
-
date,
|
|
68
|
-
description,
|
|
37
|
+
for (let page = 0; page < numberOfPages; page += 1) {
|
|
38
|
+
pages.push({
|
|
39
|
+
items: blogPosts
|
|
40
|
+
.slice(page * postsPerPage, (page + 1) * postsPerPage)
|
|
41
|
+
.map((item) => item.id),
|
|
42
|
+
metadata: {
|
|
43
|
+
permalink: permalink(page),
|
|
44
|
+
page: page + 1,
|
|
45
|
+
postsPerPage,
|
|
46
|
+
totalPages: numberOfPages,
|
|
47
|
+
totalCount,
|
|
48
|
+
previousPage: page !== 0 ? permalink(page - 1) : undefined,
|
|
49
|
+
nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined,
|
|
50
|
+
blogDescription,
|
|
51
|
+
blogTitle,
|
|
52
|
+
},
|
|
69
53
|
});
|
|
70
|
-
}
|
|
71
|
-
return
|
|
54
|
+
}
|
|
55
|
+
return pages;
|
|
72
56
|
}
|
|
73
|
-
exports.
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
57
|
+
exports.paginateBlogPosts = paginateBlogPosts;
|
|
58
|
+
function getBlogTags({ blogPosts, ...params }) {
|
|
59
|
+
const groups = (0, utils_1.groupTaggedItems)(blogPosts, (blogPost) => blogPost.metadata.tags);
|
|
60
|
+
return lodash_1.default.mapValues(groups, ({ tag, items: tagBlogPosts }) => ({
|
|
61
|
+
label: tag.label,
|
|
62
|
+
items: tagBlogPosts.map((item) => item.id),
|
|
63
|
+
permalink: tag.permalink,
|
|
64
|
+
pages: paginateBlogPosts({
|
|
65
|
+
blogPosts: tagBlogPosts,
|
|
66
|
+
basePageUrl: tag.permalink,
|
|
67
|
+
...params,
|
|
68
|
+
}),
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
exports.getBlogTags = getBlogTags;
|
|
72
|
+
const DATE_FILENAME_REGEX = /^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(?:\/index)?.mdx?$/;
|
|
73
|
+
function parseBlogFileName(blogSourceRelative) {
|
|
74
|
+
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
|
|
75
|
+
if (dateFilenameMatch) {
|
|
76
|
+
const { folder, text, date: dateString } = dateFilenameMatch.groups;
|
|
77
|
+
// Always treat dates as UTC by adding the `Z`
|
|
78
|
+
const date = new Date(`${dateString}Z`);
|
|
79
|
+
const slugDate = dateString.replace(/-/g, '/');
|
|
80
|
+
const slug = `/${slugDate}/${folder}${text}`;
|
|
81
|
+
return { date, text: text, slug };
|
|
78
82
|
}
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const blogDirPath = await utils_1.getFolderContainingFile(getContentPathList(contentPaths), blogSourceFile);
|
|
88
|
-
const source = path_1.default.join(blogDirPath, blogSourceFile);
|
|
89
|
-
const { frontMatter: unsafeFrontMatter, content, contentTitle, excerpt, } = await utils_1.parseMarkdownFile(source);
|
|
90
|
-
const frontMatter = blogFrontMatter_1.validateBlogPostFrontMatter(unsafeFrontMatter);
|
|
91
|
-
const aliasedSource = utils_1.aliasedSitePath(source, siteDir);
|
|
92
|
-
const blogFileName = path_1.default.basename(blogSourceFile);
|
|
93
|
-
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (frontMatter.id) {
|
|
97
|
-
console.warn(chalk_1.default.yellow(`${blogFileName} - 'id' header option is deprecated. Please use 'slug' option instead.`));
|
|
98
|
-
}
|
|
99
|
-
let date;
|
|
100
|
-
// Extract date and title from filename.
|
|
101
|
-
const dateFilenameMatch = blogFileName.match(DATE_FILENAME_PATTERN);
|
|
102
|
-
let linkName = blogFileName.replace(/\.mdx?$/, '');
|
|
103
|
-
if (dateFilenameMatch) {
|
|
104
|
-
const [, dateString, name] = dateFilenameMatch;
|
|
105
|
-
date = new Date(dateString);
|
|
106
|
-
linkName = name;
|
|
107
|
-
}
|
|
108
|
-
// Prefer user-defined date.
|
|
109
|
-
if (frontMatter.date) {
|
|
110
|
-
date = frontMatter.date;
|
|
111
|
-
}
|
|
112
|
-
// Use file create time for blog.
|
|
113
|
-
date = date !== null && date !== void 0 ? date : (await fs_extra_1.default.stat(source)).birthtime;
|
|
114
|
-
const formattedDate = new Intl.DateTimeFormat(i18n.currentLocale, {
|
|
83
|
+
const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
|
|
84
|
+
const slug = `/${text}`;
|
|
85
|
+
return { date: undefined, text, slug };
|
|
86
|
+
}
|
|
87
|
+
exports.parseBlogFileName = parseBlogFileName;
|
|
88
|
+
function formatBlogPostDate(locale, date, calendar) {
|
|
89
|
+
try {
|
|
90
|
+
return new Intl.DateTimeFormat(locale, {
|
|
115
91
|
day: 'numeric',
|
|
116
92
|
month: 'long',
|
|
117
93
|
year: 'numeric',
|
|
94
|
+
timeZone: 'UTC',
|
|
95
|
+
calendar,
|
|
118
96
|
}).format(date);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
logger_1.default.error `Can't format blog post date "${String(date)}"`;
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async function parseBlogPostMarkdownFile(blogSourceAbsolute) {
|
|
104
|
+
const markdownString = await fs_extra_1.default.readFile(blogSourceAbsolute, 'utf-8');
|
|
105
|
+
try {
|
|
106
|
+
const result = (0, utils_1.parseMarkdownString)(markdownString, {
|
|
107
|
+
removeContentTitle: true,
|
|
108
|
+
});
|
|
109
|
+
return {
|
|
110
|
+
...result,
|
|
111
|
+
frontMatter: (0, frontMatter_1.validateBlogPostFrontMatter)(result.frontMatter),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
logger_1.default.error `Error while parsing blog post file path=${blogSourceAbsolute}.`;
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const defaultReadingTime = ({ content, options }) => (0, reading_time_1.default)(content, options).minutes;
|
|
120
|
+
async function processBlogSourceFile(blogSourceRelative, contentPaths, context, options, authorsMap) {
|
|
121
|
+
const { siteConfig: { baseUrl }, siteDir, i18n, } = context;
|
|
122
|
+
const { routeBasePath, tagsBasePath: tagsRouteBasePath, truncateMarker, showReadingTime, editUrl, } = options;
|
|
123
|
+
// Lookup in localized folder in priority
|
|
124
|
+
const blogDirPath = await (0, utils_1.getFolderContainingFile)((0, utils_1.getContentPathList)(contentPaths), blogSourceRelative);
|
|
125
|
+
const blogSourceAbsolute = path_1.default.join(blogDirPath, blogSourceRelative);
|
|
126
|
+
const { frontMatter, content, contentTitle, excerpt } = await parseBlogPostMarkdownFile(blogSourceAbsolute);
|
|
127
|
+
const aliasedSource = (0, utils_1.aliasedSitePath)(blogSourceAbsolute, siteDir);
|
|
128
|
+
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
if (frontMatter.id) {
|
|
132
|
+
logger_1.default.warn `name=${'id'} header option is deprecated in path=${blogSourceRelative} file. Please use name=${'slug'} option instead.`;
|
|
133
|
+
}
|
|
134
|
+
const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
|
|
135
|
+
async function getDate() {
|
|
136
|
+
// Prefer user-defined date.
|
|
137
|
+
if (frontMatter.date) {
|
|
138
|
+
if (typeof frontMatter.date === 'string') {
|
|
139
|
+
// Always treat dates as UTC by adding the `Z`
|
|
140
|
+
return new Date(`${frontMatter.date}Z`);
|
|
147
141
|
}
|
|
142
|
+
// YAML only converts YYYY-MM-DD to dates and leaves others as strings.
|
|
143
|
+
return frontMatter.date;
|
|
148
144
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
else if (parsedBlogFileName.date) {
|
|
146
|
+
return parsedBlogFileName.date;
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
const result = (0, utils_1.getFileCommitDate)(blogSourceAbsolute, {
|
|
150
|
+
age: 'oldest',
|
|
151
|
+
includeAuthor: false,
|
|
152
|
+
});
|
|
153
|
+
return result.date;
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
logger_1.default.warn(err);
|
|
157
|
+
return (await fs_extra_1.default.stat(blogSourceAbsolute)).birthtime;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const date = await getDate();
|
|
161
|
+
const formattedDate = formatBlogPostDate(i18n.currentLocale, date, i18n.localeConfigs[i18n.currentLocale].calendar);
|
|
162
|
+
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
|
|
163
|
+
const description = frontMatter.description ?? excerpt ?? '';
|
|
164
|
+
const slug = frontMatter.slug ?? parsedBlogFileName.slug;
|
|
165
|
+
const permalink = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath, slug]);
|
|
166
|
+
function getBlogEditUrl() {
|
|
167
|
+
const blogPathRelative = path_1.default.relative(blogDirPath, path_1.default.resolve(blogSourceAbsolute));
|
|
168
|
+
if (typeof editUrl === 'function') {
|
|
169
|
+
return editUrl({
|
|
170
|
+
blogDirPath: (0, utils_1.posixPath)(path_1.default.relative(siteDir, blogDirPath)),
|
|
171
|
+
blogPath: (0, utils_1.posixPath)(blogPathRelative),
|
|
152
172
|
permalink,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
173
|
+
locale: i18n.currentLocale,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else if (typeof editUrl === 'string') {
|
|
177
|
+
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
178
|
+
const fileContentPath = isLocalized && options.editLocalizedFiles
|
|
179
|
+
? contentPaths.contentPathLocalized
|
|
180
|
+
: contentPaths.contentPath;
|
|
181
|
+
const contentPathEditUrl = (0, utils_1.normalizeUrl)([
|
|
182
|
+
editUrl,
|
|
183
|
+
(0, utils_1.posixPath)(path_1.default.relative(siteDir, fileContentPath)),
|
|
184
|
+
]);
|
|
185
|
+
return (0, utils_1.getEditUrl)(blogPathRelative, contentPathEditUrl);
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
const tagsBasePath = (0, utils_1.normalizeUrl)([
|
|
190
|
+
baseUrl,
|
|
191
|
+
routeBasePath,
|
|
192
|
+
tagsRouteBasePath,
|
|
193
|
+
]);
|
|
194
|
+
const authors = (0, authors_1.getBlogPostAuthors)({ authorsMap, frontMatter });
|
|
195
|
+
return {
|
|
196
|
+
id: slug,
|
|
197
|
+
metadata: {
|
|
198
|
+
permalink,
|
|
199
|
+
editUrl: getBlogEditUrl(),
|
|
200
|
+
source: aliasedSource,
|
|
201
|
+
title,
|
|
202
|
+
description,
|
|
203
|
+
date,
|
|
204
|
+
formattedDate,
|
|
205
|
+
tags: (0, utils_1.normalizeFrontMatterTags)(tagsBasePath, frontMatter.tags),
|
|
206
|
+
readingTime: showReadingTime
|
|
207
|
+
? options.readingTime({
|
|
208
|
+
content,
|
|
209
|
+
frontMatter,
|
|
210
|
+
defaultReadingTime,
|
|
211
|
+
})
|
|
212
|
+
: undefined,
|
|
213
|
+
hasTruncateMarker: truncateMarker.test(content),
|
|
214
|
+
authors,
|
|
215
|
+
frontMatter,
|
|
216
|
+
},
|
|
217
|
+
content,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function generateBlogPosts(contentPaths, context, options) {
|
|
221
|
+
const { include, exclude } = options;
|
|
222
|
+
if (!(await fs_extra_1.default.pathExists(contentPaths.contentPath))) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
const blogSourceFiles = await (0, utils_1.Globby)(include, {
|
|
226
|
+
cwd: contentPaths.contentPath,
|
|
227
|
+
ignore: exclude,
|
|
228
|
+
});
|
|
229
|
+
const authorsMap = await (0, authors_1.getAuthorsMap)({
|
|
230
|
+
contentPaths,
|
|
231
|
+
authorsMapPath: options.authorsMapPath,
|
|
232
|
+
});
|
|
233
|
+
const blogPosts = (await Promise.all(blogSourceFiles.map(async (blogSourceFile) => {
|
|
234
|
+
try {
|
|
235
|
+
return await processBlogSourceFile(blogSourceFile, contentPaths, context, options, authorsMap);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
logger_1.default.error `Processing of blog source file path=${blogSourceFile} failed.`;
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
}))).filter(Boolean);
|
|
167
242
|
blogPosts.sort((a, b) => b.metadata.date.getTime() - a.metadata.date.getTime());
|
|
243
|
+
if (options.sortPosts === 'ascending') {
|
|
244
|
+
return blogPosts.reverse();
|
|
245
|
+
}
|
|
168
246
|
return blogPosts;
|
|
169
247
|
}
|
|
170
248
|
exports.generateBlogPosts = generateBlogPosts;
|
|
171
249
|
function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }) {
|
|
172
|
-
const { newContent, brokenMarkdownLinks } = utils_1.replaceMarkdownLinks({
|
|
250
|
+
const { newContent, brokenMarkdownLinks } = (0, utils_1.replaceMarkdownLinks)({
|
|
173
251
|
siteDir,
|
|
174
252
|
fileString,
|
|
175
253
|
filePath,
|
|
@@ -180,8 +258,3 @@ function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalin
|
|
|
180
258
|
return newContent;
|
|
181
259
|
}
|
|
182
260
|
exports.linkify = linkify;
|
|
183
|
-
// Order matters: we look in priority in localized folder
|
|
184
|
-
function getContentPathList(contentPaths) {
|
|
185
|
-
return [contentPaths.contentPathLocalized, contentPaths.contentPath];
|
|
186
|
-
}
|
|
187
|
-
exports.getContentPathList = getContentPathList;
|
package/lib/feed.d.ts
ADDED
|
@@ -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 type { DocusaurusConfig } from '@docusaurus/types';
|
|
8
|
+
import type { PluginOptions, BlogPost } from '@docusaurus/plugin-content-blog';
|
|
9
|
+
export declare function createBlogFeedFiles({ blogPosts, options, siteConfig, outDir, locale, }: {
|
|
10
|
+
blogPosts: BlogPost[];
|
|
11
|
+
options: PluginOptions;
|
|
12
|
+
siteConfig: DocusaurusConfig;
|
|
13
|
+
outDir: string;
|
|
14
|
+
locale: string;
|
|
15
|
+
}): Promise<void>;
|