@docusaurus/plugin-content-blog 2.0.0-beta.138b4c997 → 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.
Files changed (49) hide show
  1. package/lib/authors.d.ts +23 -0
  2. package/lib/authors.js +147 -0
  3. package/lib/blogFrontMatter.d.ts +19 -6
  4. package/lib/blogFrontMatter.js +31 -19
  5. package/lib/blogUtils.d.ts +10 -4
  6. package/lib/blogUtils.js +142 -137
  7. package/lib/feed.d.ts +20 -0
  8. package/lib/feed.js +105 -0
  9. package/lib/index.js +104 -106
  10. package/lib/markdownLoader.d.ts +3 -6
  11. package/lib/markdownLoader.js +5 -5
  12. package/lib/pluginOptionSchema.d.ts +3 -26
  13. package/lib/pluginOptionSchema.js +30 -9
  14. package/lib/translations.d.ts +10 -0
  15. package/lib/translations.js +53 -0
  16. package/lib/types.d.ts +55 -15
  17. package/package.json +17 -13
  18. package/src/authors.ts +196 -0
  19. package/src/blogFrontMatter.ts +71 -33
  20. package/src/blogUtils.ts +196 -181
  21. package/{types.d.ts → src/deps.d.ts} +0 -0
  22. package/src/feed.ts +149 -0
  23. package/src/index.ts +123 -107
  24. package/src/markdownLoader.ts +8 -12
  25. package/{index.d.ts → src/plugin-content-blog.d.ts} +35 -31
  26. package/src/pluginOptionSchema.ts +34 -12
  27. package/src/translations.ts +63 -0
  28. package/src/types.ts +69 -16
  29. package/lib/.tsbuildinfo +0 -1
  30. package/src/__tests__/__fixtures__/website/blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  31. package/src/__tests__/__fixtures__/website/blog/complex-slug.md +0 -7
  32. package/src/__tests__/__fixtures__/website/blog/date-matter.md +0 -5
  33. package/src/__tests__/__fixtures__/website/blog/draft.md +0 -6
  34. package/src/__tests__/__fixtures__/website/blog/heading-as-title.md +0 -5
  35. package/src/__tests__/__fixtures__/website/blog/simple-slug.md +0 -7
  36. package/src/__tests__/__fixtures__/website/blog-with-ref/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  37. package/src/__tests__/__fixtures__/website/blog-with-ref/post-with-broken-links.md +0 -11
  38. package/src/__tests__/__fixtures__/website/blog-with-ref/post.md +0 -5
  39. package/src/__tests__/__fixtures__/website/i18n/en/docusaurus-plugin-content-blog/2018-12-14-Happy-First-Birthday-Slash.md +0 -5
  40. package/src/__tests__/__fixtures__/website-blog-without-date/blog/no date.md +0 -1
  41. package/src/__tests__/__snapshots__/generateBlogFeed.test.ts.snap +0 -76
  42. package/src/__tests__/__snapshots__/linkify.test.ts.snap +0 -24
  43. package/src/__tests__/__snapshots__/pluginOptionSchema.test.ts.snap +0 -5
  44. package/src/__tests__/blogFrontMatter.test.ts +0 -317
  45. package/src/__tests__/generateBlogFeed.test.ts +0 -100
  46. package/src/__tests__/index.test.ts +0 -336
  47. package/src/__tests__/linkify.test.ts +0 -93
  48. package/src/__tests__/pluginOptionSchema.test.ts +0 -150
  49. package/tsconfig.json +0 -9
@@ -8,8 +8,9 @@
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: {},
@@ -26,21 +27,29 @@ exports.DEFAULT_OPTIONS = {
26
27
  blogSidebarCount: 5,
27
28
  blogSidebarTitle: 'Recent posts',
28
29
  postsPerPage: 10,
29
- include: ['*.md', '*.mdx'],
30
+ include: ['**/*.{md,mdx}'],
31
+ exclude: utils_1.GlobExcludeDefault,
30
32
  routeBasePath: 'blog',
33
+ tagsBasePath: 'tags',
34
+ archiveBasePath: 'archive',
31
35
  path: 'blog',
32
36
  editLocalizedFiles: false,
37
+ authorsMapPath: 'authors.yml',
38
+ readingTime: ({ content, defaultReadingTime }) => defaultReadingTime({ content }),
39
+ sortPosts: 'descending',
33
40
  };
34
41
  exports.PluginOptionSchema = utils_validation_1.Joi.object({
35
42
  path: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.path),
43
+ archiveBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.archiveBasePath),
36
44
  routeBasePath: utils_validation_1.Joi.string()
37
45
  // '' not allowed, see https://github.com/facebook/docusaurus/issues/3374
38
46
  // .allow('')
39
47
  .default(exports.DEFAULT_OPTIONS.routeBasePath),
48
+ tagsBasePath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.tagsBasePath),
40
49
  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)
50
+ exclude: utils_validation_1.Joi.array().items(utils_validation_1.Joi.string()).default(exports.DEFAULT_OPTIONS.exclude),
51
+ postsPerPage: utils_validation_1.Joi.alternatives()
52
+ .try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().integer().min(1).required())
44
53
  .default(exports.DEFAULT_OPTIONS.postsPerPage),
45
54
  blogListComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogListComponent),
46
55
  blogPostComponent: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogPostComponent),
@@ -51,7 +60,7 @@ exports.PluginOptionSchema = utils_validation_1.Joi.object({
51
60
  .allow('')
52
61
  .default(exports.DEFAULT_OPTIONS.blogDescription),
53
62
  blogSidebarCount: utils_validation_1.Joi.alternatives()
54
- .try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().required())
63
+ .try(utils_validation_1.Joi.equal('ALL').required(), utils_validation_1.Joi.number().integer().min(0).required())
55
64
  .default(exports.DEFAULT_OPTIONS.blogSidebarCount),
56
65
  blogSidebarTitle: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.blogSidebarTitle),
57
66
  showReadingTime: utils_validation_1.Joi.bool().default(exports.DEFAULT_OPTIONS.showReadingTime),
@@ -65,14 +74,26 @@ exports.PluginOptionSchema = utils_validation_1.Joi.object({
65
74
  beforeDefaultRehypePlugins: utils_validation_1.RehypePluginsSchema.default(exports.DEFAULT_OPTIONS.beforeDefaultRehypePlugins),
66
75
  feedOptions: utils_validation_1.Joi.object({
67
76
  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]),
77
+ .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'), {
78
+ then: utils_validation_1.Joi.custom((val) => val === 'all' ? ['rss', 'atom', 'json'] : [val]),
70
79
  }))
71
80
  .allow(null)
72
81
  .default(exports.DEFAULT_OPTIONS.feedOptions.type),
73
82
  title: utils_validation_1.Joi.string().allow(''),
74
83
  description: utils_validation_1.Joi.string().allow(''),
75
- copyright: utils_validation_1.Joi.string(),
84
+ // only add default value when user actually wants a feed (type is not null)
85
+ copyright: utils_validation_1.Joi.when('type', {
86
+ is: utils_validation_1.Joi.any().valid(null),
87
+ then: utils_validation_1.Joi.string().optional(),
88
+ otherwise: utils_validation_1.Joi.string()
89
+ .allow('')
90
+ .default(exports.DEFAULT_OPTIONS.feedOptions.copyright),
91
+ }),
76
92
  language: utils_validation_1.Joi.string(),
77
93
  }).default(exports.DEFAULT_OPTIONS.feedOptions),
94
+ authorsMapPath: utils_validation_1.Joi.string().default(exports.DEFAULT_OPTIONS.authorsMapPath),
95
+ readingTime: utils_validation_1.Joi.function().default(() => exports.DEFAULT_OPTIONS.readingTime),
96
+ sortPosts: utils_validation_1.Joi.string()
97
+ .valid('descending', 'ascending')
98
+ .default(exports.DEFAULT_OPTIONS.sortPosts),
78
99
  });
