docusaurus/packages/docusaurus-plugin-content-blog/src/authors.ts
ozaki 2aef92cb9e
fix(blog): normalize inline authors socials (#10424)
Co-authored-by: OzakIOne <OzakIOne@users.noreply.github.com>
Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com>
2024-08-29 18:42:03 +02:00

191 lines
5.4 KiB
TypeScript

/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import _ from 'lodash';
import {normalizeUrl} from '@docusaurus/utils';
import {normalizeSocials} from './authorsSocials';
import type {
Author,
AuthorsMap,
BlogPost,
BlogPostFrontMatter,
BlogPostFrontMatterAuthor,
} from '@docusaurus/plugin-content-blog';
type AuthorsParam = {
frontMatter: BlogPostFrontMatter;
authorsMap: AuthorsMap | undefined;
baseUrl: string;
};
export function normalizeImageUrl({
imageURL,
baseUrl,
}: {
imageURL: string | undefined;
baseUrl: string;
}): string | undefined {
return imageURL?.startsWith('/')
? normalizeUrl([baseUrl, imageURL])
: imageURL;
}
function normalizeAuthorUrl({
author,
baseUrl,
}: {
author: Author;
baseUrl: string;
}): string | undefined {
if (author.key) {
// Ensures invariant: global authors should have already been normalized
if (
author.imageURL?.startsWith('/') &&
!author.imageURL.startsWith(baseUrl)
) {
throw new Error(
`Docusaurus internal bug: global authors image ${author.imageURL} should start with the expected baseUrl=${baseUrl}`,
);
}
return author.imageURL;
}
return normalizeImageUrl({imageURL: author.imageURL, baseUrl});
}
// Legacy v1/early-v2 front matter fields
// We may want to deprecate those in favor of using only frontMatter.authors
// TODO Docusaurus v4: remove this legacy front matter
function getFrontMatterAuthorLegacy({
baseUrl,
frontMatter,
}: {
baseUrl: string;
frontMatter: BlogPostFrontMatter;
}): Author | undefined {
const name = frontMatter.author;
const title = frontMatter.author_title ?? frontMatter.authorTitle;
const url = frontMatter.author_url ?? frontMatter.authorURL;
const imageURL = normalizeImageUrl({
imageURL: frontMatter.author_image_url ?? frontMatter.authorImageURL,
baseUrl,
});
if (name || title || url || imageURL) {
return {
name,
title,
url,
imageURL,
// legacy front matter authors do not have an author key/page
key: null,
page: null,
};
}
return undefined;
}
function getFrontMatterAuthors(params: AuthorsParam): Author[] {
const {authorsMap, frontMatter, baseUrl} = params;
return normalizeFrontMatterAuthors().map(toAuthor);
function normalizeFrontMatterAuthors(): BlogPostFrontMatterAuthor[] {
if (frontMatter.authors === undefined) {
return [];
}
function normalizeAuthor(
authorInput: string | BlogPostFrontMatterAuthor,
): BlogPostFrontMatterAuthor {
if (typeof authorInput === 'string') {
// We could allow users to provide an author's name here, but we only
// support keys, otherwise, a typo in a key would fall back to
// becoming a name and may end up unnoticed
return {key: authorInput};
}
return {
...authorInput,
socials: normalizeSocials(authorInput.socials ?? {}),
};
}
return Array.isArray(frontMatter.authors)
? frontMatter.authors.map(normalizeAuthor)
: [normalizeAuthor(frontMatter.authors)];
}
function getAuthorsMapAuthor(key: string | undefined): Author | undefined {
if (key) {
if (!authorsMap || Object.keys(authorsMap).length === 0) {
throw new Error(`Can't reference blog post authors by a key (such as '${key}') because no authors map file could be loaded.
Please double-check your blog plugin config (in particular 'authorsMapPath'), ensure the file exists at the configured path, is not empty, and is valid!`);
}
const author = authorsMap[key];
if (!author) {
throw Error(`Blog author with key "${key}" not found in the authors map file.
Valid author keys are:
${Object.keys(authorsMap)
.map((validKey) => `- ${validKey}`)
.join('\n')}`);
}
return author;
}
return undefined;
}
function toAuthor(frontMatterAuthor: BlogPostFrontMatterAuthor): Author {
const author = {
// Author def from authorsMap can be locally overridden by front matter
...getAuthorsMapAuthor(frontMatterAuthor.key),
...frontMatterAuthor,
} as Author;
return {
...author,
key: author.key ?? null,
page: author.page ?? null,
// global author images have already been normalized
imageURL: normalizeAuthorUrl({author, baseUrl}),
};
}
}
export function getBlogPostAuthors(params: AuthorsParam): Author[] {
const authorLegacy = getFrontMatterAuthorLegacy(params);
const authors = getFrontMatterAuthors(params);
if (authorLegacy) {
// Technically, we could allow mixing legacy/authors front matter, but do we
// really want to?
if (authors.length > 0) {
throw new Error(
`To declare blog post authors, use the 'authors' front matter in priority.
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time.`,
);
}
return [authorLegacy];
}
return authors;
}
/**
* Group blog posts by author key
* Blog posts with only inline authors are ignored
*/
export function groupBlogPostsByAuthorKey({
blogPosts,
authorsMap,
}: {
blogPosts: BlogPost[];
authorsMap: AuthorsMap | undefined;
}): Record<string, BlogPost[]> {
return _.mapValues(authorsMap, (author, key) =>
blogPosts.filter((p) => p.metadata.authors.some((a) => a.key === key)),
);
}