fix(blog): apply baseUrl to relative image in blog authors (#10440)

Co-authored-by: sebastien <lorber.sebastien@gmail.com>
This commit is contained in:
ozaki 2024-08-29 14:40:42 +02:00 committed by GitHub
parent 95880282b1
commit 02ed7d9132
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 333 additions and 10 deletions

View file

@ -1,4 +1,3 @@
JMarcey: JMarcey:
name: Joel Marcey name: Joel Marcey
title: Technical Lead & Developer Advocate at Facebook title: Technical Lead & Developer Advocate at Facebook
@ -32,3 +31,13 @@ lex111:
title: Open-source enthusiast title: Open-source enthusiast
url: https://github.com/lex111 url: https://github.com/lex111
image_url: https://github.com/lex111.png image_url: https://github.com/lex111.png
ozaki:
name: ozaki
title: ozaki
image_url: /ozaki.png
ozakione:
name: ozakione
title: ozakione
image_url: /img/ozaki.png

View file

@ -216,7 +216,7 @@ describe('getBlogPostAuthors', () => {
authorsMap: { authorsMap: {
slorber: { slorber: {
name: 'Sébastien Lorber', name: 'Sébastien Lorber',
imageURL: '/img/slorber.png', imageURL: '/baseUrl/img/slorber.png',
key: 'slorber', key: 'slorber',
page: null, page: null,
}, },
@ -419,7 +419,6 @@ describe('getBlogPostAuthors', () => {
frontMatter: { frontMatter: {
authors: ['yangshun', 'jmarcey', 'slorber'], authors: ['yangshun', 'jmarcey', 'slorber'],
}, },
authorsMap: { authorsMap: {
yangshun: {name: 'Yangshun Tay', key: 'yangshun', page: null}, yangshun: {name: 'Yangshun Tay', key: 'yangshun', page: null},
jmarcey: {name: 'Joel Marcey', key: 'jmarcey', page: null}, jmarcey: {name: 'Joel Marcey', key: 'jmarcey', page: null},
@ -486,6 +485,235 @@ describe('getBlogPostAuthors', () => {
Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time." Don't mix 'authors' with other existing 'author_*' front matter. Choose one or the other, not both at the same time."
`); `);
}); });
// Global author without baseUrl
it('getBlogPostAuthors do not modify global authors imageUrl without baseUrl', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: ['ozaki'],
},
authorsMap: {
ozaki: {
key: 'ozaki',
imageURL: '/ozaki.png',
page: null,
},
},
baseUrl: '/',
}),
).toEqual([
{
imageURL: '/ozaki.png',
key: 'ozaki',
page: null,
},
]);
});
// Global author with baseUrl
it('getBlogPostAuthors do not modify global authors imageUrl with baseUrl', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: ['ozaki'],
},
authorsMap: {
ozaki: {
key: 'ozaki',
imageURL: '/img/ozaki.png',
page: null,
},
},
baseUrl: '/img/',
}),
).toEqual([
{
imageURL: '/img/ozaki.png',
key: 'ozaki',
page: null,
},
]);
});
// Global author without baseUrl with a subfolder in img
it('getBlogPostAuthors do not modify globalAuthor imageUrl with subfolder without baseUrl', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: ['ozaki'],
},
authorsMap: {
ozaki: {
key: 'ozaki',
imageURL: '/img/ozaki.png',
page: null,
},
},
baseUrl: '/',
}),
).toEqual([
{
imageURL: '/img/ozaki.png',
key: 'ozaki',
page: null,
},
]);
});
// Global author with baseUrl with a subfolder in img
it('getBlogPostAuthors do not modify globalAuthor imageUrl with subfolder with baseUrl', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: ['ozaki'],
},
authorsMap: {
ozaki: {
key: 'ozaki',
imageURL: '/img/ozaki.png',
page: null,
},
},
baseUrl: '/img/',
}),
).toEqual([
{
imageURL: '/img/ozaki.png',
key: 'ozaki',
page: null,
},
]);
});
it('getBlogPostAuthors throws if global author imageURL does not have baseUrl', async () => {
expect(() =>
getBlogPostAuthors({
frontMatter: {
authors: ['ozaki'],
},
authorsMap: {
ozaki: {
key: 'ozaki',
imageURL: '/ozaki.png',
page: null,
},
},
baseUrl: '/baseUrl/',
}),
).toThrowErrorMatchingInlineSnapshot(
`"Docusaurus internal bug: global authors image /ozaki.png should start with the expected baseUrl=/baseUrl/"`,
);
});
it('getBlogPostAuthors do not throws if inline author imageURL is a link to a file', async () => {
const baseUrlTest = getBlogPostAuthors({
frontMatter: {
authors: [{imageURL: './ozaki.png'}],
},
authorsMap: undefined,
baseUrl: '/baseUrl/',
});
const withoutBaseUrlTest = getBlogPostAuthors({
frontMatter: {
authors: [{imageURL: './ozaki.png'}],
},
authorsMap: undefined,
baseUrl: '/',
});
expect(() => baseUrlTest).not.toThrow();
expect(baseUrlTest).toEqual([
{
imageURL: './ozaki.png',
key: null,
page: null,
},
]);
expect(() => withoutBaseUrlTest).not.toThrow();
expect(withoutBaseUrlTest).toEqual([
{
imageURL: './ozaki.png',
key: null,
page: null,
},
]);
});
// Inline author without baseUrl
it('getBlogPostAuthors can return imageURL without baseUrl for inline authors', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: [{imageURL: '/ozaki.png'}],
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([
{
imageURL: '/ozaki.png',
key: null,
page: null,
},
]);
});
// Inline author with baseUrl
it('getBlogPostAuthors normalize imageURL with baseUrl for inline authors', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: [{imageURL: '/ozaki.png'}],
},
authorsMap: undefined,
baseUrl: '/img/',
}),
).toEqual([
{
imageURL: '/img/ozaki.png',
key: null,
page: null,
},
]);
});
// Inline author without baseUrl with a subfolder in img
it('getBlogPostAuthors normalize imageURL from subfolder without baseUrl for inline authors', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: [{imageURL: '/img/ozaki.png'}],
},
authorsMap: undefined,
baseUrl: '/',
}),
).toEqual([
{
imageURL: '/img/ozaki.png',
key: null,
page: null,
},
]);
});
// Inline author with baseUrl with a subfolder in img
it('getBlogPostAuthors normalize imageURL from subfolder with baseUrl for inline authors', async () => {
expect(
getBlogPostAuthors({
frontMatter: {
authors: [{imageURL: '/img/ozaki.png'}],
},
authorsMap: undefined,
baseUrl: '/img/',
}),
).toEqual([
{
imageURL: '/img/img/ozaki.png',
key: null,
page: null,
},
]);
});
}); });
describe('groupBlogPostsByAuthorKey', () => { describe('groupBlogPostsByAuthorKey', () => {

View file

@ -80,6 +80,7 @@ describe('getAuthorsMap', () => {
contentPaths, contentPaths,
authorsMapPath: 'authors.yml', authorsMapPath: 'authors.yml',
authorsBaseRoutePath: '/authors', authorsBaseRoutePath: '/authors',
baseUrl: '/',
}), }),
).resolves.toBeDefined(); ).resolves.toBeDefined();
}); });
@ -90,6 +91,7 @@ describe('getAuthorsMap', () => {
contentPaths, contentPaths,
authorsMapPath: 'authors.json', authorsMapPath: 'authors.json',
authorsBaseRoutePath: '/authors', authorsBaseRoutePath: '/authors',
baseUrl: '/',
}), }),
).resolves.toBeDefined(); ).resolves.toBeDefined();
}); });
@ -100,9 +102,60 @@ describe('getAuthorsMap', () => {
contentPaths, contentPaths,
authorsMapPath: 'authors_does_not_exist.yml', authorsMapPath: 'authors_does_not_exist.yml',
authorsBaseRoutePath: '/authors', authorsBaseRoutePath: '/authors',
baseUrl: '/',
}), }),
).resolves.toBeUndefined(); ).resolves.toBeUndefined();
}); });
it('getAuthorsMap return imageURL with relative path', async () => {
const authorsMap = await getAuthorsMap({
contentPaths,
authorsMapPath: 'authors.yml',
authorsBaseRoutePath: '/authors',
baseUrl: '/',
});
expect(authorsMap?.ozaki?.imageURL).toBe('/ozaki.png');
});
it('getAuthorsMap normalize imageURL with baseUrl', async () => {
const authorsMap = await getAuthorsMap({
contentPaths,
authorsMapPath: 'authors.yml',
authorsBaseRoutePath: '/authors',
baseUrl: '/baseUrl/',
});
expect(authorsMap?.ozaki?.imageURL).toBe('/baseUrl/ozaki.png');
});
it('getAuthorsMap return imageURL with relative subdir path', async () => {
const authorsMap = await getAuthorsMap({
contentPaths,
authorsMapPath: 'authors.yml',
authorsBaseRoutePath: '/authors',
baseUrl: '/',
});
expect(authorsMap?.ozakione?.imageURL).toBe('/img/ozaki.png');
});
it('getAuthorsMap normalize imageURL with baseUrl and subdir same value', async () => {
const authorsMap = await getAuthorsMap({
contentPaths,
authorsMapPath: 'authors.yml',
authorsBaseRoutePath: '/authors',
baseUrl: '/img/',
});
expect(authorsMap?.ozakione?.imageURL).toBe('/img/img/ozaki.png');
});
it('getAuthorsMap normalize imageURL subdir with baseUrl', async () => {
const authorsMap = await getAuthorsMap({
contentPaths,
authorsMapPath: 'authors.yml',
authorsBaseRoutePath: '/authors',
baseUrl: '/blog/',
});
expect(authorsMap?.ozakione?.imageURL).toBe('/blog/img/ozaki.png');
});
}); });
describe('validateAuthorsMapInput', () => { describe('validateAuthorsMapInput', () => {

View file

@ -67,6 +67,7 @@ async function testGenerateFeeds(
contentPaths, contentPaths,
authorsMapPath: options.authorsMapPath, authorsMapPath: options.authorsMapPath,
authorsBaseRoutePath: '/authors', authorsBaseRoutePath: '/authors',
baseUrl: '/',
}); });
const blogPosts = await generateBlogPosts( const blogPosts = await generateBlogPosts(

View file

@ -21,18 +21,41 @@ type AuthorsParam = {
baseUrl: string; baseUrl: string;
}; };
function normalizeImageUrl({ export function normalizeImageUrl({
imageURL, imageURL,
baseUrl, baseUrl,
}: { }: {
imageURL: string | undefined; imageURL: string | undefined;
baseUrl: string; baseUrl: string;
}) { }): string | undefined {
return imageURL?.startsWith('/') return imageURL?.startsWith('/')
? normalizeUrl([baseUrl, imageURL]) ? normalizeUrl([baseUrl, imageURL])
: 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 // Legacy v1/early-v2 front matter fields
// We may want to deprecate those in favor of using only frontMatter.authors // We may want to deprecate those in favor of using only frontMatter.authors
// TODO Docusaurus v4: remove this legacy front matter // TODO Docusaurus v4: remove this legacy front matter
@ -116,13 +139,14 @@ ${Object.keys(authorsMap)
// Author def from authorsMap can be locally overridden by front matter // Author def from authorsMap can be locally overridden by front matter
...getAuthorsMapAuthor(frontMatterAuthor.key), ...getAuthorsMapAuthor(frontMatterAuthor.key),
...frontMatterAuthor, ...frontMatterAuthor,
}; } as Author;
return { return {
...author, ...author,
key: author.key ?? null, key: author.key ?? null,
page: author.page ?? null, page: author.page ?? null,
imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}), // global author images have already been normalized
imageURL: normalizeAuthorUrl({author, baseUrl}),
}; };
} }
} }

View file

@ -9,12 +9,13 @@ import _ from 'lodash';
import {readDataFile, normalizeUrl} from '@docusaurus/utils'; import {readDataFile, normalizeUrl} from '@docusaurus/utils';
import {Joi, URISchema} from '@docusaurus/utils-validation'; import {Joi, URISchema} from '@docusaurus/utils-validation';
import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials'; import {AuthorSocialsSchema, normalizeSocials} from './authorsSocials';
import {normalizeImageUrl} from './authors';
import type {BlogContentPaths} from './types'; import type {BlogContentPaths} from './types';
import type { import type {
Author,
AuthorAttributes, AuthorAttributes,
AuthorPage, AuthorPage,
AuthorsMap, AuthorsMap,
AuthorWithKey,
} from '@docusaurus/plugin-content-blog'; } from '@docusaurus/plugin-content-blog';
type AuthorInput = AuthorAttributes & { type AuthorInput = AuthorAttributes & {
@ -93,12 +94,14 @@ export function checkAuthorsMapPermalinkCollisions(
function normalizeAuthor({ function normalizeAuthor({
authorsBaseRoutePath, authorsBaseRoutePath,
authorKey, authorKey,
baseUrl,
author, author,
}: { }: {
authorsBaseRoutePath: string; authorsBaseRoutePath: string;
authorKey: string; authorKey: string;
baseUrl: string;
author: AuthorInput; author: AuthorInput;
}): Author & {key: string} { }): AuthorWithKey {
function getAuthorPage(): AuthorPage | null { function getAuthorPage(): AuthorPage | null {
if (!author.page) { if (!author.page) {
return null; return null;
@ -114,6 +117,7 @@ function normalizeAuthor({
...author, ...author,
key: authorKey, key: authorKey,
page: getAuthorPage(), page: getAuthorPage(),
imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}),
socials: author.socials ? normalizeSocials(author.socials) : undefined, socials: author.socials ? normalizeSocials(author.socials) : undefined,
}; };
} }
@ -121,12 +125,14 @@ function normalizeAuthor({
function normalizeAuthorsMap({ function normalizeAuthorsMap({
authorsBaseRoutePath, authorsBaseRoutePath,
authorsMapInput, authorsMapInput,
baseUrl,
}: { }: {
authorsBaseRoutePath: string; authorsBaseRoutePath: string;
authorsMapInput: AuthorsMapInput; authorsMapInput: AuthorsMapInput;
baseUrl: string;
}): AuthorsMap { }): AuthorsMap {
return _.mapValues(authorsMapInput, (author, authorKey) => { return _.mapValues(authorsMapInput, (author, authorKey) => {
return normalizeAuthor({authorsBaseRoutePath, authorKey, author}); return normalizeAuthor({authorsBaseRoutePath, authorKey, author, baseUrl});
}); });
} }
@ -153,6 +159,7 @@ export async function getAuthorsMap(params: {
authorsMapPath: string; authorsMapPath: string;
authorsBaseRoutePath: string; authorsBaseRoutePath: string;
contentPaths: BlogContentPaths; contentPaths: BlogContentPaths;
baseUrl: string;
}): Promise<AuthorsMap | undefined> { }): Promise<AuthorsMap | undefined> {
const authorsMapInput = await getAuthorsMapInput(params); const authorsMapInput = await getAuthorsMapInput(params);
if (!authorsMapInput) { if (!authorsMapInput) {

View file

@ -271,6 +271,7 @@ export default async function pluginContentBlog(
routeBasePath, routeBasePath,
authorsBasePath, authorsBasePath,
]), ]),
baseUrl,
}); });
checkAuthorsMapPermalinkCollisions(authorsMap); checkAuthorsMapPermalinkCollisions(authorsMap);