@docusaurus/plugin-content-blog 2.0.0-beta.138b4c997 → 2.0.0-beta.14
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 +23 -0
- package/lib/authors.js +147 -0
- package/lib/blogFrontMatter.d.ts +19 -6
- package/lib/blogFrontMatter.js +31 -19
- package/lib/blogUtils.d.ts +10 -4
- package/lib/blogUtils.js +142 -137
- package/lib/feed.d.ts +20 -0
- package/lib/feed.js +105 -0
- package/lib/index.js +104 -106
- package/lib/markdownLoader.d.ts +3 -6
- package/lib/markdownLoader.js +5 -5
- package/lib/pluginOptionSchema.d.ts +3 -26
- package/lib/pluginOptionSchema.js +30 -9
- package/lib/translations.d.ts +10 -0
- package/lib/translations.js +53 -0
- package/lib/types.d.ts +55 -15
- package/package.json +17 -13
- package/src/authors.ts +196 -0
- package/src/blogFrontMatter.ts +71 -33
- package/src/blogUtils.ts +196 -181
- package/{types.d.ts → src/deps.d.ts} +0 -0
- package/src/feed.ts +149 -0
- package/src/index.ts +123 -107
- package/src/markdownLoader.ts +8 -12
- package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
- package/src/pluginOptionSchema.ts +34 -12
- package/src/translations.ts +63 -0
- package/src/types.ts +69 -16
- package/lib/.tsbuildinfo +0 -1
- 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 -76
- 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 -317
- 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/tsconfig.json +0 -9
package/lib/authors.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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 { Author, BlogContentPaths } from './types';
|
|
8
|
+
import { BlogPostFrontMatter } from './blogFrontMatter';
|
|
9
|
+
export declare type AuthorsMap = Record<string, Author>;
|
|
10
|
+
export declare function validateAuthorsMapFile(content: unknown): AuthorsMap;
|
|
11
|
+
export declare function readAuthorsMapFile(filePath: string): Promise<AuthorsMap | undefined>;
|
|
12
|
+
declare type AuthorsMapParams = {
|
|
13
|
+
authorsMapPath: string;
|
|
14
|
+
contentPaths: BlogContentPaths;
|
|
15
|
+
};
|
|
16
|
+
export declare function getAuthorsMapFilePath({ authorsMapPath, contentPaths, }: AuthorsMapParams): Promise<string | undefined>;
|
|
17
|
+
export declare function getAuthorsMap(params: AuthorsMapParams): Promise<AuthorsMap | undefined>;
|
|
18
|
+
declare type AuthorsParam = {
|
|
19
|
+
frontMatter: BlogPostFrontMatter;
|
|
20
|
+
authorsMap: AuthorsMap | undefined;
|
|
21
|
+
};
|
|
22
|
+
export declare function getBlogPostAuthors(params: AuthorsParam): Author[];
|
|
23
|
+
export {};
|
package/lib/authors.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
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.getAuthorsMapFilePath = exports.readAuthorsMapFile = exports.validateAuthorsMapFile = void 0;
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
|
|
12
|
+
const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
|
|
13
|
+
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
14
|
+
const utils_1 = require("@docusaurus/utils");
|
|
15
|
+
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
16
|
+
const blogUtils_1 = require("./blogUtils");
|
|
17
|
+
const js_yaml_1 = (0, tslib_1.__importDefault)(require("js-yaml"));
|
|
18
|
+
const AuthorsMapSchema = utils_validation_1.Joi.object().pattern(utils_validation_1.Joi.string(), utils_validation_1.Joi.object({
|
|
19
|
+
name: utils_validation_1.Joi.string().required(),
|
|
20
|
+
url: utils_validation_1.URISchema,
|
|
21
|
+
imageURL: utils_validation_1.URISchema,
|
|
22
|
+
title: utils_validation_1.Joi.string(),
|
|
23
|
+
})
|
|
24
|
+
.rename('image_url', 'imageURL')
|
|
25
|
+
.unknown()
|
|
26
|
+
.required());
|
|
27
|
+
function validateAuthorsMapFile(content) {
|
|
28
|
+
return utils_validation_1.Joi.attempt(content, AuthorsMapSchema);
|
|
29
|
+
}
|
|
30
|
+
exports.validateAuthorsMapFile = validateAuthorsMapFile;
|
|
31
|
+
async function readAuthorsMapFile(filePath) {
|
|
32
|
+
if (await fs_extra_1.default.pathExists(filePath)) {
|
|
33
|
+
const contentString = await fs_extra_1.default.readFile(filePath, { encoding: 'utf8' });
|
|
34
|
+
try {
|
|
35
|
+
const unsafeContent = js_yaml_1.default.load(contentString);
|
|
36
|
+
return validateAuthorsMapFile(unsafeContent);
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// TODO replace later by error cause: see https://v8.dev/features/error-cause
|
|
40
|
+
logger_1.default.error('The author list file looks invalid!');
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
exports.readAuthorsMapFile = readAuthorsMapFile;
|
|
47
|
+
async function getAuthorsMapFilePath({ authorsMapPath, contentPaths, }) {
|
|
48
|
+
// Useful to load an eventually localize authors map
|
|
49
|
+
const contentPath = await (0, utils_1.findFolderContainingFile)((0, blogUtils_1.getContentPathList)(contentPaths), authorsMapPath);
|
|
50
|
+
if (contentPath) {
|
|
51
|
+
return path_1.default.join(contentPath, authorsMapPath);
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
exports.getAuthorsMapFilePath = getAuthorsMapFilePath;
|
|
56
|
+
async function getAuthorsMap(params) {
|
|
57
|
+
const filePath = await getAuthorsMapFilePath(params);
|
|
58
|
+
if (!filePath) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
return await readAuthorsMapFile(filePath);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
// TODO replace later by error cause, see https://v8.dev/features/error-cause
|
|
66
|
+
logger_1.default.error `Couldn't read blog authors map at path=${filePath}`;
|
|
67
|
+
throw e;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
exports.getAuthorsMap = getAuthorsMap;
|
|
71
|
+
// Legacy v1/early-v2 frontmatter fields
|
|
72
|
+
// We may want to deprecate those in favor of using only frontMatter.authors
|
|
73
|
+
function getFrontMatterAuthorLegacy(frontMatter) {
|
|
74
|
+
var _a, _b, _c;
|
|
75
|
+
const name = frontMatter.author;
|
|
76
|
+
const title = (_a = frontMatter.author_title) !== null && _a !== void 0 ? _a : frontMatter.authorTitle;
|
|
77
|
+
const url = (_b = frontMatter.author_url) !== null && _b !== void 0 ? _b : frontMatter.authorURL;
|
|
78
|
+
const imageURL = (_c = frontMatter.author_image_url) !== null && _c !== void 0 ? _c : frontMatter.authorImageURL;
|
|
79
|
+
// Shouldn't we require at least an author name?
|
|
80
|
+
if (name || title || url || imageURL) {
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
title,
|
|
84
|
+
url,
|
|
85
|
+
imageURL,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function normalizeFrontMatterAuthors(frontMatterAuthors = []) {
|
|
91
|
+
function normalizeAuthor(authorInput) {
|
|
92
|
+
if (typeof authorInput === 'string') {
|
|
93
|
+
// Technically, we could allow users to provide an author's name here
|
|
94
|
+
// IMHO it's better to only support keys here
|
|
95
|
+
// Reason: a typo in a key would fallback to becoming a name and may end-up un-noticed
|
|
96
|
+
return { key: authorInput };
|
|
97
|
+
}
|
|
98
|
+
return authorInput;
|
|
99
|
+
}
|
|
100
|
+
return Array.isArray(frontMatterAuthors)
|
|
101
|
+
? frontMatterAuthors.map(normalizeAuthor)
|
|
102
|
+
: [normalizeAuthor(frontMatterAuthors)];
|
|
103
|
+
}
|
|
104
|
+
function getFrontMatterAuthors(params) {
|
|
105
|
+
const { authorsMap } = params;
|
|
106
|
+
const frontMatterAuthors = normalizeFrontMatterAuthors(params.frontMatter.authors);
|
|
107
|
+
function getAuthorsMapAuthor(key) {
|
|
108
|
+
if (key) {
|
|
109
|
+
if (!authorsMap || Object.keys(authorsMap).length === 0) {
|
|
110
|
+
throw new Error(`Can't reference blog post authors by a key (such as '${key}') because no authors map file could be loaded.
|
|
111
|
+
Please double-check your blog plugin config (in particular 'authorsMapPath'), ensure the file exists at the configured path, is not empty, and is valid!`);
|
|
112
|
+
}
|
|
113
|
+
const author = authorsMap[key];
|
|
114
|
+
if (!author) {
|
|
115
|
+
throw Error(`Blog author with key "${key}" not found in the authors map file.
|
|
116
|
+
Valid author keys are:
|
|
117
|
+
${Object.keys(authorsMap)
|
|
118
|
+
.map((validKey) => `- ${validKey}`)
|
|
119
|
+
.join('\n')}`);
|
|
120
|
+
}
|
|
121
|
+
return author;
|
|
122
|
+
}
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
function toAuthor(frontMatterAuthor) {
|
|
126
|
+
return {
|
|
127
|
+
// Author def from authorsMap can be locally overridden by frontmatter
|
|
128
|
+
...getAuthorsMapAuthor(frontMatterAuthor.key),
|
|
129
|
+
...frontMatterAuthor,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return frontMatterAuthors.map(toAuthor);
|
|
133
|
+
}
|
|
134
|
+
function getBlogPostAuthors(params) {
|
|
135
|
+
const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
|
|
136
|
+
const authors = getFrontMatterAuthors(params);
|
|
137
|
+
if (authorLegacy) {
|
|
138
|
+
// Technically, we could allow mixing legacy/authors frontmatter, but do we really want to?
|
|
139
|
+
if (authors.length > 0) {
|
|
140
|
+
throw new Error(`To declare blog post authors, use the 'authors' FrontMatter in priority.
|
|
141
|
+
Don't mix 'authors' with other existing 'author_*' FrontMatter. Choose one or the other, not both at the same time.`);
|
|
142
|
+
}
|
|
143
|
+
return [authorLegacy];
|
|
144
|
+
}
|
|
145
|
+
return authors;
|
|
146
|
+
}
|
|
147
|
+
exports.getBlogPostAuthors = getBlogPostAuthors;
|
package/lib/blogFrontMatter.d.ts
CHANGED
|
@@ -4,25 +4,38 @@
|
|
|
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 {
|
|
7
|
+
import type { FrontMatterTag } from '@docusaurus/utils';
|
|
8
|
+
export declare type BlogPostFrontMatterAuthor = Record<string, unknown> & {
|
|
9
|
+
key?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
imageURL?: string;
|
|
12
|
+
url?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
};
|
|
15
|
+
export declare type BlogPostFrontMatterAuthors = string | BlogPostFrontMatterAuthor | (string | BlogPostFrontMatterAuthor)[];
|
|
8
16
|
export declare type BlogPostFrontMatter = {
|
|
9
17
|
id?: string;
|
|
10
18
|
title?: string;
|
|
11
19
|
description?: string;
|
|
12
|
-
tags?:
|
|
20
|
+
tags?: FrontMatterTag[];
|
|
13
21
|
slug?: string;
|
|
14
22
|
draft?: boolean;
|
|
15
|
-
date?: Date;
|
|
23
|
+
date?: Date | string;
|
|
24
|
+
authors?: BlogPostFrontMatterAuthors;
|
|
16
25
|
author?: string;
|
|
17
26
|
author_title?: string;
|
|
18
27
|
author_url?: string;
|
|
19
28
|
author_image_url?: string;
|
|
20
|
-
image?: string;
|
|
21
|
-
keywords?: string[];
|
|
22
|
-
hide_table_of_contents?: boolean;
|
|
23
29
|
/** @deprecated */
|
|
24
30
|
authorTitle?: string;
|
|
31
|
+
/** @deprecated */
|
|
25
32
|
authorURL?: string;
|
|
33
|
+
/** @deprecated */
|
|
26
34
|
authorImageURL?: string;
|
|
35
|
+
image?: string;
|
|
36
|
+
keywords?: string[];
|
|
37
|
+
hide_table_of_contents?: boolean;
|
|
38
|
+
toc_min_heading_level?: number;
|
|
39
|
+
toc_max_heading_level?: number;
|
|
27
40
|
};
|
|
28
41
|
export declare function validateBlogPostFrontMatter(frontMatter: Record<string, unknown>): BlogPostFrontMatter;
|
package/lib/blogFrontMatter.js
CHANGED
|
@@ -7,44 +7,56 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.validateBlogPostFrontMatter = void 0;
|
|
10
|
-
/* eslint-disable camelcase */
|
|
11
10
|
const utils_validation_1 = require("@docusaurus/utils-validation");
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
const BlogPostFrontMatterAuthorSchema = utils_validation_1.JoiFrontMatter.object({
|
|
12
|
+
key: utils_validation_1.JoiFrontMatter.string(),
|
|
13
|
+
name: utils_validation_1.JoiFrontMatter.string(),
|
|
14
|
+
title: utils_validation_1.JoiFrontMatter.string(),
|
|
15
|
+
url: utils_validation_1.URISchema,
|
|
16
|
+
imageURL: utils_validation_1.JoiFrontMatter.string(),
|
|
17
|
+
})
|
|
18
|
+
.or('key', 'name')
|
|
19
|
+
.rename('image_url', 'imageURL', { alias: true });
|
|
20
|
+
const FrontMatterAuthorErrorMessage = '{{#label}} does not look like a valid blog post author. Please use an author key or an author object (with a key and/or name).';
|
|
20
21
|
const BlogFrontMatterSchema = utils_validation_1.JoiFrontMatter.object({
|
|
21
22
|
id: utils_validation_1.JoiFrontMatter.string(),
|
|
22
23
|
title: utils_validation_1.JoiFrontMatter.string().allow(''),
|
|
23
24
|
description: utils_validation_1.JoiFrontMatter.string().allow(''),
|
|
24
|
-
tags: utils_validation_1.
|
|
25
|
+
tags: utils_validation_1.FrontMatterTagsSchema,
|
|
25
26
|
draft: utils_validation_1.JoiFrontMatter.boolean(),
|
|
26
27
|
date: utils_validation_1.JoiFrontMatter.date().raw(),
|
|
28
|
+
// New multi-authors frontmatter:
|
|
29
|
+
authors: utils_validation_1.JoiFrontMatter.alternatives()
|
|
30
|
+
.try(utils_validation_1.JoiFrontMatter.string(), BlogPostFrontMatterAuthorSchema, utils_validation_1.JoiFrontMatter.array()
|
|
31
|
+
.items(utils_validation_1.JoiFrontMatter.string(), BlogPostFrontMatterAuthorSchema)
|
|
32
|
+
.messages({
|
|
33
|
+
'array.sparse': FrontMatterAuthorErrorMessage,
|
|
34
|
+
'array.includes': FrontMatterAuthorErrorMessage,
|
|
35
|
+
}))
|
|
36
|
+
.messages({
|
|
37
|
+
'alternatives.match': FrontMatterAuthorErrorMessage,
|
|
38
|
+
}),
|
|
39
|
+
// Legacy author frontmatter
|
|
27
40
|
author: utils_validation_1.JoiFrontMatter.string(),
|
|
28
41
|
author_title: utils_validation_1.JoiFrontMatter.string(),
|
|
29
42
|
author_url: utils_validation_1.URISchema,
|
|
30
43
|
author_image_url: utils_validation_1.URISchema,
|
|
31
|
-
|
|
32
|
-
image: utils_validation_1.URISchema,
|
|
33
|
-
keywords: utils_validation_1.JoiFrontMatter.array().items(utils_validation_1.JoiFrontMatter.string().required()),
|
|
34
|
-
hide_table_of_contents: utils_validation_1.JoiFrontMatter.boolean(),
|
|
35
|
-
// TODO re-enable warnings later, our v1 blog posts use those older frontmatter fields
|
|
44
|
+
// TODO enable deprecation warnings later
|
|
36
45
|
authorURL: utils_validation_1.URISchema,
|
|
37
46
|
// .warning('deprecate.error', { alternative: '"author_url"'}),
|
|
38
47
|
authorTitle: utils_validation_1.JoiFrontMatter.string(),
|
|
39
48
|
// .warning('deprecate.error', { alternative: '"author_title"'}),
|
|
40
49
|
authorImageURL: utils_validation_1.URISchema,
|
|
41
50
|
// .warning('deprecate.error', { alternative: '"author_image_url"'}),
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
.
|
|
51
|
+
slug: utils_validation_1.JoiFrontMatter.string(),
|
|
52
|
+
image: utils_validation_1.URISchema,
|
|
53
|
+
keywords: utils_validation_1.JoiFrontMatter.array().items(utils_validation_1.JoiFrontMatter.string().required()),
|
|
54
|
+
hide_table_of_contents: utils_validation_1.JoiFrontMatter.boolean(),
|
|
55
|
+
...utils_validation_1.FrontMatterTOCHeadingLevels,
|
|
56
|
+
}).messages({
|
|
45
57
|
'deprecate.error': '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
|
46
58
|
});
|
|
47
59
|
function validateBlogPostFrontMatter(frontMatter) {
|
|
48
|
-
return utils_validation_1.validateFrontMatter(frontMatter, BlogFrontMatterSchema);
|
|
60
|
+
return (0, utils_validation_1.validateFrontMatter)(frontMatter, BlogFrontMatterSchema);
|
|
49
61
|
}
|
|
50
62
|
exports.validateBlogPostFrontMatter = validateBlogPostFrontMatter;
|
package/lib/blogUtils.d.ts
CHANGED
|
@@ -4,16 +4,22 @@
|
|
|
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, BlogContentPaths, BlogMarkdownLoaderOptions } from './types';
|
|
7
|
+
import { PluginOptions, BlogPost, BlogContentPaths, BlogMarkdownLoaderOptions, BlogTags } from './types';
|
|
9
8
|
import { LoadContext } from '@docusaurus/types';
|
|
10
9
|
export declare function truncate(fileString: string, truncateMarker: RegExp): string;
|
|
11
10
|
export declare function getSourceToPermalink(blogPosts: BlogPost[]): Record<string, string>;
|
|
12
|
-
export declare function
|
|
13
|
-
|
|
11
|
+
export declare function getBlogTags(blogPosts: BlogPost[]): BlogTags;
|
|
12
|
+
declare type ParsedBlogFileName = {
|
|
13
|
+
date: Date | undefined;
|
|
14
|
+
text: string;
|
|
15
|
+
slug: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function parseBlogFileName(blogSourceRelative: string): ParsedBlogFileName;
|
|
18
|
+
export declare function generateBlogPosts(contentPaths: BlogContentPaths, context: LoadContext, options: PluginOptions): Promise<BlogPost[]>;
|
|
14
19
|
export declare type LinkifyParams = {
|
|
15
20
|
filePath: string;
|
|
16
21
|
fileString: string;
|
|
17
22
|
} & Pick<BlogMarkdownLoaderOptions, 'sourceToPermalink' | 'siteDir' | 'contentPaths' | 'onBrokenMarkdownLink'>;
|
|
18
23
|
export declare function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }: LinkifyParams): string;
|
|
19
24
|
export declare function getContentPathList(contentPaths: BlogContentPaths): string[];
|
|
25
|
+
export {};
|
package/lib/blogUtils.js
CHANGED
|
@@ -6,34 +6,52 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.getContentPathList = exports.linkify = exports.generateBlogPosts = exports.
|
|
9
|
+
exports.getContentPathList = exports.linkify = exports.generateBlogPosts = exports.parseBlogFileName = exports.getBlogTags = exports.getSourceToPermalink = exports.truncate = void 0;
|
|
10
10
|
const tslib_1 = require("tslib");
|
|
11
|
-
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const path_1 = tslib_1.__importDefault(require("path"));
|
|
15
|
-
const reading_time_1 = tslib_1.__importDefault(require("reading-time"));
|
|
16
|
-
const feed_1 = require("feed");
|
|
11
|
+
const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
|
|
12
|
+
const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
13
|
+
const reading_time_1 = (0, tslib_1.__importDefault)(require("reading-time"));
|
|
17
14
|
const lodash_1 = require("lodash");
|
|
18
15
|
const utils_1 = require("@docusaurus/utils");
|
|
19
16
|
const blogFrontMatter_1 = require("./blogFrontMatter");
|
|
17
|
+
const authors_1 = require("./authors");
|
|
18
|
+
const logger_1 = (0, tslib_1.__importDefault)(require("@docusaurus/logger"));
|
|
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 lodash_1.mapValues(lodash_1.keyBy(blogPosts, (item) => item.metadata.source), (v) => v.metadata.permalink);
|
|
24
|
+
return (0, lodash_1.mapValues)((0, lodash_1.keyBy)(blogPosts, (item) => item.metadata.source), (v) => v.metadata.permalink);
|
|
26
25
|
}
|
|
27
26
|
exports.getSourceToPermalink = getSourceToPermalink;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
function getBlogTags(blogPosts) {
|
|
28
|
+
const groups = (0, utils_1.groupTaggedItems)(blogPosts, (blogPost) => blogPost.metadata.tags);
|
|
29
|
+
return (0, lodash_1.mapValues)(groups, (group) => ({
|
|
30
|
+
name: group.tag.label,
|
|
31
|
+
items: group.items.map((item) => item.id),
|
|
32
|
+
permalink: group.tag.permalink,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
exports.getBlogTags = getBlogTags;
|
|
36
|
+
const DATE_FILENAME_REGEX = /^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
|
|
37
|
+
function parseBlogFileName(blogSourceRelative) {
|
|
38
|
+
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
|
|
39
|
+
if (dateFilenameMatch) {
|
|
40
|
+
const dateString = dateFilenameMatch.groups.date;
|
|
41
|
+
const text = dateFilenameMatch.groups.text;
|
|
42
|
+
// Always treat dates as UTC by adding the `Z`
|
|
43
|
+
const date = new Date(`${dateString}Z`);
|
|
44
|
+
const slugDate = dateString.replace(/-/g, '/');
|
|
45
|
+
const slug = `/${slugDate}/${text}`;
|
|
46
|
+
return { date, text, slug };
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
|
|
50
|
+
const slug = `/${text}`;
|
|
51
|
+
return { date: undefined, text, slug };
|
|
52
|
+
}
|
|
36
53
|
}
|
|
54
|
+
exports.parseBlogFileName = parseBlogFileName;
|
|
37
55
|
function formatBlogPostDate(locale, date) {
|
|
38
56
|
try {
|
|
39
57
|
return new Intl.DateTimeFormat(locale, {
|
|
@@ -47,146 +65,133 @@ function formatBlogPostDate(locale, date) {
|
|
|
47
65
|
throw new Error(`Can't format blog post date "${date}"`);
|
|
48
66
|
}
|
|
49
67
|
}
|
|
50
|
-
async function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
const { siteConfig } = context;
|
|
55
|
-
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
56
|
-
if (!blogPosts.length) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
const { feedOptions, routeBasePath } = options;
|
|
60
|
-
const { url: siteUrl, baseUrl, title, favicon } = siteConfig;
|
|
61
|
-
const blogBaseUrl = utils_1.normalizeUrl([siteUrl, baseUrl, routeBasePath]);
|
|
62
|
-
const updated = (blogPosts[0] && blogPosts[0].metadata.date) ||
|
|
63
|
-
new Date('2015-10-25T16:29:00.000-07:00');
|
|
64
|
-
const feed = new feed_1.Feed({
|
|
65
|
-
id: blogBaseUrl,
|
|
66
|
-
title: feedOptions.title || `${title} Blog`,
|
|
67
|
-
updated,
|
|
68
|
-
language: feedOptions.language,
|
|
69
|
-
link: blogBaseUrl,
|
|
70
|
-
description: feedOptions.description || `${siteConfig.title} Blog`,
|
|
71
|
-
favicon: favicon ? utils_1.normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
|
|
72
|
-
copyright: feedOptions.copyright,
|
|
73
|
-
});
|
|
74
|
-
blogPosts.forEach((post) => {
|
|
75
|
-
const { id, metadata: { title: metadataTitle, permalink, date, description }, } = post;
|
|
76
|
-
feed.addItem({
|
|
77
|
-
title: metadataTitle,
|
|
78
|
-
id,
|
|
79
|
-
link: utils_1.normalizeUrl([siteUrl, permalink]),
|
|
80
|
-
date,
|
|
81
|
-
description,
|
|
82
|
-
});
|
|
68
|
+
async function parseBlogPostMarkdownFile(blogSourceAbsolute) {
|
|
69
|
+
const result = await (0, utils_1.parseMarkdownFile)(blogSourceAbsolute, {
|
|
70
|
+
removeContentTitle: true,
|
|
83
71
|
});
|
|
84
|
-
return
|
|
72
|
+
return {
|
|
73
|
+
...result,
|
|
74
|
+
frontMatter: (0, blogFrontMatter_1.validateBlogPostFrontMatter)(result.frontMatter),
|
|
75
|
+
};
|
|
85
76
|
}
|
|
86
|
-
|
|
87
|
-
async function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
const defaultReadingTime = ({ content, options }) => (0, reading_time_1.default)(content, options).minutes;
|
|
78
|
+
async function processBlogSourceFile(blogSourceRelative, contentPaths, context, options, authorsMap) {
|
|
79
|
+
var _a, _b, _c, _d;
|
|
80
|
+
const { siteConfig: { baseUrl }, siteDir, i18n, } = context;
|
|
81
|
+
const { routeBasePath, tagsBasePath: tagsRouteBasePath, truncateMarker, showReadingTime, editUrl, } = options;
|
|
82
|
+
// Lookup in localized folder in priority
|
|
83
|
+
const blogDirPath = await (0, utils_1.getFolderContainingFile)(getContentPathList(contentPaths), blogSourceRelative);
|
|
84
|
+
const blogSourceAbsolute = path_1.default.join(blogDirPath, blogSourceRelative);
|
|
85
|
+
const { frontMatter, content, contentTitle, excerpt } = await parseBlogPostMarkdownFile(blogSourceAbsolute);
|
|
86
|
+
const aliasedSource = (0, utils_1.aliasedSitePath)(blogSourceAbsolute, siteDir);
|
|
87
|
+
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
|
88
|
+
return undefined;
|
|
91
89
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
async function processBlogSourceFile(blogSourceFile) {
|
|
98
|
-
var _a, _b, _c, _d, _e, _f;
|
|
99
|
-
// Lookup in localized folder in priority
|
|
100
|
-
const blogDirPath = await utils_1.getFolderContainingFile(getContentPathList(contentPaths), blogSourceFile);
|
|
101
|
-
const source = path_1.default.join(blogDirPath, blogSourceFile);
|
|
102
|
-
const { frontMatter: unsafeFrontMatter, content, contentTitle, excerpt, } = await utils_1.parseMarkdownFile(source, { removeContentTitle: true });
|
|
103
|
-
const frontMatter = blogFrontMatter_1.validateBlogPostFrontMatter(unsafeFrontMatter);
|
|
104
|
-
const aliasedSource = utils_1.aliasedSitePath(source, siteDir);
|
|
105
|
-
const blogFileName = path_1.default.basename(blogSourceFile);
|
|
106
|
-
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (frontMatter.id) {
|
|
110
|
-
console.warn(chalk_1.default.yellow(`"id" header option is deprecated in ${blogFileName} file. Please use "slug" option instead.`));
|
|
111
|
-
}
|
|
112
|
-
let date;
|
|
113
|
-
// Extract date and title from filename.
|
|
114
|
-
const dateFilenameMatch = blogFileName.match(DATE_FILENAME_PATTERN);
|
|
115
|
-
let linkName = blogFileName.replace(/\.mdx?$/, '');
|
|
116
|
-
if (dateFilenameMatch) {
|
|
117
|
-
const [, dateString, name] = dateFilenameMatch;
|
|
118
|
-
// Always treat dates as UTC by adding the `Z`
|
|
119
|
-
date = new Date(`${dateString}Z`);
|
|
120
|
-
linkName = name;
|
|
121
|
-
}
|
|
90
|
+
if (frontMatter.id) {
|
|
91
|
+
logger_1.default.warn `name=${'id'} header option is deprecated in path=${blogSourceRelative} file. Please use name=${'slug'} option instead.`;
|
|
92
|
+
}
|
|
93
|
+
const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
|
|
94
|
+
async function getDate() {
|
|
122
95
|
// Prefer user-defined date.
|
|
123
96
|
if (frontMatter.date) {
|
|
124
|
-
|
|
97
|
+
return new Date(frontMatter.date);
|
|
125
98
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const formattedDate = formatBlogPostDate(i18n.currentLocale, date);
|
|
129
|
-
const title = (_b = (_a = frontMatter.title) !== null && _a !== void 0 ? _a : contentTitle) !== null && _b !== void 0 ? _b : linkName;
|
|
130
|
-
const description = (_d = (_c = frontMatter.description) !== null && _c !== void 0 ? _c : excerpt) !== null && _d !== void 0 ? _d : '';
|
|
131
|
-
const slug = frontMatter.slug ||
|
|
132
|
-
(dateFilenameMatch ? toUrl({ date, link: linkName }) : linkName);
|
|
133
|
-
const permalink = utils_1.normalizeUrl([baseUrl, routeBasePath, slug]);
|
|
134
|
-
function getBlogEditUrl() {
|
|
135
|
-
const blogPathRelative = path_1.default.relative(blogDirPath, path_1.default.resolve(source));
|
|
136
|
-
if (typeof editUrl === 'function') {
|
|
137
|
-
return editUrl({
|
|
138
|
-
blogDirPath: utils_1.posixPath(path_1.default.relative(siteDir, blogDirPath)),
|
|
139
|
-
blogPath: utils_1.posixPath(blogPathRelative),
|
|
140
|
-
permalink,
|
|
141
|
-
locale: i18n.currentLocale,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
else if (typeof editUrl === 'string') {
|
|
145
|
-
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
146
|
-
const fileContentPath = isLocalized && options.editLocalizedFiles
|
|
147
|
-
? contentPaths.contentPathLocalized
|
|
148
|
-
: contentPaths.contentPath;
|
|
149
|
-
const contentPathEditUrl = utils_1.normalizeUrl([
|
|
150
|
-
editUrl,
|
|
151
|
-
utils_1.posixPath(path_1.default.relative(siteDir, fileContentPath)),
|
|
152
|
-
]);
|
|
153
|
-
return utils_1.getEditUrl(blogPathRelative, contentPathEditUrl);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
return undefined;
|
|
157
|
-
}
|
|
99
|
+
else if (parsedBlogFileName.date) {
|
|
100
|
+
return parsedBlogFileName.date;
|
|
158
101
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
102
|
+
// Fallback to file create time
|
|
103
|
+
return (await fs_extra_1.default.stat(blogSourceAbsolute)).birthtime;
|
|
104
|
+
}
|
|
105
|
+
const date = await getDate();
|
|
106
|
+
const formattedDate = formatBlogPostDate(i18n.currentLocale, date);
|
|
107
|
+
const title = (_b = (_a = frontMatter.title) !== null && _a !== void 0 ? _a : contentTitle) !== null && _b !== void 0 ? _b : parsedBlogFileName.text;
|
|
108
|
+
const description = (_d = (_c = frontMatter.description) !== null && _c !== void 0 ? _c : excerpt) !== null && _d !== void 0 ? _d : '';
|
|
109
|
+
const slug = frontMatter.slug || parsedBlogFileName.slug;
|
|
110
|
+
const permalink = (0, utils_1.normalizeUrl)([baseUrl, routeBasePath, slug]);
|
|
111
|
+
function getBlogEditUrl() {
|
|
112
|
+
const blogPathRelative = path_1.default.relative(blogDirPath, path_1.default.resolve(blogSourceAbsolute));
|
|
113
|
+
if (typeof editUrl === 'function') {
|
|
114
|
+
return editUrl({
|
|
115
|
+
blogDirPath: (0, utils_1.posixPath)(path_1.default.relative(siteDir, blogDirPath)),
|
|
116
|
+
blogPath: (0, utils_1.posixPath)(blogPathRelative),
|
|
162
117
|
permalink,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
118
|
+
locale: i18n.currentLocale,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
else if (typeof editUrl === 'string') {
|
|
122
|
+
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
123
|
+
const fileContentPath = isLocalized && options.editLocalizedFiles
|
|
124
|
+
? contentPaths.contentPathLocalized
|
|
125
|
+
: contentPaths.contentPath;
|
|
126
|
+
const contentPathEditUrl = (0, utils_1.normalizeUrl)([
|
|
127
|
+
editUrl,
|
|
128
|
+
(0, utils_1.posixPath)(path_1.default.relative(siteDir, fileContentPath)),
|
|
129
|
+
]);
|
|
130
|
+
return (0, utils_1.getEditUrl)(blogPathRelative, contentPathEditUrl);
|
|
131
|
+
}
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
const tagsBasePath = (0, utils_1.normalizeUrl)([
|
|
135
|
+
baseUrl,
|
|
136
|
+
routeBasePath,
|
|
137
|
+
tagsRouteBasePath,
|
|
138
|
+
]);
|
|
139
|
+
const authors = (0, authors_1.getBlogPostAuthors)({ authorsMap, frontMatter });
|
|
140
|
+
return {
|
|
141
|
+
id: slug,
|
|
142
|
+
metadata: {
|
|
143
|
+
permalink,
|
|
144
|
+
editUrl: getBlogEditUrl(),
|
|
145
|
+
source: aliasedSource,
|
|
146
|
+
title,
|
|
147
|
+
description,
|
|
148
|
+
date,
|
|
149
|
+
formattedDate,
|
|
150
|
+
tags: (0, utils_1.normalizeFrontMatterTags)(tagsBasePath, frontMatter.tags),
|
|
151
|
+
readingTime: showReadingTime
|
|
152
|
+
? options.readingTime({
|
|
153
|
+
content,
|
|
154
|
+
frontMatter,
|
|
155
|
+
defaultReadingTime,
|
|
156
|
+
})
|
|
157
|
+
: undefined,
|
|
158
|
+
truncated: (truncateMarker === null || truncateMarker === void 0 ? void 0 : truncateMarker.test(content)) || false,
|
|
159
|
+
authors,
|
|
160
|
+
},
|
|
161
|
+
content,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
async function generateBlogPosts(contentPaths, context, options) {
|
|
165
|
+
const { include, exclude } = options;
|
|
166
|
+
if (!fs_extra_1.default.existsSync(contentPaths.contentPath)) {
|
|
167
|
+
return [];
|
|
174
168
|
}
|
|
175
|
-
await
|
|
169
|
+
const blogSourceFiles = await (0, utils_1.Globby)(include, {
|
|
170
|
+
cwd: contentPaths.contentPath,
|
|
171
|
+
ignore: exclude,
|
|
172
|
+
});
|
|
173
|
+
const authorsMap = await (0, authors_1.getAuthorsMap)({
|
|
174
|
+
contentPaths,
|
|
175
|
+
authorsMapPath: options.authorsMapPath,
|
|
176
|
+
});
|
|
177
|
+
const blogPosts = (await Promise.all(blogSourceFiles.map(async (blogSourceFile) => {
|
|
176
178
|
try {
|
|
177
|
-
return await processBlogSourceFile(blogSourceFile);
|
|
179
|
+
return await processBlogSourceFile(blogSourceFile, contentPaths, context, options, authorsMap);
|
|
178
180
|
}
|
|
179
181
|
catch (e) {
|
|
180
|
-
|
|
182
|
+
logger_1.default.error `Processing of blog source file failed for path path=${blogSourceFile}.`;
|
|
181
183
|
throw e;
|
|
182
184
|
}
|
|
183
|
-
}));
|
|
185
|
+
}))).filter(Boolean);
|
|
184
186
|
blogPosts.sort((a, b) => b.metadata.date.getTime() - a.metadata.date.getTime());
|
|
187
|
+
if (options.sortPosts === 'ascending') {
|
|
188
|
+
return blogPosts.reverse();
|
|
189
|
+
}
|
|
185
190
|
return blogPosts;
|
|
186
191
|
}
|
|
187
192
|
exports.generateBlogPosts = generateBlogPosts;
|
|
188
193
|
function linkify({ filePath, contentPaths, fileString, siteDir, sourceToPermalink, onBrokenMarkdownLink, }) {
|
|
189
|
-
const { newContent, brokenMarkdownLinks } = utils_1.replaceMarkdownLinks({
|
|
194
|
+
const { newContent, brokenMarkdownLinks } = (0, utils_1.replaceMarkdownLinks)({
|
|
190
195
|
siteDir,
|
|
191
196
|
fileString,
|
|
192
197
|
filePath,
|