feat(plugin-blog): multi-authors support + authors.yml global configuration (#5396)

* Complete function

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* A lot of blank lines

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* More lenient validation

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Remove or

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Simpler logic

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Expand docs

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Better docs

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Dogfood

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* More writeup

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Polish

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Polish

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Move mergeAuthorMap to authors.ts

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Unbreak relative assets

* Update docs

* Clarify in docs

* simplify feed authors

* rename authorMap -> authorsMap

* mergeAuthorsMap -> getBlogPostAuthors

* website => 5 blog posts per page

* improve authors map file

* Extract new theme authors components + display in row

* add comment for meta array syntaxes

* blog => getPathsToWatch should watch authorsMap file

* remove useless v1 blog FBID frontmatter

* keep older frontmatter syntax for now

* revert blog frontmatter

* Better console message

* better blog authors frontmatter impl

* add multi authors to beta blog post + fix some authors margins

* fix React key

* Refactor: mdx loader should support a more flexible assets system (poc, not documented yet)

* better display of blog post authors: adapt layout to authors count + add line clamp

* smaller local image

* fix blog feed tests

* fix blog frontmatter tests + improve validation schema

* add more frontmatter tests

* add tests for getAuthorsMapFilePath

* tests for validateAuthorsMapFile

* add tests for readAuthorsMapFile

* test getAuthorsMap

* exhaustive tests for getBlogPostAuthors

* fix remaining tests

* missing blog plugin author tests

* fix windows tests

* improve blog multi-author's doc

* Use new format in init template

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* Improve error message

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* update feed snapshot

* blog authors: limit to 2 cols + fix margins for no authors

* minor doc improvements

* better init template blog posts, demonstrating Blog features

* replace the legacy blog author frontmatter in remaining places

* Prefer using clsx

Signed-off-by: Josh-Cena <sidachen2003@gmail.com>

* cleanup getColClassName

* remove blog author name/title line-clamping

Co-authored-by: slorber <lorber.sebastien@gmail.com>
This commit is contained in:
Joshua Chen 2021-08-26 18:21:58 +08:00 committed by GitHub
parent 8779c8ff4a
commit 493225a3c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
79 changed files with 1871 additions and 285 deletions

View file

@ -0,0 +1,202 @@
/**
* 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 fs from 'fs-extra';
import chalk from 'chalk';
import path from 'path';
import {Author, BlogContentPaths} from './types';
import {findFolderContainingFile} from '@docusaurus/utils';
import {Joi, URISchema} from '@docusaurus/utils-validation';
import {
BlogPostFrontMatter,
BlogPostFrontMatterAuthor,
BlogPostFrontMatterAuthors,
} from './blogFrontMatter';
import {getContentPathList} from './blogUtils';
import Yaml from 'js-yaml';
export type AuthorsMap = Record<string, Author>;
const AuthorsMapSchema = Joi.object<AuthorsMap>().pattern(
Joi.string(),
Joi.object({
name: Joi.string().required(),
url: URISchema,
imageURL: URISchema,
title: Joi.string(),
})
.rename('image_url', 'imageURL')
.unknown()
.required(),
);
export function validateAuthorsMapFile(content: unknown): AuthorsMap {
return Joi.attempt(content, AuthorsMapSchema);
}
export async function readAuthorsMapFile(
filePath: string,
): Promise<AuthorsMap | undefined> {
if (await fs.pathExists(filePath)) {
const contentString = await fs.readFile(filePath, {encoding: 'utf8'});
const parse =
filePath.endsWith('.yml') || filePath.endsWith('.yaml')
? Yaml.load
: JSON.parse;
try {
const unsafeContent = parse(contentString);
return validateAuthorsMapFile(unsafeContent);
} catch (e) {
// TODO replace later by error cause: see https://v8.dev/features/error-cause
console.error(chalk.red('The author list file looks invalid!'));
throw e;
}
}
return undefined;
}
type AuthorsMapParams = {
authorsMapPath: string;
contentPaths: BlogContentPaths;
};
export async function getAuthorsMapFilePath({
authorsMapPath,
contentPaths,
}: AuthorsMapParams): Promise<string | undefined> {
// Useful to load an eventually localize authors map
const contentPath = await findFolderContainingFile(
getContentPathList(contentPaths),
authorsMapPath,
);
if (contentPath) {
return path.join(contentPath, authorsMapPath);
}
return undefined;
}
export async function getAuthorsMap(
params: AuthorsMapParams,
): Promise<AuthorsMap | undefined> {
const filePath = await getAuthorsMapFilePath(params);
if (!filePath) {
return undefined;
}
try {
return await readAuthorsMapFile(filePath);
} catch (e) {
// TODO replace later by error cause, see https://v8.dev/features/error-cause
console.error(
chalk.red(`Couldn't read blog authors map at path ${filePath}`),
);
throw e;
}
}
type AuthorsParam = {
frontMatter: BlogPostFrontMatter;
authorsMap: AuthorsMap | undefined;
};
// Legacy v1/early-v2 frontmatter fields
// We may want to deprecate those in favor of using only frontMatter.authors
function getFrontMatterAuthorLegacy(
frontMatter: BlogPostFrontMatter,
): BlogPostFrontMatterAuthor | undefined {
const name = frontMatter.author;
const title = frontMatter.author_title ?? frontMatter.authorTitle;
const url = frontMatter.author_url ?? frontMatter.authorURL;
const imageURL = frontMatter.author_image_url ?? frontMatter.authorImageURL;
// Shouldn't we require at least an author name?
if (name || title || url || imageURL) {
return {
name,
title,
url,
imageURL,
};
}
return undefined;
}
function normalizeFrontMatterAuthors(
frontMatterAuthors: BlogPostFrontMatterAuthors = [],
): BlogPostFrontMatterAuthor[] {
function normalizeAuthor(
authorInput: string | BlogPostFrontMatterAuthor,
): BlogPostFrontMatterAuthor {
if (typeof authorInput === 'string') {
// Technically, we could allow users to provide an author's name here
// IMHO it's better to only support keys here
// Reason: a typo in a key would fallback to becoming a name and may end-up un-noticed
return {key: authorInput};
}
return authorInput;
}
return Array.isArray(frontMatterAuthors)
? frontMatterAuthors.map(normalizeAuthor)
: [normalizeAuthor(frontMatterAuthors)];
}
function getFrontMatterAuthors(params: AuthorsParam): Author[] {
const {authorsMap} = params;
const frontMatterAuthors = normalizeFrontMatterAuthors(
params.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 {
return {
// Author def from authorsMap can be locally overridden by frontmatter
...getAuthorsMapAuthor(frontMatterAuthor.key),
...frontMatterAuthor,
};
}
return frontMatterAuthors.map(toAuthor);
}
export function getBlogPostAuthors(params: AuthorsParam): Author[] {
const authorLegacy = getFrontMatterAuthorLegacy(params.frontMatter);
const authors = getFrontMatterAuthors(params);
if (authorLegacy) {
// Technically, we could allow mixing legacy/authors frontmatter, but do we really want to?
if (authors.length > 0) {
throw new Error(
`To declare blog post authors, use the 'authors' FrontMatter in priority.
Don't mix 'authors' with other existing 'author_*' FrontMatter. Choose one or the other, not both at the same time.`,
);
}
return [authorLegacy];
}
return authors;
}