@@ -0,0 +1,10 @@
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, PluginOptions } from './types';
8
+ import type { TranslationFiles } from '@docusaurus/types';
9
+ export declare function getTranslationFiles(options: PluginOptions): TranslationFiles;
10
+ 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: optonsTranslations }] = translationFiles;
47
+ return {
48
+ ...content,
49
+ blogSidebarTitle: optonsTranslations['sidebar.title'].message,
50
+ blogListPaginated: translateListPage(content.blogListPaginated, optonsTranslations),
51
+ };
52
+ }
53
+ exports.translateContent = translateContent;
package/lib/types.d.ts CHANGED
@@ -5,31 +5,56 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import type { RemarkAndRehypePluginOptions } from '@docusaurus/mdx-loader';
8
- import { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
8
+ import type { Tag } from '@docusaurus/utils';
9
+ import type { BrokenMarkdownLink, ContentPaths } from '@docusaurus/utils/lib/markdownLinks';
10
+ import { Overwrite } from 'utility-types';
11
+ import { BlogPostFrontMatter } from './blogFrontMatter';
9
12
  export declare type BlogContentPaths = ContentPaths;
10
13
  export interface BlogContent {
14
+ blogSidebarTitle: string;
11
15
  blogPosts: BlogPost[];
12
16
  blogListPaginated: BlogPaginated[];
13
17
  blogTags: BlogTags;
14
18
  blogTagsListPath: string | null;
15
19
  }
16
- export interface DateLink {
17
- date: Date;
18
- link: string;
19
- }
20
- export declare type FeedType = 'rss' | 'atom';
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
+ }>;
21
31
  export declare type EditUrlFunction = (editUrlParams: {
22
32
  blogDirPath: string;
23
33
  blogPath: string;
24
34
  permalink: string;
25
35
  locale: string;
26
36
  }) => string | undefined;
