Skip to content

Commit ac2b5dd

Browse files
committed
fix(core): various broken anchor link fixes (#9732)
1 parent 7b1b890 commit ac2b5dd

File tree

63 files changed

+3180
-95
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3180
-95
lines changed

.eslintrc.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,10 @@ module.exports = {
203203
})),
204204
],
205205
'no-template-curly-in-string': WARNING,
206-
'no-unused-expressions': [WARNING, {allowTaggedTemplates: true}],
206+
'no-unused-expressions': [
207+
WARNING,
208+
{allowTaggedTemplates: true, allowShortCircuit: true},
209+
],
207210
'no-useless-escape': WARNING,
208211
'no-void': [ERROR, {allowAsStatement: true}],
209212
'prefer-destructuring': WARNING,

packages/docusaurus-module-type-aliases/src/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ declare module '@docusaurus/useRouteContext' {
262262

263263
declare module '@docusaurus/useBrokenLinks' {
264264
export type BrokenLinks = {
265-
collectLink: (link: string) => void;
266-
collectAnchor: (anchor: string) => void;
265+
collectLink: (link: string | undefined) => void;
266+
collectAnchor: (anchor: string | undefined) => void;
267267
};
268268

269269
export default function useBrokenLinks(): BrokenLinks;

packages/docusaurus-theme-classic/src/theme-classic.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,14 @@ declare module '@theme/MDXComponents/Ul' {
867867
export default function MDXUl(props: Props): JSX.Element;
868868
}
869869

870+
declare module '@theme/MDXComponents/Li' {
871+
import type {ComponentProps} from 'react';
872+
873+
export interface Props extends ComponentProps<'li'> {}
874+
875+
export default function MDXLi(props: Props): JSX.Element;
876+
}
877+
870878
declare module '@theme/MDXComponents/Img' {
871879
import type {ComponentProps} from 'react';
872880

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import React, {type ReactNode} from 'react';
9+
import useBrokenLinks from '@docusaurus/useBrokenLinks';
10+
import type {Props} from '@theme/MDXComponents/Li';
11+
12+
export default function MDXLi(props: Props): ReactNode | undefined {
13+
// MDX Footnotes have ids such as <li id="user-content-fn-1-953011">
14+
useBrokenLinks().collectAnchor(props.id);
15+
16+
return <li {...props} />;
17+
}

packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import MDXPre from '@theme/MDXComponents/Pre';
1313
import MDXDetails from '@theme/MDXComponents/Details';
1414
import MDXHeading from '@theme/MDXComponents/Heading';
1515
import MDXUl from '@theme/MDXComponents/Ul';
16+
import MDXLi from '@theme/MDXComponents/Li';
1617
import MDXImg from '@theme/MDXComponents/Img';
1718
import Admonition from '@theme/Admonition';
1819
import Mermaid from '@theme/Mermaid';
@@ -27,6 +28,7 @@ const MDXComponents: MDXComponentsObject = {
2728
a: MDXA,
2829
pre: MDXPre,
2930
ul: MDXUl,
31+
li: MDXLi,
3032
img: MDXImg,
3133
h1: (props: ComponentProps<'h1'>) => <MDXHeading as="h1" {...props} />,
3234
h2: (props: ComponentProps<'h2'>) => <MDXHeading as="h2" {...props} />,

packages/docusaurus-theme-classic/src/theme/NavbarItem/DropdownNavbarItem/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ function DropdownNavbarItemDesktop({
8989
aria-haspopup="true"
9090
aria-expanded={showDropdown}
9191
role="button"
92+
// # hash permits to make the <a> tag focusable in case no link target
93+
// See https://github.com/facebook/docusaurus/pull/6003
94+
// There's probably a better solution though...
9295
href={props.to ? undefined : '#'}
9396
className={clsx('navbar__link', className)}
9497
{...props}

packages/docusaurus-theme-common/src/components/Details/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import React, {
1212
type ReactElement,
1313
} from 'react';
1414
import clsx from 'clsx';
15+
import useBrokenLinks from '@docusaurus/useBrokenLinks';
1516
import useIsBrowser from '@docusaurus/useIsBrowser';
1617
import {useCollapsible, Collapsible} from '../Collapsible';
1718
import styles from './styles.module.css';
@@ -47,6 +48,8 @@ export function Details({
4748
children,
4849
...props
4950
}: DetailsProps): JSX.Element {
51+
useBrokenLinks().collectAnchor(props.id);
52+
5053
const isBrowser = useIsBrowser();
5154
const detailsRef = useRef<HTMLDetailsElement>(null);
5255

packages/docusaurus-utils/src/__tests__/urlUtils.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,29 @@ describe('parseURLPath', () => {
301301
});
302302
});
303303

304+
it('parse anchor', () => {
305+
expect(parseURLPath('#anchor')).toEqual({
306+
pathname: '/',
307+
search: undefined,
308+
hash: 'anchor',
309+
});
310+
expect(parseURLPath('#anchor', '/page')).toEqual({
311+
pathname: '/page',
312+
search: undefined,
313+
hash: 'anchor',
314+
});
315+
expect(parseURLPath('#')).toEqual({
316+
pathname: '/',
317+
search: undefined,
318+
hash: '',
319+
});
320+
expect(parseURLPath('#', '/page')).toEqual({
321+
pathname: '/page',
322+
search: undefined,
323+
hash: '',
324+
});
325+
});
326+
304327
it('parse hash', () => {
305328
expect(parseURLPath('/page')).toEqual({
306329
pathname: '/page',

packages/docusaurus/src/client/BrokenLinksContext.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ export const createStatefulBrokenLinks = (): StatefulBrokenLinks => {
1818
const allAnchors = new Set<string>();
1919
const allLinks = new Set<string>();
2020
return {
21-
collectAnchor: (anchor: string): void => {
22-
allAnchors.add(anchor);
21+
collectAnchor: (anchor: string | undefined): void => {
22+
typeof anchor !== 'undefined' && allAnchors.add(anchor);
2323
},
24-
collectLink: (link: string): void => {
25-
allLinks.add(link);
24+
collectLink: (link: string | undefined): void => {
25+
typeof link !== 'undefined' && allLinks.add(link);
2626
},
2727
getCollectedAnchors: (): string[] => [...allAnchors],
2828
getCollectedLinks: (): string[] => [...allLinks],

packages/docusaurus/src/client/exports/Link.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,20 @@ function Link(
140140
};
141141
}, [ioRef, targetLink, IOSupported, isInternal]);
142142

143+
// It is simple local anchor link targeting current page?
143144
const isAnchorLink = targetLink?.startsWith('#') ?? false;
145+
146+
// Should we use a regular <a> tag instead of React-Router Link component?
144147
const isRegularHtmlLink = !targetLink || !isInternal || isAnchorLink;
145148

146-
if (!isRegularHtmlLink && !noBrokenLinkCheck) {
149+
if (!noBrokenLinkCheck && (isAnchorLink || !isRegularHtmlLink)) {
147150
brokenLinks.collectLink(targetLink!);
148151
}
149152

153+
if (props.id) {
154+
brokenLinks.collectAnchor(props.id);
155+
}
156+
150157
return isRegularHtmlLink ? (
151158
// eslint-disable-next-line jsx-a11y/anchor-has-content, @docusaurus/no-html-links
152159
<a

0 commit comments

Comments
 (0)