Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: break up api rendering #611

Merged
merged 9 commits into from
Nov 9, 2021
8 changes: 8 additions & 0 deletions .projen/deps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .projenrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const project = new web.ReactTypeScriptProject({
"framer-motion@^4",
"jsii-reflect",
"lunr",
"node-emoji",
"prism-react-renderer",
"react-helmet",
"react-markdown",
Expand All @@ -61,6 +62,7 @@ const project = new web.ReactTypeScriptProject({

devDeps: [
"@types/lunr",
"@types/node-emoji",
"@types/react-helmet",
"@types/react-router-dom",
"eslint-plugin-jsx-a11y",
Expand Down
2 changes: 2 additions & 0 deletions package.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/__mocks__/remark-emoji.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Don't process emoji for tests
export default (i: any) => i;
13 changes: 0 additions & 13 deletions src/components/Markdown/Markdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
import remarkEmoji from "remark-emoji";
import remarkGfm from "remark-gfm";
import { CONSTRUCT_HUB_REPO_URL } from "../../constants/links";
import { Code } from "./Code";
import { Headings } from "./Headings";
import { Hr } from "./Hr";
Expand All @@ -21,8 +20,6 @@ import { Table, Thead, Tbody, Tfoot, Tr, Th, Td, TableCaption } from "./Table";
import testIds from "./testIds";
import { A, Blockquote, Em, P, Pre, Sup } from "./Text";

const ONE_MEGABYTE = 1024 * 1024;

const components: ReactMarkdownOptions["components"] = {
a: A,
blockquote: Blockquote,
Expand Down Expand Up @@ -126,16 +123,6 @@ export const Markdown: FunctionComponent<{
return `https://${githubPrefix}/${owner}/${repo}/${githubSuffix}/${url}`;
};

const byteLength = Buffer.byteLength(children);
if (byteLength > ONE_MEGABYTE) {
children = children.substring(0, children.lastIndexOf("# API Reference"));
children = [
children,
"# API Reference",
"The API Reference for this package could not be rendered.",
`If this issue persists, please let us know by creating an [issue](${CONSTRUCT_HUB_REPO_URL}/issues/new)`,
].join("\n");
}
return (
<Box
data-testid={testIds.container}
Expand Down
71 changes: 43 additions & 28 deletions src/components/NavTree/NavTree.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { ChevronDownIcon, ChevronRightIcon } from "@chakra-ui/icons";
import { Box, Flex, Link, IconButton, useDisclosure } from "@chakra-ui/react";
import { FunctionComponent, useMemo } from "react";
import { useLocation } from "react-router-dom";
import { Box, Flex, IconButton, Text, useDisclosure } from "@chakra-ui/react";
import { FunctionComponent, useMemo, ReactNode } from "react";
import { NavLink } from "../NavLink";

export interface NavItemConfig {
children?: NavItemConfig[];
display: string;
url: string;
children: NavItemConfig[];
title: string;
path?: string;
}

export interface NavItemProps extends NavItemConfig {
Expand All @@ -28,22 +27,49 @@ const iconProps = {
w: 4,
};

const NavItem: FunctionComponent<NavItemProps> = ({
interface NavItemWrapperProps {
path?: string;
title: string;
showToggle: boolean;
children: ReactNode;
}

const NavItemWrapper: FunctionComponent<NavItemWrapperProps> = ({
children,
display,
url,
path,
title,
showToggle,
}) => {
const sharedProps = {
_hover: { bg: "rgba(0, 124, 253, 0.05)" },
overflow: "hidden",
pl: showToggle ? 1 : 2,
py: 1.5,
textOverflow: "ellipsis",
w: "100%",
};

return path ? (
<NavLink title={title} to={path} {...sharedProps}>
{children}
</NavLink>
) : (
<Text {...sharedProps}>{children}</Text>
);
};

export const NavItem: FunctionComponent<NavItemProps> = ({
children,
title,
path,
onOpen,
}) => {
const { pathname, hash } = useLocation();
const isHashUrl = url.startsWith("#");
const linkIsActive = isHashUrl ? hash === url : pathname === url;
const linkIsActive = false;
const disclosure = useDisclosure({ onOpen, defaultIsOpen: true });

const showToggle = (children?.length ?? 0) > 0;
const showChildren = disclosure.isOpen && showToggle;

const LinkComponent = isHashUrl ? Link : NavLink;

const nestedItems = useMemo(
() =>
children?.map((item, idx) => {
Expand Down Expand Up @@ -74,20 +100,9 @@ const NavItem: FunctionComponent<NavItemProps> = ({
w={4}
/>
)}
<LinkComponent
_hover={{ bg: "rgba(0, 124, 253, 0.05)" }}
href={url}
overflow="hidden"
pl={showToggle ? 1 : 2}
py={1.5}
textOverflow="ellipsis"
title={display}
to={url}
w="100%"
whiteSpace="nowrap"
>
{display}
</LinkComponent>
<NavItemWrapper path={path} showToggle={showToggle} title={title}>
{title}
</NavItemWrapper>
</Flex>
<Box
_before={{
Expand Down
114 changes: 37 additions & 77 deletions src/views/Package/PackageDocs.tsx
Original file line number Diff line number Diff line change
@@ -1,82 +1,44 @@
import { Box, Flex, Grid } from "@chakra-ui/react";
import type { Assembly } from "@jsii/spec";
import { useState, useEffect, FunctionComponent, useMemo } from "react";
import { useLocation } from "react-router-dom";
import { Markdown } from "../../components/Markdown";
import { NavTree, NavItemConfig } from "../../components/NavTree";
import { FunctionComponent, useEffect } from "react";
import { Route, Switch, useRouteMatch, useLocation } from "react-router-dom";
import { NavTree } from "../../components/NavTree";
import { ChooseSubmodule } from "./ChooseSubmodule";

export interface PackageDocsProps {
markdown: string;
assembly: Assembly;
}

type Item = NavItemConfig & { level: number; children: Item[] };

const appendItem = (itemTree: Item[], item: Element): Item[] => {
if (!(item instanceof HTMLElement)) {
return itemTree;
}

const { headingId, headingLevel = "100" } = item.dataset;
const { innerText } = item;
const level = parseInt(headingLevel);

// Don't create nav items for items with no title / url
if (level > 3 || !innerText || !headingId) {
return itemTree;
}

const last = itemTree[itemTree.length - 1];

if (last == null || last.level >= level) {
return [
...itemTree,
{
display: innerText,
url: `#${headingId}`,
level,
children: [],
},
];
} else {
last.children = appendItem(last.children, item);
return itemTree;
}
};
import { PackageReadme } from "./PackageReadme";
import { usePackageState } from "./PackageState";
import { PackageTypeDocs } from "./PackageTypeDocs";

// We want the nav to be sticky, but it should account for the sticky heading as well, which is 72px
const TOP_OFFSET = "4.5rem";

export const PackageDocs: FunctionComponent<PackageDocsProps> = ({
markdown: source,
assembly,
}) => {
const [navItems, setNavItems] = useState<Item[]>([]);

useEffect(() => {
const tree = [
...document.querySelectorAll(
`[data-heading-id][data-heading-title][data-heading-level]`
),
].reduce(appendItem, []);
const SubmoduleSelector: FunctionComponent = () => {
const {
assembly: { data },
} = usePackageState();

setNavItems(tree);
}, [source]);
return Object.keys(data?.submodules ?? {}).length > 0 ? (
<Flex
borderBottom="1px solid"
borderColor="blue.50"
justify="center"
py={4}
>
<ChooseSubmodule assembly={data} />
</Flex>
) : null;
};
export const PackageDocs: FunctionComponent = () => {
const { path } = useRouteMatch();
const { menuItems } = usePackageState();

const { hash } = useLocation();
useEffect(() => {
if (hash) {
const target = document.querySelector(`${hash}`) as HTMLElement;
target?.scrollIntoView(true);
} else {
window.scrollTo(0, 0);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [source]);

const markdown = useMemo(
() => <Markdown repository={assembly.repository}>{source}</Markdown>,
[assembly.repository, source]
);
});

return (
<Grid
Expand All @@ -99,18 +61,9 @@ export const PackageDocs: FunctionComponent<PackageDocsProps> = ({
pr={4}
top={TOP_OFFSET}
>
{Object.keys(assembly?.submodules ?? {}).length > 0 && (
<Flex
borderBottom="1px solid"
borderColor="blue.50"
justify="center"
py={4}
>
<ChooseSubmodule assembly={assembly} />
</Flex>
)}
<SubmoduleSelector />
<Box overflowY="auto" py={4}>
<NavTree items={navItems} />
<NavTree items={menuItems} />
</Box>
</Flex>
<Box
Expand All @@ -124,7 +77,14 @@ export const PackageDocs: FunctionComponent<PackageDocsProps> = ({
},
}}
>
{markdown}
<Switch>
<Route exact path={path}>
<PackageReadme />
</Route>
<Route exact path={`${path}/api/:typeId`}>
<PackageTypeDocs />
</Route>
MrArnoldPalmer marked this conversation as resolved.
Show resolved Hide resolved
</Switch>
</Box>
</Grid>
);
Expand Down
35 changes: 2 additions & 33 deletions src/views/Package/PackageLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {
Center,
Spinner,
Grid,
Tab,
TabList,
Expand All @@ -13,23 +11,12 @@ import { Page } from "../../components/Page";
import { DependenciesList } from "./DependenciesList";
import { FeedbackLinks } from "./FeedbackLinks";
import { PackageDocs } from "./PackageDocs";
import { PackageDocsError } from "./PackageDocsError";
import { PackageDocsUnsupported } from "./PackageDocsUnsupported";
import { PackageHeader } from "./PackageHeader";
import { usePackageState } from "./PackageState";
import testIds from "./testIds";

export const PackageLayout: FunctionComponent = () => {
const {
assembly,
hasDocs,
hasError,
isLoadingDocs,
isSupported,
markdown,
pageDescription,
pageTitle,
} = usePackageState();
const { pageDescription, pageTitle } = usePackageState();

const [tabIndex, setTabIndex] = useState(0);

Expand Down Expand Up @@ -58,25 +45,7 @@ export const PackageLayout: FunctionComponent = () => {
</TabList>
<TabPanels maxW="full">
<TabPanel p={0}>
{/* Readme and Api Reference Area */}
{isSupported ? (
hasError ? (
<PackageDocsError />
) : isLoadingDocs ? (
<Center minH="16rem">
<Spinner size="xl" />
</Center>
) : (
hasDocs && (
<PackageDocs
assembly={assembly.data!}
markdown={markdown.data!}
/>
)
)
) : (
<PackageDocsUnsupported />
)}
<PackageDocs />
</TabPanel>

<TabPanel>
Expand Down
Loading