@docusaurus/plugin-content-blog 2.0.0-beta.16 → 2.0.0-beta.19
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 +3 -1
- package/lib/authors.js +14 -6
- package/lib/blogUtils.d.ts +5 -3
- package/lib/blogUtils.js +21 -19
- package/lib/feed.d.ts +3 -3
- package/lib/feed.js +16 -17
- package/lib/{blogFrontMatter.d.ts → frontMatter.d.ts} +3 -1
- package/lib/{blogFrontMatter.js → frontMatter.js} +0 -0
- package/lib/index.d.ts +3 -4
- package/lib/index.js +50 -58
- package/lib/markdownLoader.js +1 -1
- package/lib/options.d.ts +10 -0
- package/lib/{pluginOptionSchema.js → options.js} +9 -4
- package/lib/remark/footnoteIDFixer.d.ts +14 -0
- package/lib/remark/footnoteIDFixer.js +29 -0
- package/lib/translations.d.ts +4 -5
- package/lib/translations.js +4 -4
- package/lib/types.d.ts +4 -73
- package/package.json +13 -12
- package/src/authors.ts +29 -17
- package/src/blogUtils.ts +30 -22
- package/src/deps.d.ts +1 -1
- package/src/feed.ts +53 -56
- package/src/{blogFrontMatter.ts → frontMatter.ts} +3 -3
- package/src/index.ts +74 -98
- package/src/markdownLoader.ts +1 -1
- package/src/{pluginOptionSchema.ts → options.ts} +16 -4
- package/src/plugin-content-blog.d.ts +408 -97
- package/src/remark/footnoteIDFixer.ts +29 -0
- package/src/translations.ts +14 -9
- package/src/types.ts +2 -94
- package/lib/pluginOptionSchema.d.ts +0 -10
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
const tslib_1 = require("tslib");
|
|
10
|
+
const unist_util_visit_1 = tslib_1.__importDefault(require("unist-util-visit"));
|
|
11
|
+
const utils_1 = require("@docusaurus/utils");
|
|
12
|
+
/**
|
|
13
|
+
* In the blog list view, each post will be compiled separately. However, they
|
|
14
|
+
* may use the same footnote IDs. This leads to duplicated DOM IDs and inability
|
|
15
|
+
* to navigate to footnote references. This plugin fixes it by appending a
|
|
16
|
+
* unique hash to each reference/definition.
|
|
17
|
+
*/
|
|
18
|
+
function plugin() {
|
|
19
|
+
return (root, vfile) => {
|
|
20
|
+
const suffix = `-${(0, utils_1.simpleHash)(vfile.path, 6)}`;
|
|
21
|
+
(0, unist_util_visit_1.default)(root, 'footnoteReference', (node) => {
|
|
22
|
+
node.identifier += suffix;
|
|
23
|
+
});
|
|
24
|
+
(0, unist_util_visit_1.default)(root, 'footnoteDefinition', (node) => {
|
|
25
|
+
node.identifier += suffix;
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
exports.default = plugin;
|
package/lib/translations.d.ts
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
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 {
|
|
8
|
-
import type {
|
|
9
|
-
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function translateContent(content: BlogContent, translationFiles: TranslationFiles): BlogContent;
|
|
7
|
+
import type { TranslationFile } from '@docusaurus/types';
|
|
8
|
+
import type { PluginOptions, BlogContent } from '@docusaurus/plugin-content-blog';
|
|
9
|
+
export declare function getTranslationFiles(options: PluginOptions): TranslationFile[];
|
|
10
|
+
export declare function translateContent(content: BlogContent, translationFiles: TranslationFile[]): BlogContent;
|
package/lib/translations.js
CHANGED
|
@@ -14,8 +14,8 @@ function translateListPage(blogListPaginated, translations) {
|
|
|
14
14
|
items,
|
|
15
15
|
metadata: {
|
|
16
16
|
...metadata,
|
|
17
|
-
blogTitle: translations.title.
|
|
18
|
-
blogDescription: translations.description.
|
|
17
|
+
blogTitle: translations.title?.message ?? page.metadata.blogTitle,
|
|
18
|
+
blogDescription: translations.description?.message ?? page.metadata.blogDescription,
|
|
19
19
|
},
|
|
20
20
|
};
|
|
21
21
|
});
|
|
@@ -43,10 +43,10 @@ function getTranslationFiles(options) {
|
|
|
43
43
|
}
|
|
44
44
|
exports.getTranslationFiles = getTranslationFiles;
|
|
45
45
|
function translateContent(content, translationFiles) {
|
|
46
|
-
const
|
|
46
|
+
const { content: optionsTranslations } = translationFiles[0];
|
|
47
47
|
return {
|
|
48
48
|
...content,
|
|
49
|
-
blogSidebarTitle: optionsTranslations['sidebar.title'].
|
|
49
|
+
blogSidebarTitle: optionsTranslations['sidebar.title']?.message ?? content.blogSidebarTitle,
|
|
50
50
|
blogListPaginated: translateListPage(content.blogListPaginated, optionsTranslations),
|
|
51
51
|
};
|
|
52
52
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -4,84 +4,15 @@
|
|
|
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 {
|
|
8
|
-
import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
|
|
9
|
-
import type { BlogPostFrontMatter, Author } from '@docusaurus/plugin-content-blog';
|
|
7
|
+
import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils';
|
|
10
8
|
export declare type BlogContentPaths = ContentPaths;
|
|
11
|
-
export interface BlogContent {
|
|
12
|
-
blogSidebarTitle: string;
|
|
13
|
-
blogPosts: BlogPost[];
|
|
14
|
-
blogListPaginated: BlogPaginated[];
|
|
15
|
-
blogTags: BlogTags;
|
|
16
|
-
blogTagsListPath: string | null;
|
|
17
|
-
}
|
|
18
|
-
export interface BlogTags {
|
|
19
|
-
[tagKey: string]: BlogTag;
|
|
20
|
-
}
|
|
21
|
-
export interface BlogTag {
|
|
22
|
-
name: string;
|
|
23
|
-
items: string[];
|
|
24
|
-
permalink: string;
|
|
25
|
-
pages: BlogPaginated[];
|
|
26
|
-
}
|
|
27
|
-
export interface BlogPost {
|
|
28
|
-
id: string;
|
|
29
|
-
metadata: MetaData;
|
|
30
|
-
content: string;
|
|
31
|
-
}
|
|
32
|
-
export interface BlogPaginatedMetadata {
|
|
33
|
-
permalink: string;
|
|
34
|
-
page: number;
|
|
35
|
-
postsPerPage: number;
|
|
36
|
-
totalPages: number;
|
|
37
|
-
totalCount: number;
|
|
38
|
-
previousPage: string | null;
|
|
39
|
-
nextPage: string | null;
|
|
40
|
-
blogTitle: string;
|
|
41
|
-
blogDescription: string;
|
|
42
|
-
}
|
|
43
|
-
export interface BlogPaginated {
|
|
44
|
-
metadata: BlogPaginatedMetadata;
|
|
45
|
-
items: string[];
|
|
46
|
-
}
|
|
47
|
-
export interface MetaData {
|
|
48
|
-
permalink: string;
|
|
49
|
-
source: string;
|
|
50
|
-
description: string;
|
|
51
|
-
date: Date;
|
|
52
|
-
formattedDate: string;
|
|
53
|
-
tags: Tag[];
|
|
54
|
-
title: string;
|
|
55
|
-
readingTime?: number;
|
|
56
|
-
prevItem?: Paginator;
|
|
57
|
-
nextItem?: Paginator;
|
|
58
|
-
truncated: boolean;
|
|
59
|
-
editUrl?: string;
|
|
60
|
-
authors: Author[];
|
|
61
|
-
frontMatter: BlogPostFrontMatter & Record<string, unknown>;
|
|
62
|
-
}
|
|
63
|
-
export interface Paginator {
|
|
64
|
-
title: string;
|
|
65
|
-
permalink: string;
|
|
66
|
-
}
|
|
67
|
-
export interface BlogItemsToMetadata {
|
|
68
|
-
[key: string]: MetaData;
|
|
69
|
-
}
|
|
70
|
-
export interface TagsModule {
|
|
71
|
-
[key: string]: TagModule;
|
|
72
|
-
}
|
|
73
|
-
export interface TagModule {
|
|
74
|
-
allTagsPath: string;
|
|
75
|
-
slug: string;
|
|
76
|
-
name: string;
|
|
77
|
-
count: number;
|
|
78
|
-
permalink: string;
|
|
79
|
-
}
|
|
80
9
|
export declare type BlogBrokenMarkdownLink = BrokenMarkdownLink<BlogContentPaths>;
|
|
81
10
|
export declare type BlogMarkdownLoaderOptions = {
|
|
82
11
|
siteDir: string;
|
|
83
12
|
contentPaths: BlogContentPaths;
|
|
84
13
|
truncateMarker: RegExp;
|
|
85
|
-
sourceToPermalink:
|
|
14
|
+
sourceToPermalink: {
|
|
15
|
+
[aliasedPath: string]: string;
|
|
16
|
+
};
|
|
86
17
|
onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
|
|
87
18
|
};
|
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.19",
|
|
4
4
|
"description": "Blog plugin for Docusaurus.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-content-blog.d.ts",
|
|
@@ -18,24 +18,25 @@
|
|
|
18
18
|
},
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@docusaurus/core": "2.0.0-beta.
|
|
22
|
-
"@docusaurus/logger": "2.0.0-beta.
|
|
23
|
-
"@docusaurus/mdx-loader": "2.0.0-beta.
|
|
24
|
-
"@docusaurus/utils": "2.0.0-beta.
|
|
25
|
-
"@docusaurus/utils-common": "2.0.0-beta.
|
|
26
|
-
"@docusaurus/utils-validation": "2.0.0-beta.
|
|
21
|
+
"@docusaurus/core": "2.0.0-beta.19",
|
|
22
|
+
"@docusaurus/logger": "2.0.0-beta.19",
|
|
23
|
+
"@docusaurus/mdx-loader": "2.0.0-beta.19",
|
|
24
|
+
"@docusaurus/utils": "2.0.0-beta.19",
|
|
25
|
+
"@docusaurus/utils-common": "2.0.0-beta.19",
|
|
26
|
+
"@docusaurus/utils-validation": "2.0.0-beta.19",
|
|
27
27
|
"cheerio": "^1.0.0-rc.10",
|
|
28
28
|
"feed": "^4.2.2",
|
|
29
|
-
"fs-extra": "^10.0
|
|
29
|
+
"fs-extra": "^10.1.0",
|
|
30
30
|
"lodash": "^4.17.21",
|
|
31
31
|
"reading-time": "^1.5.0",
|
|
32
32
|
"remark-admonitions": "^1.2.1",
|
|
33
|
-
"tslib": "^2.
|
|
33
|
+
"tslib": "^2.4.0",
|
|
34
|
+
"unist-util-visit": "^2.0.3",
|
|
34
35
|
"utility-types": "^3.10.0",
|
|
35
|
-
"webpack": "^5.
|
|
36
|
+
"webpack": "^5.72.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@docusaurus/types": "2.0.0-beta.
|
|
39
|
+
"@docusaurus/types": "2.0.0-beta.19",
|
|
39
40
|
"escape-string-regexp": "^4.0.0"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
@@ -45,5 +46,5 @@
|
|
|
45
46
|
"engines": {
|
|
46
47
|
"node": ">=14"
|
|
47
48
|
},
|
|
48
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "a71e60a49cce93c1006ef10c41ac03187f057102"
|
|
49
50
|
}
|
package/src/authors.ts
CHANGED
|
@@ -15,21 +15,33 @@ import type {
|
|
|
15
15
|
BlogPostFrontMatterAuthors,
|
|
16
16
|
} from '@docusaurus/plugin-content-blog';
|
|
17
17
|
|
|
18
|
-
export type AuthorsMap =
|
|
19
|
-
|
|
20
|
-
const AuthorsMapSchema = Joi.object<AuthorsMap>()
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
18
|
+
export type AuthorsMap = {[authorKey: string]: Author};
|
|
19
|
+
|
|
20
|
+
const AuthorsMapSchema = Joi.object<AuthorsMap>()
|
|
21
|
+
.pattern(
|
|
22
|
+
Joi.string(),
|
|
23
|
+
Joi.object({
|
|
24
|
+
name: Joi.string(),
|
|
25
|
+
url: URISchema,
|
|
26
|
+
imageURL: URISchema,
|
|
27
|
+
title: Joi.string(),
|
|
28
|
+
email: Joi.string(),
|
|
29
|
+
})
|
|
30
|
+
.rename('image_url', 'imageURL')
|
|
31
|
+
.or('name', 'imageURL')
|
|
32
|
+
.unknown()
|
|
33
|
+
.required()
|
|
34
|
+
.messages({
|
|
35
|
+
'object.base':
|
|
36
|
+
'{#label} should be an author object containing properties like name, title, and imageURL.',
|
|
37
|
+
'any.required':
|
|
38
|
+
'{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.',
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
.messages({
|
|
42
|
+
'object.base':
|
|
43
|
+
"The authors map file should contain an object where each entry contains an author key and the corresponding author's data.",
|
|
44
|
+
});
|
|
33
45
|
|
|
34
46
|
export function validateAuthorsMap(content: unknown): AuthorsMap {
|
|
35
47
|
return Joi.attempt(content, AuthorsMapSchema);
|
|
@@ -58,7 +70,7 @@ type AuthorsParam = {
|
|
|
58
70
|
// We may want to deprecate those in favor of using only frontMatter.authors
|
|
59
71
|
function getFrontMatterAuthorLegacy(
|
|
60
72
|
frontMatter: BlogPostFrontMatter,
|
|
61
|
-
):
|
|
73
|
+
): Author | undefined {
|
|
62
74
|
const name = frontMatter.author;
|
|
63
75
|
const title = frontMatter.author_title ?? frontMatter.authorTitle;
|
|
64
76
|
const url = frontMatter.author_url ?? frontMatter.authorURL;
|
|
@@ -80,7 +92,7 @@ function normalizeFrontMatterAuthors(
|
|
|
80
92
|
frontMatterAuthors: BlogPostFrontMatterAuthors = [],
|
|
81
93
|
): BlogPostFrontMatterAuthor[] {
|
|
82
94
|
function normalizeAuthor(
|
|
83
|
-
authorInput: string |
|
|
95
|
+
authorInput: string | Author,
|
|
84
96
|
): BlogPostFrontMatterAuthor {
|
|
85
97
|
if (typeof authorInput === 'string') {
|
|
86
98
|
// Technically, we could allow users to provide an author's name here, but
|
package/src/blogUtils.ts
CHANGED
|
@@ -9,13 +9,7 @@ import fs from 'fs-extra';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import readingTime from 'reading-time';
|
|
11
11
|
import _ from 'lodash';
|
|
12
|
-
import type {
|
|
13
|
-
BlogPost,
|
|
14
|
-
BlogContentPaths,
|
|
15
|
-
BlogMarkdownLoaderOptions,
|
|
16
|
-
BlogTags,
|
|
17
|
-
BlogPaginated,
|
|
18
|
-
} from './types';
|
|
12
|
+
import type {BlogContentPaths, BlogMarkdownLoaderOptions} from './types';
|
|
19
13
|
import {
|
|
20
14
|
parseMarkdownString,
|
|
21
15
|
normalizeUrl,
|
|
@@ -31,21 +25,24 @@ import {
|
|
|
31
25
|
getContentPathList,
|
|
32
26
|
} from '@docusaurus/utils';
|
|
33
27
|
import type {LoadContext} from '@docusaurus/types';
|
|
34
|
-
import {validateBlogPostFrontMatter} from './
|
|
28
|
+
import {validateBlogPostFrontMatter} from './frontMatter';
|
|
35
29
|
import {type AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
36
30
|
import logger from '@docusaurus/logger';
|
|
37
31
|
import type {
|
|
38
32
|
PluginOptions,
|
|
39
33
|
ReadingTimeFunction,
|
|
34
|
+
BlogPost,
|
|
35
|
+
BlogTags,
|
|
36
|
+
BlogPaginated,
|
|
40
37
|
} from '@docusaurus/plugin-content-blog';
|
|
41
38
|
|
|
42
39
|
export function truncate(fileString: string, truncateMarker: RegExp): string {
|
|
43
40
|
return fileString.split(truncateMarker, 1).shift()!;
|
|
44
41
|
}
|
|
45
42
|
|
|
46
|
-
export function getSourceToPermalink(
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
export function getSourceToPermalink(blogPosts: BlogPost[]): {
|
|
44
|
+
[aliasedPath: string]: string;
|
|
45
|
+
} {
|
|
49
46
|
return Object.fromEntries(
|
|
50
47
|
blogPosts.map(({metadata: {source, permalink}}) => [source, permalink]),
|
|
51
48
|
);
|
|
@@ -72,7 +69,9 @@ export function paginateBlogPosts({
|
|
|
72
69
|
const pages: BlogPaginated[] = [];
|
|
73
70
|
|
|
74
71
|
function permalink(page: number) {
|
|
75
|
-
return page > 0
|
|
72
|
+
return page > 0
|
|
73
|
+
? normalizeUrl([basePageUrl, `page/${page + 1}`])
|
|
74
|
+
: basePageUrl;
|
|
76
75
|
}
|
|
77
76
|
|
|
78
77
|
for (let page = 0; page < numberOfPages; page += 1) {
|
|
@@ -86,8 +85,8 @@ export function paginateBlogPosts({
|
|
|
86
85
|
postsPerPage,
|
|
87
86
|
totalPages: numberOfPages,
|
|
88
87
|
totalCount,
|
|
89
|
-
previousPage: page !== 0 ? permalink(page - 1) :
|
|
90
|
-
nextPage: page < numberOfPages - 1 ? permalink(page + 1) :
|
|
88
|
+
previousPage: page !== 0 ? permalink(page - 1) : undefined,
|
|
89
|
+
nextPage: page < numberOfPages - 1 ? permalink(page + 1) : undefined,
|
|
91
90
|
blogDescription,
|
|
92
91
|
blogTitle,
|
|
93
92
|
},
|
|
@@ -112,7 +111,7 @@ export function getBlogTags({
|
|
|
112
111
|
);
|
|
113
112
|
|
|
114
113
|
return _.mapValues(groups, ({tag, items: tagBlogPosts}) => ({
|
|
115
|
-
|
|
114
|
+
label: tag.label,
|
|
116
115
|
items: tagBlogPosts.map((item) => item.id),
|
|
117
116
|
permalink: tag.permalink,
|
|
118
117
|
pages: paginateBlogPosts({
|
|
@@ -139,23 +138,28 @@ export function parseBlogFileName(
|
|
|
139
138
|
if (dateFilenameMatch) {
|
|
140
139
|
const {folder, text, date: dateString} = dateFilenameMatch.groups!;
|
|
141
140
|
// Always treat dates as UTC by adding the `Z`
|
|
142
|
-
const date = new Date(`${dateString}Z`);
|
|
143
|
-
const slugDate = dateString
|
|
144
|
-
const slug = `/${slugDate}/${folder}${text}`;
|
|
145
|
-
return {date, text
|
|
141
|
+
const date = new Date(`${dateString!}Z`);
|
|
142
|
+
const slugDate = dateString!.replace(/-/g, '/');
|
|
143
|
+
const slug = `/${slugDate}/${folder!}${text!}`;
|
|
144
|
+
return {date, text: text!, slug};
|
|
146
145
|
}
|
|
147
146
|
const text = blogSourceRelative.replace(/(?:\/index)?\.mdx?$/, '');
|
|
148
147
|
const slug = `/${text}`;
|
|
149
148
|
return {date: undefined, text, slug};
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
function formatBlogPostDate(
|
|
151
|
+
function formatBlogPostDate(
|
|
152
|
+
locale: string,
|
|
153
|
+
date: Date,
|
|
154
|
+
calendar: string,
|
|
155
|
+
): string {
|
|
153
156
|
try {
|
|
154
157
|
return new Intl.DateTimeFormat(locale, {
|
|
155
158
|
day: 'numeric',
|
|
156
159
|
month: 'long',
|
|
157
160
|
year: 'numeric',
|
|
158
161
|
timeZone: 'UTC',
|
|
162
|
+
calendar,
|
|
159
163
|
}).format(date);
|
|
160
164
|
} catch (err) {
|
|
161
165
|
logger.error`Can't format blog post date "${String(date)}"`;
|
|
@@ -245,13 +249,17 @@ async function processBlogSourceFile(
|
|
|
245
249
|
});
|
|
246
250
|
return result.date;
|
|
247
251
|
} catch (err) {
|
|
248
|
-
logger.
|
|
252
|
+
logger.warn(err);
|
|
249
253
|
return (await fs.stat(blogSourceAbsolute)).birthtime;
|
|
250
254
|
}
|
|
251
255
|
}
|
|
252
256
|
|
|
253
257
|
const date = await getDate();
|
|
254
|
-
const formattedDate = formatBlogPostDate(
|
|
258
|
+
const formattedDate = formatBlogPostDate(
|
|
259
|
+
i18n.currentLocale,
|
|
260
|
+
date,
|
|
261
|
+
i18n.localeConfigs[i18n.currentLocale]!.calendar,
|
|
262
|
+
);
|
|
255
263
|
|
|
256
264
|
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
|
|
257
265
|
const description = frontMatter.description ?? excerpt ?? '';
|
package/src/deps.d.ts
CHANGED
package/src/feed.ts
CHANGED
|
@@ -6,14 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
|
|
9
|
-
import
|
|
10
|
-
import {
|
|
11
|
-
normalizeUrl,
|
|
12
|
-
posixPath,
|
|
13
|
-
mapAsyncSequential,
|
|
14
|
-
readOutputHTMLFile,
|
|
15
|
-
} from '@docusaurus/utils';
|
|
16
|
-
import cheerio from 'cheerio';
|
|
9
|
+
import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
|
|
10
|
+
import {load as cheerioLoad} from 'cheerio';
|
|
17
11
|
import type {DocusaurusConfig} from '@docusaurus/types';
|
|
18
12
|
import path from 'path';
|
|
19
13
|
import fs from 'fs-extra';
|
|
@@ -21,6 +15,7 @@ import type {
|
|
|
21
15
|
FeedType,
|
|
22
16
|
PluginOptions,
|
|
23
17
|
Author,
|
|
18
|
+
BlogPost,
|
|
24
19
|
} from '@docusaurus/plugin-content-blog';
|
|
25
20
|
import {blogPostContainerID} from '@docusaurus/utils-common';
|
|
26
21
|
|
|
@@ -29,11 +24,13 @@ async function generateBlogFeed({
|
|
|
29
24
|
options,
|
|
30
25
|
siteConfig,
|
|
31
26
|
outDir,
|
|
27
|
+
locale,
|
|
32
28
|
}: {
|
|
33
29
|
blogPosts: BlogPost[];
|
|
34
30
|
options: PluginOptions;
|
|
35
31
|
siteConfig: DocusaurusConfig;
|
|
36
32
|
outDir: string;
|
|
33
|
+
locale: string;
|
|
37
34
|
}): Promise<Feed | null> {
|
|
38
35
|
if (!blogPosts.length) {
|
|
39
36
|
return null;
|
|
@@ -43,65 +40,65 @@ async function generateBlogFeed({
|
|
|
43
40
|
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
|
|
44
41
|
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
|
|
45
42
|
|
|
46
|
-
const updated = blogPosts[0]
|
|
43
|
+
const updated = blogPosts[0]?.metadata.date;
|
|
47
44
|
|
|
48
45
|
const feed = new Feed({
|
|
49
46
|
id: blogBaseUrl,
|
|
50
|
-
title: feedOptions.title
|
|
47
|
+
title: feedOptions.title ?? `${title} Blog`,
|
|
51
48
|
updated,
|
|
52
|
-
language: feedOptions.language,
|
|
49
|
+
language: feedOptions.language ?? locale,
|
|
53
50
|
link: blogBaseUrl,
|
|
54
|
-
description: feedOptions.description
|
|
51
|
+
description: feedOptions.description ?? `${siteConfig.title} Blog`,
|
|
55
52
|
favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
|
|
56
53
|
copyright: feedOptions.copyright,
|
|
57
54
|
});
|
|
58
55
|
|
|
59
56
|
function toFeedAuthor(author: Author): FeedAuthor {
|
|
60
|
-
|
|
61
|
-
// RSS feed requires email to render authors
|
|
62
|
-
return {name: author.name, link: author.url};
|
|
57
|
+
return {name: author.name, link: author.url, email: author.email};
|
|
63
58
|
}
|
|
64
59
|
|
|
65
|
-
await
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
await Promise.all(
|
|
61
|
+
blogPosts.map(async (post) => {
|
|
62
|
+
const {
|
|
63
|
+
id,
|
|
64
|
+
metadata: {
|
|
65
|
+
title: metadataTitle,
|
|
66
|
+
permalink,
|
|
67
|
+
date,
|
|
68
|
+
description,
|
|
69
|
+
authors,
|
|
70
|
+
tags,
|
|
71
|
+
},
|
|
72
|
+
} = post;
|
|
73
|
+
|
|
74
|
+
const content = await readOutputHTMLFile(
|
|
75
|
+
permalink.replace(siteConfig.baseUrl, ''),
|
|
76
|
+
outDir,
|
|
77
|
+
siteConfig.trailingSlash,
|
|
78
|
+
);
|
|
79
|
+
const $ = cheerioLoad(content);
|
|
80
|
+
|
|
81
|
+
const feedItem: FeedItem = {
|
|
69
82
|
title: metadataTitle,
|
|
70
|
-
|
|
83
|
+
id,
|
|
84
|
+
link: normalizeUrl([siteUrl, permalink]),
|
|
71
85
|
date,
|
|
72
86
|
description,
|
|
73
|
-
|
|
74
|
-
tags,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
link: normalizeUrl([siteUrl, permalink]),
|
|
89
|
-
date,
|
|
90
|
-
description,
|
|
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()!,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// json1() method takes the first item of authors array
|
|
97
|
-
// it causes an error when authors array is empty
|
|
98
|
-
const feedItemAuthors = authors.map(toFeedAuthor);
|
|
99
|
-
if (feedItemAuthors.length > 0) {
|
|
100
|
-
feedItem.author = feedItemAuthors;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
feed.addItem(feedItem);
|
|
104
|
-
});
|
|
87
|
+
// Atom feed demands the "term", while other feeds use "name"
|
|
88
|
+
category: tags.map((tag) => ({name: tag.label, term: tag.label})),
|
|
89
|
+
content: $(`#${blogPostContainerID}`).html()!,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// json1() method takes the first item of authors array
|
|
93
|
+
// it causes an error when authors array is empty
|
|
94
|
+
const feedItemAuthors = authors.map(toFeedAuthor);
|
|
95
|
+
if (feedItemAuthors.length > 0) {
|
|
96
|
+
feedItem.author = feedItemAuthors;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return feedItem;
|
|
100
|
+
}),
|
|
101
|
+
).then((items) => items.forEach(feed.addItem));
|
|
105
102
|
|
|
106
103
|
return feed;
|
|
107
104
|
}
|
|
@@ -128,10 +125,7 @@ async function createBlogFeedFile({
|
|
|
128
125
|
}
|
|
129
126
|
})();
|
|
130
127
|
try {
|
|
131
|
-
await fs.outputFile(
|
|
132
|
-
posixPath(path.join(generatePath, feedPath)),
|
|
133
|
-
feedContent,
|
|
134
|
-
);
|
|
128
|
+
await fs.outputFile(path.join(generatePath, feedPath), feedContent);
|
|
135
129
|
} catch (err) {
|
|
136
130
|
throw new Error(`Generating ${feedType} feed failed: ${err}.`);
|
|
137
131
|
}
|
|
@@ -142,17 +136,20 @@ export async function createBlogFeedFiles({
|
|
|
142
136
|
options,
|
|
143
137
|
siteConfig,
|
|
144
138
|
outDir,
|
|
139
|
+
locale,
|
|
145
140
|
}: {
|
|
146
141
|
blogPosts: BlogPost[];
|
|
147
142
|
options: PluginOptions;
|
|
148
143
|
siteConfig: DocusaurusConfig;
|
|
149
144
|
outDir: string;
|
|
145
|
+
locale: string;
|
|
150
146
|
}): Promise<void> {
|
|
151
147
|
const feed = await generateBlogFeed({
|
|
152
148
|
blogPosts,
|
|
153
149
|
options,
|
|
154
150
|
siteConfig,
|
|
155
151
|
outDir,
|
|
152
|
+
locale,
|
|
156
153
|
});
|
|
157
154
|
|
|
158
155
|
const feedTypes = options.feedOptions.type;
|
|
@@ -74,8 +74,8 @@ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|
|
74
74
|
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
-
export function validateBlogPostFrontMatter(
|
|
78
|
-
|
|
79
|
-
): BlogPostFrontMatter {
|
|
77
|
+
export function validateBlogPostFrontMatter(frontMatter: {
|
|
78
|
+
[key: string]: unknown;
|
|
79
|
+
}): BlogPostFrontMatter {
|
|
80
80
|
return validateFrontMatter(frontMatter, BlogFrontMatterSchema);
|
|
81
81
|
}
|