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 { HyperlinkedText } from './HyperlinkedText';
import { TSDoc } from './tsdoc/TSDoc';
import type { DocItem } from '~/DocModel/DocItem';
import type { AnyDocNodeJSON } from '~/DocModel/comment/CommentNode';
import type { TokenDocumentation } from '~/util/parse.server';
import type { DocProperty } from '~/DocModel/DocProperty';
export enum CodeListingSeparatorType {
Type = ':',
@@ -12,35 +10,30 @@ export enum CodeListingSeparatorType {
}
export function CodeListing({
name,
prop,
separator = CodeListingSeparatorType.Type,
summary,
typeTokens,
children,
comment,
}: {
name: string;
summary?: ReturnType<DocItem['toJSON']>['summary'];
typeTokens: TokenDocumentation[];
prop: ReturnType<DocProperty['toJSON']>;
separator?: CodeListingSeparatorType;
children?: ReactNode;
className?: string | undefined;
comment?: AnyDocNodeJSON | null;
}) {
return (
<Stack spacing="xs" key={name}>
<Stack spacing="xs" key={prop.name}>
<Group>
{prop.readonly ? <Badge variant="filled">Readonly</Badge> : null}
{prop.optional ? <Badge variant="filled">Optional</Badge> : null}
<Title order={4} className="font-mono">
{name}
{prop.name}
</Title>
<Title order={4}>{separator}</Title>
<Title order={4} className="font-mono break-all">
<HyperlinkedText tokens={typeTokens} />
<HyperlinkedText tokens={prop.propertyTypeTokens} />
</Title>
</Group>
<Group>
{summary && <TSDoc node={summary} />}
{comment && <TSDoc node={comment} />}
{prop.summary && <TSDoc node={prop.summary} />}
{prop.comment && <TSDoc node={prop.comment} />}
{children}
</Group>
</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 { ParameterTable } from './ParameterTable';
import { TSDoc } from './tsdoc/TSDoc';
import type { DocMethod } from '~/DocModel/DocMethod';
import type { DocMethodSignature } from '~/DocModel/DocMethodSignature';
import { Visibility } from '~/DocModel/Visibility';
type MethodResolvable = ReturnType<DocMethod['toJSON']> | ReturnType<DocMethodSignature['toJSON']>;
@@ -18,11 +19,21 @@ function getShorthandName(data: MethodResolvable) {
}
export function MethodItem({ data }: { data: MethodResolvable }) {
const method = data as ReturnType<DocMethod['toJSON']>;
return (
<Stack spacing="xs">
<Stack
id={`${data.name}${data.overloadIndex && data.overloadIndex > 1 ? `:${data.overloadIndex}` : ''}`}
className="scroll-mt-30"
spacing="xs"
>
<Group>
<Stack>
<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}>:</Title>
<Title order={4} className="font-mono break-all">

View File

@@ -12,7 +12,10 @@ export function MethodList({
<Stack>
{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" />
</>
))}

View File

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

View File

@@ -11,7 +11,7 @@ const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
fontSize: theme.fontSizes.sm,
'&: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,
},
},

View File

@@ -4,10 +4,15 @@ import { MethodList } from './MethodList';
import { ParameterTable } from './ParameterTable';
import { PropertyList } from './PropertyList';
import { Section } from './Section';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocInterface } from '~/DocModel/DocInterface';
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 });
return data.length ? (
@@ -17,7 +22,11 @@ export function PropertiesSection({ data }: { data: ReturnType<DocInterface['toJ
) : 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 });
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]}`,
'&: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,
},
},

View File

@@ -3,7 +3,7 @@ import {
AppShell,
Navbar,
MediaQuery,
// Aside,
Aside,
Header,
Burger,
Anchor,
@@ -26,6 +26,8 @@ import { type PropsWithChildren, useState } from 'react';
import { VscChevronDown, VscPackage } from 'react-icons/vsc';
import { WiDaySunny, WiNightClear } from 'react-icons/wi';
import { SidebarItems } from './SidebarItems';
import { TableOfContentsItems } from './TableOfContentsItems';
import type { DocClass } from '~/DocModel/DocClass';
import type { DocItem } from '~/DocModel/DocItem';
import type { findMember } from '~/util/model.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,
'&: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,
},
},
@@ -153,19 +155,21 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
) : null}
</Navbar>
}
// aside={
// packageName && data?.member ? (
// <MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
// <Aside hiddenBreakpoint="sm" width={{ sm: 200, lg: 300 }}>
// <ScrollArea p="xs">
// <SidebarItems members={data.members} />
// </ScrollArea>
// </Aside>
// </MediaQuery>
// ) : (
// <></>
// )
// }
aside={
packageName && data?.member && data.member.kind === 'Class' ? (
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<Aside hiddenBreakpoint="sm" width={{ sm: 200, lg: 300 }}>
<ScrollArea p="xs">
<TableOfContentsItems
members={(data.member as unknown as ReturnType<DocClass['toJSON']>).methods}
></TableOfContentsItems>
</ScrollArea>
</Aside>
</MediaQuery>
) : (
<></>
)
}
// footer={
// <Footer height={60} p="md">
// Application footer
@@ -195,7 +199,7 @@ export function SidebarLayout({ packageName, data, children }: PropsWithChildren
onClick={() => toggleColorScheme()}
title="Toggle color scheme"
>
{dark ? <WiDaySunny size={18} /> : <WiNightClear size={18} />}
{dark ? <WiDaySunny size={20} /> : <WiNightClear size={20} />}
</ActionIcon>
</Box>
</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']> }) {
return (
<DocContainer
name={`${data.name}${data.overloadIndex ? ` (${data.overloadIndex})` : ''}`}
name={`${data.name}${data.overloadIndex && data.overloadIndex > 1 ? ` (${data.overloadIndex})` : ''}`}
kind={data.kind}
excerpt={data.excerpt}
summary={data.summary}

View File

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

View File

@@ -34,7 +34,11 @@ export function generatePath(items: readonly ApiItem[]) {
path += `${item.displayName}/`;
break;
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;
default:
path += `${item.displayName}/`;