Skip to content

Commit 730ce48

Browse files
authored
feat(theme): make it possible to provide your own page title formatter (#11090)
1 parent 5b944d6 commit 730ce48

File tree

17 files changed

+386
-107
lines changed

17 files changed

+386
-107
lines changed

packages/docusaurus-module-type-aliases/src/index.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,15 @@ declare module '@theme/Root' {
122122
export default function Root({children}: Props): ReactNode;
123123
}
124124

125+
declare module '@theme/ThemeProvider' {
126+
import type {ReactNode} from 'react';
127+
128+
export interface Props {
129+
readonly children: ReactNode;
130+
}
131+
export default function ThemeProvider({children}: Props): ReactNode;
132+
}
133+
125134
declare module '@theme/SiteMetadata' {
126135
import type {ReactNode} from 'react';
127136

packages/docusaurus-plugin-content-blog/src/__tests__/__fixtures__/website/build-snap/blog/atom.xsl

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/docusaurus-theme-classic/src/theme-classic.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,6 +1569,17 @@ declare module '@theme/ThemedImage' {
15691569
export default function ThemedImage(props: Props): ReactNode;
15701570
}
15711571

1572+
declare module '@theme/ThemeProvider/TitleFormatter' {
1573+
import type {ReactNode} from 'react';
1574+
1575+
export interface Props {
1576+
readonly children: ReactNode;
1577+
}
1578+
export default function ThemeProviderTitleFormatter({
1579+
children,
1580+
}: Props): ReactNode;
1581+
}
1582+
15721583
declare module '@theme/Details' {
15731584
import {Details, type DetailsProps} from '@docusaurus/theme-common/Details';
15741585

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import React, {type ComponentProps, type ReactNode} from 'react';
9+
import {TitleFormatterProvider} from '@docusaurus/theme-common/internal';
10+
import type {Props} from '@theme/ThemeProvider/TitleFormatter';
11+
12+
type FormatterProp = ComponentProps<typeof TitleFormatterProvider>['formatter'];
13+
14+
const formatter: FormatterProp = (params) => {
15+
// Add your own title formatting logic here!
16+
return params.defaultFormatter(params);
17+
};
18+
19+
export default function ThemeProviderTitleFormatter({
20+
children,
21+
}: Props): ReactNode {
22+
return (
23+
<TitleFormatterProvider formatter={formatter}>
24+
{children}
25+
</TitleFormatterProvider>
26+
);
27+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import React, {type ReactNode} from 'react';
9+
import TitleFormatterProvider from '@theme/ThemeProvider/TitleFormatter';
10+
import type {Props} from '@theme/ThemeProvider';
11+
12+
export default function ThemeProvider({children}: Props): ReactNode {
13+
return <TitleFormatterProvider>{children}</TitleFormatterProvider>;
14+
}

packages/docusaurus-theme-common/src/internal.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ export {
4343

4444
export {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
4545

46-
export {useTitleFormatter} from './utils/generalUtils';
46+
export {
47+
TitleFormatterProvider,
48+
useTitleFormatter,
49+
} from './utils/titleFormatterUtils';
4750

4851
export {useLocationChange} from './utils/useLocationChange';
4952

packages/docusaurus-theme-common/src/utils/__tests__/generalUtils.test.tsx

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {TitleFormatterFnDefault} from '../titleFormatterUtils';
9+
10+
describe('TitleFormatterFnDefault', () => {
11+
it('works', () => {
12+
expect(
13+
TitleFormatterFnDefault({
14+
title: 'a page',
15+
siteTitle: 'my site',
16+
titleDelimiter: '·',
17+
}),
18+
).toBe('a page · my site');
19+
});
20+
21+
it('ignores empty title', () => {
22+
expect(
23+
TitleFormatterFnDefault({
24+
title: ' ',
25+
siteTitle: 'my site',
26+
titleDelimiter: '·',
27+
}),
28+
).toBe('my site');
29+
});
30+
31+
it('does not duplicate site title', () => {
32+
// Users may pass <Layout title={siteTitle}> leading to duplicate titles
33+
// By default it's preferable to avoid duplicate siteTitle
34+
// See also https://github.com/facebook/docusaurus/issues/5878#issuecomment-961505856
35+
expect(
36+
TitleFormatterFnDefault({
37+
title: 'my site',
38+
siteTitle: 'my site',
39+
titleDelimiter: '·',
40+
}),
41+
).toBe('my site');
42+
});
43+
});

packages/docusaurus-theme-common/src/utils/generalUtils.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/docusaurus-theme-common/src/utils/metadataUtils.tsx

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import clsx from 'clsx';
1010
import Head from '@docusaurus/Head';
1111
import useRouteContext from '@docusaurus/useRouteContext';
1212
import {useBaseUrlUtils} from '@docusaurus/useBaseUrl';
13-
import {useTitleFormatter} from './generalUtils';
13+
import {useTitleFormatter} from './titleFormatterUtils';
1414

1515
type PageMetadataProps = {
1616
readonly title?: string;
@@ -20,6 +20,55 @@ type PageMetadataProps = {
2020
readonly children?: ReactNode;
2121
};
2222

23+
function TitleMetadata({title}: {title: string}) {
24+
const titleFormatter = useTitleFormatter();
25+
const formattedTitle = titleFormatter.format(title);
26+
return (
27+
<Head>
28+
<title>{formattedTitle}</title>
29+
<meta property="og:title" content={formattedTitle} />
30+
</Head>
31+
);
32+
}
33+
34+
function DescriptionMetadata({description}: {description: string}) {
35+
return (
36+
<Head>
37+
<meta name="description" content={description} />
38+
<meta property="og:description" content={description} />
39+
</Head>
40+
);
41+
}
42+
43+
function ImageMetadata({image}: {image: string}) {
44+
const {withBaseUrl} = useBaseUrlUtils();
45+
const pageImage = withBaseUrl(image, {absolute: true});
46+
return (
47+
<Head>
48+
<meta property="og:image" content={pageImage} />
49+
<meta name="twitter:image" content={pageImage} />
50+
</Head>
51+
);
52+
}
53+
54+
function KeywordsMetadata({
55+
keywords,
56+
}: {
57+
keywords: PageMetadataProps['keywords'];
58+
}) {
59+
return (
60+
<Head>
61+
<meta
62+
name="keywords"
63+
content={
64+
// https://github.com/microsoft/TypeScript/issues/17002
65+
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
66+
}
67+
/>
68+
</Head>
69+
);
70+
}
71+
2372
/**
2473
* Helper component to manipulate page metadata and override site defaults.
2574
* Works in the same way as Helmet.
@@ -31,33 +80,14 @@ export function PageMetadata({
3180
image,
3281
children,
3382
}: PageMetadataProps): ReactNode {
34-
const pageTitle = useTitleFormatter(title);
35-
const {withBaseUrl} = useBaseUrlUtils();
36-
const pageImage = image ? withBaseUrl(image, {absolute: true}) : undefined;
37-
3883
return (
39-
<Head>
40-
{title && <title>{pageTitle}</title>}
41-
{title && <meta property="og:title" content={pageTitle} />}
42-
43-
{description && <meta name="description" content={description} />}
44-
{description && <meta property="og:description" content={description} />}
45-
46-
{keywords && (
47-
<meta
48-
name="keywords"
49-
content={
50-
// https://github.com/microsoft/TypeScript/issues/17002
51-
(Array.isArray(keywords) ? keywords.join(',') : keywords) as string
52-
}
53-
/>
54-
)}
55-
56-
{pageImage && <meta property="og:image" content={pageImage} />}
57-
{pageImage && <meta name="twitter:image" content={pageImage} />}
58-
59-
{children}
60-
</Head>
84+
<>
85+
{title && <TitleMetadata title={title} />}
86+
{description && <DescriptionMetadata description={description} />}
87+
{keywords && <KeywordsMetadata keywords={keywords} />}
88+
{image && <ImageMetadata image={image} />}
89+
{children && <Head>{children}</Head>}
90+
</>
6191
);
6292
}
6393

0 commit comments

Comments
 (0)