docusaurus/packages/docusaurus/src/client/exports/Link.tsx
Sébastien Lorber 72dcd0d8c5
chore: release Docusaurus 3.1.1 (#9793)
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)
2024-01-26 14:11:06 +01:00

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);