完善 docs

This commit is contained in:
SmallMain
2022-06-01 14:40:10 +08:00
parent 113677baf9
commit 69fba27b50
40 changed files with 911 additions and 206 deletions

View File

@@ -0,0 +1,27 @@
import React from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import isInternalUrl from '@docusaurus/isInternalUrl';
import IconExternalLink from '@theme/IconExternalLink';
export default function FooterLinkItem({item}) {
const {to, href, label, prependBaseUrlToHref, ...props} = item;
const toUrl = useBaseUrl(to);
const normalizedHref = useBaseUrl(href, {
forcePrependBaseUrl: true,
});
return (
<Link
className="footer__link-item"
{...(href
? {
href: prependBaseUrlToHref ? normalizedHref : href,
}
: {
to: toUrl,
})}
{...props}>
{label}
{/* {href && !isInternalUrl(href) && <IconExternalLink />} */}
</Link>
);
}

View File

@@ -0,0 +1,26 @@
import React from 'react';
import {useThemeConfig} from '@docusaurus/theme-common';
import FooterLinks from '@theme/Footer/Links';
import FooterLogo from '@theme/Footer/Logo';
import FooterCopyright from '@theme/Footer/Copyright';
import FooterLayout from '@theme/Footer/Layout';
function Footer() {
const {footer} = useThemeConfig();
if (!footer) {
return null;
}
const {copyright, links, logo, style} = footer;
return (
<FooterLayout
style={style}
links={links && links.length > 0 && <FooterLinks links={links} />}
logo={logo && <FooterLogo logo={logo} />}
copyright={copyright && <FooterCopyright copyright={copyright} />}
/>
);
}
export default React.memo(Footer);

View File

@@ -0,0 +1,21 @@
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
import HtmlNavbarItem from '@theme/NavbarItem/HtmlNavbarItem';
import DocNavbarItem from '@theme/NavbarItem/DocNavbarItem';
import DocSidebarNavbarItem from '@theme/NavbarItem/DocSidebarNavbarItem';
import DocsVersionNavbarItem from '@theme/NavbarItem/DocsVersionNavbarItem';
import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
const ComponentTypes = {
default: DefaultNavbarItem,
localeDropdown: LocaleDropdownNavbarItem,
search: SearchNavbarItem,
dropdown: DropdownNavbarItem,
html: HtmlNavbarItem,
doc: DocNavbarItem,
docSidebar: DocSidebarNavbarItem,
docsVersion: DocsVersionNavbarItem,
docsVersionDropdown: DocsVersionDropdownNavbarItem,
};
export default ComponentTypes;

View File

@@ -0,0 +1,51 @@
import React from 'react';
import clsx from 'clsx';
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
import {getInfimaActiveClassName} from '@theme/NavbarItem/utils';
function DefaultNavbarItemDesktop({
className,
isDropdownItem = false,
...props
}) {
const element = (
<NavbarNavLink
className={clsx(
isDropdownItem ? 'dropdown__link' : 'navbar__item navbar__link',
className,
)}
{...props}
/>
);
if (isDropdownItem) {
return <li>{element}</li>;
}
return element;
}
function DefaultNavbarItemMobile({className, isDropdownItem, ...props}) {
return (
<li className="menu__list-item">
<NavbarNavLink className={clsx('menu__link', className)} {...props} />
</li>
);
}
export default function DefaultNavbarItem({
mobile = false,
position,
// Need to destructure position from props so that it doesn't get passed on.
...props
}) {
const Comp = mobile ? DefaultNavbarItemMobile : DefaultNavbarItemDesktop;
return (
<Comp
{...props}
activeClassName={
props.activeClassName ?? getInfimaActiveClassName(mobile)
}
/>
);
}

View File

@@ -0,0 +1,37 @@
import React from 'react';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client';
import clsx from 'clsx';
import {getInfimaActiveClassName} from '@theme/NavbarItem/utils';
import {useLayoutDoc} from '@docusaurus/theme-common';
export default function DocNavbarItem({
docId,
label: staticLabel,
docsPluginId,
...props
}) {
const {activeDoc} = useActiveDocContext(docsPluginId);
const doc = useLayoutDoc(docId, docsPluginId); // Draft items are not displayed in the navbar.
if (doc === null) {
return null;
}
const activeDocInfimaClassName = getInfimaActiveClassName(props.mobile);
return (
<DefaultNavbarItem
exact
{...props}
className={clsx(props.className, {
// Do not make the item active if the active doc doesn't have sidebar.
[activeDocInfimaClassName]:
// If `activeDoc === doc` react-router will make it active anyways,
// regardless of the existence of a sidebar
activeDoc?.sidebar && activeDoc.sidebar === doc.sidebar,
})}
activeClassName={activeDocInfimaClassName}
label={staticLabel ?? doc.id}
to={doc.path}
/>
);
}

View File

@@ -0,0 +1,35 @@
import React from 'react';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client';
import clsx from 'clsx';
import {getInfimaActiveClassName} from '@theme/NavbarItem/utils';
import {useLayoutDocsSidebar} from '@docusaurus/theme-common';
export default function DocSidebarNavbarItem({
sidebarId,
label,
docsPluginId,
...props
}) {
const {activeDoc} = useActiveDocContext(docsPluginId);
const sidebarLink = useLayoutDocsSidebar(sidebarId, docsPluginId).link;
if (!sidebarLink) {
throw new Error(
`DocSidebarNavbarItem: Sidebar with ID "${sidebarId}" doesn't have anything to be linked to.`,
);
}
const activeDocInfimaClassName = getInfimaActiveClassName(props.mobile);
return (
<DefaultNavbarItem
exact
{...props}
className={clsx(props.className, {
[activeDocInfimaClassName]: activeDoc?.sidebar === sidebarId,
})}
activeClassName={activeDocInfimaClassName}
label={label ?? sidebarLink.label}
to={sidebarLink.path}
/>
);
}

View File

@@ -0,0 +1,87 @@
import React from 'react';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
import {
useVersions,
useActiveDocContext,
} from '@docusaurus/plugin-content-docs/client';
import {
useDocsPreferredVersion,
useDocsVersionCandidates,
} from '@docusaurus/theme-common';
import {translate} from '@docusaurus/Translate';
const getVersionMainDoc = (version) =>
version.docs.find((doc) => doc.id === version.mainDocId);
export default function DocsVersionDropdownNavbarItem({
mobile,
docsPluginId,
dropdownActiveClassDisabled,
dropdownItemsBefore,
dropdownItemsAfter,
...props
}) {
const activeDocContext = useActiveDocContext(docsPluginId);
const versions = useVersions(docsPluginId);
const {savePreferredVersionName} = useDocsPreferredVersion(docsPluginId);
const versionLinks = versions.map((version) => {
// We try to link to the same doc, in another version
// When not possible, fallback to the "main doc" of the version
const versionDoc =
activeDocContext?.alternateDocVersions[version.name] ??
getVersionMainDoc(version);
return {
isNavLink: true,
label: version.label,
to: versionDoc.path,
isActive: () => version === activeDocContext?.activeVersion,
onClick: () => savePreferredVersionName(version.name),
};
});
const items = [
...dropdownItemsBefore,
...versionLinks,
...dropdownItemsAfter,
];
const dropdownVersion = useDocsVersionCandidates(docsPluginId)[0]; // Mobile dropdown is handled a bit differently
const dropdownLabel =
mobile && items.length > 1
? translate({
id: 'theme.navbar.mobileVersionsDropdown.label',
message: 'Versions',
description:
'The label for the navbar versions dropdown on mobile view',
})
: dropdownVersion.label;
const dropdownTo =
mobile && items.length > 1
? undefined
: getVersionMainDoc(dropdownVersion).path; // We don't want to render a version dropdown with 0 or 1 item. If we build
// the site with a single docs version (onlyIncludeVersions: ['1.0.0']),
// We'd rather render a button instead of a dropdown
if (items.length <= 1) {
return (
<DefaultNavbarItem
{...props}
mobile={mobile}
label={dropdownLabel}
to={dropdownTo}
isActive={dropdownActiveClassDisabled ? () => false : undefined}
/>
);
}
return (
<DropdownNavbarItem
{...props}
mobile={mobile}
label={dropdownLabel}
to={dropdownTo}
items={items}
isActive={dropdownActiveClassDisabled ? () => false : undefined}
/>
);
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import {useDocsVersionCandidates} from '@docusaurus/theme-common';
const getVersionMainDoc = (version) =>
version.docs.find((doc) => doc.id === version.mainDocId);
export default function DocsVersionNavbarItem({
label: staticLabel,
to: staticTo,
docsPluginId,
...props
}) {
const version = useDocsVersionCandidates(docsPluginId)[0];
const label = staticLabel ?? version.label;
const path = staticTo ?? getVersionMainDoc(version).path;
return <DefaultNavbarItem {...props} label={label} to={path} />;
}

View File

@@ -0,0 +1,161 @@
import React, {useState, useRef, useEffect} from 'react';
import clsx from 'clsx';
import {
isSamePath,
useCollapsible,
Collapsible,
isRegexpStringMatch,
useLocalPathname,
} from '@docusaurus/theme-common';
import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink';
import NavbarItem from '@theme/NavbarItem';
const dropdownLinkActiveClass = 'dropdown__link--active';
function isItemActive(item, localPathname) {
if (isSamePath(item.to, localPathname)) {
return true;
}
if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) {
return true;
}
if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) {
return true;
}
return false;
}
function containsActiveItems(items, localPathname) {
return items.some((item) => isItemActive(item, localPathname));
}
function DropdownNavbarItemDesktop({items, position, className, ...props}) {
const dropdownRef = useRef(null);
const [showDropdown, setShowDropdown] = useState(false);
useEffect(() => {
const handleClickOutside = (event) => {
if (!dropdownRef.current || dropdownRef.current.contains(event.target)) {
return;
}
setShowDropdown(false);
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('touchstart', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('touchstart', handleClickOutside);
};
}, [dropdownRef]);
return (
<div
ref={dropdownRef}
className={clsx('navbar__item', 'dropdown', 'dropdown--hoverable', {
'dropdown--right': position === 'right',
'dropdown--show': showDropdown,
})}>
<NavbarNavLink
aria-haspopup="true"
aria-expanded={showDropdown}
role="button"
href={props.to ? undefined : '#'}
className={clsx('navbar__link', className)}
{...props}
onClick={props.to ? undefined : (e) => e.preventDefault()}
onKeyDown={(e) => {
if (e.key === 'Enter') {
e.preventDefault();
setShowDropdown(!showDropdown);
}
}}>
{props.children ?? props.label}
</NavbarNavLink>
<ul className="dropdown__menu">
{items.map((childItemProps, i) => (
<NavbarItem
isDropdownItem
onKeyDown={(e) => {
if (i === items.length - 1 && e.key === 'Tab') {
e.preventDefault();
setShowDropdown(false);
const nextNavbarItem = dropdownRef.current.nextElementSibling;
if (nextNavbarItem) {
const targetItem =
nextNavbarItem instanceof HTMLAnchorElement
? nextNavbarItem // Next item is another dropdown; focus on the inner
: // anchor element instead so there's outline
nextNavbarItem.querySelector('a');
targetItem.focus();
}
}
}}
activeClassName={dropdownLinkActiveClass}
{...childItemProps}
key={i}
/>
))}
</ul>
</div>
);
}
function DropdownNavbarItemMobile({
items,
className,
position,
// Need to destructure position from props so that it doesn't get passed on.
...props
}) {
const localPathname = useLocalPathname();
const containsActive = containsActiveItems(items, localPathname);
const {collapsed, toggleCollapsed, setCollapsed} = useCollapsible({
initialState: () => !containsActive,
}); // Expand/collapse if any item active after a navigation
useEffect(() => {
if (containsActive) {
setCollapsed(!containsActive);
}
}, [localPathname, containsActive, setCollapsed]);
return (
<li
className={clsx('menu__list-item', {
'menu__list-item--collapsed': collapsed,
})}>
<NavbarNavLink
role="button"
className={clsx(
'menu__link menu__link--sublist menu__link--sublist-caret',
className,
)}
{...props}
onClick={(e) => {
e.preventDefault();
toggleCollapsed();
}}>
{props.children ?? props.label}
</NavbarNavLink>
<Collapsible lazy as="ul" className="menu__list" collapsed={collapsed}>
{items.map((childItemProps, i) => (
<NavbarItem
mobile
isDropdownItem
onClick={props.onClick}
activeClassName="menu__link--active"
{...childItemProps}
key={i}
/>
))}
</Collapsible>
</li>
);
}
export default function DropdownNavbarItem({mobile = false, ...props}) {
const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop;
return <Comp {...props} />;
}

View File

@@ -0,0 +1,24 @@
import React from 'react';
import clsx from 'clsx';
export default function HtmlNavbarItem({
value,
className,
mobile = false,
isDropdownItem = false,
}) {
const Comp = isDropdownItem ? 'li' : 'div';
return (
<Comp
className={clsx(
{
navbar__item: !mobile && !isDropdownItem,
'menu__list-item': mobile,
},
className,
)}
dangerouslySetInnerHTML={{
__html: value,
}}
/>
);
}

View File

@@ -0,0 +1,69 @@
import React from 'react';
import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl';
import isInternalUrl from '@docusaurus/isInternalUrl';
import {isRegexpStringMatch} from '@docusaurus/theme-common';
const dropdownLinkActiveClass = 'dropdown__link--active';
export default function NavbarNavLink({
activeBasePath,
activeBaseRegex,
to,
href,
label,
html,
activeClassName = '',
prependBaseUrlToHref,
...props
}) {
// TODO all this seems hacky
// {to: 'version'} should probably be forbidden, in favor of {to: '/version'}
const toUrl = useBaseUrl(to);
const activeBaseUrl = useBaseUrl(activeBasePath);
const normalizedHref = useBaseUrl(href, {
forcePrependBaseUrl: true,
});
const isExternalLink = label && href && !isInternalUrl(href);
const isDropdownLink = activeClassName === dropdownLinkActiveClass; // Link content is set through html XOR label
const linkContentProps = html
? {
dangerouslySetInnerHTML: {
__html: html,
},
}
: {
children: (
<>
{label}
</>
),
};
if (href) {
return (
<Link
href={prependBaseUrlToHref ? normalizedHref : href}
{...props}
{...linkContentProps}
/>
);
}
return (
<Link
to={toUrl}
isNavLink
activeClassName={
!props.className?.includes(activeClassName) ? activeClassName : ''
}
{...((activeBasePath || activeBaseRegex) && {
isActive: (_match, location) =>
activeBaseRegex
? isRegexpStringMatch(activeBaseRegex, location.pathname)
: location.pathname.startsWith(activeBaseUrl),
})}
{...props}
{...linkContentProps}
/>
);
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import SearchBar from '@theme/SearchBar';
import NavbarSearch from '@theme/Navbar/Search';
export default function SearchNavbarItem({mobile}) {
if (mobile) {
return null;
}
return (
<NavbarSearch>
<SearchBar />
</NavbarSearch>
);
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import ComponentTypes from '@theme/NavbarItem/ComponentTypes';
const getNavbarItemComponent = (type) => {
const component = ComponentTypes[type];
if (!component) {
throw new Error(`No NavbarItem component found for type "${type}".`);
}
return component;
};
function getComponentType(type, isDropdown) {
// Backward compatibility: navbar item with no type set
// but containing dropdown items should use the type "dropdown"
if (!type || type === 'default') {
return isDropdown ? 'dropdown' : 'default';
}
return type;
}
export default function NavbarItem({type, ...props}) {
const componentType = getComponentType(type, props.items !== undefined);
const NavbarItemComponent = getNavbarItemComponent(componentType);
return <NavbarItemComponent {...props} />;
}

View File

@@ -0,0 +1,3 @@
/* eslint-disable import/no-named-export */
export const getInfimaActiveClassName = (mobile) =>
mobile ? 'menu__link--active' : 'navbar__link--active';