diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml index 1d0cb11cd6ea..0dffb1f16c8a 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/authorsMapFiles/authors.yml @@ -1,4 +1,3 @@ - JMarcey: name: Joel Marcey title: Technical Lead & Developer Advocate at Facebook @@ -32,3 +31,13 @@ lex111: title: Open-source enthusiast url: https://github.com/lex111 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 diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts index 7072e8124caa..c1bf962c90ac 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authors.test.ts @@ -216,7 +216,7 @@ describe('getBlogPostAuthors', () => { authorsMap: { slorber: { name: 'Sébastien Lorber', - imageURL: '/img/slorber.png', + imageURL: '/baseUrl/img/slorber.png', key: 'slorber', page: null, }, @@ -419,7 +419,6 @@ describe('getBlogPostAuthors', () => { frontMatter: { authors: ['yangshun', 'jmarcey', 'slorber'], }, - authorsMap: { yangshun: {name: 'Yangshun Tay', key: 'yangshun', 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." `); }); + + // 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', () => { diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts index b6393ede6854..f9442ee82688 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/authorsMap.test.ts @@ -80,6 +80,7 @@ describe('getAuthorsMap', () => { contentPaths, authorsMapPath: 'authors.yml', authorsBaseRoutePath: '/authors', + baseUrl: '/', }), ).resolves.toBeDefined(); }); @@ -90,6 +91,7 @@ describe('getAuthorsMap', () => { contentPaths, authorsMapPath: 'authors.json', authorsBaseRoutePath: '/authors', + baseUrl: '/', }), ).resolves.toBeDefined(); }); @@ -100,9 +102,60 @@ describe('getAuthorsMap', () => { contentPaths, authorsMapPath: 'authors_does_not_exist.yml', authorsBaseRoutePath: '/authors', + baseUrl: '/', }), ).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', () => { diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts index 3cca6f94aaf9..96d0d857d706 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/feed.test.ts @@ -67,6 +67,7 @@ async function testGenerateFeeds( contentPaths, authorsMapPath: options.authorsMapPath, authorsBaseRoutePath: '/authors', + baseUrl: '/', }); const blogPosts = await generateBlogPosts( diff --git a/packages/docusaurus-plugin-content-blog/src/authors.ts b/packages/docusaurus-plugin-content-blog/src/authors.ts index 1fe0ef923815..bc6b152157b2 100644 --- a/packages/docusaurus-plugin-content-blog/src/authors.ts +++ b/packages/docusaurus-plugin-content-blog/src/authors.ts @@ -21,18 +21,41 @@ type AuthorsParam = { baseUrl: string; }; -function normalizeImageUrl({ +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 @@ -116,13 +139,14 @@ ${Object.keys(authorsMap) // 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, - imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}), + // global author images have already been normalized + imageURL: normalizeAuthorUrl({author, baseUrl}), }; } } diff --git a/packages/docusaurus-plugin-content-blog/src/authorsMap.ts b/packages/docusaurus-plugin-content-blog/src/authorsMap.ts index d1378aa3f26d..e5c358b422f5 100644 --- a/packages/docusaurus-plugin-content-blog/src/authorsMap.ts +++ b/packages/docusaurus-plugin-content-blog/src/authorsMap.ts @@ -9,12 +9,13 @@ 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 { - Author, AuthorAttributes, AuthorPage, AuthorsMap, + AuthorWithKey, } from '@docusaurus/plugin-content-blog'; type AuthorInput = AuthorAttributes & { @@ -93,12 +94,14 @@ export function checkAuthorsMapPermalinkCollisions( function normalizeAuthor({ authorsBaseRoutePath, authorKey, + baseUrl, author, }: { authorsBaseRoutePath: string; authorKey: string; + baseUrl: string; author: AuthorInput; -}): Author & {key: string} { +}): AuthorWithKey { function getAuthorPage(): AuthorPage | null { if (!author.page) { return null; @@ -114,6 +117,7 @@ function normalizeAuthor({ ...author, key: authorKey, page: getAuthorPage(), + imageURL: normalizeImageUrl({imageURL: author.imageURL, baseUrl}), socials: author.socials ? normalizeSocials(author.socials) : undefined, }; } @@ -121,12 +125,14 @@ function normalizeAuthor({ function normalizeAuthorsMap({ authorsBaseRoutePath, authorsMapInput, + baseUrl, }: { authorsBaseRoutePath: string; authorsMapInput: AuthorsMapInput; + baseUrl: string; }): AuthorsMap { 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; authorsBaseRoutePath: string; contentPaths: BlogContentPaths; + baseUrl: string; }): Promise { const authorsMapInput = await getAuthorsMapInput(params); if (!authorsMapInput) { diff --git a/packages/docusaurus-plugin-content-blog/src/index.ts b/packages/docusaurus-plugin-content-blog/src/index.ts index e40cfeab6196..8325cc06d302 100644 --- a/packages/docusaurus-plugin-content-blog/src/index.ts +++ b/packages/docusaurus-plugin-content-blog/src/index.ts @@ -271,6 +271,7 @@ export default async function pluginContentBlog( routeBasePath, authorsBasePath, ]), + baseUrl, }); checkAuthorsMapPermalinkCollisions(authorsMap);