diff --git a/.eslintrc.js b/.eslintrc.js index 6a0b15228f..c6cea664f8 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 5cb44f06e4..0819c6efea 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 b894974807..a54f0799a8 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 0000000000..74a8a4add4 --- /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 5d7ce92f61..49d438bc5f 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 fc1d8d3560..7a99b6fb0a 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 0876cf9b94..1046cae05c 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 301a91ae32..7ed1cbbf9c 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 e04e8ab147..8c8512ef81 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 8b886c8e70..3c02a025a8 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 ccbaadcd3f..b0b8ae77fd 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 6a63fa71ca..907f77816b 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 2366a73e1d..9f4393e471 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 0000000000..dbc3c790b7 --- /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 583054e541..823f10a165 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 dd77268610..f4d37d296d 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 06d896da5f..41a90b4058 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 b0fa4907c4..7730d8fe46 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 7dcb84a822..8894e7861b 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 cd37b1f083..e580466ce5 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 555e5bea72..45d5189b48 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 501b6c2f42..16fab6fbd2 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 43182aec07..e444a53877 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 0d6b72763c..29832b4ddb 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 cdb5a6fcf4..8c598e0bd8 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 8b2ac4e764..1dd0c0100e 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 61e07cc4c1..ff4139d2e7 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 049e2d4bcf..29e9890bea 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 8b2ac4e764..1dd0c0100e 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 61e07cc4c1..ff4139d2e7 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: