@docusaurus/plugin-content-blog 2.0.0-beta.14 → 2.0.0-beta.15
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 +5 -8
- package/lib/authors.js +15 -53
- package/lib/blogFrontMatter.d.ts +1 -34
- package/lib/blogFrontMatter.js +3 -3
- package/lib/blogUtils.d.ts +3 -3
- package/lib/blogUtils.js +25 -19
- package/lib/feed.d.ts +3 -8
- package/lib/feed.js +19 -23
- package/lib/index.d.ts +4 -3
- package/lib/index.js +28 -28
- package/lib/markdownLoader.d.ts +1 -1
- package/lib/markdownLoader.js +1 -2
- package/lib/pluginOptionSchema.d.ts +1 -1
- package/lib/pluginOptionSchema.js +3 -1
- package/lib/translations.d.ts +2 -1
- package/lib/translations.js +3 -3
- package/lib/types.d.ts +2 -79
- package/package.json +11 -12
- package/src/authors.ts +22 -67
- package/src/blogFrontMatter.ts +5 -50
- package/src/blogUtils.ts +35 -23
- package/src/feed.ts +50 -27
- package/src/index.ts +43 -44
- package/src/markdownLoader.ts +2 -3
- package/src/plugin-content-blog.d.ts +147 -4
- package/src/pluginOptionSchema.ts +4 -2
- package/src/translations.ts +5 -4
- package/src/types.ts +5 -97
package/lib/types.d.ts
CHANGED
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
import type { RemarkAndRehypePluginOptions } from '@docusaurus/mdx-loader';
|
|
8
7
|
import type { Tag } from '@docusaurus/utils';
|
|
9
8
|
import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
|
|
10
|
-
import {
|
|
11
|
-
import { BlogPostFrontMatter } from './blogFrontMatter';
|
|
9
|
+
import type { BlogPostFrontMatter, Author } from '@docusaurus/plugin-content-blog';
|
|
12
10
|
export declare type BlogContentPaths = ContentPaths;
|
|
13
11
|
export interface BlogContent {
|
|
14
12
|
blogSidebarTitle: string;
|
|
@@ -17,71 +15,6 @@ export interface BlogContent {
|
|
|
17
15
|
blogTags: BlogTags;
|
|
18
16
|
blogTagsListPath: string | null;
|
|
19
17
|
}
|
|
20
|
-
export declare type FeedType = 'rss' | 'atom' | 'json';
|
|
21
|
-
export declare type FeedOptions = {
|
|
22
|
-
type?: FeedType[] | null;
|
|
23
|
-
title?: string;
|
|
24
|
-
description?: string;
|
|
25
|
-
copyright: string;
|
|
26
|
-
language?: string;
|
|
27
|
-
};
|
|
28
|
-
export declare type UserFeedOptions = Overwrite<Partial<FeedOptions>, {
|
|
29
|
-
type?: FeedOptions['type'] | 'all';
|
|
30
|
-
}>;
|
|
31
|
-
export declare type EditUrlFunction = (editUrlParams: {
|
|
32
|
-
blogDirPath: string;
|
|
33
|
-
blogPath: string;
|
|
34
|
-
permalink: string;
|
|
35
|
-
locale: string;
|
|
36
|
-
}) => string | undefined;
|
|
37
|
-
declare type ReadingTimeOptions = {
|
|
38
|
-
wordsPerMinute?: number;
|
|
39
|
-
wordBound?: (char: string) => boolean;
|
|
40
|
-
};
|
|
41
|
-
export declare type ReadingTimeFunction = (params: {
|
|
42
|
-
content: string;
|
|
43
|
-
frontMatter?: BlogPostFrontMatter & Record<string, unknown>;
|
|
44
|
-
options?: ReadingTimeOptions;
|
|
45
|
-
}) => number;
|
|
46
|
-
export declare type ReadingTimeFunctionOption = (params: Required<Omit<Parameters<ReadingTimeFunction>[0], 'options'>> & {
|
|
47
|
-
defaultReadingTime: ReadingTimeFunction;
|
|
48
|
-
}) => number | undefined;
|
|
49
|
-
export declare type PluginOptions = RemarkAndRehypePluginOptions & {
|
|
50
|
-
id?: string;
|
|
51
|
-
path: string;
|
|
52
|
-
routeBasePath: string;
|
|
53
|
-
tagsBasePath: string;
|
|
54
|
-
archiveBasePath: string;
|
|
55
|
-
include: string[];
|
|
56
|
-
exclude: string[];
|
|
57
|
-
postsPerPage: number | 'ALL';
|
|
58
|
-
blogListComponent: string;
|
|
59
|
-
blogPostComponent: string;
|
|
60
|
-
blogTagsListComponent: string;
|
|
61
|
-
blogTagsPostsComponent: string;
|
|
62
|
-
blogTitle: string;
|
|
63
|
-
blogDescription: string;
|
|
64
|
-
blogSidebarCount: number | 'ALL';
|
|
65
|
-
blogSidebarTitle: string;
|
|
66
|
-
truncateMarker: RegExp;
|
|
67
|
-
showReadingTime: boolean;
|
|
68
|
-
feedOptions: {
|
|
69
|
-
type?: FeedType[] | null;
|
|
70
|
-
title?: string;
|
|
71
|
-
description?: string;
|
|
72
|
-
copyright: string;
|
|
73
|
-
language?: string;
|
|
74
|
-
};
|
|
75
|
-
editUrl?: string | EditUrlFunction;
|
|
76
|
-
editLocalizedFiles?: boolean;
|
|
77
|
-
admonitions: Record<string, unknown>;
|
|
78
|
-
authorsMapPath: string;
|
|
79
|
-
readingTime: ReadingTimeFunctionOption;
|
|
80
|
-
sortPosts: 'ascending' | 'descending';
|
|
81
|
-
};
|
|
82
|
-
export declare type UserPluginOptions = Overwrite<Partial<PluginOptions>, {
|
|
83
|
-
feedOptions?: UserFeedOptions;
|
|
84
|
-
}>;
|
|
85
18
|
export interface BlogTags {
|
|
86
19
|
[key: string]: BlogTag;
|
|
87
20
|
}
|
|
@@ -110,12 +43,6 @@ export interface BlogPaginated {
|
|
|
110
43
|
metadata: BlogPaginatedMetadata;
|
|
111
44
|
items: string[];
|
|
112
45
|
}
|
|
113
|
-
export interface Author extends Record<string, unknown> {
|
|
114
|
-
name?: string;
|
|
115
|
-
imageURL?: string;
|
|
116
|
-
url?: string;
|
|
117
|
-
title?: string;
|
|
118
|
-
}
|
|
119
46
|
export interface MetaData {
|
|
120
47
|
permalink: string;
|
|
121
48
|
source: string;
|
|
@@ -130,10 +57,7 @@ export interface MetaData {
|
|
|
130
57
|
truncated: boolean;
|
|
131
58
|
editUrl?: string;
|
|
132
59
|
authors: Author[];
|
|
133
|
-
|
|
134
|
-
export interface Assets {
|
|
135
|
-
image?: string;
|
|
136
|
-
authorsImageUrls: (string | undefined)[];
|
|
60
|
+
frontMatter: BlogPostFrontMatter & Record<string, unknown>;
|
|
137
61
|
}
|
|
138
62
|
export interface Paginator {
|
|
139
63
|
title: string;
|
|
@@ -160,4 +84,3 @@ export declare type BlogMarkdownLoaderOptions = {
|
|
|
160
84
|
sourceToPermalink: Record<string, string>;
|
|
161
85
|
onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
|
|
162
86
|
};
|
|
163
|
-
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-content-blog",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.15",
|
|
4
4
|
"description": "Blog plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-content-blog.d.ts",
|
|
@@ -18,17 +18,15 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@docusaurus/core": "2.0.0-beta.
|
|
22
|
-
"@docusaurus/logger": "2.0.0-beta.
|
|
23
|
-
"@docusaurus/mdx-loader": "2.0.0-beta.
|
|
24
|
-
"@docusaurus/utils": "2.0.0-beta.
|
|
25
|
-
"@docusaurus/utils-
|
|
26
|
-
"
|
|
21
|
+
"@docusaurus/core": "2.0.0-beta.15",
|
|
22
|
+
"@docusaurus/logger": "2.0.0-beta.15",
|
|
23
|
+
"@docusaurus/mdx-loader": "2.0.0-beta.15",
|
|
24
|
+
"@docusaurus/utils": "2.0.0-beta.15",
|
|
25
|
+
"@docusaurus/utils-common": "2.0.0-beta.15",
|
|
26
|
+
"@docusaurus/utils-validation": "2.0.0-beta.15",
|
|
27
|
+
"cheerio": "^1.0.0-rc.10",
|
|
27
28
|
"feed": "^4.2.2",
|
|
28
29
|
"fs-extra": "^10.0.0",
|
|
29
|
-
"globby": "^11.0.2",
|
|
30
|
-
"js-yaml": "^4.0.0",
|
|
31
|
-
"loader-utils": "^2.0.0",
|
|
32
30
|
"lodash": "^4.17.20",
|
|
33
31
|
"reading-time": "^1.5.0",
|
|
34
32
|
"remark-admonitions": "^1.2.1",
|
|
@@ -37,7 +35,8 @@
|
|
|
37
35
|
"webpack": "^5.61.0"
|
|
38
36
|
},
|
|
39
37
|
"devDependencies": {
|
|
40
|
-
"@docusaurus/types": "2.0.0-beta.
|
|
38
|
+
"@docusaurus/types": "2.0.0-beta.15",
|
|
39
|
+
"escape-string-regexp": "^4.0.0"
|
|
41
40
|
},
|
|
42
41
|
"peerDependencies": {
|
|
43
42
|
"react": "^16.8.4 || ^17.0.0",
|
|
@@ -46,5 +45,5 @@
|
|
|
46
45
|
"engines": {
|
|
47
46
|
"node": ">=14"
|
|
48
47
|
},
|
|
49
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "6cfad16436c07d8d11e5c2e1486dc59afd483e33"
|
|
50
49
|
}
|
package/src/authors.ts
CHANGED
|
@@ -5,92 +5,48 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import path from 'path';
|
|
11
|
-
import {Author, BlogContentPaths} from './types';
|
|
12
|
-
import {findFolderContainingFile} from '@docusaurus/utils';
|
|
8
|
+
import type {BlogContentPaths} from './types';
|
|
9
|
+
import {getDataFileData} from '@docusaurus/utils';
|
|
13
10
|
import {Joi, URISchema} from '@docusaurus/utils-validation';
|
|
14
|
-
import {
|
|
11
|
+
import type {
|
|
12
|
+
Author,
|
|
15
13
|
BlogPostFrontMatter,
|
|
16
14
|
BlogPostFrontMatterAuthor,
|
|
17
15
|
BlogPostFrontMatterAuthors,
|
|
18
|
-
} from '
|
|
19
|
-
import {getContentPathList} from './blogUtils';
|
|
20
|
-
import Yaml from 'js-yaml';
|
|
16
|
+
} from '@docusaurus/plugin-content-blog';
|
|
21
17
|
|
|
22
18
|
export type AuthorsMap = Record<string, Author>;
|
|
23
19
|
|
|
24
20
|
const AuthorsMapSchema = Joi.object<AuthorsMap>().pattern(
|
|
25
21
|
Joi.string(),
|
|
26
22
|
Joi.object({
|
|
27
|
-
name: Joi.string()
|
|
23
|
+
name: Joi.string(),
|
|
28
24
|
url: URISchema,
|
|
29
25
|
imageURL: URISchema,
|
|
30
26
|
title: Joi.string(),
|
|
31
27
|
})
|
|
32
28
|
.rename('image_url', 'imageURL')
|
|
29
|
+
.or('name', 'imageURL')
|
|
33
30
|
.unknown()
|
|
34
31
|
.required(),
|
|
35
32
|
);
|
|
36
33
|
|
|
37
|
-
export function
|
|
34
|
+
export function validateAuthorsMap(content: unknown): AuthorsMap {
|
|
38
35
|
return Joi.attempt(content, AuthorsMapSchema);
|
|
39
36
|
}
|
|
40
37
|
|
|
41
|
-
export async function
|
|
42
|
-
filePath: string,
|
|
43
|
-
): Promise<AuthorsMap | undefined> {
|
|
44
|
-
if (await fs.pathExists(filePath)) {
|
|
45
|
-
const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
|
|
46
|
-
try {
|
|
47
|
-
const unsafeContent = Yaml.load(contentString);
|
|
48
|
-
return validateAuthorsMapFile(unsafeContent);
|
|
49
|
-
} catch (e) {
|
|
50
|
-
// TODO replace later by error cause: see https://v8.dev/features/error-cause
|
|
51
|
-
logger.error('The author list file looks invalid!');
|
|
52
|
-
throw e;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
type AuthorsMapParams = {
|
|
38
|
+
export async function getAuthorsMap(params: {
|
|
59
39
|
authorsMapPath: string;
|
|
60
40
|
contentPaths: BlogContentPaths;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
getContentPathList(contentPaths),
|
|
70
|
-
authorsMapPath,
|
|
41
|
+
}): Promise<AuthorsMap | undefined> {
|
|
42
|
+
return getDataFileData(
|
|
43
|
+
{
|
|
44
|
+
filePath: params.authorsMapPath,
|
|
45
|
+
contentPaths: params.contentPaths,
|
|
46
|
+
fileType: 'authors map',
|
|
47
|
+
},
|
|
48
|
+
validateAuthorsMap,
|
|
71
49
|
);
|
|
72
|
-
|
|
73
|
-
if (contentPath) {
|
|
74
|
-
return path.join(contentPath, authorsMapPath);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function getAuthorsMap(
|
|
81
|
-
params: AuthorsMapParams,
|
|
82
|
-
): Promise<AuthorsMap | undefined> {
|
|
83
|
-
const filePath = await getAuthorsMapFilePath(params);
|
|
84
|
-
if (!filePath) {
|
|
85
|
-
return undefined;
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
return await readAuthorsMapFile(filePath);
|
|
89
|
-
} catch (e) {
|
|
90
|
-
// TODO replace later by error cause, see https://v8.dev/features/error-cause
|
|
91
|
-
logger.error`Couldn't read blog authors map at path=${filePath}`;
|
|
92
|
-
throw e;
|
|
93
|
-
}
|
|
94
50
|
}
|
|
95
51
|
|
|
96
52
|
type AuthorsParam = {
|
|
@@ -98,7 +54,7 @@ type AuthorsParam = {
|
|
|
98
54
|
authorsMap: AuthorsMap | undefined;
|
|
99
55
|
};
|
|
100
56
|
|
|
101
|
-
// Legacy v1/early-v2
|
|
57
|
+
// Legacy v1/early-v2 front matter fields
|
|
102
58
|
// We may want to deprecate those in favor of using only frontMatter.authors
|
|
103
59
|
function getFrontMatterAuthorLegacy(
|
|
104
60
|
frontMatter: BlogPostFrontMatter,
|
|
@@ -108,7 +64,6 @@ function getFrontMatterAuthorLegacy(
|
|
|
108
64
|
const url = frontMatter.author_url ?? frontMatter.authorURL;
|
|
109
65
|
const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
|
|
110
66
|
|
|
111
|
-
// Shouldn't we require at least an author name?
|
|
112
67
|
if (name || title || url || imageURL) {
|
|
113
68
|
return {
|
|
114
69
|
name,
|
|
@@ -168,7 +123,7 @@ ${Object.keys(authorsMap)
|
|
|
168
123
|
|
|
169
124
|
function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author {
|
|
170
125
|
return {
|
|
171
|
-
// Author def from authorsMap can be locally overridden by
|
|
126
|
+
// Author def from authorsMap can be locally overridden by front matter
|
|
172
127
|
...getAuthorsMapAuthor(frontMatterAuthor.key),
|
|
173
128
|
...frontMatterAuthor,
|
|
174
129
|
};
|
|
@@ -182,11 +137,11 @@ export function getBlogPostAuthors(params: AuthorsParam): Author[] {
|
|
|
182
137
|
const authors = getFrontMatterAuthors(params);
|
|
183
138
|
|
|
184
139
|
if (authorLegacy) {
|
|
185
|
-
// Technically, we could allow mixing legacy/authors
|
|
140
|
+
// Technically, we could allow mixing legacy/authors front matter, but do we really want to?
|
|
186
141
|
if (authors.length > 0) {
|
|
187
142
|
throw new Error(
|
|
188
|
-
`To declare blog post authors, use the 'authors'
|
|
189
|
-
Don't mix 'authors' with other existing 'author_*'
|
|
143
|
+
`To declare blog post authors, use the 'authors' front matter in priority.
|
|
144
|
+
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`,
|
|
190
145
|
);
|
|
191
146
|
}
|
|
192
147
|
return [authorLegacy];
|
package/src/blogFrontMatter.ts
CHANGED
|
@@ -6,27 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
JoiFrontMatter as Joi, // Custom instance for
|
|
9
|
+
JoiFrontMatter as Joi, // Custom instance for front matter
|
|
10
10
|
URISchema,
|
|
11
11
|
validateFrontMatter,
|
|
12
12
|
FrontMatterTagsSchema,
|
|
13
13
|
FrontMatterTOCHeadingLevels,
|
|
14
14
|
} from '@docusaurus/utils-validation';
|
|
15
|
-
import type {
|
|
16
|
-
|
|
17
|
-
export type BlogPostFrontMatterAuthor = Record<string, unknown> & {
|
|
18
|
-
key?: string;
|
|
19
|
-
name?: string;
|
|
20
|
-
imageURL?: string;
|
|
21
|
-
url?: string;
|
|
22
|
-
title?: string;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// All the possible variants that the user can use for convenience
|
|
26
|
-
export type BlogPostFrontMatterAuthors =
|
|
27
|
-
| string
|
|
28
|
-
| BlogPostFrontMatterAuthor
|
|
29
|
-
| (string | BlogPostFrontMatterAuthor)[];
|
|
15
|
+
import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
|
|
30
16
|
|
|
31
17
|
const BlogPostFrontMatterAuthorSchema = Joi.object({
|
|
32
18
|
key: Joi.string(),
|
|
@@ -35,40 +21,9 @@ const BlogPostFrontMatterAuthorSchema = Joi.object({
|
|
|
35
21
|
url: URISchema,
|
|
36
22
|
imageURL: Joi.string(),
|
|
37
23
|
})
|
|
38
|
-
.or('key', 'name')
|
|
24
|
+
.or('key', 'name', 'imageURL')
|
|
39
25
|
.rename('image_url', 'imageURL', {alias: true});
|
|
40
26
|
|
|
41
|
-
export type BlogPostFrontMatter = {
|
|
42
|
-
id?: string;
|
|
43
|
-
title?: string;
|
|
44
|
-
description?: string;
|
|
45
|
-
tags?: FrontMatterTag[];
|
|
46
|
-
slug?: string;
|
|
47
|
-
draft?: boolean;
|
|
48
|
-
date?: Date | string; // Yaml automagically convert some string patterns as Date, but not all
|
|
49
|
-
|
|
50
|
-
authors?: BlogPostFrontMatterAuthors;
|
|
51
|
-
|
|
52
|
-
// We may want to deprecate those older author frontmatter fields later:
|
|
53
|
-
author?: string;
|
|
54
|
-
author_title?: string;
|
|
55
|
-
author_url?: string;
|
|
56
|
-
author_image_url?: string;
|
|
57
|
-
|
|
58
|
-
/** @deprecated */
|
|
59
|
-
authorTitle?: string;
|
|
60
|
-
/** @deprecated */
|
|
61
|
-
authorURL?: string;
|
|
62
|
-
/** @deprecated */
|
|
63
|
-
authorImageURL?: string;
|
|
64
|
-
|
|
65
|
-
image?: string;
|
|
66
|
-
keywords?: string[];
|
|
67
|
-
hide_table_of_contents?: boolean;
|
|
68
|
-
toc_min_heading_level?: number;
|
|
69
|
-
toc_max_heading_level?: number;
|
|
70
|
-
};
|
|
71
|
-
|
|
72
27
|
const FrontMatterAuthorErrorMessage =
|
|
73
28
|
'{{#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).';
|
|
74
29
|
|
|
@@ -80,7 +35,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|
|
80
35
|
draft: Joi.boolean(),
|
|
81
36
|
date: Joi.date().raw(),
|
|
82
37
|
|
|
83
|
-
// New multi-authors
|
|
38
|
+
// New multi-authors front matter:
|
|
84
39
|
authors: Joi.alternatives()
|
|
85
40
|
.try(
|
|
86
41
|
Joi.string(),
|
|
@@ -95,7 +50,7 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|
|
95
50
|
.messages({
|
|
96
51
|
'alternatives.match': FrontMatterAuthorErrorMessage,
|
|
97
52
|
}),
|
|
98
|
-
// Legacy author
|
|
53
|
+
// Legacy author front matter
|
|
99
54
|
author: Joi.string(),
|
|
100
55
|
author_title: Joi.string(),
|
|
101
56
|
author_url: URISchema,
|
package/src/blogUtils.ts
CHANGED
|
@@ -9,16 +9,14 @@ import fs from 'fs-extra';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import readingTime from 'reading-time';
|
|
11
11
|
import {keyBy, mapValues} from 'lodash';
|
|
12
|
-
import {
|
|
13
|
-
PluginOptions,
|
|
12
|
+
import type {
|
|
14
13
|
BlogPost,
|
|
15
14
|
BlogContentPaths,
|
|
16
15
|
BlogMarkdownLoaderOptions,
|
|
17
16
|
BlogTags,
|
|
18
|
-
ReadingTimeFunction,
|
|
19
17
|
} from './types';
|
|
20
18
|
import {
|
|
21
|
-
|
|
19
|
+
parseMarkdownString,
|
|
22
20
|
normalizeUrl,
|
|
23
21
|
aliasedSitePath,
|
|
24
22
|
getEditUrl,
|
|
@@ -28,11 +26,16 @@ import {
|
|
|
28
26
|
Globby,
|
|
29
27
|
normalizeFrontMatterTags,
|
|
30
28
|
groupTaggedItems,
|
|
29
|
+
getContentPathList,
|
|
31
30
|
} from '@docusaurus/utils';
|
|
32
|
-
import {LoadContext} from '@docusaurus/types';
|
|
31
|
+
import type {LoadContext} from '@docusaurus/types';
|
|
33
32
|
import {validateBlogPostFrontMatter} from './blogFrontMatter';
|
|
34
|
-
import {AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
33
|
+
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
35
34
|
import logger from '@docusaurus/logger';
|
|
35
|
+
import type {
|
|
36
|
+
PluginOptions,
|
|
37
|
+
ReadingTimeFunction,
|
|
38
|
+
} from '@docusaurus/plugin-content-blog';
|
|
36
39
|
|
|
37
40
|
export function truncate(fileString: string, truncateMarker: RegExp): string {
|
|
38
41
|
return fileString.split(truncateMarker, 1).shift()!;
|
|
@@ -60,7 +63,7 @@ export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
const DATE_FILENAME_REGEX =
|
|
63
|
-
/^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
|
|
66
|
+
/^(?<folder>.*)(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
|
|
64
67
|
|
|
65
68
|
type ParsedBlogFileName = {
|
|
66
69
|
date: Date | undefined;
|
|
@@ -73,12 +76,11 @@ export function parseBlogFileName(
|
|
|
73
76
|
): ParsedBlogFileName {
|
|
74
77
|
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
|
|
75
78
|
if (dateFilenameMatch) {
|
|
76
|
-
const dateString = dateFilenameMatch.groups
|
|
77
|
-
const text = dateFilenameMatch.groups!.text!;
|
|
79
|
+
const {folder, text, date: dateString} = dateFilenameMatch.groups!;
|
|
78
80
|
// Always treat dates as UTC by adding the `Z`
|
|
79
81
|
const date = new Date(`${dateString}Z`);
|
|
80
82
|
const slugDate = dateString.replace(/-/g, '/');
|
|
81
|
-
const slug = `/${slugDate}/${text}`;
|
|
83
|
+
const slug = `/${slugDate}/${folder}${text}`;
|
|
82
84
|
return {date, text, slug};
|
|
83
85
|
} else {
|
|
84
86
|
const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
|
|
@@ -101,13 +103,22 @@ function formatBlogPostDate(locale: string, date: Date): string {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
const markdownString = await fs.readFile(blogSourceAbsolute, 'utf-8');
|
|
107
|
+
try {
|
|
108
|
+
const result = parseMarkdownString(markdownString, {
|
|
109
|
+
removeContentTitle: true,
|
|
110
|
+
});
|
|
111
|
+
return {
|
|
112
|
+
...result,
|
|
113
|
+
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
|
|
114
|
+
};
|
|
115
|
+
} catch (e) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Error while parsing blog post file ${blogSourceAbsolute}: "${
|
|
118
|
+
(e as Error).message
|
|
119
|
+
}".`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
|
|
@@ -159,7 +170,12 @@ async function processBlogSourceFile(
|
|
|
159
170
|
async function getDate(): Promise<Date> {
|
|
160
171
|
// Prefer user-defined date.
|
|
161
172
|
if (frontMatter.date) {
|
|
162
|
-
|
|
173
|
+
if (typeof frontMatter.date === 'string') {
|
|
174
|
+
// Always treat dates as UTC by adding the `Z`
|
|
175
|
+
return new Date(`${frontMatter.date}Z`);
|
|
176
|
+
}
|
|
177
|
+
// YAML only converts YYYY-MM-DD to dates and leaves others as strings.
|
|
178
|
+
return frontMatter.date;
|
|
163
179
|
} else if (parsedBlogFileName.date) {
|
|
164
180
|
return parsedBlogFileName.date;
|
|
165
181
|
}
|
|
@@ -234,6 +250,7 @@ async function processBlogSourceFile(
|
|
|
234
250
|
: undefined,
|
|
235
251
|
truncated: truncateMarker?.test(content) || false,
|
|
236
252
|
authors,
|
|
253
|
+
frontMatter,
|
|
237
254
|
},
|
|
238
255
|
content,
|
|
239
256
|
};
|
|
@@ -317,8 +334,3 @@ export function linkify({
|
|
|
317
334
|
|
|
318
335
|
return newContent;
|
|
319
336
|
}
|
|
320
|
-
|
|
321
|
-
// Order matters: we look in priority in localized folder
|
|
322
|
-
export function getContentPathList(contentPaths: BlogContentPaths): string[] {
|
|
323
|
-
return [contentPaths.contentPathLocalized, contentPaths.contentPath];
|
|
324
|
-
}
|
package/src/feed.ts
CHANGED
|
@@ -5,34 +5,35 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {Feed, Author as FeedAuthor, Item as FeedItem} from 'feed';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
8
|
+
import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
|
|
9
|
+
import type {BlogPost} from './types';
|
|
10
|
+
import {
|
|
11
|
+
normalizeUrl,
|
|
12
|
+
posixPath,
|
|
13
|
+
mapAsyncSequential,
|
|
14
|
+
readOutputHTMLFile,
|
|
15
|
+
} from '@docusaurus/utils';
|
|
16
|
+
import cheerio from 'cheerio';
|
|
17
|
+
import type {DocusaurusConfig} from '@docusaurus/types';
|
|
12
18
|
import path from 'path';
|
|
13
19
|
import fs from 'fs-extra';
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
// TODO will we need a plugin option to configure how to handle such an error
|
|
23
|
-
// Swallow the error on purpose for now, until we understand better the problem space
|
|
24
|
-
return undefined;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function generateBlogFeed({
|
|
20
|
+
import type {
|
|
21
|
+
FeedType,
|
|
22
|
+
PluginOptions,
|
|
23
|
+
Author,
|
|
24
|
+
} from '@docusaurus/plugin-content-blog';
|
|
25
|
+
import {blogPostContainerID} from '@docusaurus/utils-common';
|
|
26
|
+
|
|
27
|
+
async function generateBlogFeed({
|
|
29
28
|
blogPosts,
|
|
30
29
|
options,
|
|
31
30
|
siteConfig,
|
|
31
|
+
outDir,
|
|
32
32
|
}: {
|
|
33
33
|
blogPosts: BlogPost[];
|
|
34
34
|
options: PluginOptions;
|
|
35
35
|
siteConfig: DocusaurusConfig;
|
|
36
|
+
outDir: string;
|
|
36
37
|
}): Promise<Feed | null> {
|
|
37
38
|
if (!blogPosts.length) {
|
|
38
39
|
return null;
|
|
@@ -42,9 +43,7 @@ export async function generateBlogFeed({
|
|
|
42
43
|
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
|
|
43
44
|
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
|
|
44
45
|
|
|
45
|
-
const updated =
|
|
46
|
-
(blogPosts[0] && blogPosts[0].metadata.date) ||
|
|
47
|
-
new Date('2015-10-25T16:29:00.000-07:00'); // weird legacy magic date
|
|
46
|
+
const updated = blogPosts[0] && blogPosts[0].metadata.date;
|
|
48
47
|
|
|
49
48
|
const feed = new Feed({
|
|
50
49
|
id: blogBaseUrl,
|
|
@@ -63,19 +62,35 @@ export async function generateBlogFeed({
|
|
|
63
62
|
return {name: author.name, link: author.url};
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
blogPosts
|
|
65
|
+
await mapAsyncSequential(blogPosts, async (post) => {
|
|
67
66
|
const {
|
|
68
67
|
id,
|
|
69
|
-
metadata: {
|
|
68
|
+
metadata: {
|
|
69
|
+
title: metadataTitle,
|
|
70
|
+
permalink,
|
|
71
|
+
date,
|
|
72
|
+
description,
|
|
73
|
+
authors,
|
|
74
|
+
tags,
|
|
75
|
+
},
|
|
70
76
|
} = post;
|
|
71
77
|
|
|
78
|
+
const content = await readOutputHTMLFile(
|
|
79
|
+
permalink.replace(siteConfig.baseUrl, ''),
|
|
80
|
+
outDir,
|
|
81
|
+
siteConfig.trailingSlash,
|
|
82
|
+
);
|
|
83
|
+
const $ = cheerio.load(content);
|
|
84
|
+
|
|
72
85
|
const feedItem: FeedItem = {
|
|
73
86
|
title: metadataTitle,
|
|
74
87
|
id,
|
|
75
88
|
link: normalizeUrl([siteUrl, permalink]),
|
|
76
89
|
date,
|
|
77
90
|
description,
|
|
78
|
-
|
|
91
|
+
// Atom feed demands the "term", while other feeds use "name"
|
|
92
|
+
category: tags.map((tag) => ({name: tag.label, term: tag.label})),
|
|
93
|
+
content: $(`#${blogPostContainerID}`).html()!,
|
|
79
94
|
};
|
|
80
95
|
|
|
81
96
|
// json1() method takes the first item of authors array
|
|
@@ -113,7 +128,10 @@ async function createBlogFeedFile({
|
|
|
113
128
|
}
|
|
114
129
|
})();
|
|
115
130
|
try {
|
|
116
|
-
await fs.outputFile(
|
|
131
|
+
await fs.outputFile(
|
|
132
|
+
posixPath(path.join(generatePath, feedPath)),
|
|
133
|
+
feedContent,
|
|
134
|
+
);
|
|
117
135
|
} catch (err) {
|
|
118
136
|
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
|
|
119
137
|
}
|
|
@@ -130,7 +148,12 @@ export async function createBlogFeedFiles({
|
|
|
130
148
|
siteConfig: DocusaurusConfig;
|
|
131
149
|
outDir: string;
|
|
132
150
|
}): Promise<void> {
|
|
133
|
-
const feed = await generateBlogFeed({
|
|
151
|
+
const feed = await generateBlogFeed({
|
|
152
|
+
blogPosts,
|
|
153
|
+
options,
|
|
154
|
+
siteConfig,
|
|
155
|
+
outDir,
|
|
156
|
+
});
|
|
134
157
|
|
|
135
158
|
const feedTypes = options.feedOptions.type;
|
|
136
159
|
if (!feed || !feedTypes) {
|