27
- export interface PluginOptions extends RemarkAndRehypePluginOptions {
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 & {
28
50
  id?: string;
29
51
  path: string;
30
52
  routeBasePath: string;
53
+ tagsBasePath: string;
54
+ archiveBasePath: string;
31
55
  include: string[];
32
- postsPerPage: number;
56
+ exclude: string[];
57
+ postsPerPage: number | 'ALL';
33
58
  blogListComponent: string;
34
59
  blogPostComponent: string;
35
60
  blogTagsListComponent: string;
@@ -41,7 +66,7 @@ export interface PluginOptions extends RemarkAndRehypePluginOptions {
41
66
  truncateMarker: RegExp;
42
67
  showReadingTime: boolean;
43
68
  feedOptions: {
44
- type?: [FeedType] | null;
69
+ type?: FeedType[] | null;
45
70
  title?: string;
46
71
  description?: string;
47
72
  copyright: string;
@@ -50,7 +75,13 @@ export interface PluginOptions extends RemarkAndRehypePluginOptions {
50
75
  editUrl?: string | EditUrlFunction;
51
76
  editLocalizedFiles?: boolean;
52
77
  admonitions: Record<string, unknown>;
53
- }
78
+ authorsMapPath: string;
79
+ readingTime: ReadingTimeFunctionOption;
80
+ sortPosts: 'ascending' | 'descending';
81
+ };
82
+ export declare type UserPluginOptions = Overwrite<Partial<PluginOptions>, {
83
+ feedOptions?: UserFeedOptions;
84
+ }>;
54
85
  export interface BlogTags {
55
86
  [key: string]: BlogTag;
56
87
  }
@@ -62,6 +93,7 @@ export interface BlogTag {
62
93
  export interface BlogPost {
63
94
  id: string;
64
95
  metadata: MetaData;
96
+ content: string;
65
97
  }
66
98
  export interface BlogPaginatedMetadata {
67
99
  permalink: string;
@@ -78,28 +110,35 @@ export interface BlogPaginated {
78
110
  metadata: BlogPaginatedMetadata;
79
111
  items: string[];
80
112
  }
113
+ export interface Author extends Record<string, unknown> {
114
+ name?: string;
115
+ imageURL?: string;
116
+ url?: string;
117
+ title?: string;
118
+ }
81
119
  export interface MetaData {
82
120
  permalink: string;
83
121
  source: string;
84
122
  description: string;
85
123
  date: Date;
86
124
  formattedDate: string;
87
- tags: (Tag | string)[];
125
+ tags: Tag[];
88
126
  title: string;
89
127
  readingTime?: number;
90
128
  prevItem?: Paginator;
91
129
  nextItem?: Paginator;
92
130
  truncated: boolean;
93
131
  editUrl?: string;
132
+ authors: Author[];
133
+ }
134
+ export interface Assets {
135
+ image?: string;
136
+ authorsImageUrls: (string | undefined)[];
94
137
  }
95
138
  export interface Paginator {
96
139
  title: string;
97
140
  permalink: string;
98
141
  }
99
- export interface Tag {
100
- label: string;
101
- permalink: string;
102
- }
103
142
  export interface BlogItemsToMetadata {
104
143
  [key: string]: MetaData;
105
144
  }
@@ -121,3 +160,4 @@ export declare type BlogMarkdownLoaderOptions = {
121
160
  sourceToPermalink: Record<string, string>;
122
161
  onBrokenMarkdownLink: (brokenMarkdownLink: BlogBrokenMarkdownLink) => void;
123
162
  };
163
+ export {};
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@docusaurus/plugin-content-blog",
3
- "version": "2.0.0-beta.138b4c997",
3
+ "version": "2.0.0-beta.14",
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,33 @@
18
18
  },
19
19
  "license": "MIT",
20
20
  "dependencies": {
21
- "@docusaurus/core": "2.0.0-beta.138b4c997",
22
- "@docusaurus/mdx-loader": "2.0.0-beta.138b4c997",
23
- "@docusaurus/types": "2.0.0-beta.138b4c997",
24
- "@docusaurus/utils": "2.0.0-beta.138b4c997",
25
- "@docusaurus/utils-validation": "2.0.0-beta.138b4c997",
26
- "chalk": "^4.1.1",
21
+ "@docusaurus/core": "2.0.0-beta.14",
22
+ "@docusaurus/logger": "2.0.0-beta.14",
23
+ "@docusaurus/mdx-loader": "2.0.0-beta.14",
24
+ "@docusaurus/utils": "2.0.0-beta.14",
25
+ "@docusaurus/utils-validation": "2.0.0-beta.14",
27
26
  "escape-string-regexp": "^4.0.0",
28
27
  "feed": "^4.2.2",
29
28
  "fs-extra": "^10.0.0",
30
29
  "globby": "^11.0.2",
30
+ "js-yaml": "^4.0.0",
31
31
  "loader-utils": "^2.0.0",
32
32
  "lodash": "^4.17.20",
33
- "reading-time": "^1.3.0",
33
+ "reading-time": "^1.5.0",
34
34
  "remark-admonitions": "^1.2.1",
35
- "tslib": "^2.2.0",
36
- "webpack": "^5.40.0"
35
+ "tslib": "^2.3.1",
36
+ "utility-types": "^3.10.0",
37
+ "webpack": "^5.61.0"
38
+ },
39
+ "devDependencies": {
40
+ "@docusaurus/types": "2.0.0-beta.14"
37
41
  },
38
42
  "peerDependencies": {
39
43
  "react": "^16.8.4 || ^17.0.0",
40
44
  "react-dom": "^16.8.4 || ^17.0.0"
41
45
  },
42
46
  "engines": {
43
- "node": ">=12.13.0"
47
+ "node": ">=14"
44
48
  },
45
- "gitHead": "8cc620648710971acc459aabd2c6f505ef757701"
49
+ "gitHead": "c4824a8937d8f1aa0806667749cbc74058e2b294"
46
50
  }
package/src/authors.ts ADDED
@@ -0,0 +1,196 @@
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 fs from 'fs-extra';
9
+ import logger from '@docusaurus/logger';
10
+ import path from 'path';
11
+ import {Author, BlogContentPaths} from './types';
12
+ import {findFolderContainingFile} from '@docusaurus/utils';
13
+ import {Joi, URISchema} from '@docusaurus/utils-validation';
14
+ import {
15
+ BlogPostFrontMatter,
16
+ BlogPostFrontMatterAuthor,
17
+ BlogPostFrontMatterAuthors,
18
+ } from './blogFrontMatter';
19
+ import {getContentPathList} from './blogUtils';
20
+ import Yaml from 'js-yaml';
21
+
22
+ export type AuthorsMap = Record<string, Author>;
23
+
24
+ const AuthorsMapSchema = Joi.object<AuthorsMap>().pattern(
25
+ Joi.string(),
26
+ Joi.object({
27
+ name: Joi.string().required(),
28
+ url: URISchema,
29
+ imageURL: URISchema,
30
+ title: Joi.string(),
31
+ })
32
+ .rename('image_url', 'imageURL')
33
+ .unknown()
34
+ .required(),
35
+ );
36
+
37
+ export function validateAuthorsMapFile(content: unknown): AuthorsMap {
38
+ return Joi.attempt(content, AuthorsMapSchema);
39
+ }
40
+
41
+ export async function readAuthorsMapFile(
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 = {
59
+ authorsMapPath: string;
60
+ contentPaths: BlogContentPaths;
61
+ };
62
+
63
+ export async function getAuthorsMapFilePath({
64
+ authorsMapPath,
65
+ contentPaths,
66
+ }: AuthorsMapParams): Promise<string | undefined> {
67
+ // Useful to load an eventually localize authors map
68
+ const contentPath = await findFolderContainingFile(
69
+ getContentPathList(contentPaths),
70
+ authorsMapPath,
71
+ );
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
+ }
95
+
96
+ type AuthorsParam = {
97
+ frontMatter: BlogPostFrontMatter;
98
+ authorsMap: AuthorsMap | undefined;
99
+ };
100
+
101
+ // Legacy v1/early-v2 frontmatter fields
102
+ // We may want to deprecate those in favor of using only frontMatter.authors
103
+ function getFrontMatterAuthorLegacy(
104
+ frontMatter: BlogPostFrontMatter,
105
+ ): BlogPostFrontMatterAuthor | undefined {
106
+ const name = frontMatter.author;
107
+ const title = frontMatter.author_title ?? frontMatter.authorTitle;
108
+ const url = frontMatter.author_url ?? frontMatter.authorURL;
109
+ const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
110
+
111
+ // Shouldn't we require at least an author name?
112
+ if (name || title || url || imageURL) {
113
+ return {
114
+ name,
115
+ title,
116
+ url,
117
+ imageURL,
118
+ };
119
+ }
120
+
121
+ return undefined;
122
+ }
123
+
124
+ function normalizeFrontMatterAuthors(
125
+ frontMatterAuthors: BlogPostFrontMatterAuthors = [],
126
+ ): BlogPostFrontMatterAuthor[] {
127
+ function normalizeAuthor(
128
+ authorInput: string | BlogPostFrontMatterAuthor,
129
+ ): BlogPostFrontMatterAuthor {
130
+ if (typeof authorInput === 'string') {
131
+ // Technically, we could allow users to provide an author's name here
132
+ // IMHO it's better to only support keys here
133
+ // Reason: a typo in a key would fallback to becoming a name and may end-up un-noticed
134
+ return {key: authorInput};
135
+ }
136
+ return authorInput;
137
+ }
138
+
139
+ return Array.isArray(frontMatterAuthors)
140
+ ? frontMatterAuthors.map(normalizeAuthor)
141
+ : [normalizeAuthor(frontMatterAuthors)];
142
+ }
143
+
144
+ function getFrontMatterAuthors(params: AuthorsParam): Author[] {
145
+ const {authorsMap} = params;
146
+ const frontMatterAuthors = normalizeFrontMatterAuthors(
147
+ params.frontMatter.authors,
148
+ );
149
+
150
+ function getAuthorsMapAuthor(key: string | undefined): Author | undefined {
151
+ if (key) {
152
+ if (!authorsMap || Object.keys(authorsMap).length === 0) {
153
+ throw new Error(`Can't reference blog post authors by a key (such as '${key}') because no authors map file could be loaded.
154
+ Please double-check your blog plugin config (in particular 'authorsMapPath'), ensure the file exists at the configured path, is not empty, and is valid!`);
155
+ }
156
+ const author = authorsMap[key];
157
+ if (!author) {
158
+ throw Error(`Blog author with key "${key}" not found in the authors map file.
159
+ Valid author keys are:
160
+ ${Object.keys(authorsMap)
161
+ .map((validKey) => `- ${validKey}`)
162
+ .join('\n')}`);
163
+ }
164
+ return author;
165
+ }
166
+ return undefined;
167
+ }
168
+
169
+ function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author {
170
+ return {
171
+ // Author def from authorsMap can be locally overridden by frontmatter
172
+ ...getAuthorsMapAuthor(frontMatterAuthor.key),
173
+ ...frontMatterAuthor,
174
+ };
175
+ }
176
+
177
+ return frontMatterAuthors.map(toAuthor);
178
+ }
179
+
180
+ export function getBlogPostAuthors(params: AuthorsParam): Author[] {
181
+ const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
182
+ const authors = getFrontMatterAuthors(params);
183
+
184
+ if (authorLegacy) {
185
+ // Technically, we could allow mixing legacy/authors frontmatter, but do we really want to?
186
+ if (authors.length > 0) {
187
+ throw new Error(
188
+ `To declare blog post authors, use the 'authors' FrontMatter in priority.
189
+ Don't mix 'authors' with other existing 'author_*' FrontMatter. Choose one or the other, not both at the same time.`,
190
+ );
191
+ }
192
+ return [authorLegacy];
193
+ }
194
+
195
+ return authors;
196
+ }