@docusaurus/plugin-content-blog 2.0.0-beta.15a2b59f9 → 2.0.0-beta.17

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 (51) hide show
  1. package/lib/authors.d.ts +20 -0
  2. package/lib/authors.js +110 -0
  3. package/lib/blogFrontMatter.d.ts +1 -21
  4. package/lib/blogFrontMatter.js +31 -19
  5. package/lib/blogUtils.d.ts +24 -6
  6. package/lib/blogUtils.js +196 -143
  7. package/lib/feed.d.ts +15 -0
  8. package/lib/feed.js +99 -0
  9. package/lib/index.d.ts +4 -3
  10. package/lib/index.js +149 -163
  11. package/lib/markdownLoader.d.ts +3 -6
  12. package/lib/markdownLoader.js +5 -6
  13. package/lib/pluginOptionSchema.d.ts +3 -26
  14. package/lib/pluginOptionSchema.js +35 -10
  15. package/lib/translations.d.ts +11 -0
  16. package/lib/translations.js +53 -0
  17. package/lib/types.d.ts +10 -46
  18. package/package.json +21 -18
  19. package/src/authors.ts +153 -0
  20. package/src/blogFrontMatter.ts +44 -51
  21. package/src/blogUtils.ts +289 -195
  22. package/{types.d.ts → src/deps.d.ts} +0 -0
  23. package/src/feed.ts +170 -0
  24. package/src/index.ts +197 -194
  25. package/src/markdownLoader.ts +10 -15
  26. package/src/plugin-content-blog.d.ts +270 -0
  27. package/src/pluginOptionSchema.ts +41 -13
  28. package/src/translations.ts +64 -0
  29. package/src/types.ts +19 -53
  30. package/index.d.ts +0 -138
  31. package/lib/.tsbuildinfo +0 -1
  32. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  33. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  34. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  35. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  36. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  37. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
  38. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  39. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  40. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  41. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  42. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  43. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -76
  44. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  45. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  46. package/src/__tests__/blogFrontMatter.test.ts +0 -317
  47. package/src/__tests__/generateBlogFeed.test.ts +0 -100
  48. package/src/__tests__/index.test.ts +0 -336
  49. package/src/__tests__/linkify.test.ts +0 -93
  50. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  51. package/tsconfig.json +0 -9
@@ -8,12 +8,13 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.PluginOptionSchema = exports.DEFAULT_OPTIONS = void 0;
10
10
  const utils_validation_1 = require("@docusaurus/utils-validation");
11
+ const utils_1 = require("@docusaurus/utils");
11
12
  exports.DEFAULT_OPTIONS = {
12
- feedOptions: { type: ['rss', 'atom'] },
13
+ feedOptions: { type: ['rss', 'atom'], copyright: '' },
13
14
  beforeDefaultRehypePlugins: [],
14
15
  beforeDefaultRemarkPlugins: [],
15
16
  admonitions: {},
16
- truncateMarker: /<!--\s*(truncate)\s*-->/,
17
+ truncateMarker: /<!--\s*truncate\s*-->/,
17
18
  rehypePlugins: [],
18
19
  remarkPlugins: [],
19
20
  showReadingTime: true,
@@ -21,37 +22,49 @@ exports.DEFAULT_OPTIONS = {
21
22
  blogTagsListComponent: '@theme/BlogTagsListPage',
22
23
  blogPostComponent: '@theme/BlogPostPage',
23
24
  blogListComponent: '@theme/BlogListPage',
25
+ blogArchiveComponent: '@theme/BlogArchivePage',
24
26
  blogDescription: 'Blog',
25
27
  blogTitle: 'Blog',
26
28
  blogSidebarCount: 5,
27
29
  blogSidebarTitle: 'Recent posts',
28
30
  postsPerPage: 10,
29
- include: ['*.md', '*.mdx'],
31
+ include: ['**/*.{md,mdx}'],
32
+ exclude: utils_1.GlobExcludeDefault,
30
33
  routeBasePath: 'blog',
34
+ tagsBasePath: 'tags',
35
+ archiveBasePath: 'archive',
31
36
  path: 'blog',
32
37
  editLocalizedFiles: false,
38
+ authorsMapPath: 'authors.yml',
39
+ readingTime: ({ content, defaultReadingTime }) => defaultReadingTime({ content }),
40
+ sortPosts: 'descending',
33
41
  };
34
42
  exports.PluginOptionSchema = utils_validation_1.Joi.object({
35
43
  path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
44
+ archiveBasePath: utils_validation_1.Joi.string()
45
+ .default(exports.DEFAULT_OPTIONS.archiveBasePath)
46
+ .allow(null),
36
47
  routeBasePath: utils_validation_1.Joi.string()
37
48
  // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
38
49
  // .allow('')
39
50
  .default(exports.DEFAULT_OPTIONS.routeBasePath),
51
+ tagsBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.tagsBasePath),
40
52
  include: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.include),
41
- postsPerPage: utils_validation_1.Joi.number()
42
- .integer()
43
- .min(1)
53
+ exclude: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.exclude),
54
+ postsPerPage: utils_validation_1.Joi.alternatives()
55
+ .try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().integer().min(1).required())
44
56
  .default(exports.DEFAULT_OPTIONS.postsPerPage),
