feat: table of contents / method visibility / property modifiers

This commit is contained in:
iCrawl
2022-08-17 21:12:09 +02:00
parent 0f83402985
commit 2f1ec7401c
12 changed files with 130 additions and 51 deletions

View File

@@ -1,10 +1,8 @@
import { Group, Stack, Title } from '@mantine/core'; import { Badge, Group, Stack, Title } from '@mantine/core';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { HyperlinkedText } from './HyperlinkedText'; import { HyperlinkedText } from './HyperlinkedText';
import { TSDoc } from './tsdoc/TSDoc'; import { TSDoc } from './tsdoc/TSDoc';
import type { DocItem } from '~/DocModel/DocItem'; import type { DocProperty } from '~/DocModel/DocProperty';
import type { AnyDocNodeJSON } from '~/DocModel/comment/CommentNode';
import type { TokenDocumentation } from '~/util/parse.server';
export enum CodeListingSeparatorType { export enum CodeListingSeparatorType {
Type = ':', Type = ':',
@@ -12,35 +10,30 @@ export enum CodeListingSeparatorType {
} }
export function CodeListing({ export function CodeListing({
name, prop,
separator = CodeListingSeparatorType.Type, separator = CodeListingSeparatorType.Type,
summary,
typeTokens,
children, children,
comment,
}: { }: {
name: string; prop: ReturnType<DocProperty['toJSON']>;
summary?: ReturnType<DocItem['toJSON']>['summary'];
typeTokens: TokenDocumentation[];
separator?: CodeListingSeparatorType; separator?: CodeListingSeparatorType;
children?: ReactNode; children?: ReactNode;
className?: string | undefined;
comment?: AnyDocNodeJSON | null;
}) { }) {
return ( return (
<Stack spacing="xs" key={name}> <Stack spacing="xs" key={prop.name}>
<Group> <Group>
{prop.readonly ? <Badge variant="filled">Readonly</Badge> : null}
{prop.optional ? <Badge variant="filled">Optional</Badge> : null}
<Title order={4} className="font-mono"> <Title order={4} className="font-mono">
{name} {prop.name}
</Title> </Title>
<Title order={4}>{separator}</Title> <Title order={4}>{separator}</Title>
<Title order={4} className="font-mono break-all"> <Title order={4} className="font-mono break-all">
<HyperlinkedText tokens={typeTokens} /> <HyperlinkedText tokens={prop.propertyTypeTokens} />
</Title> </Title>
</Group> </Group>
<Group> <Group>
{summary && <TSDoc node={summary} />} {prop.summary && <TSDoc node={prop.summary} />}
{comment && <TSDoc node={comment} />} {prop.comment && <TSDoc node={prop.comment} />}
{children} {children}
</Group> </Group>
</Stack> </Stack>

View File

@@ -1,9 +1,10 @@
import { Group, Stack, Title } from '@mantine/core'; import { Badge, Group, Stack, Title } from '@mantine/core';
import { HyperlinkedText } from './HyperlinkedText'; import { HyperlinkedText } from './HyperlinkedText';
import { ParameterTable } from './ParameterTable'; import { ParameterTable } from './ParameterTable';
import { TSDoc } from './tsdoc/TSDoc'; import { TSDoc } from './tsdoc/TSDoc';
import type { DocMethod } from '~/DocModel/DocMethod'; import type { DocMethod } from '~/DocModel/DocMethod';
import type { DocMethodSignature } from '~/DocModel/DocMethodSignature'; import type { DocMethodSignature } from '~/DocModel/DocMethodSignature';
import { Visibility } from '~/DocModel/Visibility';
type MethodResolvable = ReturnType<DocMethod['toJSON']> | ReturnType<DocMethodSignature['toJSON']>; type MethodResolvable = ReturnType<DocMethod['toJSON']> | ReturnType<DocMethodSignature['toJSON']>;
@@ -18,11 +19,21 @@ function getShorthandName(data: MethodResolvable) {
} }
export function MethodItem({ data }: { data: MethodResolvable }) { export function MethodItem({ data }: { data: MethodResolvable }) {
const method = data as ReturnType<DocMethod['toJSON']>;
return ( return (
<Stack spacing="xs"> <Stack
id={`${data.name}${data.overloadIndex && data.overloadIndex > 1 ? `:${data.overloadIndex}` : ''}`}
className="scroll-mt-30"
spacing="xs"
>
<Group> <Group>
<Stack> <Stack>
<Group> <Group>
{data.kind === 'Method' && method.visibility === Visibility.Protected ? (
<Badge variant="filled">Protected</Badge>
) : null}
{data.kind === 'Method' && method.static ? <Badge variant="filled">Static</Badge> : null}
<Title order={4} className="font-mono break-all">{`${getShorthandName(data)}`}</Title> <Title order={4} className="font-mono break-all">{`${getShorthandName(data)}`}</Title>
<Title order={4}>:</Title> <Title order={4}>:</Title>
<Title order={4} className="font-mono break-all"> <Title order={4} className="font-mono break-all">

View File

@@ -12,7 +12,10 @@ export function MethodList({
<Stack> <Stack>
{data.map((method) => ( {data.map((method) => (
<> <>
<MethodItem key={method.name} data={method} /> <MethodItem
key={`${method.name}${method.overloadIndex && method.overloadIndex > 1 ? `:${method.overloadIndex}` : ''}`}
data={method}
/>
<Divider className="bg-gray-100" size="md" /> <Divider className="bg-gray-100" size="md" />
</> </>
))} ))}

View File

@@ -6,13 +6,7 @@ export function PropertyList({ data }: { data: ReturnType<DocProperty['toJSON']>
return ( return (
<Stack> <Stack>
{data.map((prop) => ( {data.map((prop) => (
<CodeListing <CodeListing key={prop.name} prop={prop} />
key={prop.name}
name={prop.name}
typeTokens={prop.propertyTypeTokens}
summary={prop.summary}
comment={prop.comment}
/>
))} ))}
</Stack> </Stack>
); );

View File

@@ -11,7 +11,7 @@ const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
fontSize: theme.fontSizes.sm, fontSize: theme.fontSizes.sm,
'&:hover': { '&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0], backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black, color: theme.colorScheme === 'dark' ? theme.white : theme.black,
}, },
}, },

View File

@@ -4,10 +4,15 @@ import { MethodList } from './MethodList';
import { ParameterTable } from './ParameterTable'; import { ParameterTable } from './ParameterTable';
import { PropertyList } from './PropertyList'; import { PropertyList } from './PropertyList';
import { Section } from './Section'; import { Section } from './Section';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocInterface } from '~/DocModel/DocInterface'; import type { DocInterface } from '~/DocModel/DocInterface';
import type { ParameterDocumentation } from '~/util/parse.server'; import type { ParameterDocumentation } from '~/util/parse.server';
export function PropertiesSection({ data }: { data: ReturnType<DocInterface['toJSON']>['properties'] }) { export function PropertiesSection({
data,
}: {
data: ReturnType<DocClass['toJSON']>['properties'] | ReturnType<DocInterface['toJSON']>['properties'];
}) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false }); const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
return data.length ? ( return data.length ? (
@@ -17,7 +22,11 @@ export function PropertiesSection({ data }: { data: ReturnType<DocInterface['toJ
) : null; ) : null;
} }
export function MethodsSection({ data }: { data: ReturnType<DocInterface['toJSON']>['methods'] }) { export function MethodsSection({
data,
}: {
data: ReturnType<DocClass['toJSON']>['methods'] | ReturnType<DocInterface['toJSON']>['methods'];
}) {
const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false }); const matches = useMediaQuery('(max-width: 768px)', true, { getInitialValueInEffect: false });
return data.length ? ( return data.length ? (

View File

@@ -77,7 +77,7 @@ const useStyles = createStyles((theme) => ({
borderLeft: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark![4] : theme.colors.gray![3]}`, borderLeft: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark![4] : theme.colors.gray![3]}`,
'&:hover': { '&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0], backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black, color: theme.colorScheme === 'dark' ? theme.white : theme.black,
}, },
}, },

View File

@@ -3,7 +3,7 @@ import {
AppShell, AppShell,
Navbar, Navbar,
MediaQuery, MediaQuery,
// Aside, Aside,
Header, Header,
Burger, Burger,
Anchor, Anchor,
@@ -26,6 +26,8 @@ import { type PropsWithChildren, useState } from 'react';
import { VscChevronDown, VscPackage } from 'react-icons/vsc'; import { VscChevronDown, VscPackage } from 'react-icons/vsc';
import { WiDaySunny, WiNightClear } from 'react-icons/wi'; import { WiDaySunny, WiNightClear } from 'react-icons/wi';
import { SidebarItems } from './SidebarItems'; import { SidebarItems } from './SidebarItems';
import { TableOfContentsItems } from './TableOfContentsItems';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocItem } from '~/DocModel/DocItem'; import type { DocItem } from '~/DocModel/DocItem';
import type { findMember } from '~/util/model.server'; import type { findMember } from '~/util/model.server';
import type { getMembers } from '~/util/parse.server'; import type { getMembers } from '~/util/parse.server';
@@ -59,7 +61,7 @@ const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black, color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
'&:hover': { '&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0], backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black, color: theme.colorScheme === 'dark' ? theme.white : theme.black,
}, },
}, },
@@ -153,19 +155,21 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
) : null} ) : null}
</Navbar> </Navbar>
} }
// aside={ aside={
// packageName && data?.member ? ( packageName && data?.member && data.member.kind === 'Class' ? (
// <MediaQuery smallerThan="sm" styles={{ display: 'none' }}> <MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
// <Aside hiddenBreakpoint="sm" width={{ sm: 200, lg: 300 }}> <Aside hiddenBreakpoint="sm" width={{ sm: 200, lg: 300 }}>
// <ScrollArea p="xs"> <ScrollArea p="xs">
// <SidebarItems members={data.members} /> <TableOfContentsItems
// </ScrollArea> members={(data.member as unknown as ReturnType<DocClass['toJSON']>).methods}
// </Aside> ></TableOfContentsItems>
// </MediaQuery> </ScrollArea>
// ) : ( </Aside>
// <></> </MediaQuery>
// ) ) : (
// } <></>
)
}
// footer={ // footer={
// <Footer height={60} p="md"> // <Footer height={60} p="md">
// Application footer // Application footer
@@ -195,7 +199,7 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
onClick={() => toggleColorScheme()} onClick={() => toggleColorScheme()}
title="Toggle color scheme" title="Toggle color scheme"
> >
{dark ? <WiDaySunny size={18} /> : <WiNightClear size={18} />} {dark ? <WiDaySunny size={20} /> : <WiNightClear size={20} />}
</ActionIcon> </ActionIcon>
</Box> </Box>
</Header> </Header>

