@docusaurus/plugin-content-blog 2.0.0-beta.12faed89d → 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/src/blogFrontMatter.ts
CHANGED
|
@@ -5,81 +5,119 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/* eslint-disable camelcase */
|
|
9
|
-
|
|
10
8
|
import {
|
|
11
9
|
JoiFrontMatter as Joi, // Custom instance for frontmatter
|
|
12
10
|
URISchema,
|
|
13
11
|
validateFrontMatter,
|
|
12
|
+
FrontMatterTagsSchema,
|
|
13
|
+
FrontMatterTOCHeadingLevels,
|
|
14
14
|
} from '@docusaurus/utils-validation';
|
|
15
|
-
import {
|
|
15
|
+
import type {FrontMatterTag} from '@docusaurus/utils';
|
|
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)[];
|
|
30
|
+
|
|
31
|
+
const BlogPostFrontMatterAuthorSchema = Joi.object({
|
|
32
|
+
key: Joi.string(),
|
|
33
|
+
name: Joi.string(),
|
|
34
|
+
title: Joi.string(),
|
|
35
|
+
url: URISchema,
|
|
36
|
+
imageURL: Joi.string(),
|
|
37
|
+
})
|
|
38
|
+
.or('key', 'name')
|
|
39
|
+
.rename('image_url', 'imageURL', {alias: true});
|
|
16
40
|
|
|
17
41
|
export type BlogPostFrontMatter = {
|
|
18
42
|
id?: string;
|
|
19
43
|
title?: string;
|
|
20
44
|
description?: string;
|
|
21
|
-
tags?:
|
|
45
|
+
tags?: FrontMatterTag[];
|
|
22
46
|
slug?: string;
|
|
23
47
|
draft?: boolean;
|
|
24
|
-
date?: Date;
|
|
48
|
+
date?: Date | string; // Yaml automagically convert some string patterns as Date, but not all
|
|
49
|
+
|
|
50
|
+
authors?: BlogPostFrontMatterAuthors;
|
|
25
51
|
|
|
52
|
+
// We may want to deprecate those older author frontmatter fields later:
|
|
26
53
|
author?: string;
|
|
27
54
|
author_title?: string;
|
|
28
55
|
author_url?: string;
|
|
29
56
|
author_image_url?: string;
|
|
30
57
|
|
|
31
|
-
image?: string;
|
|
32
|
-
keywords?: string[];
|
|
33
|
-
hide_table_of_contents?: boolean;
|
|
34
|
-
|
|
35
58
|
/** @deprecated */
|
|
36
59
|
authorTitle?: string;
|
|
60
|
+
/** @deprecated */
|
|
37
61
|
authorURL?: string;
|
|
62
|
+
/** @deprecated */
|
|
38
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;
|
|
39
70
|
};
|
|
40
71
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// While the user did not provide those values explicitly
|
|
44
|
-
// We use default values in code instead
|
|
45
|
-
const BlogTagSchema = Joi.alternatives().try(
|
|
46
|
-
Joi.string().required(),
|
|
47
|
-
Joi.object<Tag>({
|
|
48
|
-
label: Joi.string().required(),
|
|
49
|
-
permalink: Joi.string().required(),
|
|
50
|
-
}),
|
|
51
|
-
);
|
|
72
|
+
const FrontMatterAuthorErrorMessage =
|
|
73
|
+
'{{#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).';
|
|
52
74
|
|
|
53
75
|
const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
|
|
54
76
|
id: Joi.string(),
|
|
55
77
|
title: Joi.string().allow(''),
|
|
56
78
|
description: Joi.string().allow(''),
|
|
57
|
-
tags:
|
|
79
|
+
tags: FrontMatterTagsSchema,
|
|
58
80
|
draft: Joi.boolean(),
|
|
59
81
|
date: Joi.date().raw(),
|
|
60
82
|
|
|
83
|
+
// New multi-authors frontmatter:
|
|
84
|
+
authors: Joi.alternatives()
|
|
85
|
+
.try(
|
|
86
|
+
Joi.string(),
|
|
87
|
+
BlogPostFrontMatterAuthorSchema,
|
|
88
|
+
Joi.array()
|
|
89
|
+
.items(Joi.string(), BlogPostFrontMatterAuthorSchema)
|
|
90
|
+
.messages({
|
|
91
|
+
'array.sparse': FrontMatterAuthorErrorMessage,
|
|
92
|
+
'array.includes': FrontMatterAuthorErrorMessage,
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
.messages({
|
|
96
|
+
'alternatives.match': FrontMatterAuthorErrorMessage,
|
|
97
|
+
}),
|
|
98
|
+
// Legacy author frontmatter
|
|
61
99
|
author: Joi.string(),
|
|
62
100
|
author_title: Joi.string(),
|
|
63
101
|
author_url: URISchema,
|
|
64
102
|
author_image_url: URISchema,
|
|
65
|
-
|
|
66
|
-
image: URISchema,
|
|
67
|
-
keywords: Joi.array().items(Joi.string().required()),
|
|
68
|
-
hide_table_of_contents: Joi.boolean(),
|
|
69
|
-
|
|
70
|
-
// TODO re-enable warnings later, our v1 blog posts use those older frontmatter fields
|
|
103
|
+
// TODO enable deprecation warnings later
|
|
71
104
|
authorURL: URISchema,
|
|
72
105
|
// .warning('deprecate.error', { alternative: '"author_url"'}),
|
|
73
106
|
authorTitle: Joi.string(),
|
|
74
107
|
// .warning('deprecate.error', { alternative: '"author_title"'}),
|
|
75
108
|
authorImageURL: URISchema,
|
|
76
109
|
// .warning('deprecate.error', { alternative: '"author_image_url"'}),
|
|
77
|
-
|
|
78
|
-
.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
110
|
+
|
|
111
|
+
slug: Joi.string(),
|
|
112
|
+
image: URISchema,
|
|
113
|
+
keywords: Joi.array().items(Joi.string().required()),
|
|
114
|
+
hide_table_of_contents: Joi.boolean(),
|
|
115
|
+
|
|
116
|
+
...FrontMatterTOCHeadingLevels,
|
|
117
|
+
}).messages({
|
|
118
|
+
'deprecate.error':
|
|
119
|
+
'{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
|
|
120
|
+
});
|
|
83
121
|
|
|
84
122
|
export function validateBlogPostFrontMatter(
|
|
85
123
|
frontMatter: Record<string, unknown>,
|
package/src/blogUtils.ts
CHANGED
|
@@ -6,18 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
|
-
import globby from 'globby';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
9
|
import path from 'path';
|
|
12
10
|
import readingTime from 'reading-time';
|
|
13
|
-
import {Feed} from 'feed';
|
|
14
11
|
import {keyBy, mapValues} from 'lodash';
|
|
15
12
|
import {
|
|
16
13
|
PluginOptions,
|
|
17
14
|
BlogPost,
|
|
18
|
-
DateLink,
|
|
19
15
|
BlogContentPaths,
|
|
20
16
|
BlogMarkdownLoaderOptions,
|
|
17
|
+
BlogTags,
|
|
18
|
+
ReadingTimeFunction,
|
|
21
19
|
} from './types';
|
|
22
20
|
import {
|
|
23
21
|
parseMarkdownFile,
|
|
@@ -27,9 +25,14 @@ import {
|
|
|
27
25
|
getFolderContainingFile,
|
|
28
26
|
posixPath,
|
|
29
27
|
replaceMarkdownLinks,
|
|
28
|
+
Globby,
|
|
29
|
+
normalizeFrontMatterTags,
|
|
30
|
+
groupTaggedItems,
|
|
30
31
|
} from '@docusaurus/utils';
|
|
31
32
|
import {LoadContext} from '@docusaurus/types';
|
|
32
33
|
import {validateBlogPostFrontMatter} from './blogFrontMatter';
|
|
34
|
+
import {AuthorsMap, getAuthorsMap, getBlogPostAuthors} from './authors';
|
|
35
|
+
import logger from '@docusaurus/logger';
|
|
33
36
|
|
|
34
37
|
export function truncate(fileString: string, truncateMarker: RegExp): string {
|
|
35
38
|
return fileString.split(truncateMarker, 1).shift()!;
|
|
@@ -44,15 +47,44 @@ export function getSourceToPermalink(
|
|
|
44
47
|
);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
export function getBlogTags(blogPosts: BlogPost[]): BlogTags {
|
|
51
|
+
const groups = groupTaggedItems(
|
|
52
|
+
blogPosts,
|
|
53
|
+
(blogPost) => blogPost.metadata.tags,
|
|
54
|
+
);
|
|
55
|
+
return mapValues(groups, (group) => ({
|
|
56
|
+
name: group.tag.label,
|
|
57
|
+
items: group.items.map((item) => item.id),
|
|
58
|
+
permalink: group.tag.permalink,
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
50
61
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
const DATE_FILENAME_REGEX =
|
|
63
|
+
/^(?<date>\d{4}[-/]\d{1,2}[-/]\d{1,2})[-/]?(?<text>.*?)(\/index)?.mdx?$/;
|
|
64
|
+
|
|
65
|
+
type ParsedBlogFileName = {
|
|
66
|
+
date: Date | undefined;
|
|
67
|
+
text: string;
|
|
68
|
+
slug: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function parseBlogFileName(
|
|
72
|
+
blogSourceRelative: string,
|
|
73
|
+
): ParsedBlogFileName {
|
|
74
|
+
const dateFilenameMatch = blogSourceRelative.match(DATE_FILENAME_REGEX);
|
|
75
|
+
if (dateFilenameMatch) {
|
|
76
|
+
const dateString = dateFilenameMatch.groups!.date!;
|
|
77
|
+
const text = dateFilenameMatch.groups!.text!;
|
|
78
|
+
// Always treat dates as UTC by adding the `Z`
|
|
79
|
+
const date = new Date(`${dateString}Z`);
|
|
80
|
+
const slugDate = dateString.replace(/-/g, '/');
|
|
81
|
+
const slug = `/${slugDate}/${text}`;
|
|
82
|
+
return {date, text, slug};
|
|
83
|
+
} else {
|
|
84
|
+
const text = blogSourceRelative.replace(/(\/index)?\.mdx?$/, '');
|
|
85
|
+
const slug = `/${text}`;
|
|
86
|
+
return {date: undefined, text, slug};
|
|
87
|
+
}
|
|
56
88
|
}
|
|
57
89
|
|
|
58
90
|
function formatBlogPostDate(locale: string, date: Date): string {
|
|
@@ -68,209 +100,192 @@ function formatBlogPostDate(locale: string, date: Date): string {
|
|
|
68
100
|
}
|
|
69
101
|
}
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
options: PluginOptions,
|
|
75
|
-
): Promise<Feed | null> {
|
|
76
|
-
if (!options.feedOptions) {
|
|
77
|
-
throw new Error(
|
|
78
|
-
'Invalid options: "feedOptions" is not expected to be null.',
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
const {siteConfig} = context;
|
|
82
|
-
const blogPosts = await generateBlogPosts(contentPaths, context, options);
|
|
83
|
-
if (!blogPosts.length) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const {feedOptions, routeBasePath} = options;
|
|
88
|
-
const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
|
|
89
|
-
const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
|
|
90
|
-
|
|
91
|
-
const updated =
|
|
92
|
-
(blogPosts[0] && blogPosts[0].metadata.date) ||
|
|
93
|
-
new Date('2015-10-25T16:29:00.000-07:00');
|
|
94
|
-
|
|
95
|
-
const feed = new Feed({
|
|
96
|
-
id: blogBaseUrl,
|
|
97
|
-
title: feedOptions.title || `${title} Blog`,
|
|
98
|
-
updated,
|
|
99
|
-
language: feedOptions.language,
|
|
100
|
-
link: blogBaseUrl,
|
|
101
|
-
description: feedOptions.description || `${siteConfig.title} Blog`,
|
|
102
|
-
favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
|
|
103
|
-
copyright: feedOptions.copyright,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
blogPosts.forEach((post) => {
|
|
107
|
-
const {
|
|
108
|
-
id,
|
|
109
|
-
metadata: {title: metadataTitle, permalink, date, description},
|
|
110
|
-
} = post;
|
|
111
|
-
feed.addItem({
|
|
112
|
-
title: metadataTitle,
|
|
113
|
-
id,
|
|
114
|
-
link: normalizeUrl([siteUrl, permalink]),
|
|
115
|
-
date,
|
|
116
|
-
description,
|
|
117
|
-
});
|
|
103
|
+
async function parseBlogPostMarkdownFile(blogSourceAbsolute: string) {
|
|
104
|
+
const result = await parseMarkdownFile(blogSourceAbsolute, {
|
|
105
|
+
removeContentTitle: true,
|
|
118
106
|
});
|
|
119
|
-
|
|
120
|
-
|
|
107
|
+
return {
|
|
108
|
+
...result,
|
|
109
|
+
frontMatter: validateBlogPostFrontMatter(result.frontMatter),
|
|
110
|
+
};
|
|
121
111
|
}
|
|
122
112
|
|
|
123
|
-
|
|
113
|
+
const defaultReadingTime: ReadingTimeFunction = ({content, options}) =>
|
|
114
|
+
readingTime(content, options).minutes;
|
|
115
|
+
|
|
116
|
+
async function processBlogSourceFile(
|
|
117
|
+
blogSourceRelative: string,
|
|
124
118
|
contentPaths: BlogContentPaths,
|
|
125
|
-
|
|
119
|
+
context: LoadContext,
|
|
126
120
|
options: PluginOptions,
|
|
127
|
-
|
|
121
|
+
authorsMap?: AuthorsMap,
|
|
122
|
+
): Promise<BlogPost | undefined> {
|
|
123
|
+
const {
|
|
124
|
+
siteConfig: {baseUrl},
|
|
125
|
+
siteDir,
|
|
126
|
+
i18n,
|
|
127
|
+
} = context;
|
|
128
128
|
const {
|
|
129
|
-
include,
|
|
130
129
|
routeBasePath,
|
|
130
|
+
tagsBasePath: tagsRouteBasePath,
|
|
131
131
|
truncateMarker,
|
|
132
132
|
showReadingTime,
|
|
133
133
|
editUrl,
|
|
134
134
|
} = options;
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const blogSourceFiles = await globby(include, {
|
|
142
|
-
cwd: contentPaths.contentPath,
|
|
143
|
-
});
|
|
136
|
+
// Lookup in localized folder in priority
|
|
137
|
+
const blogDirPath = await getFolderContainingFile(
|
|
138
|
+
getContentPathList(contentPaths),
|
|
139
|
+
blogSourceRelative,
|
|
140
|
+
);
|
|
144
141
|
|
|
145
|
-
const
|
|
142
|
+
const blogSourceAbsolute = path.join(blogDirPath, blogSourceRelative);
|
|
146
143
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
const blogDirPath = await getFolderContainingFile(
|
|
150
|
-
getContentPathList(contentPaths),
|
|
151
|
-
blogSourceFile,
|
|
152
|
-
);
|
|
144
|
+
const {frontMatter, content, contentTitle, excerpt} =
|
|
145
|
+
await parseBlogPostMarkdownFile(blogSourceAbsolute);
|
|
153
146
|
|
|
154
|
-
|
|
147
|
+
const aliasedSource = aliasedSitePath(blogSourceAbsolute, siteDir);
|
|
155
148
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
contentTitle,
|
|
160
|
-
excerpt,
|
|
161
|
-
} = await parseMarkdownFile(source, {removeContentTitle: true});
|
|
162
|
-
const frontMatter = validateBlogPostFrontMatter(unsafeFrontMatter);
|
|
149
|
+
if (frontMatter.draft && process.env.NODE_ENV === 'production') {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
163
152
|
|
|
164
|
-
|
|
153
|
+
if (frontMatter.id) {
|
|
154
|
+
logger.warn`name=${'id'} header option is deprecated in path=${blogSourceRelative} file. Please use name=${'slug'} option instead.`;
|
|
155
|
+
}
|
|
165
156
|
|
|
166
|
-
|
|
157
|
+
const parsedBlogFileName = parseBlogFileName(blogSourceRelative);
|
|
167
158
|
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
async function getDate(): Promise<Date> {
|
|
160
|
+
// Prefer user-defined date.
|
|
161
|
+
if (frontMatter.date) {
|
|
162
|
+
return new Date(frontMatter.date);
|
|
163
|
+
} else if (parsedBlogFileName.date) {
|
|
164
|
+
return parsedBlogFileName.date;
|
|
170
165
|
}
|
|
166
|
+
// Fallback to file create time
|
|
167
|
+
return (await fs.stat(blogSourceAbsolute)).birthtime;
|
|
168
|
+
}
|
|
171
169
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
chalk.yellow(
|
|
175
|
-
`"id" header option is deprecated in ${blogFileName} file. Please use "slug" option instead.`,
|
|
176
|
-
),
|
|
177
|
-
);
|
|
178
|
-
}
|
|
170
|
+
const date = await getDate();
|
|
171
|
+
const formattedDate = formatBlogPostDate(i18n.currentLocale, date);
|
|
179
172
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const dateFilenameMatch = blogFileName.match(DATE_FILENAME_PATTERN);
|
|
183
|
-
let linkName = blogFileName.replace(/\.mdx?$/, '');
|
|
173
|
+
const title = frontMatter.title ?? contentTitle ?? parsedBlogFileName.text;
|
|
174
|
+
const description = frontMatter.description ?? excerpt ?? '';
|
|
184
175
|
|
|
185
|
-
|
|
186
|
-
const [, dateString, name] = dateFilenameMatch;
|
|
187
|
-
// Always treat dates as UTC by adding the `Z`
|
|
188
|
-
date = new Date(`${dateString}Z`);
|
|
189
|
-
linkName = name;
|
|
190
|
-
}
|
|
176
|
+
const slug = frontMatter.slug || parsedBlogFileName.slug;
|
|
191
177
|
|
|
192
|
-
|
|
193
|
-
if (frontMatter.date) {
|
|
194
|
-
date = frontMatter.date;
|
|
195
|
-
}
|
|
178
|
+
const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
|
|
196
179
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const description = frontMatter.description ?? excerpt ?? '';
|
|
203
|
-
|
|
204
|
-
const slug =
|
|
205
|
-
frontMatter.slug ||
|
|
206
|
-
(dateFilenameMatch ? toUrl({date, link: linkName}) : linkName);
|
|
207
|
-
|
|
208
|
-
const permalink = normalizeUrl([baseUrl, routeBasePath, slug]);
|
|
209
|
-
|
|
210
|
-
function getBlogEditUrl() {
|
|
211
|
-
const blogPathRelative = path.relative(blogDirPath, path.resolve(source));
|
|
212
|
-
|
|
213
|
-
if (typeof editUrl === 'function') {
|
|
214
|
-
return editUrl({
|
|
215
|
-
blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
|
|
216
|
-
blogPath: posixPath(blogPathRelative),
|
|
217
|
-
permalink,
|
|
218
|
-
locale: i18n.currentLocale,
|
|
219
|
-
});
|
|
220
|
-
} else if (typeof editUrl === 'string') {
|
|
221
|
-
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
222
|
-
const fileContentPath =
|
|
223
|
-
isLocalized && options.editLocalizedFiles
|
|
224
|
-
? contentPaths.contentPathLocalized
|
|
225
|
-
: contentPaths.contentPath;
|
|
226
|
-
|
|
227
|
-
const contentPathEditUrl = normalizeUrl([
|
|
228
|
-
editUrl,
|
|
229
|
-
posixPath(path.relative(siteDir, fileContentPath)),
|
|
230
|
-
]);
|
|
231
|
-
|
|
232
|
-
return getEditUrl(blogPathRelative, contentPathEditUrl);
|
|
233
|
-
} else {
|
|
234
|
-
return undefined;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
180
|
+
function getBlogEditUrl() {
|
|
181
|
+
const blogPathRelative = path.relative(
|
|
182
|
+
blogDirPath,
|
|
183
|
+
path.resolve(blogSourceAbsolute),
|
|
184
|
+
);
|
|
237
185
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
186
|
+
if (typeof editUrl === 'function') {
|
|
187
|
+
return editUrl({
|
|
188
|
+
blogDirPath: posixPath(path.relative(siteDir, blogDirPath)),
|
|
189
|
+
blogPath: posixPath(blogPathRelative),
|
|
241
190
|
permalink,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
191
|
+
locale: i18n.currentLocale,
|
|
192
|
+
});
|
|
193
|
+
} else if (typeof editUrl === 'string') {
|
|
194
|
+
const isLocalized = blogDirPath === contentPaths.contentPathLocalized;
|
|
195
|
+
const fileContentPath =
|
|
196
|
+
isLocalized && options.editLocalizedFiles
|
|
197
|
+
? contentPaths.contentPathLocalized
|
|
198
|
+
: contentPaths.contentPath;
|
|
199
|
+
|
|
200
|
+
const contentPathEditUrl = normalizeUrl([
|
|
201
|
+
editUrl,
|
|
202
|
+
posixPath(path.relative(siteDir, fileContentPath)),
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
return getEditUrl(blogPathRelative, contentPathEditUrl);
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
253
208
|
}
|
|
254
209
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
210
|
+
const tagsBasePath = normalizeUrl([
|
|
211
|
+
baseUrl,
|
|
212
|
+
routeBasePath,
|
|
213
|
+
tagsRouteBasePath,
|
|
214
|
+
]);
|
|
215
|
+
const authors = getBlogPostAuthors({authorsMap, frontMatter});
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
id: slug,
|
|
219
|
+
metadata: {
|
|
220
|
+
permalink,
|
|
221
|
+
editUrl: getBlogEditUrl(),
|
|
222
|
+
source: aliasedSource,
|
|
223
|
+
title,
|
|
224
|
+
description,
|
|
225
|
+
date,
|
|
226
|
+
formattedDate,
|
|
227
|
+
tags: normalizeFrontMatterTags(tagsBasePath, frontMatter.tags),
|
|
228
|
+
readingTime: showReadingTime
|
|
229
|
+
? options.readingTime({
|
|
230
|
+
content,
|
|
231
|
+
frontMatter,
|
|
232
|
+
defaultReadingTime,
|
|
233
|
+
})
|
|
234
|
+
: undefined,
|
|
235
|
+
truncated: truncateMarker?.test(content) || false,
|
|
236
|
+
authors,
|
|
237
|
+
},
|
|
238
|
+
content,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function generateBlogPosts(
|
|
243
|
+
contentPaths: BlogContentPaths,
|
|
244
|
+
context: LoadContext,
|
|
245
|
+
options: PluginOptions,
|
|
246
|
+
): Promise<BlogPost[]> {
|
|
247
|
+
const {include, exclude} = options;
|
|
248
|
+
|
|
249
|
+
if (!fs.existsSync(contentPaths.contentPath)) {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const blogSourceFiles = await Globby(include, {
|
|
254
|
+
cwd: contentPaths.contentPath,
|
|
255
|
+
ignore: exclude,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const authorsMap = await getAuthorsMap({
|
|
259
|
+
contentPaths,
|
|
260
|
+
authorsMapPath: options.authorsMapPath,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const blogPosts = (
|
|
264
|
+
await Promise.all(
|
|
265
|
+
blogSourceFiles.map(async (blogSourceFile: string) => {
|
|
266
|
+
try {
|
|
267
|
+
return await processBlogSourceFile(
|
|
268
|
+
blogSourceFile,
|
|
269
|
+
contentPaths,
|
|
270
|
+
context,
|
|
271
|
+
options,
|
|
272
|
+
authorsMap,
|
|
273
|
+
);
|
|
274
|
+
} catch (e) {
|
|
275
|
+
logger.error`Processing of blog source file failed for path path=${blogSourceFile}.`;
|
|
276
|
+
throw e;
|
|
277
|
+
}
|
|
278
|
+
}),
|
|
279
|
+
)
|
|
280
|
+
).filter(Boolean) as BlogPost[];
|
|
269
281
|
|
|
270
282
|
blogPosts.sort(
|
|
271
283
|
(a, b) => b.metadata.date.getTime() - a.metadata.date.getTime(),
|
|
272
284
|
);
|
|
273
285
|
|
|
286
|
+
if (options.sortPosts === 'ascending') {
|
|
287
|
+
return blogPosts.reverse();
|
|
288
|
+
}
|
|
274
289
|
return blogPosts;
|
|
275
290
|
}
|
|
276
291
|
|
|
File without changes
|