diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx index 301d357c16..9ddc7fea17 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DefaultNavbarItem.tsx @@ -82,12 +82,18 @@ function NavItemDesktop({items, position, className, ...props}) { e.preventDefault()} + onClick={props.to ? undefined : (e) => e.preventDefault()} onKeyDown={(e) => { - if (e.key === 'Enter') { + function toggle() { ((e.target as HTMLElement) .parentNode as HTMLElement).classList.toggle('dropdown--show'); } + if (e.key === 'Enter' && !props.to) { + toggle(); + } + if (e.key === 'Tab') { + toggle(); + } }}> {props.label} diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx index cd3e924bea..275d6cfe9c 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx @@ -13,14 +13,16 @@ import { useActiveDocContext, } from '@theme/hooks/useDocs'; -const versionLabel = (version) => - version.name === 'next' ? 'Next/Master' : version.name; +const versionLabel = (version, nextVersionLabel) => + version.name === 'next' ? nextVersionLabel : version.name; const getVersionMainDoc = (version) => version.docs.find((doc) => doc.id === version.mainDocId); export default function DocsVersionDropdownNavbarItem({ + mobile, docsPluginId, + nextVersionLabel, ...props }) { const activeDocContext = useActiveDocContext(docsPluginId); @@ -35,7 +37,7 @@ export default function DocsVersionDropdownNavbarItem({ getVersionMainDoc(version); return { isNavLink: true, - label: versionLabel(version), + label: versionLabel(version, nextVersionLabel), to: versionDoc.path, isActive: () => version === activeDocContext?.activeVersion, }; @@ -43,11 +45,20 @@ export default function DocsVersionDropdownNavbarItem({ const dropdownVersion = activeDocContext.activeVersion ?? latestVersion; + // Mobile is handled a bit differently + const dropdownLabel = mobile + ? 'Versions' + : versionLabel(dropdownVersion, nextVersionLabel); + const dropdownTo = mobile + ? undefined + : getVersionMainDoc(dropdownVersion).path; + return ( ); diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx index 39a71b63de..11bd3e037f 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DocsVersionNavbarItem.tsx @@ -12,16 +12,20 @@ import {useActiveVersion, useLatestVersion} from '@theme/hooks/useDocs'; const getVersionMainDoc = (version) => version.docs.find((doc) => doc.id === version.mainDocId); +const versionLabel = (version, nextVersionLabel) => + version.name === 'next' ? nextVersionLabel : version.name; + export default function DocsVersionNavbarItem({ label: staticLabel, to: staticTo, docsPluginId, + nextVersionLabel, ...props }) { const activeVersion = useActiveVersion(docsPluginId); const latestVersion = useLatestVersion(docsPluginId); const version = activeVersion ?? latestVersion; - const label = staticLabel ?? version.name; + const label = staticLabel ?? versionLabel(version, nextVersionLabel); const path = staticTo ?? getVersionMainDoc(version).path; return ; } diff --git a/packages/docusaurus-theme-classic/src/themeConfigSchema.js b/packages/docusaurus-theme-classic/src/themeConfigSchema.js index 76a62ac274..ec1b90ce89 100644 --- a/packages/docusaurus-theme-classic/src/themeConfigSchema.js +++ b/packages/docusaurus-theme-classic/src/themeConfigSchema.js @@ -13,7 +13,6 @@ const DefaultNavbarItemSchema = Joi.object({ items: Joi.array().optional().items(Joi.link('...')), to: Joi.string(), href: Joi.string().uri(), - prependBaseUrlToHref: Joi.bool().default(true), label: Joi.string(), position: NavbarItemPosition, activeBasePath: Joi.string(), @@ -28,12 +27,14 @@ const DocsVersionNavbarItemSchema = Joi.object({ label: Joi.string(), to: Joi.string(), docsPluginId: Joi.string(), + nextVersionLabel: Joi.string().default('Next'), }); const DocsVersionDropdownNavbarItemSchema = Joi.object({ type: Joi.string().equal('docsVersionDropdown').required(), position: NavbarItemPosition, docsPluginId: Joi.string(), + nextVersionLabel: Joi.string().default('Next'), }); // Can this be made easier? :/ diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx index b8f3a6e592..a523eb4c88 100644 --- a/packages/docusaurus/src/client/exports/Link.tsx +++ b/packages/docusaurus/src/client/exports/Link.tsx @@ -22,7 +22,7 @@ interface Props { readonly isNavLink?: boolean; readonly to?: string; readonly activeClassName?: string; - readonly href: string; + readonly href?: string; readonly children?: ReactNode; } @@ -89,14 +89,16 @@ function Link({isNavLink, activeClassName, ...props}: Props): JSX.Element { const isAnchorLink = targetLink?.startsWith('#') ?? false; const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink; - if (isInternal && !isAnchorLink) { + if (targetLink && isInternal && !isAnchorLink) { + if (targetLink && targetLink.startsWith('/http')) { + console.log('collectLink', props); + } linksCollector.collectLink(targetLink); } return isRegularHtmlLink ? ( // eslint-disable-next-line jsx-a11y/anchor-has-content { + test('should be true for empty links', () => { + expect(isInternalUrl('')).toBeTruthy(); + }); + test('should be true for root relative links', () => { expect(isInternalUrl('/foo/bar')).toBeTruthy(); }); @@ -35,4 +39,12 @@ describe('isInternalUrl', () => { test('should be false for mailto links', () => { expect(isInternalUrl('mailto:someone@example.com')).toBeFalsy(); }); + + test('should be false for undefined links', () => { + expect(isInternalUrl(undefined)).toBeFalsy(); + }); + + test('should be true for root relative links', () => { + expect(isInternalUrl('//reactjs.org')).toBeFalsy(); + }); }); diff --git a/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.ts b/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.ts index 96b08f718e..797da6a655 100644 --- a/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.ts +++ b/packages/docusaurus/src/client/exports/__tests__/useBaseUrl.ts @@ -12,6 +12,8 @@ jest.mock('../useDocusaurusContext', () => jest.fn(), {virtual: true}); const mockedContext = useDocusaurusContext; +const forcePrepend = {forcePrependBaseUrl: true}; + describe('useBaseUrl', () => { test('empty base URL', () => { mockedContext.mockImplementation(() => ({ @@ -31,8 +33,9 @@ describe('useBaseUrl', () => { expect(useBaseUrl('/hello/byebye/')).toEqual('/hello/byebye/'); expect(useBaseUrl('https://github.com')).toEqual('https://github.com'); expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect(useBaseUrl('https://site.com', {forcePrependBaseUrl: true})).toEqual( - '/https://site.com', + expect(useBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); + expect(useBaseUrl('https://site.com', forcePrepend)).toEqual( + 'https://site.com', ); expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual( 'https://v2.docusaurus.io/hello/byebye', @@ -57,8 +60,9 @@ describe('useBaseUrl', () => { expect(useBaseUrl('/hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); expect(useBaseUrl('https://github.com')).toEqual('https://github.com'); expect(useBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect(useBaseUrl('https://site.com', {forcePrependBaseUrl: true})).toEqual( - '/docusaurus/https://site.com', + expect(useBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); + expect(useBaseUrl('https://site.com', forcePrepend)).toEqual( + 'https://site.com', ); expect(useBaseUrl('/hello/byebye', {absolute: true})).toEqual( 'https://v2.docusaurus.io/docusaurus/hello/byebye', @@ -86,9 +90,10 @@ describe('useBaseUrlUtils().withBaseUrl()', () => { expect(withBaseUrl('/hello/byebye/')).toEqual('/hello/byebye/'); expect(withBaseUrl('https://github.com')).toEqual('https://github.com'); expect(withBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect( - withBaseUrl('https://site.com', {forcePrependBaseUrl: true}), - ).toEqual('/https://site.com'); + expect(withBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); + expect(withBaseUrl('https://site.com', forcePrepend)).toEqual( + 'https://site.com', + ); expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual( 'https://v2.docusaurus.io/hello/byebye', ); @@ -113,9 +118,10 @@ describe('useBaseUrlUtils().withBaseUrl()', () => { expect(withBaseUrl('/hello/byebye/')).toEqual('/docusaurus/hello/byebye/'); expect(withBaseUrl('https://github.com')).toEqual('https://github.com'); expect(withBaseUrl('//reactjs.org')).toEqual('//reactjs.org'); - expect( - withBaseUrl('https://site.com', {forcePrependBaseUrl: true}), - ).toEqual('/docusaurus/https://site.com'); + expect(withBaseUrl('//reactjs.org', forcePrepend)).toEqual('//reactjs.org'); + expect(withBaseUrl('https://site.com', forcePrepend)).toEqual( + 'https://site.com', + ); expect(withBaseUrl('/hello/byebye', {absolute: true})).toEqual( 'https://v2.docusaurus.io/docusaurus/hello/byebye', ); diff --git a/packages/docusaurus/src/client/exports/isInternalUrl.ts b/packages/docusaurus/src/client/exports/isInternalUrl.ts index 134f0336a3..d4755e658a 100644 --- a/packages/docusaurus/src/client/exports/isInternalUrl.ts +++ b/packages/docusaurus/src/client/exports/isInternalUrl.ts @@ -5,6 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -export default function isInternalUrl(url: string): boolean { - return /^(\w*:|\/\/)/.test(url) === false; +export function hasProtocol(url: string) { + return /^(\w*:|\/\/)/.test(url) === true; +} + +export default function isInternalUrl(url?: string): boolean { + return typeof url !== 'undefined' && !hasProtocol(url); } diff --git a/packages/docusaurus/src/client/exports/useBaseUrl.ts b/packages/docusaurus/src/client/exports/useBaseUrl.ts index 622bca7090..116fa2ab9e 100644 --- a/packages/docusaurus/src/client/exports/useBaseUrl.ts +++ b/packages/docusaurus/src/client/exports/useBaseUrl.ts @@ -9,6 +9,8 @@ import useDocusaurusContext from './useDocusaurusContext'; import isInternalUrl from './isInternalUrl'; type BaseUrlOptions = Partial<{ + // note: if the url has a protocol, we never prepend it + // (it never makes any sense to do so) forcePrependBaseUrl: boolean; absolute: boolean; }>; @@ -23,21 +25,21 @@ function addBaseUrl( return url; } - if (forcePrependBaseUrl) { - return baseUrl + url; - } - if (!isInternalUrl(url)) { return url; } + if (forcePrependBaseUrl) { + return baseUrl + url; + } + const basePath = baseUrl + url.replace(/^\//, ''); return absolute ? siteUrl + basePath : basePath; } export type BaseUrlUtils = { - withBaseUrl: (url: string, options: BaseUrlOptions) => string; + withBaseUrl: (url: string, options?: BaseUrlOptions) => string; }; export function useBaseUrlUtils(): BaseUrlUtils { @@ -53,7 +55,7 @@ export function useBaseUrlUtils(): BaseUrlUtils { export default function useBaseUrl( url: string, - options: BaseUrlOptions, + options: BaseUrlOptions = {}, ): string { const {withBaseUrl} = useBaseUrlUtils(); return withBaseUrl(url, options); diff --git a/packages/docusaurus/src/server/index.ts b/packages/docusaurus/src/server/index.ts index 72b3bfd64b..f00056a3b6 100644 --- a/packages/docusaurus/src/server/index.ts +++ b/packages/docusaurus/src/server/index.ts @@ -71,11 +71,6 @@ export async function load( // Context. const context: LoadContext = loadContext(siteDir, customOutDir); const {generatedFilesDir, siteConfig, outDir, baseUrl} = context; - const genSiteConfig = generate( - generatedFilesDir, - CONFIG_FILE_NAME, - `export default ${JSON.stringify(siteConfig, null, 2)};`, - ); // Plugins. const pluginConfigs: PluginConfig[] = loadPluginConfigs(context); @@ -84,6 +79,14 @@ export async function load( context, }); + // Site config must be generated after plugins + // We want the generated config to have been normalized by the plugins! + const genSiteConfig = generate( + generatedFilesDir, + CONFIG_FILE_NAME, + `export default ${JSON.stringify(siteConfig, null, 2)};`, + ); + // Themes. const fallbackTheme = path.resolve(__dirname, '../client/theme-fallback'); const pluginThemes: string[] = plugins diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 2bf3315688..7974d9e139 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -178,6 +178,7 @@ module.exports = { { type: 'docsVersionDropdown', position: 'left', + nextVersionLabel: '2.0.0-next', }, {to: 'blog', label: 'Blog', position: 'left'}, {to: 'showcase', label: 'Showcase', position: 'left'}, @@ -188,8 +189,8 @@ module.exports = { activeBaseRegex: `docs/next/(support|team|resources)`, }, { - type: 'docsVersion', to: 'versions', + label: 'All versions', position: 'right', }, { diff --git a/website/src/css/custom.css b/website/src/css/custom.css index 92272191ee..fc157bc6a2 100644 --- a/website/src/css/custom.css +++ b/website/src/css/custom.css @@ -48,3 +48,11 @@ html[data-theme='dark'] .header-github-link:before { background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='white' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E") no-repeat; } + +/* +TODO temporary, should be handled by infima next release +https://github.com/facebookincubator/infima/commit/7820399af53c182b1879aa6d7fceb4d296f78ce0 + */ +.navbar__item.dropdown .navbar__link[href] { + pointer-events: all; +}