View File

@@ -0,0 +1,61 @@
import { createStyles, Group, Text, Box } from '@mantine/core';
import { VscListSelection } from 'react-icons/vsc';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocInterface } from '~/DocModel/DocInterface';
const useStyles = createStyles((theme) => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
link: {
...theme.fn.focusStyles(),
display: 'block',
textDecoration: 'none',
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
lineHeight: 1.2,
fontSize: theme.fontSizes.sm,
padding: theme.spacing.xs,
paddingLeft: theme.spacing.md,
marginLeft: 8,
borderTopRightRadius: theme.radius.sm,
borderBottomRightRadius: theme.radius.sm,
borderLeft: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark![4] : theme.colors.gray![3]}`,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
},
},
}));
export function TableOfContentsItems({
members,
}: {
members: ReturnType<DocClass['toJSON']>['methods'] | ReturnType<DocInterface['toJSON']>['methods'];
}) {
const { classes } = useStyles();
const items = members.map((member) => {
const key = `${member.name}${member.overloadIndex && member.overloadIndex > 1 ? `:${member.overloadIndex}` : ''}`;
return (
<Box<'a'> key={key} href={`#${key}`} component="a" className={classes.link}>
<Group>
<Text className="line-clamp-1 text-ellipsis overflow-hidden">{member.name}</Text>
{member.overloadIndex && member.overloadIndex > 1 ? (
<Text size="xs" color="dimmed">
{member.overloadIndex}
</Text>
) : null}
</Group>
</Box>
);
});
return (
<Box>
<Group mb="md">
<VscListSelection size={20} />
<Text>Table of contents</Text>
</Group>
{items}
</Box>
);
}

