From 4388267c2602b8b63d01b5ce9093c5f094859a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Fri, 12 Jan 2024 16:09:45 +0100 Subject: [PATCH] fix(core): various broken anchor link fixes (#9732) --- .eslintrc.js | 5 +- .../src/index.d.ts | 4 +- .../src/theme-classic.d.ts | 8 + .../src/theme/MDXComponents/Li.tsx | 17 ++ .../src/theme/MDXComponents/index.tsx | 2 + .../NavbarItem/DropdownNavbarItem/index.tsx | 3 + .../src/components/Details/index.tsx | 3 + .../src/__tests__/urlUtils.test.ts | 23 +++ .../src/client/BrokenLinksContext.tsx | 8 +- .../docusaurus/src/client/exports/Link.tsx | 9 +- .../src/server/__tests__/brokenLinks.test.ts | 152 +++++++++++++++--- packages/docusaurus/src/server/brokenLinks.ts | 68 ++++++-- packages/docusaurus/src/server/routes.ts | 11 +- project-words.txt | 2 + .../tests/links/broken-anchors-tests.mdx | 9 ++ .../index.mdx | 4 +- website/docs/advanced/client.mdx | 2 +- .../docs/api/plugins/plugin-content-blog.mdx | 2 +- .../docs/api/plugins/plugin-content-docs.mdx | 2 +- .../docs/api/plugins/plugin-content-pages.mdx | 2 +- website/docs/api/plugins/plugin-debug.mdx | 2 +- .../api/plugins/plugin-google-analytics.mdx | 2 +- .../docs/api/plugins/plugin-google-gtag.mdx | 2 +- .../api/plugins/plugin-google-tag-manager.mdx | 2 +- website/docs/api/plugins/plugin-sitemap.mdx | 2 +- website/docs/docusaurus-core.mdx | 18 +-- website/docs/guides/docs/sidebar/items.mdx | 4 +- .../docs/migration/v2/migration-automated.mdx | 2 +- website/src/components/APITable/index.tsx | 2 + .../version-2.x/advanced/client.mdx | 2 +- .../api/plugins/plugin-content-blog.mdx | 2 +- .../api/plugins/plugin-content-docs.mdx | 2 +- .../api/plugins/plugin-content-pages.mdx | 2 +- .../version-2.x/api/plugins/plugin-debug.mdx | 2 +- .../api/plugins/plugin-google-analytics.mdx | 2 +- .../api/plugins/plugin-google-gtag.mdx | 2 +- .../api/plugins/plugin-google-tag-manager.mdx | 2 +- .../api/plugins/plugin-sitemap.mdx | 2 +- .../version-2.x/guides/docs/sidebar/items.mdx | 4 +- .../migration/migration-automated.mdx | 2 +- .../version-3.0.1/advanced/client.mdx | 2 +- .../api/plugins/plugin-content-blog.mdx | 2 +- .../api/plugins/plugin-content-docs.mdx | 2 +- .../api/plugins/plugin-content-pages.mdx | 2 +- .../api/plugins/plugin-debug.mdx | 2 +- .../api/plugins/plugin-google-analytics.mdx | 2 +- .../api/plugins/plugin-google-gtag.mdx | 2 +- .../api/plugins/plugin-google-tag-manager.mdx | 2 +- .../api/plugins/plugin-sitemap.mdx | 2 +- .../guides/docs/sidebar/items.mdx | 4 +- .../migration/v2/migration-automated.mdx | 2 +- .../version-3.1.0/advanced/client.mdx | 2 +- .../api/plugins/plugin-content-blog.mdx | 2 +- .../api/plugins/plugin-content-docs.mdx | 2 +- .../api/plugins/plugin-content-pages.mdx | 2 +- .../api/plugins/plugin-debug.mdx | 2 +- .../api/plugins/plugin-google-analytics.mdx | 2 +- .../api/plugins/plugin-google-gtag.mdx | 2 +- .../api/plugins/plugin-google-tag-manager.mdx | 2 +- .../api/plugins/plugin-sitemap.mdx | 2 +- .../version-3.1.0/docusaurus-core.mdx | 16 +- .../guides/docs/sidebar/items.mdx | 4 +- .../migration/v2/migration-automated.mdx | 2 +- 63 files changed, 345 insertions(+), 115 deletions(-) create mode 100644 packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx create mode 100644 website/_dogfooding/_docs tests/tests/links/broken-anchors-tests.mdx diff --git a/.eslintrc.js b/.eslintrc.js index 6a0b15228fd0..c6cea664f8dd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -203,7 +203,10 @@ module.exports = { })), ], 'no-template-curly-in-string': WARNING, - 'no-unused-expressions': [WARNING, {allowTaggedTemplates: true}], + 'no-unused-expressions': [ + WARNING, + {allowTaggedTemplates: true, allowShortCircuit: true}, + ], 'no-useless-escape': WARNING, 'no-void': [ERROR, {allowAsStatement: true}], 'prefer-destructuring': WARNING, diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index 5cb44f06e402..0819c6efeaad 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -262,8 +262,8 @@ declare module '@docusaurus/useRouteContext' { declare module '@docusaurus/useBrokenLinks' { export type BrokenLinks = { - collectLink: (link: string) => void; - collectAnchor: (anchor: string) => void; + collectLink: (link: string | undefined) => void; + collectAnchor: (anchor: string | undefined) => void; }; export default function useBrokenLinks(): BrokenLinks; diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index b894974807d8..a54f0799a81e 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -867,6 +867,14 @@ declare module '@theme/MDXComponents/Ul' { export default function MDXUl(props: Props): JSX.Element; } +declare module '@theme/MDXComponents/Li' { + import type {ComponentProps} from 'react'; + + export interface Props extends ComponentProps<'li'> {} + + export default function MDXLi(props: Props): JSX.Element; +} + declare module '@theme/MDXComponents/Img' { import type {ComponentProps} from 'react'; diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx new file mode 100644 index 000000000000..74a8a4add4f1 --- /dev/null +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx @@ -0,0 +1,17 @@ +/** + * 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. + */ + +import React, {type ReactNode} from 'react'; +import useBrokenLinks from '@docusaurus/useBrokenLinks'; +import type {Props} from '@theme/MDXComponents/Li'; + +export default function MDXLi(props: Props): ReactNode | undefined { + // MDX Footnotes have ids such as
  • + useBrokenLinks().collectAnchor(props.id); + + return
  • ; +} diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx index 5d7ce92f61f6..49d438bc5f68 100644 --- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx @@ -13,6 +13,7 @@ import MDXPre from '@theme/MDXComponents/Pre'; import MDXDetails from '@theme/MDXComponents/Details'; import MDXHeading from '@theme/MDXComponents/Heading'; import MDXUl from '@theme/MDXComponents/Ul'; +import MDXLi from '@theme/MDXComponents/Li'; import MDXImg from '@theme/MDXComponents/Img'; import Admonition from '@theme/Admonition'; import Mermaid from '@theme/Mermaid'; @@ -27,6 +28,7 @@ const MDXComponents: MDXComponentsObject = { a: MDXA, pre: MDXPre, ul: MDXUl, + li: MDXLi, img: MDXImg, h1: (props: ComponentProps<'h1'>) => , h2: (props: ComponentProps<'h2'>) => , diff --git a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem/index.tsx b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem/index.tsx index fc1d8d356048..7a99b6fb0a50 100644 --- a/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem/index.tsx @@ -89,6 +89,9 @@ function DropdownNavbarItemDesktop({ aria-haspopup="true" aria-expanded={showDropdown} role="button" + // # hash permits to make the tag focusable in case no link target + // See https://github.com/facebook/docusaurus/pull/6003 + // There's probably a better solution though... href={props.to ? undefined : '#'} className={clsx('navbar__link', className)} {...props} diff --git a/packages/docusaurus-theme-common/src/components/Details/index.tsx b/packages/docusaurus-theme-common/src/components/Details/index.tsx index 0876cf9b94d3..1046cae05c41 100644 --- a/packages/docusaurus-theme-common/src/components/Details/index.tsx +++ b/packages/docusaurus-theme-common/src/components/Details/index.tsx @@ -12,6 +12,7 @@ import React, { type ReactElement, } from 'react'; import clsx from 'clsx'; +import useBrokenLinks from '@docusaurus/useBrokenLinks'; import useIsBrowser from '@docusaurus/useIsBrowser'; import {useCollapsible, Collapsible} from '../Collapsible'; import styles from './styles.module.css'; @@ -47,6 +48,8 @@ export function Details({ children, ...props }: DetailsProps): JSX.Element { + useBrokenLinks().collectAnchor(props.id); + const isBrowser = useIsBrowser(); const detailsRef = useRef(null); diff --git a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts index 301a91ae3224..7ed1cbbf9cea 100644 --- a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts @@ -301,6 +301,29 @@ describe('parseURLPath', () => { }); }); + it('parse anchor', () => { + expect(parseURLPath('#anchor')).toEqual({ + pathname: '/', + search: undefined, + hash: 'anchor', + }); + expect(parseURLPath('#anchor', '/page')).toEqual({ + pathname: '/page', + search: undefined, + hash: 'anchor', + }); + expect(parseURLPath('#')).toEqual({ + pathname: '/', + search: undefined, + hash: '', + }); + expect(parseURLPath('#', '/page')).toEqual({ + pathname: '/page', + search: undefined, + hash: '', + }); + }); + it('parse hash', () => { expect(parseURLPath('/page')).toEqual({ pathname: '/page', diff --git a/packages/docusaurus/src/client/BrokenLinksContext.tsx b/packages/docusaurus/src/client/BrokenLinksContext.tsx index e04e8ab14731..8c8512ef81e8 100644 --- a/packages/docusaurus/src/client/BrokenLinksContext.tsx +++ b/packages/docusaurus/src/client/BrokenLinksContext.tsx @@ -18,11 +18,11 @@ export const createStatefulBrokenLinks = (): StatefulBrokenLinks => { const allAnchors = new Set(); const allLinks = new Set(); return { - collectAnchor: (anchor: string): void => { - allAnchors.add(anchor); + collectAnchor: (anchor: string | undefined): void => { + typeof anchor !== 'undefined' && allAnchors.add(anchor); }, - collectLink: (link: string): void => { - allLinks.add(link); + collectLink: (link: string | undefined): void => { + typeof link !== 'undefined' && allLinks.add(link); }, getCollectedAnchors: (): string[] => [...allAnchors], getCollectedLinks: (): string[] => [...allLinks], diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx index 8b886c8e7073..3c02a025a81a 100644 --- a/packages/docusaurus/src/client/exports/Link.tsx +++ b/packages/docusaurus/src/client/exports/Link.tsx @@ -140,13 +140,20 @@ function Link( }; }, [ioRef, targetLink, IOSupported, isInternal]); + // It is simple local anchor link targeting current page? const isAnchorLink = targetLink?.startsWith('#') ?? false; + + // Should we use a regular tag instead of React-Router Link component? const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink; - if (!isRegularHtmlLink && !noBrokenLinkCheck) { + if (!noBrokenLinkCheck && (isAnchorLink || !isRegularHtmlLink)) { brokenLinks.collectLink(targetLink!); } + if (props.id) { + brokenLinks.collectAnchor(props.id); + } + return isRegularHtmlLink ? ( // eslint-disable-next-line jsx-a11y/anchor-has-content, @docusaurus/no-html-links { }); }); + it('accepts valid link with anchor reported with hash prefix', async () => { + await testBrokenLinks({ + routes: [{path: '/page1'}, {path: '/page2'}], + collectedLinks: { + '/page1': {links: ['/page2#page2anchor'], anchors: []}, + '/page2': {links: [], anchors: ['#page2anchor']}, + }, + }); + }); + + it('accepts valid links and anchors, sparse arrays', async () => { + await testBrokenLinks({ + routes: [{path: '/page1'}, {path: '/page2'}], + collectedLinks: { + '/page1': { + links: [ + '/page1', + // @ts-expect-error: invalid type on purpose + undefined, + // @ts-expect-error: invalid type on purpose + null, + // @ts-expect-error: invalid type on purpose + 42, + '/page2', + '/page2#page2anchor1', + '/page2#page2anchor2', + ], + anchors: [], + }, + '/page2': { + links: [], + anchors: [ + 'page2anchor1', + // @ts-expect-error: invalid type on purpose + undefined, + // @ts-expect-error: invalid type on purpose + null, + // @ts-expect-error: invalid type on purpose + 42, + 'page2anchor2', + ], + }, + }, + }); + }); + + it('accepts valid link and anchor to collected pages that are not in routes', async () => { + // This tests the edge-case of the 404 page: + // We don't have a {path: '404.html'} route + // But yet we collect links/anchors to it and allow linking to it + await testBrokenLinks({ + routes: [], + collectedLinks: { + '/page 1': { + links: ['/page 2#anchor-page-2'], + anchors: ['anchor-page-1'], + }, + '/page 2': { + links: ['/page 1#anchor-page-1', '/page%201#anchor-page-1'], + anchors: ['anchor-page-2'], + }, + }, + }); + }); + it('accepts valid link with querystring + anchor', async () => { await testBrokenLinks({ routes: [{path: '/page1'}, {path: '/page2'}], @@ -132,10 +197,75 @@ describe('handleBrokenLinks', () => { '/page%202', '/page%202?age=42', '/page%202?age=42#page2anchor', + + '/some dir/page 3', + '/some dir/page 3#page3anchor', + '/some%20dir/page%203', + '/some%20dir/page%203#page3anchor', + '/some%20dir/page 3', + '/some dir/page%203', + '/some dir/page%203#page3anchor', ], anchors: [], }, '/page 2': {links: [], anchors: ['page2anchor']}, + '/some dir/page 3': {links: [], anchors: ['page3anchor']}, + }, + }); + }); + + it('accepts valid link with anchor with spaces and encoding', async () => { + await testBrokenLinks({ + routes: [{path: '/page 1'}, {path: '/page 2'}], + collectedLinks: { + '/page 1': { + links: [ + '/page 1#a b', + '#a b', + '#a%20b', + '#c d', + '#c%20d', + + '/page 2#你好', + '/page%202#你好', + '/page 2#%E4%BD%A0%E5%A5%BD', + '/page%202#%E4%BD%A0%E5%A5%BD', + + '/page 2#schrödingers-cat-principle', + '/page%202#schrödingers-cat-principle', + '/page 2#schr%C3%B6dingers-cat-principle', + '/page%202#schr%C3%B6dingers-cat-principle', + ], + anchors: ['a b', 'c%20d'], + }, + '/page 2': { + links: ['/page 1#a b', '/page%201#c d'], + anchors: ['你好', '#schr%C3%B6dingers-cat-principle'], + }, + }, + }); + }); + + it('accepts valid link with empty anchor', async () => { + await testBrokenLinks({ + routes: [{path: '/page 1'}, {path: '/page 2'}], + collectedLinks: { + '/page 1': { + links: [ + '/page 1', + '/page 2', + '/page 1#', + '/page 2#', + '/page 1?age=42#', + '/page 2?age=42#', + '#', + '#', + './page 1#', + './page 2#', + ], + anchors: [], + }, + '/page 2': {links: [], anchors: []}, }, }); }); @@ -225,28 +355,6 @@ describe('handleBrokenLinks', () => { `); }); - it('rejects valid link with empty broken anchor', async () => { - await expect(() => - testBrokenLinks({ - routes: [{path: '/page1'}, {path: '/page2'}], - collectedLinks: { - '/page1': {links: ['/page2#'], anchors: []}, - '/page2': {links: [], anchors: []}, - }, - }), - ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Docusaurus found broken anchors! - - Please check the pages of your site in the list below, and make sure you don't reference any anchor that does not exist. - Note: it's possible to ignore broken anchors with the 'onBrokenAnchors' Docusaurus configuration, and let the build pass. - - Exhaustive list of all broken anchors found: - - Broken anchor on source page path = /page1: - -> linking to /page2# - " - `); - }); - it('rejects valid link with broken anchor + query-string', async () => { await expect(() => testBrokenLinks({ diff --git a/packages/docusaurus/src/server/brokenLinks.ts b/packages/docusaurus/src/server/brokenLinks.ts index ccbaadcd3ffb..b0b8ae77fd58 100644 --- a/packages/docusaurus/src/server/brokenLinks.ts +++ b/packages/docusaurus/src/server/brokenLinks.ts @@ -38,15 +38,23 @@ function getBrokenLinksForPage({ pageAnchors: string[]; routes: RouteConfig[]; }): BrokenLink[] { - // console.log('routes:', routes); + const allCollectedPaths = new Set(Object.keys(collectedLinks)); + function isPathBrokenLink(linkPath: URLPath) { - const matchedRoutes = [linkPath.pathname, decodeURI(linkPath.pathname)] + const pathnames = [linkPath.pathname, decodeURI(linkPath.pathname)]; + const matchedRoutes = pathnames // @ts-expect-error: React router types RouteConfig with an actual React // component, but we load route components with string paths. // We don't actually access component here, so it's fine. .map((l) => matchRoutes(routes, l)) .flat(); - return matchedRoutes.length === 0; + // The link path is broken if: + // - it doesn't match any route + // - it doesn't match any collected path + return ( + matchedRoutes.length === 0 && + !pathnames.some((p) => allCollectedPaths.has(p)) + ); } function isAnchorBrokenLink(linkPath: URLPath) { @@ -57,6 +65,13 @@ function getBrokenLinksForPage({ return false; } + // Link has empty hash ("#", "/page#"...): we do not report it as broken + // Empty hashes are used for various weird reasons, by us and other users... + // See for example: https://github.com/facebook/docusaurus/pull/6003 + if (hash === '') { + return false; + } + const targetPage = collectedLinks[pathname] || collectedLinks[decodeURI(pathname)]; @@ -68,7 +83,8 @@ function getBrokenLinksForPage({ // it's a broken anchor if the target page exists // but the anchor does not exist on that page - return !targetPage.anchors.includes(hash); + const hashes = [hash, decodeURIComponent(hash)]; + return !targetPage.anchors.some((anchor) => hashes.includes(anchor)); } const brokenLinks = pageLinks.flatMap((link) => { @@ -117,15 +133,21 @@ function getBrokenLinks({ }): BrokenLinksMap { const filteredRoutes = filterIntermediateRoutes(routes); - return _.mapValues(collectedLinks, (pageCollectedData, pagePath) => - getBrokenLinksForPage({ - collectedLinks, - pageLinks: pageCollectedData.links, - pageAnchors: pageCollectedData.anchors, - pagePath, - routes: filteredRoutes, - }), - ); + return _.mapValues(collectedLinks, (pageCollectedData, pagePath) => { + try { + return getBrokenLinksForPage({ + collectedLinks, + pageLinks: pageCollectedData.links, + pageAnchors: pageCollectedData.anchors, + pagePath, + routes: filteredRoutes, + }); + } catch (e) { + throw new Error(`Unable to get broken links for page ${pagePath}.`, { + cause: e, + }); + } + }); } function brokenLinkMessage(brokenLink: BrokenLink): string { @@ -277,6 +299,21 @@ function reportBrokenLinks({ } } +// Users might use the useBrokenLinks() API in weird unexpected ways +// JS users might call "collectLink(undefined)" for example +// TS users might call "collectAnchor('#hash')" with/without # +// We clean/normalize the collected data to avoid obscure errors being thrown +function normalizeCollectedLinks( + collectedLinks: CollectedLinks, +): CollectedLinks { + return _.mapValues(collectedLinks, (pageCollectedData) => ({ + links: pageCollectedData.links.filter(_.isString), + anchors: pageCollectedData.anchors + .filter(_.isString) + .map((anchor) => (anchor.startsWith('#') ? anchor.slice(1) : anchor)), + })); +} + export async function handleBrokenLinks({ collectedLinks, onBrokenLinks, @@ -291,6 +328,9 @@ export async function handleBrokenLinks({ if (onBrokenLinks === 'ignore' && onBrokenAnchors === 'ignore') { return; } - const brokenLinks = getBrokenLinks({routes, collectedLinks}); + const brokenLinks = getBrokenLinks({ + routes, + collectedLinks: normalizeCollectedLinks(collectedLinks), + }); reportBrokenLinks({brokenLinks, onBrokenLinks, onBrokenAnchors}); } diff --git a/packages/docusaurus/src/server/routes.ts b/packages/docusaurus/src/server/routes.ts index 6a63fa71ca73..907f77816b8f 100644 --- a/packages/docusaurus/src/server/routes.ts +++ b/packages/docusaurus/src/server/routes.ts @@ -276,6 +276,15 @@ ${JSON.stringify(routeConfig)}`, }); } +/** + * Old stuff + * As far as I understand, this is what permits to SSG the 404.html file + * This is rendered through the catch-all ComponentCreator("*") route + * Note CDNs only understand the 404.html file by convention + * The extension probably permits to avoid emitting "/404/index.html" + */ +const NotFoundRoutePath = '/404.html'; + /** * Routes are prepared into three temp files: * @@ -296,7 +305,7 @@ export function loadRoutes( routesConfig: '', routesChunkNames: {}, registry: {}, - routesPaths: [normalizeUrl([baseUrl, '404.html'])], + routesPaths: [normalizeUrl([baseUrl, NotFoundRoutePath])], }; // `genRouteCode` would mutate `res` diff --git a/project-words.txt b/project-words.txt index 2366a73e1db4..9f4393e4717c 100644 --- a/project-words.txt +++ b/project-words.txt @@ -66,6 +66,7 @@ datagit Datagit's dedup devto +dingers Dmitry docsearch Docsify @@ -401,3 +402,4 @@ yangshunz Zhou zoomable zpao +ödingers diff --git a/website/_dogfooding/_docs tests/tests/links/broken-anchors-tests.mdx b/website/_dogfooding/_docs tests/tests/links/broken-anchors-tests.mdx new file mode 100644 index 000000000000..dbc3c790b763 --- /dev/null +++ b/website/_dogfooding/_docs tests/tests/links/broken-anchors-tests.mdx @@ -0,0 +1,9 @@ +# Broken Anchors tests + +import Link from '@docusaurus/Link'; + +#test-link-anchor + +[Markdown link to above anchor](#test-link-anchor) + +[Markdown link to above anchor](#) diff --git a/website/blog/2021-11-21-algolia-docsearch-migration/index.mdx b/website/blog/2021-11-21-algolia-docsearch-migration/index.mdx index 583054e5410b..823f10a1659d 100644 --- a/website/blog/2021-11-21-algolia-docsearch-migration/index.mdx +++ b/website/blog/2021-11-21-algolia-docsearch-migration/index.mdx @@ -14,7 +14,7 @@ image: ./img/social-card.png [DocSearch](https://docsearch.algolia.com/) is migrating to a new, more powerful system, which gives users their own Algolia application and new credentials. -Docusaurus site owners should upgrade their configuration with [their new credentials](#im-using-docusaurus-and-docsearch-can-i-migrate) **by February 1, 2022**, existing search indexes will be frozen and become read-only after this date. +Docusaurus site owners should upgrade their configuration with their new credentials **by February 1, 2022**, existing search indexes will be frozen and become read-only after this date. @@ -92,7 +92,7 @@ And of course, **a lot more, for free**. ## FAQ -### I'm using Docusaurus and DocSearch, can I migrate? +### I'm using Docusaurus and DocSearch, can I migrate? {#im-using-docusaurus-and-docsearch-can-i-migrate} At the time we are writing this, we are still at an early stage of the migration. We are doing small batches every week but will increase the load shortly, so please be patient and keep an eye out in your mailbox, you'll be contacted as soon as your Algolia app is ready! diff --git a/website/docs/advanced/client.mdx b/website/docs/advanced/client.mdx index dd77268610f3..f4d37d296ded 100644 --- a/website/docs/advanced/client.mdx +++ b/website/docs/advanced/client.mdx @@ -33,7 +33,7 @@ website `website/src/theme/Navbar.js` takes precedence whenever `@theme/Navbar` is imported. This behavior is called component swizzling. If you are familiar with Objective C where a function's implementation can be swapped during runtime, it's the exact same concept here with changing the target `@theme/Navbar` is pointing to! -We already talked about how the "userland theme" in `src/theme` can re-use a theme component through the [`@theme-original`](#wrapping) alias. One theme package can also wrap a component from another theme, by importing the component from the initial theme, using the `@theme-init` import. +We already talked about how the "userland theme" in `src/theme` can re-use a theme component through the [`@theme-original`](../swizzling.mdx#wrapping) alias. One theme package can also wrap a component from another theme, by importing the component from the initial theme, using the `@theme-init` import. Here's an example of using this feature to enhance the default theme `CodeBlock` component with a `react-live` playground feature. diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx index 06d896da5faa..41a90b4058ab 100644 --- a/website/docs/api/plugins/plugin-content-blog.mdx +++ b/website/docs/api/plugins/plugin-content-blog.mdx @@ -25,7 +25,7 @@ npm install --save @docusaurus/plugin-content-blog If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the [preset options](#ex-config-preset). +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-content-docs.mdx b/website/docs/api/plugins/plugin-content-docs.mdx index b0fa4907c48b..7730d8fe468d 100644 --- a/website/docs/api/plugins/plugin-content-docs.mdx +++ b/website/docs/api/plugins/plugin-content-docs.mdx @@ -19,7 +19,7 @@ npm install --save @docusaurus/plugin-content-docs If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the preset options. +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-content-pages.mdx b/website/docs/api/plugins/plugin-content-pages.mdx index 7dcb84a82213..8894e7861b4c 100644 --- a/website/docs/api/plugins/plugin-content-pages.mdx +++ b/website/docs/api/plugins/plugin-content-pages.mdx @@ -19,7 +19,7 @@ npm install --save @docusaurus/plugin-content-pages If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the preset options. +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-debug.mdx b/website/docs/api/plugins/plugin-debug.mdx index cd37b1f0834a..e580466ce5b0 100644 --- a/website/docs/api/plugins/plugin-debug.mdx +++ b/website/docs/api/plugins/plugin-debug.mdx @@ -49,7 +49,7 @@ npm install --save @docusaurus/plugin-debug If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the preset options. +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-google-analytics.mdx b/website/docs/api/plugins/plugin-google-analytics.mdx index 555e5bea7277..45d5189b4810 100644 --- a/website/docs/api/plugins/plugin-google-analytics.mdx +++ b/website/docs/api/plugins/plugin-google-analytics.mdx @@ -35,7 +35,7 @@ npm install --save @docusaurus/plugin-google-analytics If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the preset options. +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-google-gtag.mdx b/website/docs/api/plugins/plugin-google-gtag.mdx index 501b6c2f42c8..16fab6fbd270 100644 --- a/website/docs/api/plugins/plugin-google-gtag.mdx +++ b/website/docs/api/plugins/plugin-google-gtag.mdx @@ -31,7 +31,7 @@ npm install --save @docusaurus/plugin-google-gtag If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the preset options. +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-google-tag-manager.mdx b/website/docs/api/plugins/plugin-google-tag-manager.mdx index 43182aec075d..e444a5387760 100644 --- a/website/docs/api/plugins/plugin-google-tag-manager.mdx +++ b/website/docs/api/plugins/plugin-google-tag-manager.mdx @@ -31,7 +31,7 @@ npm install --save @docusaurus/plugin-google-tag-manager If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the preset options. +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/api/plugins/plugin-sitemap.mdx b/website/docs/api/plugins/plugin-sitemap.mdx index 0d6b72763c03..29832b4ddb21 100644 --- a/website/docs/api/plugins/plugin-sitemap.mdx +++ b/website/docs/api/plugins/plugin-sitemap.mdx @@ -25,7 +25,7 @@ npm install --save @docusaurus/plugin-sitemap If you use the preset `@docusaurus/preset-classic`, you don't need to install this plugin as a dependency. -You can configure this plugin through the [preset options](#ex-config-preset). +You can configure this plugin through the [preset options](../../using-plugins.mdx#docusauruspreset-classic). ::: diff --git a/website/docs/docusaurus-core.mdx b/website/docs/docusaurus-core.mdx index cdb5a6fcf4a1..8c598e0bd8bd 100644 --- a/website/docs/docusaurus-core.mdx +++ b/website/docs/docusaurus-core.mdx @@ -627,24 +627,18 @@ Usage example: ```js title="MyHeading.js" import useBrokenLinks from '@docusaurus/useBrokenLinks'; -export default function MyHeading({id, ...props}): JSX.Element { - const brokenLinks = useBrokenLinks(); - - brokenLinks.collectAnchor(id); - - return

    Heading

    ; +export default function MyHeading(props) { + useBrokenLinks().collectAnchor(props.id); + return

    ; } ``` ```js title="MyLink.js" import useBrokenLinks from '@docusaurus/useBrokenLinks'; -export default function MyLink({targetLink, ...props}): JSX.Element { - const brokenLinks = useBrokenLinks(); - - brokenLinks.collectLink(targetLink); - - return Link; +export default function MyLink(props) { + useBrokenLinks().collectLink(props.href); + return ; } ``` diff --git a/website/docs/guides/docs/sidebar/items.mdx b/website/docs/guides/docs/sidebar/items.mdx index 8b2ac4e764e2..1dd0c0100e78 100644 --- a/website/docs/guides/docs/sidebar/items.mdx +++ b/website/docs/guides/docs/sidebar/items.mdx @@ -61,11 +61,11 @@ export default { }; ``` -If you use the doc shorthand or [autogenerated](#sidebar-item-autogenerated) sidebar, you would lose the ability to customize the sidebar label through item definition. You can, however, use the `sidebar_label` Markdown front matter within that doc, which has higher precedence over the `label` key in the sidebar item. Similarly, you can use `sidebar_custom_props` to declare custom metadata for a doc page. +If you use the doc shorthand or [autogenerated](autogenerated.mdx) sidebar, you would lose the ability to customize the sidebar label through item definition. You can, however, use the `sidebar_label` Markdown front matter within that doc, which has higher precedence over the `label` key in the sidebar item. Similarly, you can use `sidebar_custom_props` to declare custom metadata for a doc page. :::note -A `doc` item sets an [implicit sidebar association](#sidebar-association). Don't assign the same doc to multiple sidebars: change the type to `ref` instead. +A `doc` item sets an [implicit sidebar association](./multiple-sidebars.mdx#sidebar-association). Don't assign the same doc to multiple sidebars: change the type to `ref` instead. ::: diff --git a/website/docs/migration/v2/migration-automated.mdx b/website/docs/migration/v2/migration-automated.mdx index 61e07cc4c1f5..ff4139d2e71d 100644 --- a/website/docs/migration/v2/migration-automated.mdx +++ b/website/docs/migration/v2/migration-automated.mdx @@ -24,7 +24,7 @@ The migration CLI migrates: To use the migration CLI, follow these steps: -1. Before using the migration CLI, ensure that `/docs`, `/blog`, `/static`, `sidebars.json`, `siteConfig.js`, `package.json` follow the [structure](#) shown at the start of this page. +1. Before using the migration CLI, ensure that `/docs`, `/blog`, `/static`, `sidebars.json`, `siteConfig.js`, `package.json` follow the expected structure. 2. To migrate your v1 website, run the migration CLI with the appropriate filesystem paths: diff --git a/website/src/components/APITable/index.tsx b/website/src/components/APITable/index.tsx index 049e2d4bcfd4..29e9890bea46 100644 --- a/website/src/components/APITable/index.tsx +++ b/website/src/components/APITable/index.tsx @@ -13,6 +13,7 @@ import React, { useRef, useEffect, } from 'react'; +import useBrokenLinks from '@docusaurus/useBrokenLinks'; import {useHistory} from '@docusaurus/router'; import styles from './styles.module.css'; @@ -41,6 +42,7 @@ function APITableRow( const id = name ? `${name}-${entryName}` : entryName; const anchor = `#${id}`; const history = useHistory(); + useBrokenLinks().collectAnchor(id); return ( Heading

    ; + +export default function MyHeading(props) { + useBrokenLinks().collectAnchor(props.id); + return

    ; } ``` ```js title="MyLink.js" import useBrokenLinks from '@docusaurus/useBrokenLinks'; -export default function MyLink({targetLink, ...props}): JSX.Element { - const brokenLinks = useBrokenLinks(); - brokenLinks.collectLink(targetLink); - return Link; + +export default function MyLink(props) { + useBrokenLinks().collectLink(props.href); + return ; } ``` diff --git a/website/versioned_docs/version-3.1.0/guides/docs/sidebar/items.mdx b/website/versioned_docs/version-3.1.0/guides/docs/sidebar/items.mdx index 8b2ac4e764e2..1dd0c0100e78 100644 --- a/website/versioned_docs/version-3.1.0/guides/docs/sidebar/items.mdx +++ b/website/versioned_docs/version-3.1.0/guides/docs/sidebar/items.mdx @@ -61,11 +61,11 @@ export default { }; ``` -If you use the doc shorthand or [autogenerated](#sidebar-item-autogenerated) sidebar, you would lose the ability to customize the sidebar label through item definition. You can, however, use the `sidebar_label` Markdown front matter within that doc, which has higher precedence over the `label` key in the sidebar item. Similarly, you can use `sidebar_custom_props` to declare custom metadata for a doc page. +If you use the doc shorthand or [autogenerated](autogenerated.mdx) sidebar, you would lose the ability to customize the sidebar label through item definition. You can, however, use the `sidebar_label` Markdown front matter within that doc, which has higher precedence over the `label` key in the sidebar item. Similarly, you can use `sidebar_custom_props` to declare custom metadata for a doc page. :::note -A `doc` item sets an [implicit sidebar association](#sidebar-association). Don't assign the same doc to multiple sidebars: change the type to `ref` instead. +A `doc` item sets an [implicit sidebar association](./multiple-sidebars.mdx#sidebar-association). Don't assign the same doc to multiple sidebars: change the type to `ref` instead. ::: diff --git a/website/versioned_docs/version-3.1.0/migration/v2/migration-automated.mdx b/website/versioned_docs/version-3.1.0/migration/v2/migration-automated.mdx index 61e07cc4c1f5..ff4139d2e71d 100644 --- a/website/versioned_docs/version-3.1.0/migration/v2/migration-automated.mdx +++ b/website/versioned_docs/version-3.1.0/migration/v2/migration-automated.mdx @@ -24,7 +24,7 @@ The migration CLI migrates: To use the migration CLI, follow these steps: -1. Before using the migration CLI, ensure that `/docs`, `/blog`, `/static`, `sidebars.json`, `siteConfig.js`, `package.json` follow the [structure](#) shown at the start of this page. +1. Before using the migration CLI, ensure that `/docs`, `/blog`, `/static`, `sidebars.json`, `siteConfig.js`, `package.json` follow the expected structure. 2. To migrate your v1 website, run the migration CLI with the appropriate filesystem paths: