docusaurus/packages/docusaurus-plugin-content-blog/src/authorsMap.ts
ozaki 02ed7d9132
fix(blog): apply baseUrl to relative image in blog authors (#10440)
Co-authored-by: sebastien <lorber.sebastien@gmail.com>
2024-08-29 14:40:42 +02:00

178 lines
4.8 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 {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<AuthorPage>({
permalink: Joi.string().required(),
});
const AuthorsMapInputSchema = Joi.object<AuthorsMapInput>()
.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<AuthorsMapInput | undefined> {
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<AuthorsMap | undefined> {
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;
}