View File

@@ -5,7 +5,7 @@ import type { DocFunction } from '~/DocModel/DocFunction';
export function Function({ data }: { data: ReturnType<DocFunction['toJSON']> }) { export function Function({ data }: { data: ReturnType<DocFunction['toJSON']> }) {
return ( return (
<DocContainer <DocContainer
name={`${data.name}${data.overloadIndex ? ` (${data.overloadIndex})` : ''}`} name={`${data.name}${data.overloadIndex && data.overloadIndex > 1 ? ` (${data.overloadIndex})` : ''}`}
kind={data.kind} kind={data.kind}
excerpt={data.excerpt} excerpt={data.excerpt}
summary={data.summary} summary={data.summary}

View File

@@ -53,7 +53,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
// causing next.js export to error // causing next.js export to error
.filter((member) => member.name !== 'RESTEvents') .filter((member) => member.name !== 'RESTEvents')
.map((member) => { .map((member) => {
if (member.kind === 'Function' && member.overloadIndex) { if (member.kind === 'Function' && member.overloadIndex && member.overloadIndex > 1) {
return { return {
params: { params: {
slug: ['main', 'packages', packageName, `${member.name}:${member.overloadIndex}`], slug: ['main', 'packages', packageName, `${member.name}:${member.overloadIndex}`],

View File

@@ -34,7 +34,11 @@ export function generatePath(items: readonly ApiItem[]) {
path += `${item.displayName}/`; path += `${item.displayName}/`;
break; break;
case ApiItemKind.Function: case ApiItemKind.Function:
path += `${item.displayName}:${(item as ApiFunction).overloadIndex}/`; // eslint-disable-next-line no-case-declarations
const functionItem = item as ApiFunction;
path += `${functionItem.displayName}${
functionItem.overloadIndex && functionItem.overloadIndex > 1 ? `:${functionItem.overloadIndex}` : ''
}:/`;
break; break;
default: default:
path += `${item.displayName}/`; path += `${item.displayName}/`;