/** * 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. */ const {Joi, URISchema} = require('@docusaurus/utils-validation'); const DEFAULT_DOCS_CONFIG = { versionPersistence: 'localStorage', }; const DocsSchema = Joi.object({ versionPersistence: Joi.string() .equal('localStorage', 'none') .default(DEFAULT_DOCS_CONFIG.versionPersistence), }).default(DEFAULT_DOCS_CONFIG); const DEFAULT_COLOR_MODE_CONFIG = { defaultMode: 'light', disableSwitch: false, respectPrefersColorScheme: false, switchConfig: { darkIcon: '🌜', darkIconStyle: {}, lightIcon: '🌞', lightIconStyle: {}, }, }; const DEFAULT_CONFIG = { colorMode: DEFAULT_COLOR_MODE_CONFIG, docs: DEFAULT_DOCS_CONFIG, metadatas: [], prism: { additionalLanguages: [], }, navbar: { hideOnScroll: false, items: [], }, hideableSidebar: false, }; exports.DEFAULT_CONFIG = DEFAULT_CONFIG; const NavbarItemPosition = Joi.string().equal('left', 'right').default('left'); const BaseNavbarItemSchema = Joi.object({ to: Joi.string(), href: URISchema, label: Joi.string(), className: Joi.string(), prependBaseUrlToHref: Joi.string(), }) // We allow any unknown attributes on the links // (users may need additional attributes like target, aria-role, data-customAttribute...) .unknown(); // TODO we should probably create a custom navbar item type "dropdown" // having this recursive structure is bad because we only support 2 levels // + parent/child don't have exactly the same props const DefaultNavbarItemSchema = BaseNavbarItemSchema.append({ items: Joi.array().optional().items(BaseNavbarItemSchema), position: NavbarItemPosition, activeBasePath: Joi.string(), activeBaseRegex: Joi.string(), }); // TODO the dropdown parent item can have no href/to // should check should not apply to dropdown parent item // .xor('href', 'to'); const DocsVersionNavbarItemSchema = Joi.object({ type: Joi.string().equal('docsVersion').required(), position: NavbarItemPosition, label: Joi.string(), to: Joi.string(), docsPluginId: Joi.string(), className: Joi.string(), }); const DocsVersionDropdownNavbarItemSchema = Joi.object({ type: Joi.string().equal('docsVersionDropdown').required(), position: NavbarItemPosition, docsPluginId: Joi.string(), dropdownActiveClassDisabled: Joi.boolean(), dropdownItemsBefore: Joi.array().items(BaseNavbarItemSchema).default([]), dropdownItemsAfter: Joi.array().items(BaseNavbarItemSchema).default([]), className: Joi.string(), }); const DocItemSchema = Joi.object({ type: Joi.string().equal('doc').required(), position: NavbarItemPosition, docId: Joi.string().required(), label: Joi.string(), docsPluginId: Joi.string(), activeSidebarClassName: Joi.string().default('navbar__link--active'), className: Joi.string(), }); const LocaleDropdownNavbarItemSchema = Joi.object({ type: Joi.string().equal('localeDropdown').required(), position: NavbarItemPosition, dropdownItemsBefore: Joi.array().items(BaseNavbarItemSchema).default([]), dropdownItemsAfter: Joi.array().items(BaseNavbarItemSchema).default([]), className: Joi.string(), }); const SearchItemSchema = Joi.object({ type: Joi.string().equal('search').required(), position: NavbarItemPosition, }); // Can this be made easier? :/ const isOfType = (type) => { let typeSchema = Joi.string().required(); // because equal(undefined) is not supported :/ if (type) { typeSchema = typeSchema.equal(type); } return Joi.object({ type: typeSchema, }) .unknown() .required(); }; const NavbarItemSchema = Joi.object().when({ switch: [ { is: isOfType('docsVersion'), then: DocsVersionNavbarItemSchema, }, { is: isOfType('docsVersionDropdown'), then: DocsVersionDropdownNavbarItemSchema, }, { is: isOfType('doc'), then: DocItemSchema, }, { is: isOfType('localeDropdown'), then: LocaleDropdownNavbarItemSchema, }, { is: isOfType('search'), then: SearchItemSchema, }, { is: isOfType(undefined), then: Joi.forbidden().messages({ 'any.unknown': 'Bad navbar item type {.type}', }), }, ], otherwise: DefaultNavbarItemSchema, }); /* const NavbarItemSchema = Joi.object({ type: Joi.string().only(['docsVersion']) }) .when(Joi.object({ type: 'docsVersion' }).unknown(), { then: Joi.object({ pepperoni: Joi.boolean() }) }) .when(Joi.object().unknown(), { then: Joi.object({ croutons: Joi.boolean() }) }) */ /* const NavbarItemSchema = Joi.object().when('type', { is: Joi.valid('docsVersion'), then: DocsVersionNavbarItemSchema, otherwise: DefaultNavbarItemSchema, }); */ const ColorModeSchema = Joi.object({ defaultMode: Joi.string() .equal('dark', 'light') .default(DEFAULT_COLOR_MODE_CONFIG.defaultMode), disableSwitch: Joi.bool().default(DEFAULT_COLOR_MODE_CONFIG.disableSwitch), respectPrefersColorScheme: Joi.bool().default( DEFAULT_COLOR_MODE_CONFIG.respectPrefersColorScheme, ), switchConfig: Joi.object({ darkIcon: Joi.string().default( DEFAULT_COLOR_MODE_CONFIG.switchConfig.darkIcon, ), darkIconStyle: Joi.object().default( DEFAULT_COLOR_MODE_CONFIG.switchConfig.darkIconStyle, ), lightIcon: Joi.string().default( DEFAULT_COLOR_MODE_CONFIG.switchConfig.lightIcon, ), lightIconStyle: Joi.object().default( DEFAULT_COLOR_MODE_CONFIG.switchConfig.lightIconStyle, ), }).default(DEFAULT_COLOR_MODE_CONFIG.switchConfig), }).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({ to: Joi.string(), href: URISchema, html: Joi.string(), label: Joi.string(), }) .xor('to', 'href', 'html') .with('to', 'label') .with('href', 'label') .nand('html', 'label') // We allow any unknown attributes on the links // (users may need additional attributes like target, aria-role, data-customAttribute...) .unknown(); const CustomCssSchema = Joi.alternatives() .try(Joi.array().items(Joi.string().required()), Joi.string().required()) .optional(); const ThemeConfigSchema = Joi.object({ // TODO temporary (@alpha-58) disableDarkMode: Joi.any().forbidden(false).messages({ 'any.unknown': 'disableDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.disableSwitch = true', }), // TODO temporary (@alpha-58) defaultDarkMode: Joi.any().forbidden(false).messages({ 'any.unknown': 'defaultDarkMode theme config is deprecated. Please use the new colorMode attribute. You likely want: config.themeConfig.colorMode.defaultMode = "dark"', }), customCss: CustomCssSchema, colorMode: ColorModeSchema, image: Joi.string(), docs: DocsSchema, metadatas: Joi.array() .items(HtmlMetadataSchema) .default(DEFAULT_CONFIG.metadatas), announcementBar: Joi.object({ id: Joi.string().default('announcement-bar'), content: Joi.string(), backgroundColor: Joi.string().default('#fff'), textColor: Joi.string().default('#000'), isCloseable: Joi.bool().default(true), }).optional(), navbar: Joi.object({ style: Joi.string().equal('dark', 'primary'), hideOnScroll: Joi.bool().default(DEFAULT_CONFIG.navbar.hideOnScroll), // TODO temporary (@alpha-58) links: Joi.any().forbidden().messages({ 'any.unknown': 'themeConfig.navbar.links has been renamed as themeConfig.navbar.items', }), items: Joi.array() .items(NavbarItemSchema) .default(DEFAULT_CONFIG.navbar.items), title: Joi.string().allow('', null), logo: Joi.object({ alt: Joi.string().allow(''), src: Joi.string().required(), srcDark: Joi.string(), href: Joi.string(), target: Joi.string(), }), }).default(DEFAULT_CONFIG.navbar), footer: Joi.object({ style: Joi.string().equal('dark', 'light').default('light'), logo: Joi.object({ alt: Joi.string().allow(''), src: Joi.string(), srcDark: Joi.string(), href: Joi.string(), }), copyright: Joi.string(), links: Joi.array() .items( Joi.object({ title: Joi.string().allow(null), items: Joi.array().items(FooterLinkItemSchema).default([]), }), ) .default([]), }).optional(), prism: Joi.object({ theme: Joi.object({ plain: Joi.alternatives().try(Joi.array(), Joi.object()).required(), styles: Joi.alternatives().try(Joi.array(), Joi.object()).required(), }), darkTheme: Joi.object({ plain: Joi.alternatives().try(Joi.array(), Joi.object()).required(), styles: Joi.alternatives().try(Joi.array(), Joi.object()).required(), }), defaultLanguage: Joi.string(), additionalLanguages: Joi.array() .items(Joi.string()) .default(DEFAULT_CONFIG.prism.additionalLanguages), }) .default(DEFAULT_CONFIG.prism) .unknown(), hideableSidebar: Joi.bool().default(DEFAULT_CONFIG.hideableSidebar), }); exports.ThemeConfigSchema = ThemeConfigSchema; exports.validateThemeConfig = ({validate, themeConfig}) => { return validate(ThemeConfigSchema, themeConfig); };