45
57
  blogListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogListComponent),
46
58
  blogPostComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogPostComponent),
47
59
  blogTagsListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogTagsListComponent),
48
60
  blogTagsPostsComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogTagsPostsComponent),
61
+ blogArchiveComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogArchiveComponent),
49
62
  blogTitle: utils_validation_1.Joi.string().allow('').default(exports.DEFAULT_OPTIONS.blogTitle),
50
63
  blogDescription: utils_validation_1.Joi.string()
51
64
  .allow('')
52
65
  .default(exports.DEFAULT_OPTIONS.blogDescription),
53
66
  blogSidebarCount: utils_validation_1.Joi.alternatives()
54
- .try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().required())
67
+ .try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().integer().min(0).required())
55
68
  .default(exports.DEFAULT_OPTIONS.blogSidebarCount),
56
69
  blogSidebarTitle: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogSidebarTitle),
57
70
  showReadingTime: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showReadingTime),
@@ -65,14 +78,26 @@ exports.PluginOptionSchema = utils_validation_1.Joi.object({
65
78
  beforeDefaultRehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
66
79
  feedOptions: utils_validation_1.Joi.object({
67
80
  type: utils_validation_1.Joi.alternatives()
68
- .try(utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()), utils_validation_1.Joi.alternatives().conditional(utils_validation_1.Joi.string().equal('all', 'rss', 'atom'), {
69
- then: utils_validation_1.Joi.custom((val) => val === 'all' ? ['rss', 'atom'] : [val]),
81
+ .try(utils_validation_1.Joi.array().items(utils_validation_1.Joi.string().equal('rss', 'atom', 'json')), utils_validation_1.Joi.alternatives().conditional(utils_validation_1.Joi.string().equal('all', 'rss', 'atom', 'json'), {
82
+ then: utils_validation_1.Joi.custom((val) => val === 'all' ? ['rss', 'atom', 'json'] : [val]),
70
83
  }))
71
84
  .allow(null)
72
85
  .default(exports.DEFAULT_OPTIONS.feedOptions.type),
73
86
  title: utils_validation_1.Joi.string().allow(''),
74
87
  description: utils_validation_1.Joi.string().allow(''),
75
- copyright: utils_validation_1.Joi.string(),
88
+ // only add default value when user actually wants a feed (type is not null)
89
+ copyright: utils_validation_1.Joi.when('type', {
90
+ is: utils_validation_1.Joi.any().valid(null),
91
+ then: utils_validation_1.Joi.string().optional(),
92
+ otherwise: utils_validation_1.Joi.string()
93
+ .allow('')
94
+ .default(exports.DEFAULT_OPTIONS.feedOptions.copyright),
95
+ }),
76
96
  language: utils_validation_1.Joi.string(),
77
97
  }).default(exports.DEFAULT_OPTIONS.feedOptions),
98
+ authorsMapPath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.authorsMapPath),
99
+ readingTime: utils_validation_1.Joi.function().default(() => exports.DEFAULT_OPTIONS.readingTime),
100
+ sortPosts: utils_validation_1.Joi.string()
101
+ .valid('descending', 'ascending')
102
+ .default(exports.DEFAULT_OPTIONS.sortPosts),
78
103
  });
@@ -0,0 +1,11 @@
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 type { BlogContent } from './types';
8
+ import type { TranslationFiles } from '@docusaurus/types';
9
+ import type { PluginOptions } from '@docusaurus/plugin-content-blog';
10
+ export declare function getTranslationFiles(options: PluginOptions): TranslationFiles;
11
+ export declare function translateContent(content: BlogContent, translationFiles: TranslationFiles): BlogContent;
@@ -0,0 +1,53 @@
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.translateContent = exports.getTranslationFiles = void 0;
10
+ function translateListPage(blogListPaginated, translations) {
11
+ return blogListPaginated.map((page) => {
12
+ const { items, metadata } = page;
13
+ return {
14
+ items,
15
+ metadata: {
16
+ ...metadata,
17
+ blogTitle: translations.title.message,
18
+ blogDescription: translations.description.message,
19
+ },
20
+ };
21
+ });
22
+ }
23
+ function getTranslationFiles(options) {
24
+ return [
25
+ {
26
+ path: 'options',
27
+ content: {
28
+ title: {
29
+ message: options.blogTitle,
30
+ description: 'The title for the blog used in SEO',
31
+ },
32
+ description: {
33
+ message: options.blogDescription,
34
+ description: 'The description for the blog used in SEO',
35
+ },
36
+ 'sidebar.title': {
37
+ message: options.blogSidebarTitle,
38
+ description: 'The label for the left sidebar',
39
+ },
40
+ },
41
+ },
42
+ ];
43
+ }
44
+ exports.getTranslationFiles = getTranslationFiles;
45
+ function translateContent(content, translationFiles) {
46
+ const [{ content: optionsTranslations }] = translationFiles;
47
+ return {
48
+ ...content,
49
+ blogSidebarTitle: optionsTranslations['sidebar.title'].message,
50
+ blogListPaginated: translateListPage(content.blogListPaginated, optionsTranslations),
51
+ };
52
+ }
53
+ exports.translateContent = translateContent;
package/lib/types.d.ts CHANGED
@@ -4,64 +4,30 @@
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
- import { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
7
+ import type { Tag } from '@docusaurus/utils';
8
+ import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
9
+ import type { BlogPostFrontMatter, Author } from '@docusaurus/plugin-content-blog';
9
10
  export declare type BlogContentPaths = ContentPaths;
10
11
  export interface BlogContent {
12
+ blogSidebarTitle: string;
11
13
  blogPosts: BlogPost[];
12
14
  blogListPaginated: BlogPaginated[];
13
15
  blogTags: BlogTags;
14
16
  blogTagsListPath: string | null;
15
17
  }
16
- export interface DateLink {
17
- date: Date;
18
- link: string;
19
- }
20
- export declare type FeedType = 'rss' | 'atom';
21
- export declare type EditUrlFunction = (editUrlParams: {
22
- blogDirPath: string;
23
- blogPath: string;
24
- permalink: string;
25
- locale: string;
26
- }) => string | undefined;
27
- export interface PluginOptions extends RemarkAndRehypePluginOptions {
28
- id?: string;
29
- path: string;
30
- routeBasePath: string;
31
- include: string[];
32
- postsPerPage: number;
33
- blogListComponent: string;
34
- blogPostComponent: string;
35
- blogTagsListComponent: string;
36
- blogTagsPostsComponent: string;
37
- blogTitle: string;
38
- blogDescription: string;
39
- blogSidebarCount: number | 'ALL';
40
- blogSidebarTitle: string;
41
- truncateMarker: RegExp;
42
- showReadingTime: boolean;
43
- feedOptions: {
44
- type?: [FeedType] | null;
45
- title?: string;
46
- description?: string;
47
- copyright: string;
48
- language?: string;
49
- };
50
- editUrl?: string | EditUrlFunction;
51
- editLocalizedFiles?: boolean;
52
- admonitions: Record<string, unknown>;
53
- }
54
18
  export interface BlogTags {
55
- [key: string]: BlogTag;
19
+ [tagKey: string]: BlogTag;
56
20
  }
57
21
  export interface BlogTag {
58
22
  name: string;
59
23
  items: string[];
60
24
  permalink: string;
25
+ pages: BlogPaginated[];
61
26
  }
62
27
  export interface BlogPost {
63
28
  id: string;
64
29
  metadata: MetaData;
30
+ content: string;
65
31
  }
66
32
  export interface BlogPaginatedMetadata {
67
33
  permalink: string;
@@ -84,22 +50,20 @@ export interface MetaData {
84
50
  description: string;
85
51
  date: Date;
86
52
  formattedDate: string;
87
- tags: (Tag | string)[];
53
+ tags: Tag[];
88
54
  title: string;
89
55
  readingTime?: number;
90
56
  prevItem?: Paginator;
91
57
  nextItem?: Paginator;
92
58
  truncated: boolean;
93
59
  editUrl?: string;
60
+ authors: Author[];
61
+ frontMatter: BlogPostFrontMatter & Record<string, unknown>;
94
62
  }
95
63
  export interface Paginator {
96
64
  title: string;
97
65
  permalink: string;
98
66
  }
99
- export interface Tag {
100
- label: string;
101
- permalink: string;
102
- }
103
67
  export interface BlogItemsToMetadata {
104
68
  [key: string]: MetaData;
105
69
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "2.0.0-beta.15a2b59f9",
3
+ "version": "2.0.0-beta.17",
4
4
  "description": "Blog plugin for Docusaurus.",
5
5
  "main": "lib/index.js",
6
- "types": "index.d.ts",
6
+ "types": "src/plugin-content-blog.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "watch": "tsc --watch"
@@ -18,29 +18,32 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "@docusaurus/core": "2.0.0-beta.15a2b59f9",
22
- "@docusaurus/mdx-loader": "2.0.0-beta.15a2b59f9",
23
- "@docusaurus/types": "2.0.0-beta.15a2b59f9",
24
- "@docusaurus/utils": "2.0.0-beta.15a2b59f9",
25
- "@docusaurus/utils-validation": "2.0.0-beta.15a2b59f9",
26
- "chalk": "^4.1.1",
27
- "escape-string-regexp": "^4.0.0",
21
+ "@docusaurus/core": "2.0.0-beta.17",
22
+ "@docusaurus/logger": "2.0.0-beta.17",
23
+ "@docusaurus/mdx-loader": "2.0.0-beta.17",
24
+ "@docusaurus/utils": "2.0.0-beta.17",
25
+ "@docusaurus/utils-common": "2.0.0-beta.17",
26
+ "@docusaurus/utils-validation": "2.0.0-beta.17",
27
+ "cheerio": "^1.0.0-rc.10",
28
28
  "feed": "^4.2.2",
29
- "fs-extra": "^10.0.0",
30
- "globby": "^11.0.2",
31
- "loader-utils": "^2.0.0",
32
- "lodash": "^4.17.20",
33
- "reading-time": "^1.3.0",
29
+ "fs-extra": "^10.0.1",
30
+ "lodash": "^4.17.21",
31
+ "reading-time": "^1.5.0",
34
32
  "remark-admonitions": "^1.2.1",
35
- "tslib": "^2.2.0",
36
- "webpack": "^5.40.0"
33
+ "tslib": "^2.3.1",
34
+ "utility-types": "^3.10.0",
35
+ "webpack": "^5.69.1"
36
+ },
37
+ "devDependencies": {
38
+ "@docusaurus/types": "2.0.0-beta.17",
39
+ "escape-string-regexp": "^4.0.0"
37
40
  },
38
41
  "peerDependencies": {
39
42
  "react": "^16.8.4 || ^17.0.0",
40
43
  "react-dom": "^16.8.4 || ^17.0.0"
41
44
  },
42
45
  "engines": {
43
- "node": ">=12.13.0"
46
+ "node": ">=14"
44
47
  },
45
- "gitHead": "34881586224092fb9b5b2f455a46e19b3eee7f08"
48
+ "gitHead": "0032c0b0480083227af2e1b4da2d3ee6ce992403"
46
49
  }
package/src/authors.ts ADDED
@@ -0,0 +1,153 @@
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 type {BlogContentPaths} from './types';
9
+ import {getDataFileData} from '@docusaurus/utils';
10
+ import {Joi, URISchema} from '@docusaurus/utils-validation';
11
+ import type {
12
+ Author,
13
+ BlogPostFrontMatter,
14
+ BlogPostFrontMatterAuthor,
15
+ BlogPostFrontMatterAuthors,
16
+ } from '@docusaurus/plugin-content-blog';
17
+
18
+ export type AuthorsMap = Record<string, Author>;
19
+
20
+ const AuthorsMapSchema = Joi.object<AuthorsMap>().pattern(
21
+ Joi.string(),
22
+ Joi.object({
23
+ name: Joi.string(),
24
+ url: URISchema,
25
+ imageURL: URISchema,
26
+ title: Joi.string(),
27
+ email: Joi.string(),
28
+ })
29
+ .rename('image_url', 'imageURL')
30
+ .or('name', 'imageURL')
31
+ .unknown()
32
+ .required(),
33
+ );
34
+
35
+ export function validateAuthorsMap(content: unknown): AuthorsMap {
36
+ return Joi.attempt(content, AuthorsMapSchema);
37
+ }
38
+
39
+ export async function getAuthorsMap(params: {
40
+ authorsMapPath: string;
41
+ contentPaths: BlogContentPaths;
42
+ }): Promise<AuthorsMap | undefined> {
43
+ return getDataFileData(
44
+ {
45
+ filePath: params.authorsMapPath,
46
+ contentPaths: params.contentPaths,
47
+ fileType: 'authors map',
48
+ },
49
+ validateAuthorsMap,
50
+ );
51
+ }
52
+
53
+ type AuthorsParam = {
54
+ frontMatter: BlogPostFrontMatter;
55
+ authorsMap: AuthorsMap | undefined;
56
+ };
57
+
58
+ // Legacy v1/early-v2 front matter fields
59
+ // We may want to deprecate those in favor of using only frontMatter.authors
60
+ function getFrontMatterAuthorLegacy(
61
+ frontMatter: BlogPostFrontMatter,
62
+ ): BlogPostFrontMatterAuthor | undefined {
63
+ const name = frontMatter.author;
64
+ const title = frontMatter.author_title ?? frontMatter.authorTitle;
65
+ const url = frontMatter.author_url ?? frontMatter.authorURL;
66
+ const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
67
+
68
+ if (name || title || url || imageURL) {
69
+ return {
70
+ name,
71
+ title,
72
+ url,
73
+ imageURL,
74
+ };
75
+ }
76
+
77
+ return undefined;
78
+ }
79
+
80
+ function normalizeFrontMatterAuthors(
81
+ frontMatterAuthors: BlogPostFrontMatterAuthors = [],
82
+ ): BlogPostFrontMatterAuthor[] {
83
+ function normalizeAuthor(
84
+ authorInput: string | BlogPostFrontMatterAuthor,
85
+ ): BlogPostFrontMatterAuthor {
86
+ if (typeof authorInput === 'string') {
87
+ // Technically, we could allow users to provide an author's name here, but
88
+ // we only support keys, otherwise, a typo in a key would fallback to
89
+ // becoming a name and may end up unnoticed
90
+ return {key: authorInput};
91
+ }
92
+ return authorInput;
93
+ }
94
+
95
+ return Array.isArray(frontMatterAuthors)
96
+ ? frontMatterAuthors.map(normalizeAuthor)
97
+ : [normalizeAuthor(frontMatterAuthors)];
98
+ }
99
+
100
+ function getFrontMatterAuthors(params: AuthorsParam): Author[] {
101
+ const {authorsMap} = params;
102
+ const frontMatterAuthors = normalizeFrontMatterAuthors(
103
+ params.frontMatter.authors,
104
+ );
105
+
106
+ function getAuthorsMapAuthor(key: string | undefined): Author | undefined {
107
+ if (key) {
108
+ if (!authorsMap || Object.keys(authorsMap).length === 0) {
109
+ throw new Error(`Can't reference blog post authors by a key (such as '${key}') because no authors map file could be loaded.
110
+ Please double-check your blog plugin config (in particular 'authorsMapPath'), ensure the file exists at the configured path, is not empty, and is valid!`);
111
+ }
112
+ const author = authorsMap[key];
113
+ if (!author) {
114
+ throw Error(`Blog author with key "${key}" not found in the authors map file.
115
+ Valid author keys are:
116
+ ${Object.keys(authorsMap)
117
+ .map((validKey) => `- ${validKey}`)
118
+ .join('\n')}`);
119
+ }
120
+ return author;
121
+ }
122
+ return undefined;
123
+ }
124
+
125
+ function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author {
126
+ return {
127
+ // Author def from authorsMap can be locally overridden by front matter
128
+ ...getAuthorsMapAuthor(frontMatterAuthor.key),
129
+ ...frontMatterAuthor,
130
+ };
131
+ }
132
+
133
+ return frontMatterAuthors.map(toAuthor);
134
+ }
135
+
136
+ export function getBlogPostAuthors(params: AuthorsParam): Author[] {
137
+ const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
138
+ const authors = getFrontMatterAuthors(params);
139
+
140
+ if (authorLegacy) {
141
+ // Technically, we could allow mixing legacy/authors front matter, but do we
142
+ // really want to?
143
+ if (authors.length > 0) {
144
+ throw new Error(
145
+ `To declare blog post authors, use the 'authors' front matter in priority.
146
+ Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`,
147
+ );
148
+ }
149
+ return [authorLegacy];
150
+ }
151
+
152
+ return authors;
153
+ }
@@ -5,81 +5,74 @@
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
- JoiFrontMatter as Joi, // Custom instance for frontmatter
9
+ JoiFrontMatter as Joi, // Custom instance for front matter
12
10
  URISchema,
13
11
  validateFrontMatter,
12
+ FrontMatterTagsSchema,
13
+ FrontMatterTOCHeadingLevels,
14
14
  } from '@docusaurus/utils-validation';
15
- import {Tag} from './types';
16
-
17
- export type BlogPostFrontMatter = {
18
- id?: string;
19
- title?: string;
20
- description?: string;
21
- tags?: (string | Tag)[];
22
- slug?: string;
23
- draft?: boolean;
24
- date?: Date;
25
-
26
- author?: string;
27
- author_title?: string;
28
- author_url?: string;
29
- author_image_url?: string;
15
+ import type {BlogPostFrontMatter} from '@docusaurus/plugin-content-blog';
30
16
 
31
- image?: string;
32
- keywords?: string[];
33
- hide_table_of_contents?: boolean;
34
-
35
- /** @deprecated */
36
- authorTitle?: string;
37
- authorURL?: string;
38
- authorImageURL?: string;
39
- };
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});
40
26
 
