mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-19 03:57:01 +02:00
feat(v2): ability to add/override theme html metadatas (#3406)
* ability to add/override theme html metadatas see https://github.com/facebook/docusaurus/issues/3024 * refactor/fix validateThemeConfig tests
This commit is contained in:
parent
f49d8baf2f
commit
21852948ce
5 changed files with 72 additions and 29 deletions
|
@ -4,27 +4,15 @@
|
||||||
* This source code is licensed under the MIT license found in the
|
* This source code is licensed under the MIT license found in the
|
||||||
* LICENSE file in the root directory of this source tree.
|
* LICENSE file in the root directory of this source tree.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import merge from 'lodash/merge';
|
import merge from 'lodash/merge';
|
||||||
|
|
||||||
const {
|
const {ThemeConfigSchema, DEFAULT_CONFIG} = require('../validateThemeConfig');
|
||||||
validateThemeConfig,
|
|
||||||
DEFAULT_COLOR_MODE_CONFIG,
|
|
||||||
} = require('../validateThemeConfig');
|
|
||||||
|
|
||||||
const mergeDefault = (config) => merge({}, DEFAULT_COLOR_MODE_CONFIG, config);
|
const {normalizeThemeConfig} = require('@docusaurus/utils-validation');
|
||||||
|
|
||||||
function testValidateThemeConfig(themeConfig) {
|
function testValidateThemeConfig(themeConfig) {
|
||||||
function validate(schema, cfg) {
|
return normalizeThemeConfig(ThemeConfigSchema, themeConfig);
|
||||||
const {value, error} = schema.validate(cfg, {
|
|
||||||
convert: false,
|
|
||||||
});
|
|
||||||
if (error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return validateThemeConfig({themeConfig, validate});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('themeConfig', () => {
|
describe('themeConfig', () => {
|
||||||
|
@ -88,7 +76,7 @@ describe('themeConfig', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(testValidateThemeConfig(userConfig)).toEqual({
|
expect(testValidateThemeConfig(userConfig)).toEqual({
|
||||||
colorMode: DEFAULT_COLOR_MODE_CONFIG,
|
...DEFAULT_CONFIG,
|
||||||
...userConfig,
|
...userConfig,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -104,7 +92,7 @@ describe('themeConfig', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(testValidateThemeConfig(altTagConfig)).toEqual({
|
expect(testValidateThemeConfig(altTagConfig)).toEqual({
|
||||||
colorMode: DEFAULT_COLOR_MODE_CONFIG,
|
...DEFAULT_CONFIG,
|
||||||
...altTagConfig,
|
...altTagConfig,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -116,12 +104,15 @@ describe('themeConfig', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(testValidateThemeConfig(prismConfig)).toEqual({
|
expect(testValidateThemeConfig(prismConfig)).toEqual({
|
||||||
colorMode: DEFAULT_COLOR_MODE_CONFIG,
|
...DEFAULT_CONFIG,
|
||||||
...prismConfig,
|
...prismConfig,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('color mode config', () => {
|
describe('color mode config', () => {
|
||||||
|
const withDefaultValues = (colorMode) =>
|
||||||
|
merge({}, DEFAULT_CONFIG.colorMode, colorMode);
|
||||||
|
|
||||||
test('minimal config', () => {
|
test('minimal config', () => {
|
||||||
const colorMode = {
|
const colorMode = {
|
||||||
switchConfig: {
|
switchConfig: {
|
||||||
|
@ -129,7 +120,8 @@ describe('themeConfig', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||||
colorMode: mergeDefault(colorMode),
|
...DEFAULT_CONFIG,
|
||||||
|
colorMode: withDefaultValues(colorMode),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -151,21 +143,27 @@ describe('themeConfig', () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||||
colorMode: mergeDefault(colorMode),
|
...DEFAULT_CONFIG,
|
||||||
|
colorMode: withDefaultValues(colorMode),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('undefined config', () => {
|
test('undefined config', () => {
|
||||||
const colorMode = undefined;
|
const colorMode = undefined;
|
||||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||||
colorMode: mergeDefault(colorMode),
|
...DEFAULT_CONFIG,
|
||||||
|
colorMode: withDefaultValues(colorMode),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('empty config', () => {
|
test('empty config', () => {
|
||||||
const colorMode = {};
|
const colorMode = {};
|
||||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||||
colorMode: mergeDefault(colorMode),
|
...DEFAULT_CONFIG,
|
||||||
|
colorMode: {
|
||||||
|
...DEFAULT_CONFIG.colorMode,
|
||||||
|
...colorMode,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -174,7 +172,8 @@ describe('themeConfig', () => {
|
||||||
switchConfig: {},
|
switchConfig: {},
|
||||||
};
|
};
|
||||||
expect(testValidateThemeConfig({colorMode})).toEqual({
|
expect(testValidateThemeConfig({colorMode})).toEqual({
|
||||||
colorMode: mergeDefault(colorMode),
|
...DEFAULT_CONFIG,
|
||||||
|
colorMode: withDefaultValues(colorMode),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,11 +28,11 @@ function Providers({children}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Layout(props: Props): JSX.Element {
|
function Layout(props: Props): JSX.Element {
|
||||||
const {siteConfig = {}} = useDocusaurusContext();
|
const {siteConfig} = useDocusaurusContext();
|
||||||
const {
|
const {
|
||||||
favicon,
|
favicon,
|
||||||
title: siteTitle,
|
title: siteTitle,
|
||||||
themeConfig: {image: defaultImage},
|
themeConfig: {image: defaultImage, metadatas},
|
||||||
url: siteUrl,
|
url: siteUrl,
|
||||||
} = siteConfig;
|
} = siteConfig;
|
||||||
const {
|
const {
|
||||||
|
@ -48,13 +48,11 @@ function Layout(props: Props): JSX.Element {
|
||||||
const metaImage = image || defaultImage;
|
const metaImage = image || defaultImage;
|
||||||
const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
|
const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
|
||||||
const faviconUrl = useBaseUrl(favicon);
|
const faviconUrl = useBaseUrl(favicon);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Providers>
|
<Providers>
|
||||||
<Head>
|
<Head>
|
||||||
{/* TODO: Do not assume that it is in english language */}
|
{/* TODO: Do not assume that it is in english language */}
|
||||||
<html lang="en" />
|
<html lang="en" />
|
||||||
|
|
||||||
{metaTitle && <title>{metaTitle}</title>}
|
{metaTitle && <title>{metaTitle}</title>}
|
||||||
{metaTitle && <meta property="og:title" content={metaTitle} />}
|
{metaTitle && <meta property="og:title" content={metaTitle} />}
|
||||||
{favicon && <link rel="shortcut icon" href={faviconUrl} />}
|
{favicon && <link rel="shortcut icon" href={faviconUrl} />}
|
||||||
|
@ -74,6 +72,19 @@ function Layout(props: Props): JSX.Element {
|
||||||
{permalink && <link rel="canonical" href={siteUrl + permalink} />}
|
{permalink && <link rel="canonical" href={siteUrl + permalink} />}
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
|
<Head
|
||||||
|
// it's important to have an additional <Head> element here,
|
||||||
|
// as it allows react-helmet to override values set in previous <Head>
|
||||||
|
// ie we can override default metadatas such as "twitter:card"
|
||||||
|
// In same Head, the same meta would appear twice instead of overriding
|
||||||
|
// See react-helmet doc
|
||||||
|
>
|
||||||
|
{metadatas.map((metadata, i) => (
|
||||||
|
<meta key={`metadata_${i}`} {...metadata} />
|
||||||
|
))}
|
||||||
|
</Head>
|
||||||
|
|
||||||
<AnnouncementBar />
|
<AnnouncementBar />
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<div className="main-wrapper">{children}</div>
|
<div className="main-wrapper">{children}</div>
|
||||||
|
|
|
@ -19,7 +19,12 @@ const DEFAULT_COLOR_MODE_CONFIG = {
|
||||||
lightIconStyle: {},
|
lightIconStyle: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
exports.DEFAULT_COLOR_MODE_CONFIG = DEFAULT_COLOR_MODE_CONFIG;
|
|
||||||
|
const DEFAULT_CONFIG = {
|
||||||
|
colorMode: DEFAULT_COLOR_MODE_CONFIG,
|
||||||
|
metadatas: [],
|
||||||
|
};
|
||||||
|
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
||||||
|
|
||||||
const NavbarItemPosition = Joi.string().equal('left', 'right').default('left');
|
const NavbarItemPosition = Joi.string().equal('left', 'right').default('left');
|
||||||
|
|
||||||
|
@ -137,6 +142,15 @@ const ColorModeSchema = Joi.object({
|
||||||
}).default(DEFAULT_COLOR_MODE_CONFIG.switchConfig),
|
}).default(DEFAULT_COLOR_MODE_CONFIG.switchConfig),
|
||||||
}).default(DEFAULT_COLOR_MODE_CONFIG);
|
}).default(DEFAULT_COLOR_MODE_CONFIG);
|
||||||
|
|
||||||
|
// schema can probably be improved
|
||||||
|
const HtmlMetadataSchema = Joi.object({
|
||||||
|
id: Joi.string(),
|
||||||
|
name: Joi.string(),
|
||||||
|
property: Joi.string(),
|
||||||
|
content: Joi.string(),
|
||||||
|
itemprop: Joi.string(),
|
||||||
|
}).unknown();
|
||||||
|
|
||||||
const FooterLinkItemSchema = Joi.object({
|
const FooterLinkItemSchema = Joi.object({
|
||||||
to: Joi.string(),
|
to: Joi.string(),
|
||||||
href: URISchema,
|
href: URISchema,
|
||||||
|
@ -164,6 +178,9 @@ const ThemeConfigSchema = Joi.object({
|
||||||
}),
|
}),
|
||||||
colorMode: ColorModeSchema,
|
colorMode: ColorModeSchema,
|
||||||
image: Joi.string(),
|
image: Joi.string(),
|
||||||
|
metadatas: Joi.array()
|
||||||
|
.items(HtmlMetadataSchema)
|
||||||
|
.default(DEFAULT_CONFIG.metadatas),
|
||||||
announcementBar: Joi.object({
|
announcementBar: Joi.object({
|
||||||
id: Joi.string().default('announcement-bar'),
|
id: Joi.string().default('announcement-bar'),
|
||||||
content: Joi.string(),
|
content: Joi.string(),
|
||||||
|
@ -216,6 +233,7 @@ const ThemeConfigSchema = Joi.object({
|
||||||
additionalLanguages: Joi.array().items(Joi.string()),
|
additionalLanguages: Joi.array().items(Joi.string()),
|
||||||
}).unknown(),
|
}).unknown(),
|
||||||
});
|
});
|
||||||
|
exports.ThemeConfigSchema = ThemeConfigSchema;
|
||||||
|
|
||||||
exports.validateThemeConfig = ({validate, themeConfig}) => {
|
exports.validateThemeConfig = ({validate, themeConfig}) => {
|
||||||
return validate(ThemeConfigSchema, themeConfig);
|
return validate(ThemeConfigSchema, themeConfig);
|
||||||
|
|
|
@ -85,6 +85,20 @@ module.exports = {
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Metadatas
|
||||||
|
|
||||||
|
You can configure additional html metadatas (and override existing ones).
|
||||||
|
|
||||||
|
```js {4-6} title="docusaurus.config.js"
|
||||||
|
module.exports = {
|
||||||
|
// ...
|
||||||
|
themeConfig: {
|
||||||
|
metadatas: [{name: 'twitter:card', content: 'summary'}],
|
||||||
|
// ...
|
||||||
|
},
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### Announcement bar
|
### Announcement bar
|
||||||
|
|
||||||
Sometimes you want to announce something in your website. Just for such a case, you can add an announcement bar. This is a non-fixed and optionally dismissable panel above the navbar.
|
Sometimes you want to announce something in your website. Just for such a case, you can add an announcement bar. This is a non-fixed and optionally dismissable panel above the navbar.
|
||||||
|
|
|
@ -228,6 +228,7 @@ module.exports = {
|
||||||
darkTheme: require('prism-react-renderer/themes/dracula'),
|
darkTheme: require('prism-react-renderer/themes/dracula'),
|
||||||
},
|
},
|
||||||
image: 'img/docusaurus-soc.png',
|
image: 'img/docusaurus-soc.png',
|
||||||
|
// metadatas: [{name: 'twitter:card', content: 'summary'}],
|
||||||
gtag: {
|
gtag: {
|
||||||
trackingID: 'UA-141789564-1',
|
trackingID: 'UA-141789564-1',
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue