Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | import { LinkIcon } from "@chakra-ui/icons";
import { Flex, Heading, As } from "@chakra-ui/react";
import { Children, FunctionComponent, ReactNode } from "react";
import ReactDOMServer from "react-dom/server";
import { useLocation } from "react-router-dom";
import { sanitize } from "../../util/sanitize-anchor";
import { NavLink } from "../NavLink";
interface HeadingResolverProps {
level: number;
children: ReactNode;
}
/**
* Extracts the string leaves from the provided ReactNode.
*
* @param node the node from which string data should be fetched.
*
* @returns the visible string content from the node.
*/
const stringContent = (node: ReactNode): string => {
return Children.toArray(node)
.reduce((acc: string, child) => {
if (typeof child === "string") {
return acc + child;
}
if (typeof child === "object" && "props" in child) {
return acc + stringContent(child.props.children);
}
return acc;
}, "")
.trim();
};
const HeadingLink: FunctionComponent<{
id: string;
level: number;
title: string;
}> = ({ id, level, title }) => {
const { search } = useLocation();
return (
<NavLink
_active={{ visibility: "initial" }}
_focus={{ visibility: "initial" }}
alignItems="center"
data-heading-id={`#${id}`}
data-heading-level={level}
data-heading-title={title}
display="flex"
id={id}
lineHeight={1}
opacity="hidden"
replace
// Keep search query (or empty string if no query) in url to avoid breaking submodule navigation
// E.g: #some-page-element, ?submodule=foo#some-page-element
to={`${search}#${id}`}
visibility="hidden"
>
<LinkIcon boxSize={4} />
</NavLink>
);
};
export const Headings: FunctionComponent<HeadingResolverProps> = ({
level,
children,
}) => {
const size: string = ["2xl", "xl", "lg", "md", "sm", "xs"][level - 1];
const elem = `h${level}` as As<any>;
// Use DOMParser to look for data attribute for link ID
const parser = new DOMParser();
const doc = parser.parseFromString(
ReactDOMServer.renderToStaticMarkup(children as React.ReactElement),
"text/html"
);
const dataElement = doc.querySelector(
"span[data-heading-title][data-heading-id]"
) as HTMLElement;
const title = dataElement?.dataset.headingTitle ?? stringContent(children);
const id = dataElement?.dataset.headingId ?? sanitize(title);
return (
<Flex
_hover={{
"> a": {
visibility: "initial",
},
}}
align="stretch"
borderBottom="base"
justify="space-between"
mb={4}
mt={level >= 4 ? "1.5em" : 4}
px={level >= 4 ? 2 : undefined}
py={2}
>
<Heading
as={elem}
color="textPrimary"
level={level}
size={size}
sx={{ "> code": { fontSize: "inherit" } }}
>
{children}
</Heading>
<HeadingLink id={id} level={level} title={title} />
</Flex>
);
};
|