mirror of
https://github.com/facebook/docusaurus.git
synced 2025-08-03 00:39:45 +02:00
Co-authored-by: Joshua Chen <sidachen2003@gmail.com> Co-authored-by: Joey Clover <joey@popos.local> Co-authored-by: reece-white <93522192+reece-white@users.noreply.github.com> Co-authored-by: Shreesh Nautiyal <114166000+Shreesh09@users.noreply.github.com> Co-authored-by: Nick Gerleman <nick@nickgerleman.com> Co-authored-by: Chongyi Zheng <git@zcy.dev> Co-authored-by: MCR Studio <99176216+mcrstudio@users.noreply.github.com> Co-authored-by: sebastienlorber <lorber.sebastien@gmail.com> Co-authored-by: Sébastien Lorber <slorber@users.noreply.github.com> Co-authored-by: Ivan Mar (sOkam!) <7308253+heysokam@users.noreply.github.com> Co-authored-by: c0h1b4 <dwidman@gmail.com> Co-authored-by: Janessa Garrow <janessa.garrow@gmail.com> Co-authored-by: ozaki <29860391+OzakIOne@users.noreply.github.com> Co-authored-by: axmmisaka <6500159+axmmisaka@users.noreply.github.com> Co-authored-by: Tatsunori Uchino <tats.u@live.jp> Co-authored-by: Simen Bekkhus <sbekkhus91@gmail.com> Co-authored-by: Sanjaiyan Parthipan <parthipankalayini@gmail.com> Co-authored-by: Jack Robson <143492403+jack-robson@users.noreply.github.com> Co-authored-by: dawei-wang <dawei-wang@users.noreply.github.com> Co-authored-by: eitsupi <50911393+eitsupi@users.noreply.github.com> fix(create-docusaurus): fix readme docusaurus 2 ref (#9487) fix(theme): fix firefox CSS :has() support bug (#9530) fix(theme): docs html sidebar items should always be visible (#9531) fix: v3 admonitions should support v2 title syntax for nested admonitions (#9535) fix(theme-classic): fixed wrong cursor on dropdown menu in navbar, when window is small (#9398) fix(theme): upgrade prism-react-renderer, fix html script and style tag highlighting (#9567) fix: add v2 retrocompatible support for quoted admonitions (#9570) fix(i18n): complete translations for theme-common.json Brazilian Portuguese (pt-BR) (#9477) fix(content-blog): add baseUrl for author.image_url (#9581) fix(type-aliases): add `title` prop for imported inline SVG React components (#9612) fix(utils): Markdown link replacement with <> but no spaces (#9617) fix(live-codeblock): stabilize react-live transformCode callback, fix editor/preview desync (#9631) fix(cli): output help when no conventional config + no subcommand (#9648) fix CI job (#9604) fix Lint Autofix workflow (#9632) fix(pwa-plugin): upgrade workbox (#9668) fix(create-docusaurus): fix init template code blocks, and little improvements (#9696) fix(theme): allow empty code blocks and live playgrounds (#9704) fix(core): various broken anchor link fixes (#9732) fix: remove old useless mdx typedefs (#9733) fix(theme-common): fix missing code block MagicComments style in Visual Basic (.NET) 16 (#9727) fix(core): conditionally include `hostname` parameter when using… (#9407) fix(create-docusaurus): fix typo in init template sample docs (#9783) fix(mdx-loader): allow spaces before `mdx-code-block` info string (#9776) fix(core): links with target "_blank" should no be checked by the broken link checker (#9788) fix(core): broken links optimization behaves differently than non-optimized logic (#9791)
185 lines
6.1 KiB
TypeScript
185 lines
6.1 KiB
TypeScript
/**
|
|
* 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, {
|
|
useEffect,
|
|
useImperativeHandle,
|
|
useRef,
|
|
type ComponentType,
|
|
} from 'react';
|
|
import {NavLink, Link as RRLink} from 'react-router-dom';
|
|
import {applyTrailingSlash} from '@docusaurus/utils-common';
|
|
import useDocusaurusContext from './useDocusaurusContext';
|
|
import isInternalUrl from './isInternalUrl';
|
|
import ExecutionEnvironment from './ExecutionEnvironment';
|
|
import useBrokenLinks from './useBrokenLinks';
|
|
import {useBaseUrlUtils} from './useBaseUrl';
|
|
import type {Props} from '@docusaurus/Link';
|
|
|
|
// TODO all this wouldn't be necessary if we used ReactRouter basename feature
|
|
// We don't automatically add base urls to all links,
|
|
// only the "safe" ones, starting with / (like /docs/introduction)
|
|
// this is because useBaseUrl() actually transforms relative links
|
|
// like "introduction" to "/baseUrl/introduction" => bad behavior to fix
|
|
const shouldAddBaseUrlAutomatically = (to: string) => to.startsWith('/');
|
|
|
|
function Link(
|
|
{
|
|
isNavLink,
|
|
to,
|
|
href,
|
|
activeClassName,
|
|
isActive,
|
|
'data-noBrokenLinkCheck': noBrokenLinkCheck,
|
|
autoAddBaseUrl = true,
|
|
...props
|
|
}: Props,
|
|
forwardedRef: React.ForwardedRef<HTMLAnchorElement>,
|
|
): JSX.Element {
|
|
const {
|
|
siteConfig: {trailingSlash, baseUrl},
|
|
} = useDocusaurusContext();
|
|
const {withBaseUrl} = useBaseUrlUtils();
|
|
const brokenLinks = useBrokenLinks();
|
|
const innerRef = useRef<HTMLAnchorElement | null>(null);
|
|
|
|
useImperativeHandle(forwardedRef, () => innerRef.current!);
|
|
|
|
// IMPORTANT: using to or href should not change anything
|
|
// For example, MDX links will ALWAYS give us the href props
|
|
// Using one prop or the other should not be used to distinguish
|
|
// internal links (/docs/myDoc) from external links (https://github.com)
|
|
const targetLinkUnprefixed = to || href;
|
|
|
|
function maybeAddBaseUrl(str: string) {
|
|
return autoAddBaseUrl && shouldAddBaseUrlAutomatically(str)
|
|
? withBaseUrl(str)
|
|
: str;
|
|
}
|
|
|
|
const isInternal = isInternalUrl(targetLinkUnprefixed);
|
|
|
|
// pathname:// is a special "protocol" we use to tell Docusaurus link
|
|
// that a link is not "internal" and that we shouldn't use history.push()
|
|
// this is not ideal but a good enough escape hatch for now
|
|
// see https://github.com/facebook/docusaurus/issues/3309
|
|
// note: we want baseUrl to be appended (see issue for details)
|
|
// TODO read routes and automatically detect internal/external links?
|
|
const targetLinkWithoutPathnameProtocol = targetLinkUnprefixed?.replace(
|
|
'pathname://',
|
|
'',
|
|
);
|
|
|
|
// TODO we should use ReactRouter basename feature instead!
|
|
// Automatically apply base url in links that start with /
|
|
let targetLink =
|
|
typeof targetLinkWithoutPathnameProtocol !== 'undefined'
|
|
? maybeAddBaseUrl(targetLinkWithoutPathnameProtocol)
|
|
: undefined;
|
|
|
|
if (targetLink && isInternal) {
|
|
targetLink = applyTrailingSlash(targetLink, {trailingSlash, baseUrl});
|
|
}
|
|
|
|
const preloaded = useRef(false);
|
|
const LinkComponent = (isNavLink ? NavLink : RRLink) as ComponentType<Props>;
|
|
|
|
const IOSupported = ExecutionEnvironment.canUseIntersectionObserver;
|
|
|
|
const ioRef = useRef<IntersectionObserver>();
|
|
|
|
const handleRef = (el: HTMLAnchorElement | null) => {
|
|
innerRef.current = el;
|
|
|
|
if (IOSupported && el && isInternal) {
|
|
// If IO supported and element reference found, set up Observer.
|
|
ioRef.current = new window.IntersectionObserver((entries) => {
|
|
entries.forEach((entry) => {
|
|
if (el === entry.target) {
|
|
// If element is in viewport, stop observing and run callback.
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
|
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
|
ioRef.current!.unobserve(el);
|
|
ioRef.current!.disconnect();
|
|
if (targetLink != null) {
|
|
window.docusaurus.prefetch(targetLink);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
// Add element to the observer.
|
|
ioRef.current.observe(el);
|
|
}
|
|
};
|
|
|
|
const onInteractionEnter = () => {
|
|
if (!preloaded.current && targetLink != null) {
|
|
window.docusaurus.preload(targetLink);
|
|
preloaded.current = true;
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
// If IO is not supported. We prefetch by default (only once).
|
|
if (!IOSupported && isInternal) {
|
|
if (targetLink != null) {
|
|
window.docusaurus.prefetch(targetLink);
|
|
}
|
|
}
|
|
|
|
// When unmounting, stop intersection observer from watching.
|
|
return () => {
|
|
if (IOSupported && ioRef.current) {
|
|
ioRef.current.disconnect();
|
|
}
|
|
};
|
|
}, [ioRef, targetLink, IOSupported, isInternal]);
|
|
|
|
// It is simple local anchor link targeting current page?
|
|
const isAnchorLink = targetLink?.startsWith('#') ?? false;
|
|
|
|
// See also RR logic:
|
|
// https://github.com/remix-run/react-router/blob/v5/packages/react-router-dom/modules/Link.js#L47
|
|
const hasInternalTarget = !props.target || props.target === '_self';
|
|
|
|
// Should we use a regular <a> tag instead of React-Router Link component?
|
|
const isRegularHtmlLink =
|
|
!targetLink || !isInternal || !hasInternalTarget || isAnchorLink;
|
|
|
|
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
|
|
ref={innerRef}
|
|
href={targetLink}
|
|
{...(targetLinkUnprefixed &&
|
|
!isInternal && {target: '_blank', rel: 'noopener noreferrer'})}
|
|
{...props}
|
|
/>
|
|
) : (
|
|
<LinkComponent
|
|
{...props}
|
|
onMouseEnter={onInteractionEnter}
|
|
onTouchStart={onInteractionEnter}
|
|
innerRef={handleRef}
|
|
to={targetLink}
|
|
// Avoid "React does not recognize the `activeClassName` prop on a DOM
|
|
// element"
|
|
{...(isNavLink && {isActive, activeClassName})}
|
|
/>
|
|
);
|
|
}
|
|
|
|
export default React.forwardRef(Link);
|