doc/src/theme/DocPage/index.tsx (207 lines of code) (raw):
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* 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 type { ReactNode } from 'react';
import React, {
useState, useCallback, useEffect,
} from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies
import { MDXProvider } from '@mdx-js/react';
import renderRoutes from '@docusaurus/renderRoutes';
import type { PropVersionMetadata } from '@docusaurus/plugin-content-docs-types';
import Layout from '@theme/Layout';
import DocSidebar from '@theme/DocSidebar';
import MDXComponents from '@theme/MDXComponents';
import type { DocumentRoute } from '@theme/DocItem';
import type { Props } from '@theme/DocPage';
import IconArrow from '@theme/IconArrow';
import BackToTopButton from '@theme-original/BackToTopButton';
import { matchPath } from '@docusaurus/router';
import { translate } from '@docusaurus/Translate';
import clsx from 'clsx';
// eslint-disable-next-line import/no-extraneous-dependencies
import { ThemeClassNames, docVersionSearchTag } from '@docusaurus/theme-common';
import Head from '@docusaurus/Head';
import type { ImageProps } from 'rc-image';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import Link from '@docusaurus/Link';
import NotFound from '../NotFound';
import styles from './styles.module.css';
import 'react-lazy-load-image-component/src/effects/blur.css';
type DocPageContentProps = {
readonly currentDocRoute: DocumentRoute;
readonly versionMetadata: PropVersionMetadata;
readonly children: ReactNode;
};
const navbarLinkMap = {
general: 'General',
apisix: 'Apache APISIX®',
dashboard: 'Apache APISIX® Dashboard',
'ingress-controller': 'Apache APISIX® Ingress Controller',
'helm-chart': 'Apache APISIX® Helm Chart',
docker: 'Apache APISIX® Docker',
'java-plugin-runner': 'Apache APISIX® Java Plugin Runner',
'go-plugin-runner': 'Apache APISIX® Go Plugin Runner',
'python-plugin-runner': 'Apache APISIX® Python Plugin Runner',
};
const navbarLinkKeys = Object.keys(navbarLinkMap);
const components = (currentPage: string) => ({
...MDXComponents,
a: (props) => {
const { children, ...others } = props;
const inCurrent = props.href?.includes(currentPage) || props.href?.startsWith('#');
return (
<Link {...others} {...{ target: inCurrent ? null : '_blank' }}>
{children as any}
</Link>
);
},
img: (props: ImageProps) => (
<LazyLoadImage
effect="blur"
placeholder={(
<div>
<noscript>
<img alt="placeholder" {...(props as any)} />
</noscript>
<div
style={{
width: 500,
height: 300,
borderRadius: '1rem',
backgroundColor: '#d2d2d7',
}}
/>
</div>
)}
{...(props as any)}
/>
),
});
const DocPageContent = ({
currentDocRoute,
versionMetadata,
children,
}: DocPageContentProps): JSX.Element => {
const { pluginId, version } = versionMetadata;
const sidebarName = currentDocRoute.sidebar;
const sidebar = sidebarName ? versionMetadata.docsSidebars[sidebarName] : undefined;
const [hiddenSidebarContainer, setHiddenSidebarContainer] = useState(false);
const [hiddenSidebar, setHiddenSidebar] = useState(false);
const pathArr = currentDocRoute.path.split('/').slice(2, 4);
const currentPage = pathArr.reduce(
(res, cur) => (navbarLinkKeys.includes(cur) ? cur : res),
'',
);
useEffect(() => {
const navbarLink = document.querySelectorAll('.navbar__link')[0] as HTMLAnchorElement;
navbarLink.innerText = navbarLinkMap[currentPage];
}, []);
const toggleSidebar = useCallback(() => {
if (hiddenSidebar) {
setHiddenSidebar(false);
}
setHiddenSidebarContainer(!hiddenSidebarContainer);
}, [hiddenSidebar]);
return (
<Layout
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
wrapperClassName={ThemeClassNames.wrapper.docsPages}
pageClassName={ThemeClassNames.page.docsDocPage}
searchMetadatas={{
version,
tag: docVersionSearchTag(pluginId, version),
}}
>
<div className={styles.docPage}>
<BackToTopButton />
{sidebar && (
<aside
className={clsx(styles.docSidebarContainer, {
[styles.docSidebarContainerHidden]: hiddenSidebarContainer,
})}
onTransitionEnd={(e) => {
if (!e.currentTarget.classList.contains(styles.docSidebarContainer)) {
return;
}
if (hiddenSidebarContainer) {
setHiddenSidebar(true);
}
}}
>
<DocSidebar
key={
// Reset sidebar state on sidebar changes
// See https://github.com/facebook/docusaurus/issues/3414
sidebarName
}
sidebar={sidebar}
path={currentDocRoute.path}
onCollapse={toggleSidebar}
isHidden={hiddenSidebar}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
docsPluginId={pluginId}
/>
{hiddenSidebar && (
<div
className={styles.collapsedDocSidebar}
title={translate({
id: 'theme.docs.sidebar.expandButtonTitle',
message: 'Expand sidebar',
description:
'The ARIA label and title attribute for expand button of doc sidebar',
})}
aria-label={translate({
id: 'theme.docs.sidebar.expandButtonAriaLabel',
message: 'Expand sidebar',
description:
'The ARIA label and title attribute for expand button of doc sidebar',
})}
tabIndex={0}
role="button"
onKeyDown={toggleSidebar}
onClick={toggleSidebar}
>
<IconArrow className={styles.expandSidebarButtonIcon} />
</div>
)}
</aside>
)}
<main
className={clsx(styles.docMainContainer, {
[styles.docMainContainerEnhanced]: hiddenSidebarContainer || !sidebar,
})}
>
<div
className={clsx('container padding-top--md padding-bottom--lg', styles.docItemWrapper, {
[styles.docItemWrapperEnhanced]: hiddenSidebarContainer,
})}
>
<MDXProvider components={() => components(currentPage)}>
{children}
</MDXProvider>
</div>
</main>
</div>
</Layout>
);
};
const DocPage = (props: Props): JSX.Element => {
const {
route: { routes: docRoutes },
versionMetadata,
location,
} = props;
const currentDocRoute = docRoutes.find((docRoute) => matchPath(location.pathname, docRoute));
if (!currentDocRoute) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return <NotFound {...props} />;
}
return (
<>
<Head>
{/* TODO we should add a core addRoute({htmlClassName}) generic plugin option */}
{/* eslint-disable-next-line jsx-a11y/html-has-lang */}
<html className={versionMetadata.className} />
</Head>
<DocPageContent currentDocRoute={currentDocRoute} versionMetadata={versionMetadata}>
{renderRoutes(docRoutes, { versionMetadata })}
</DocPageContent>
</>
);
};
export default DocPage;