/** * 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 { return _.mapValues(authorsMap, (author, key) => blogPosts.filter((p) => p.metadata.authors.some((a) => a.key === key)), ); }