/** * 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 {readDataFile, normalizeUrl} from '@docusaurus/utils'; import {Joi, URISchema} from '@docusaurus/utils-validation'; import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials'; import {normalizeImageUrl} from './authors'; import type {BlogContentPaths} from './types'; import type { AuthorAttributes, AuthorPage, AuthorsMap, AuthorWithKey, } from '@docusaurus/plugin-content-blog'; type AuthorInput = AuthorAttributes & { page?: boolean | AuthorPage; }; export type AuthorsMapInput = {[authorKey: string]: AuthorInput}; const AuthorPageSchema = Joi.object({ permalink: Joi.string().required(), }); const AuthorsMapInputSchema = Joi.object() .pattern( Joi.string(), Joi.object({ name: Joi.string(), url: URISchema, imageURL: URISchema, title: Joi.string(), email: Joi.string(), page: Joi.alternatives(Joi.bool(), AuthorPageSchema), socials: AuthorSocialsSchema, description: Joi.string(), }) .rename('image_url', 'imageURL') .or('name', 'imageURL') .unknown() .required() .messages({ 'object.base': '{#label} should be an author object containing properties like name, title, and imageURL.', 'any.required': '{#label} cannot be undefined. It should be an author object containing properties like name, title, and imageURL.', }), ) .messages({ 'object.base': "The authors map file should contain an object where each entry contains an author key and the corresponding author's data.", }); export function checkAuthorsMapPermalinkCollisions( authorsMap: AuthorsMap | undefined, ): void { if (!authorsMap) { return; } const permalinkCounts = _(authorsMap) // Filter to keep only authors with a page .pickBy((author) => !!author.page) // Group authors by their permalink .groupBy((author) => author.page?.permalink) // Filter to keep only permalinks with more than one author .pickBy((authors) => authors.length > 1) // Transform the object into an array of [permalink, authors] pairs .toPairs() .value(); if (permalinkCounts.length > 0) { const errorMessage = permalinkCounts .map( ([permalink, authors]) => `Permalink: ${permalink}\nAuthors: ${authors .map((author) => author.name || 'Unknown') .join(', ')}`, ) .join('\n'); throw new Error( `The following permalinks are duplicated:\n${errorMessage}`, ); } } function normalizeAuthor({ authorsBaseRoutePath, authorKey, baseUrl, author, }: { authorsBaseRoutePath: string; authorKey: string; baseUrl: string; author: AuthorInput; }): AuthorWithKey { function getAuthorPage(): AuthorPage | null { if (!author.page) { return null; } const slug = author.page === true ? _.kebabCase(authorKey) : author.page.permalink; return { permalink: normalizeUrl([authorsBaseRoutePath, slug]), }; } return { ...author, key: authorKey, page: getAuthorPage(), imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}), socials: author.socials ? normalizeSocials(author.socials) : undefined, }; } function normalizeAuthorsMap({ authorsBaseRoutePath, authorsMapInput, baseUrl, }: { authorsBaseRoutePath: string; authorsMapInput: AuthorsMapInput; baseUrl: string; }): AuthorsMap { return _.mapValues(authorsMapInput, (author, authorKey) => { return normalizeAuthor({authorsBaseRoutePath, authorKey, author, baseUrl}); }); } export function validateAuthorsMapInput(content: unknown): AuthorsMapInput { const {error, value} = AuthorsMapInputSchema.validate(content); if (error) { throw error; } return value; } async function getAuthorsMapInput(params: { authorsMapPath: string; contentPaths: BlogContentPaths; }): Promise { const content = await readDataFile({ filePath: params.authorsMapPath, contentPaths: params.contentPaths, }); return content ? validateAuthorsMapInput(content) : undefined; } export async function getAuthorsMap(params: { authorsMapPath: string; authorsBaseRoutePath: string; contentPaths: BlogContentPaths; baseUrl: string; }): Promise { const authorsMapInput = await getAuthorsMapInput(params); if (!authorsMapInput) { return undefined; } const authorsMap = normalizeAuthorsMap({authorsMapInput, ...params}); return authorsMap; } export function validateAuthorsMap(content: unknown): AuthorsMapInput { const {error, value} = AuthorsMapInputSchema.validate(content); if (error) { throw error; } return value; }