41
- // NOTE: we don't add any default value on purpose here
42
- // We don't want default values to magically appear in doc metadatas and props
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
- );
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).';
52
29
 
53
30
  const BlogFrontMatterSchema = Joi.object<BlogPostFrontMatter>({
54
31
  id: Joi.string(),
55
32
  title: Joi.string().allow(''),
56
33
  description: Joi.string().allow(''),
57
- tags: Joi.array().items(BlogTagSchema),
34
+ tags: FrontMatterTagsSchema,
58
35
  draft: Joi.boolean(),
59
36
  date: Joi.date().raw(),
60
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
61
54
  author: Joi.string(),
62
55
  author_title: Joi.string(),
63
56
  author_url: URISchema,
64
57
  author_image_url: URISchema,
65
- slug: Joi.string(),
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
58
+ // TODO enable deprecation warnings later
71
59
  authorURL: URISchema,
72
60
  // .warning('deprecate.error', { alternative: '"author_url"'}),
73
61
  authorTitle: Joi.string(),
74
62
  // .warning('deprecate.error', { alternative: '"author_title"'}),
75
63
  authorImageURL: URISchema,
76
64
  // .warning('deprecate.error', { alternative: '"author_image_url"'}),
77
- })
78
- .unknown()
79
- .messages({
80
- 'deprecate.error':
81
- '{#label} blog frontMatter field is deprecated. Please use {#alternative} instead.',
82
- });
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
+ });
83
76
 
84
77
  export function validateBlogPostFrontMatter(
85
78
  frontMatter: Record<string, unknown>,