mirror of
https://github.com/facebook/docusaurus.git
synced 2025-04-28 17:57:48 +02:00
fix(core): various broken anchor link fixes (#9732)
This commit is contained in:
parent
d94adf6a6c
commit
4388267c26
63 changed files with 345 additions and 115 deletions
5
.eslintrc.js
vendored
5
.eslintrc.js
vendored
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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 <li id="user-content-fn-1-953011">
|
||||
useBrokenLinks().collectAnchor(props.id);
|
||||
|
||||
return <li {...props} />;
|
||||
}
|
|
@ -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'>) => <MDXHeading as="h1" {...props} />,
|
||||
h2: (props: ComponentProps<'h2'>) => <MDXHeading as="h2" {...props} />,
|
||||
|
|
|
@ -89,6 +89,9 @@ function DropdownNavbarItemDesktop({
|
|||
aria-haspopup="true"
|
||||
aria-expanded={showDropdown}
|
||||
role="button"
|
||||
// # hash permits to make the <a> 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}
|
||||
|
|
|
@ -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<HTMLDetailsElement>(null);
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -18,11 +18,11 @@ export const createStatefulBrokenLinks = (): StatefulBrokenLinks => {
|
|||
const allAnchors = new Set<string>();
|
||||
const allLinks = new Set<string>();
|
||||
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],
|
||||
|
|
|
@ -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 <a> 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
|
||||
<a
|
||||
|
|
|
@ -86,6 +86,71 @@ describe('handleBrokenLinks', () => {
|
|||
});
|
||||
});
|
||||
|
||||
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({
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -66,6 +66,7 @@ datagit
|
|||
Datagit's
|
||||
dedup
|
||||
devto
|
||||
dingers
|
||||
Dmitry
|
||||
docsearch
|
||||
Docsify
|
||||
|
@ -401,3 +402,4 @@ yangshunz
|
|||
Zhou
|
||||
zoomable
|
||||
zpao
|
||||
ödingers
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# Broken Anchors tests
|
||||
|
||||
import Link from '@docusaurus/Link';
|
||||
|
||||
<Link id="test-link-anchor">#test-link-anchor</Link>
|
||||
|
||||
[Markdown link to above anchor](#test-link-anchor)
|
||||
|
||||
[Markdown link to above anchor](#)
|
|
@ -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.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
|
@ -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!
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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 <h2 id={id}>Heading</h2>;
|
||||
export default function MyHeading(props) {
|
||||
useBrokenLinks().collectAnchor(props.id);
|
||||
return <h2 {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
```js title="MyLink.js"
|
||||
import useBrokenLinks from '@docusaurus/useBrokenLinks';
|
||||
|
||||
export default function MyLink({targetLink, ...props}): JSX.Element {
|
||||
const brokenLinks = useBrokenLinks();
|
||||
|
||||
brokenLinks.collectLink(targetLink);
|
||||
|
||||
return <a href={targetLink}>Link</a>;
|
||||
export default function MyLink(props) {
|
||||
useBrokenLinks().collectLink(props.href);
|
||||
return <a {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<tr
|
||||
id={id}
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -61,11 +61,11 @@ module.exports = {
|
|||
};
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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).
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -622,19 +622,19 @@ 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 <h2 id={id}>Heading</h2>;
|
||||
|
||||
export default function MyHeading(props) {
|
||||
useBrokenLinks().collectAnchor(props.id);
|
||||
return <h2 {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
```js title="MyLink.js"
|
||||
import useBrokenLinks from '@docusaurus/useBrokenLinks';
|
||||
export default function MyLink({targetLink, ...props}): JSX.Element {
|
||||
const brokenLinks = useBrokenLinks();
|
||||
brokenLinks.collectLink(targetLink);
|
||||
return <a href={targetLink}>Link</a>;
|
||||
|
||||
export default function MyLink(props) {
|
||||
useBrokenLinks().collectLink(props.href);
|
||||
return <a {...props} />;
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
:::
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue