@docusaurus/plugin-content-blog 2.0.0-beta.fc64c12e4 → 2.0.0

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.
Files changed (60) hide show
  1. package/lib/authors.d.ts +22 -0
  2. package/lib/authors.js +122 -0
  3. package/lib/blogUtils.d.ts +27 -7
  4. package/lib/blogUtils.js +201 -145
  5. package/lib/feed.d.ts +15 -0
  6. package/lib/feed.js +102 -0
  7. package/lib/frontMatter.d.ts +10 -0
  8. package/lib/{blogFrontMatter.js → frontMatter.js} +31 -19
  9. package/lib/index.d.ts +4 -4
  10. package/lib/index.js +163 -194
  11. package/lib/markdownLoader.d.ts +3 -6
  12. package/lib/markdownLoader.js +6 -7
  13. package/lib/options.d.ts +10 -0
  14. package/lib/{pluginOptionSchema.js → options.js} +41 -14
  15. package/lib/remark/footnoteIDFixer.d.ts +14 -0
  16. package/lib/remark/footnoteIDFixer.js +29 -0
  17. package/lib/translations.d.ts +10 -0
  18. package/lib/translations.js +53 -0
  19. package/lib/types.d.ts +4 -110
  20. package/package.json +23 -19
  21. package/src/authors.ts +168 -0
  22. package/src/blogUtils.ts +305 -205
  23. package/src/feed.ts +171 -0
  24. package/src/frontMatter.ts +81 -0
  25. package/src/index.ts +223 -258
  26. package/src/markdownLoader.ts +11 -16
  27. package/src/{pluginOptionSchema.ts → options.ts} +54 -16
  28. package/src/plugin-content-blog.d.ts +587 -0
  29. package/src/remark/footnoteIDFixer.ts +29 -0
  30. package/src/translations.ts +69 -0
  31. package/src/types.ts +2 -129
  32. package/index.d.ts +0 -138
  33. package/lib/.tsbuildinfo +0 -1
  34. package/lib/blogFrontMatter.d.ts +0 -28
  35. package/lib/pluginOptionSchema.d.ts +0 -34
  36. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  37. package/src/__tests__/__fixtures__/website/blog/_partials/somePartial.md +0 -3
  38. package/src/__tests__/__fixtures__/website/blog/_partials/subfolder/somePartial.md +0 -3
  39. package/src/__tests__/__fixtures__/website/blog/_somePartial.md +0 -3
  40. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  41. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  42. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  43. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  44. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
  45. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  46. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  47. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  48. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  49. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  50. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -116
  51. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  52. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  53. package/src/__tests__/blogFrontMatter.test.ts +0 -317
  54. package/src/__tests__/generateBlogFeed.test.ts +0 -102
  55. package/src/__tests__/index.test.ts +0 -336
  56. package/src/__tests__/linkify.test.ts +0 -93
  57. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  58. package/src/blogFrontMatter.ts +0 -88
  59. package/tsconfig.json +0 -9
  60. package/types.d.ts +0 -13
package/src/feed.ts ADDED
@@ -0,0 +1,171 @@
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
+
8
+ import path from 'path';
9
+ import fs from 'fs-extra';
10
+ import logger from '@docusaurus/logger';
11
+ import {Feed, type Author as FeedAuthor, type Item as FeedItem} from 'feed';
12
+ import {normalizeUrl, readOutputHTMLFile} from '@docusaurus/utils';
13
+ import {blogPostContainerID} from '@docusaurus/utils-common';
14
+ import {load as cheerioLoad} from 'cheerio';
15
+ import type {DocusaurusConfig} from '@docusaurus/types';
16
+ import type {
17
+ FeedType,
18
+ PluginOptions,
19
+ Author,
20
+ BlogPost,
21
+ } from '@docusaurus/plugin-content-blog';
22
+
23
+ async function generateBlogFeed({
24
+ blogPosts,
25
+ options,
26
+ siteConfig,
27
+ outDir,
28
+ locale,
29
+ }: {
30
+ blogPosts: BlogPost[];
31
+ options: PluginOptions;
32
+ siteConfig: DocusaurusConfig;
33
+ outDir: string;
34
+ locale: string;
35
+ }): Promise<Feed | null> {
36
+ if (!blogPosts.length) {
37
+ return null;
38
+ }
39
+
40
+ const {feedOptions, routeBasePath} = options;
41
+ const {url: siteUrl, baseUrl, title, favicon} = siteConfig;
42
+ const blogBaseUrl = normalizeUrl([siteUrl, baseUrl, routeBasePath]);
43
+
44
+ const updated = blogPosts[0]?.metadata.date;
45
+
46
+ const feed = new Feed({
47
+ id: blogBaseUrl,
48
+ title: feedOptions.title ?? `${title} Blog`,
49
+ updated,
50
+ language: feedOptions.language ?? locale,
51
+ link: blogBaseUrl,
52
+ description: feedOptions.description ?? `${siteConfig.title} Blog`,
53
+ favicon: favicon ? normalizeUrl([siteUrl, baseUrl, favicon]) : undefined,
54
+ copyright: feedOptions.copyright,
55
+ });
56
+
57
+ function toFeedAuthor(author: Author): FeedAuthor {
58
+ return {name: author.name, link: author.url, email: author.email};
59
+ }
60
+
61
+ await Promise.all(
62
+ blogPosts.map(async (post) => {
63
+ const {
64
+ id,
65
+ metadata: {
66
+ title: metadataTitle,
67
+ permalink,
68
+ date,
69
+ description,
70
+ authors,
71
+ tags,
72
+ },
73
+ } = post;
74
+
75
+ const content = await readOutputHTMLFile(
76
+ permalink.replace(siteConfig.baseUrl, ''),
77
+ outDir,
78
+ siteConfig.trailingSlash,
79
+ );
80
+ const $ = cheerioLoad(content);
81
+
82
+ const feedItem: FeedItem = {
83
+ title: metadataTitle,
84
+ id,
85
+ link: normalizeUrl([siteUrl, permalink]),
86
+ date,
87
+ description,
88
+ // Atom feed demands the "term", while other feeds use "name"
89
+ category: tags.map((tag) => ({name: tag.label, term: tag.label})),
90
+ content: $(`#${blogPostContainerID}`).html()!,
91
+ };
92
+
93
+ // json1() method takes the first item of authors array
94
+ // it causes an error when authors array is empty
95
+ const feedItemAuthors = authors.map(toFeedAuthor);
96
+ if (feedItemAuthors.length > 0) {
97
+ feedItem.author = feedItemAuthors;
98
+ }
99
+
100
+ return feedItem;
101
+ }),
102
+ ).then((items) => items.forEach(feed.addItem));
103
+
104
+ return feed;
105
+ }
106
+
107
+ async function createBlogFeedFile({
108
+ feed,
109
+ feedType,
110
+ generatePath,
111
+ }: {
112
+ feed: Feed;
113
+ feedType: FeedType;
114
+ generatePath: string;
115
+ }) {
116
+ const [feedContent, feedPath] = (() => {
117
+ switch (feedType) {
118
+ case 'rss':
119
+ return [feed.rss2(), 'rss.xml'];
120
+ case 'json':
121
+ return [feed.json1(), 'feed.json'];
122
+ case 'atom':
123
+ return [feed.atom1(), 'atom.xml'];
124
+ default:
125
+ throw new Error(`Feed type ${feedType} not supported.`);
126
+ }
127
+ })();
128
+ try {
129
+ await fs.outputFile(path.join(generatePath, feedPath), feedContent);
130
+ } catch (err) {
131
+ logger.error(`Generating ${feedType} feed failed.`);
132
+ throw err;
133
+ }
134
+ }
135
+
136
+ export async function createBlogFeedFiles({
137
+ blogPosts,
138
+ options,
139
+ siteConfig,
140
+ outDir,
141
+ locale,
142
+ }: {
143
+ blogPosts: BlogPost[];
144
+ options: PluginOptions;
145
+ siteConfig: DocusaurusConfig;
146
+ outDir: string;
147
+ locale: string;
148
+ }): Promise<void> {
149
+ const feed = await generateBlogFeed({
150
+ blogPosts,
151
+ options,
152
+ siteConfig,
153
+ outDir,
154
+ locale,
155
+ });
156
+
157
+ const feedTypes = options.feedOptions.type;
158
+ if (!feed || !feedTypes) {
159
+ return;
160
+ }
161
+
162
+ await Promise.all(
163
+ feedTypes.map((feedType) =>
164
+ createBlogFeedFile({
165
+ feed,
166
+ feedType,
167
+ generatePath: path.join(outDir, options.routeBasePath),
168
+ }),
169
+ ),
170
+ );
171
+ }
@@ -0,0 +1,81 @@
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
+
8
+ import {
9
+ JoiFrontMatter as Joi, // Custom instance for front matter
10
+ URISchema,
11
+ validateFrontMatter,
12
+ FrontMatterTagsSchema,
13
+ FrontMatterTOCHeadingLevels,
14
+ } from '@docusaurus/utils-validation';
15
+ import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
16
+
17
+ const BlogPostFrontMatterAuthorSchema = Joi.object({
18
+ key: Joi.string(),
19
+ name: Joi.string(),
20
+ title: Joi.string(),
21
+ url: URISchema,
22
+ imageURL: Joi.string(),
23
+ })
24
+ .or('key', 'name', 'imageURL')
25
+ .rename('image_url', 'imageURL', {alias: true});
26
+
27
+ const FrontMatterAuthorErrorMessage =
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).';
29
+
30
+ const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
31
+ id: Joi.string(),
32
+ title: Joi.string().allow(''),
33
+ description: Joi.string().allow(''),
34
+ tags: FrontMatterTagsSchema,
35
+ draft: Joi.boolean(),
36
+ date: Joi.date().raw(),
37
+
38
+ // New multi-authors front matter:
39
+ authors: Joi.alternatives()
40
+ .try(
41
+ Joi.string(),
42
+ BlogPostFrontMatterAuthorSchema,
43
+ Joi.array()
44
+ .items(Joi.string(), BlogPostFrontMatterAuthorSchema)
45
+ .messages({
46
+ 'array.sparse': FrontMatterAuthorErrorMessage,
47
+ 'array.includes': FrontMatterAuthorErrorMessage,
48
+ }),
49
+ )
50
+ .messages({
51
+ 'alternatives.match': FrontMatterAuthorErrorMessage,
52
+ }),
53
+ // Legacy author front matter
54
+ author: Joi.string(),
55
+ author_title: Joi.string(),
56
+ author_url: URISchema,
57
+ author_image_url: URISchema,
58
+ // TODO enable deprecation warnings later
59
+ authorURL: URISchema,
60
+ // .warning('deprecate.error', { alternative: '"author_url"'}),
61
+ authorTitle: Joi.string(),
62
+ // .warning('deprecate.error', { alternative: '"author_title"'}),
63
+ authorImageURL: URISchema,
64
+ // .warning('deprecate.error', { alternative: '"author_image_url"'}),
65
+
66
+ slug: Joi.string(),
67
+ image: URISchema,
68
+ keywords: Joi.array().items(Joi.string().required()),
69
+ hide_table_of_contents: Joi.boolean(),
70
+
71
+ ...FrontMatterTOCHeadingLevels,
72
+ }).messages({
73
+ 'deprecate.error':
74
+ '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
75
+ });
76
+
77
+ export function validateBlogPostFrontMatter(frontMatter: {
78
+ [key: string]: unknown;
79
+ }): BlogPostFrontMatter {
80
+ return validateFrontMatter(frontMatter, BlogFrontMatterSchema);
81
+ }