refactor: website components (#8600)

This commit is contained in:
Noel
2022-09-06 19:48:33 +02:00
committed by GitHub
parent f3ce4a75d0
commit c3341570d9
55 changed files with 1910 additions and 2673 deletions

View File

@@ -43,14 +43,14 @@
"tslib": "^2.4.0"
},
"devDependencies": {
"@types/node": "^16.11.56",
"@vitest/coverage-c8": "^0.23.0",
"@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -35,7 +35,7 @@
"@microsoft/tsdoc": "^0.14.1"
},
"devDependencies": {
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",

View File

@@ -164,7 +164,7 @@ export class ApiNodeJSONEncoder {
case ApiItemKind.Variable:
return this.encodeVariable(model, node as ApiVariable, version);
default:
console.log(`Unknown API item kind: ${node.kind}`);
// console.log(`Unknown API item kind: ${node.kind}`);
return undefined;
}
}

View File

@@ -64,15 +64,15 @@
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0",
"@types/node": "^16.11.56",
"@vitest/coverage-c8": "^0.23.0",
"@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.1",
"downlevel-dts": "^0.10.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -53,15 +53,15 @@
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0",
"@types/node": "^16.11.56",
"@vitest/coverage-c8": "^0.23.0",
"@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.1",
"downlevel-dts": "^0.10.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -64,7 +64,7 @@
"devDependencies": {
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"dtslint": "^4.2.1",
"eslint": "^8.23.0",
"jest": "^29.0.2",

View File

@@ -50,7 +50,7 @@
"devDependencies": {
"@favware/cliff-jumper": "^1.8.7",
"@types/jsdoc-to-markdown": "^7.0.3",
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",

View File

@@ -49,7 +49,7 @@
"tslib": "^2.4.0"
},
"devDependencies": {
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",

View File

@@ -62,9 +62,9 @@
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0",
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"@types/supertest": "^2.0.12",
"@vitest/coverage-c8": "^0.23.0",
"@vitest/coverage-c8": "^0.23.1",
"downlevel-dts": "^0.10.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
@@ -72,7 +72,7 @@
"supertest": "^6.2.4",
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -64,15 +64,15 @@
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0",
"@types/node": "^16.11.56",
"@vitest/coverage-c8": "^0.23.0",
"@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.1",
"downlevel-dts": "^0.10.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -52,14 +52,14 @@
"undici": "^5.10.0"
},
"devDependencies": {
"@types/node": "^16.11.56",
"@vitest/coverage-c8": "^0.23.0",
"@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"prettier": "^2.7.1",
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -59,14 +59,14 @@
"ws": "^8.8.1"
},
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/preset-env": "^7.18.10",
"@babel/core": "^7.19.0",
"@babel/preset-env": "^7.19.0",
"@babel/preset-typescript": "^7.18.6",
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0",
"@types/jest": "^29.0.0",
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"downlevel-dts": "^0.10.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",

View File

@@ -1,3 +1,6 @@
/* eslint-disable tsdoc/syntax */
import { URL, fileURLToPath } from 'node:url';
/**
* @type {import('next').NextConfig}
*/
@@ -12,6 +15,7 @@ export default {
images: {
allowFutureImage: true,
},
outputFileTracingRoot: fileURLToPath(new URL('../../', import.meta.url)),
},
images: {
dangerouslyAllowSVG: true,

View File

@@ -41,52 +41,50 @@
"dependencies": {
"@discordjs/api-extractor-utils": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@emotion/react": "^11.10.4",
"@emotion/server": "^11.10.0",
"@mantine/core": "^5.2.5",
"@mantine/hooks": "^5.2.5",
"@mantine/next": "^5.2.5",
"@mantine/nprogress": "^5.2.5",
"@mantine/spotlight": "^5.2.5",
"@microsoft/api-extractor-model": "7.24.0",
"@microsoft/tsdoc": "0.14.1",
"@vscode/codicons": "^0.0.32",
"ariakit": "^2.0.0-next.41",
"minisearch": "^5.0.0",
"next": "^12.2.5",
"next-mdx-remote": "^4.1.0",
"next-progress": "^2.2.0",
"next-themes": "^0.2.0",
"react": "^18.2.0",
"react-custom-scrollbars-2": "^4.5.0",
"react-dom": "^18.2.0",
"react-icons": "^4.4.0",
"react-syntax-highlighter": "^15.5.0",
"react-use": "^17.4.0",
"rehype-ignore": "^1.0.1",
"rehype-pretty-code": "^0.3.2",
"rehype-raw": "^6.1.1",
"rehype-slug": "^5.0.1",
"remark-gfm": "^3.0.1",
"sharp": "^0.30.7",
"sharp": "^0.31.0",
"shiki": "^0.11.1",
"swr": "^1.3.0"
},
"devDependencies": {
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3",
"@types/node": "^16.11.56",
"@types/node": "^16.11.57",
"@types/react-dom": "^18.0.6",
"@types/react-syntax-highlighter": "^15.5.5",
"@unocss/cli": "^0.45.15",
"@unocss/preset-web-fonts": "^0.45.15",
"@unocss/reset": "^0.45.15",
"@vitejs/plugin-react": "^2.0.1",
"@vitest/coverage-c8": "^0.23.0",
"@vitejs/plugin-react": "^2.1.0",
"@vitest/coverage-c8": "^0.23.1",
"concurrently": "^7.3.0",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
"happy-dom": "^6.0.4",
"prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"typescript": "^4.8.2",
"unocss": "^0.45.15",
"vercel": "^28.2.2",
"vitest": "^0.23.0"
"vitest": "^0.23.1"
},
"engines": {
"node": ">=16.9.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,6 +1,4 @@
import type { TokenDocumentation, ApiItemJSON, AnyDocNodeJSON, InheritanceData } from '@discordjs/api-extractor-utils';
import { ActionIcon, Badge, Box, createStyles, Group, MediaQuery, Stack, Title } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import type { PropsWithChildren } from 'react';
import { FiLink } from 'react-icons/fi';
import { HyperlinkedText } from './HyperlinkedText';
@@ -12,19 +10,6 @@ export enum CodeListingSeparatorType {
Value = '=',
}
const useStyles = createStyles((theme) => ({
outer: {
display: 'flex',
alignItems: 'center',
gap: 16,
[theme.fn.smallerThan('sm')]: {
flexDirection: 'column',
alignItems: 'unset',
},
},
}));
export function CodeListing({
name,
separator = CodeListingSeparatorType.Type,
@@ -47,48 +32,51 @@ export function CodeListing({
summary?: ApiItemJSON['summary'];
typeTokens: TokenDocumentation[];
}>) {
const { classes } = useStyles();
const matches = useMediaQuery('(max-width: 768px)');
return (
<Stack id={name} className="scroll-mt-30" spacing="xs">
<Box className={classes.outer} ml={matches ? 0 : -45}>
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<ActionIcon component="a" href={`#${name}`} variant="transparent">
<div id={name} className="scroll-mt-30 flex flex-col gap-4">
<div className={`md:-ml-8.5 flex flex-col gap-0.5 md:flex-row md:place-items-center md:gap-2`}>
<a className="hidden md:inline-block" href={`#${name}`}>
<FiLink size={20} />
</ActionIcon>
</MediaQuery>
</a>
{deprecation || readonly || optional ? (
<Group spacing={10} noWrap>
<div className="flex flex-row flex-wrap gap-1">
{deprecation ? (
<Badge variant="filled" color="red">
<div className="h-5 place-content-center rounded-full bg-red-500 px-3 text-center text-xs font-semibold uppercase text-white">
Deprecated
</Badge>
</div>
) : null}
{readonly ? <Badge variant="filled">Readonly</Badge> : null}
{optional ? <Badge variant="filled">Optional</Badge> : null}
</Group>
{readonly ? (
<div className="bg-blurple h-5 place-content-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white">
Readonly
</div>
) : null}
<Group spacing={10}>
<Title order={4} className="font-mono">
{optional ? (
<div className="bg-blurple h-5 place-content-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white">
Optional
</div>
) : null}
</div>
) : null}
<div className="flex flex-row flex-wrap place-items-center gap-1">
<h4 className="break-all font-mono text-lg font-bold">
{name}
{optional ? '?' : ''}
</Title>
<Title order={4}>{separator}</Title>
<Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">
</h4>
<h4 className="font-mono text-lg font-bold">{separator}</h4>
<h4 className="break-all font-mono text-lg font-bold">
<HyperlinkedText tokens={typeTokens} />
</Title>
</Group>
</Box>
<Group>
<Stack>
</h4>
</div>
</div>
{summary || inheritanceData ? (
<div className="flex flex-col gap-4">
{deprecation ? <TSDoc node={deprecation} /> : null}
{summary && <TSDoc node={summary} />}
{comment && <TSDoc node={comment} />}
{summary ? <TSDoc node={summary} /> : null}
{comment ? <TSDoc node={comment} /> : null}
{inheritanceData ? <InheritanceText data={inheritanceData} /> : null}
{children}
</Stack>
</Group>
</Stack>
</div>
) : null}
</div>
);
}

View File

@@ -5,22 +5,8 @@ import type {
ApiClassJSON,
ApiInterfaceJSON,
} from '@discordjs/api-extractor-utils';
import {
Group,
Stack,
Title,
Text,
Box,
MediaQuery,
Aside,
ScrollArea,
Skeleton,
Divider,
useMantineColorScheme,
} from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { useRouter } from 'next/router';
import { Fragment, type PropsWithChildren } from 'react';
import { Scrollbars } from 'react-custom-scrollbars-2';
import {
VscSymbolClass,
VscSymbolMethod,
@@ -30,10 +16,10 @@ import {
VscListSelection,
VscSymbolParameter,
} from 'react-icons/vsc';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus, ghcolors } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { useMedia } from 'react-use';
import { HyperlinkedText } from './HyperlinkedText';
import { Section } from './Section';
import { SyntaxHighlighter } from './SyntaxHighlighter';
import { TableOfContentItems } from './TableOfContentItems';
import { TypeParamTable } from './TypeParamTable';
import { TSDoc } from './tsdoc/TSDoc';
@@ -75,71 +61,47 @@ export function DocContainer({
methods,
properties,
}: DocContainerProps) {
const router = useRouter();
const matches = useMediaQuery('(max-width: 768px)');
const { colorScheme } = useMantineColorScheme();
const matches = useMedia('(max-width: 768px)', true);
return (
<Group>
<Stack sx={{ flexGrow: 1, maxWidth: '100%' }}>
<Skeleton visible={router.isFallback} radius="sm">
<Title sx={{ wordBreak: 'break-all' }} order={2} ml="xs">
<Group>
{generateIcon(kind)}
<>
<div className="flex flex-col gap-4">
<h2 className="flex flex-row place-items-center gap-2 break-all text-2xl font-bold">
<span>{generateIcon(kind)}</span>
{name}
</Group>
</Title>
</Skeleton>
</h2>
<Skeleton visible={router.isFallback} radius="sm">
<Section title="Summary" icon={<VscListSelection size={20} />} padded dense={matches}>
{summary ? <TSDoc node={summary} /> : <Text>No summary provided.</Text>}
<Divider size="md" mt={20} />
{summary ? <TSDoc node={summary} /> : <span>No summary provided.</span>}
<div className="border-light-900 -mx-8 mt-6 border-t-2" />
</Section>
</Skeleton>
<Skeleton visible={router.isFallback} radius="sm">
<Box pb="xs">
<SyntaxHighlighter
wrapLongLines
language="typescript"
style={colorScheme === 'dark' ? vscDarkPlus : ghcolors}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
>
{excerpt}
</SyntaxHighlighter>
</Box>
</Skeleton>
<SyntaxHighlighter code={excerpt} />
{extendsTokens?.length ? (
<Group pb="xs" noWrap>
<Title order={3} ml="xs">
Extends
</Title>
<Text sx={{ wordBreak: 'break-all' }} className="font-mono">
<div className="flex flex-row place-items-center gap-4">
<h3 className="text-xl font-bold">Extends</h3>
<span className="break-all font-mono">
<HyperlinkedText tokens={extendsTokens} />
</Text>
</Group>
</span>
</div>
) : null}
{implementsTokens?.length ? (
<Group pb="xs" noWrap>
<Title order={3} ml="xs">
Implements
</Title>
<Text sx={{ wordBreak: 'break-all' }} className="font-mono">
<div className="flex flex-row place-items-center gap-4">
<h3 className="text-xl font-bold">Implements</h3>
<span className="break-all font-mono">
{implementsTokens.map((token, idx) => (
<Fragment key={idx}>
<HyperlinkedText tokens={token} />
{idx < implementsTokens.length - 1 ? ', ' : ''}
</Fragment>
))}
</Text>
</Group>
</span>
</div>
) : null}
<Skeleton visible={router.isFallback} radius="sm">
<Stack>
<div className="flex flex-col gap-4">
{typeParams?.length ? (
<Section
title="Type Parameters"
@@ -151,19 +113,24 @@ export function DocContainer({
<TypeParamTable data={typeParams} />
</Section>
) : null}
<Stack>{children}</Stack>
</Stack>
</Skeleton>
</Stack>
{children}
</div>
</div>
{(kind === 'Class' || kind === 'Interface') && (methods?.length || properties?.length) ? (
<MediaQuery smallerThan="lg" styles={{ display: 'none' }}>
<Aside hiddenBreakpoint="lg" width={{ lg: 250 }} withBorder>
<ScrollArea p="sm" offsetScrollbars>
<aside className="h-[calc(100vh - 72px)] dark:bg-dark-600 dark:border-dark-100 border-light-800 fixed top-[72px] right-0 bottom-0 z-20 hidden w-64 border-l bg-white pr-2 xl:block">
<Scrollbars
universal
autoHide
hideTracksWhenNotNeeded
renderTrackVertical={(props) => (
<div {...props} className="absolute top-0.5 right-0.5 bottom-0.5 z-30 w-1.5 rounded" />
)}
renderThumbVertical={(props) => <div {...props} className="dark:bg-dark-100 bg-light-900 z-30 rounded" />}
>
<TableOfContentItems properties={properties ?? []} methods={methods ?? []} />
</ScrollArea>
</Aside>
</MediaQuery>
</Scrollbars>
</aside>
) : null}
</Group>
</>
);
}

View File

@@ -1,5 +1,4 @@
import type { TokenDocumentation } from '@discordjs/api-extractor-utils';
import { Anchor, Text } from '@mantine/core';
import Link from 'next/link';
export function HyperlinkedText({ tokens }: { tokens: TokenDocumentation[] }) {
@@ -8,19 +7,13 @@ export function HyperlinkedText({ tokens }: { tokens: TokenDocumentation[] }) {
{tokens.map((token, idx) => {
if (token.path) {
return (
<Link key={idx} href={token.path} passHref prefetch={false}>
<Anchor component="a" inherit>
{token.text}
</Anchor>
<Link key={idx} href={token.path} prefetch={false}>
<a className="text-blurple">{token.text}</a>
</Link>
);
}
return (
<Text key={idx} span unstyled>
{token.text}
</Text>
);
return <span key={idx}>{token.text}</span>;
})}
</>
);

View File

@@ -1,16 +1,13 @@
import type { InheritanceData } from '@discordjs/api-extractor-utils';
import { Anchor, Text } from '@mantine/core';
import Link from 'next/link';
export function InheritanceText({ data }: { data: InheritanceData }) {
return (
<Text weight={600}>
<span className="font-semibold">
{'Inherited from '}
<Link href={data.path} passHref prefetch={false}>
<Anchor component="a" className="font-mono">
{data.parentName}
</Anchor>
<Link href={data.path} prefetch={false}>
<a className="text-blurple font-mono">{data.parentName}</a>
</Link>
</Text>
</span>
);
}

View File

@@ -1,86 +1,77 @@
import type { ApiMethodJSON, ApiMethodSignatureJSON } from '@discordjs/api-extractor-utils';
import { ActionIcon, Badge, Box, createStyles, Group, MediaQuery, Stack, Title } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { useCallback, useMemo } from 'react';
import { FiLink } from 'react-icons/fi';
import { HyperlinkedText } from './HyperlinkedText';
import { InheritanceText } from './InheritanceText';
import { ParameterTable } from './ParameterTable';
import { TSDoc } from './tsdoc/TSDoc';
const useStyles = createStyles((theme) => ({
outer: {
display: 'flex',
alignItems: 'center',
gap: 16,
export function MethodItem({ data }: { data: ApiMethodJSON | ApiMethodSignatureJSON }) {
const method = data as ApiMethodJSON;
const key = useMemo(
() => `${data.name}${data.overloadIndex && data.overloadIndex > 1 ? `:${data.overloadIndex}` : ''}`,
[data.name, data.overloadIndex],
);
[theme.fn.smallerThan('sm')]: {
flexDirection: 'column',
alignItems: 'unset',
},
},
}));
function getShorthandName(data: ApiMethodJSON | ApiMethodSignatureJSON) {
return `${data.name}${data.optional ? '?' : ''}(${data.parameters.reduce((prev, cur, index) => {
const getShorthandName = useCallback(
(data: ApiMethodJSON | ApiMethodSignatureJSON) =>
`${data.name}${data.optional ? '?' : ''}(${data.parameters.reduce((prev, cur, index) => {
if (index === 0) {
return `${prev}${cur.isOptional ? `${cur.name}?` : cur.name}`;
}
return `${prev}, ${cur.isOptional ? `${cur.name}?` : cur.name}`;
}, '')})`;
}
export function MethodItem({ data }: { data: ApiMethodJSON | ApiMethodSignatureJSON }) {
const { classes } = useStyles();
const matches = useMediaQuery('(max-width: 768px)');
const method = data as ApiMethodJSON;
const key = `${data.name}${data.overloadIndex && data.overloadIndex > 1 ? `:${data.overloadIndex}` : ''}`;
}, '')})`,
[],
);
return (
<Stack id={key} className="scroll-mt-30" spacing="xs">
<Group>
<Stack>
<Box className={classes.outer} ml={matches ? 0 : -45}>
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}>
<ActionIcon component="a" href={`#${key}`} variant="transparent" color="dark">
<div id={key} className="scroll-mt-30 flex flex-col gap-4">
<div className="flex flex-col">
<div className="flex flex-col gap-2 md:-ml-9 md:flex-row md:place-items-center">
<a className="hidden md:inline-block" href={`#${key}`}>
<FiLink size={20} />
</ActionIcon>
</MediaQuery>
</a>
{data.deprecated ||
(data.kind === 'Method' && method.protected) ||
(data.kind === 'Method' && method.static) ? (
<Group spacing={10} noWrap>
<div className="flex flex-row flex-wrap gap-1">
{data.deprecated ? (
<Badge variant="filled" color="red">
<div className="h-5 place-content-center rounded-full bg-red-500 px-3 text-center text-xs font-semibold uppercase text-white">
Deprecated
</Badge>
</div>
) : null}
{data.kind === 'Method' && method.protected ? <Badge variant="filled">Protected</Badge> : null}
{data.kind === 'Method' && method.static ? <Badge variant="filled">Static</Badge> : null}
</Group>
{data.kind === 'Method' && method.protected ? (
<div className="bg-blurple h-5 place-content-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white">
Protected
</div>
) : null}
<Group spacing={10}>
<Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">{`${getShorthandName(
data,
)}`}</Title>
<Title order={4}>:</Title>
<Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">
{data.kind === 'Method' && method.static ? (
<div className="bg-blurple h-5 place-content-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white">
Static
</div>
) : null}
</div>
) : null}
<div className="flex flex-row flex-wrap gap-1">
<h4 className="break-all font-mono text-lg font-bold">{`${getShorthandName(data)}`}</h4>
<h4 className="font-mono text-lg font-bold">:</h4>
<h4 className="break-all font-mono text-lg font-bold">
<HyperlinkedText tokens={data.returnTypeTokens} />
</Title>
</Group>
</Box>
</Stack>
</Group>
<Group sx={{ display: data.summary || data.parameters.length ? 'block' : 'none' }} mb="lg">
<Stack>
</h4>
</div>
</div>
</div>
{data.summary || data.parameters.length ? (
<div className="mb-4 flex flex-col gap-4">
{data.deprecated ? <TSDoc node={data.deprecated} /> : null}
{data.summary ? <TSDoc node={data.summary} /> : null}
{data.remarks ? <TSDoc node={data.remarks} /> : null}
{data.comment ? <TSDoc node={data.comment} /> : null}
{data.parameters.length ? <ParameterTable data={data.parameters} /> : null}
{data.inheritanceData ? <InheritanceText data={data.inheritanceData} /> : null}
</Stack>
</Group>
</Stack>
</div>
) : null}
</div>
);
}

View File

@@ -1,19 +1,20 @@
import type { ApiMethodJSON, ApiMethodSignatureJSON } from '@discordjs/api-extractor-utils';
import { Divider, Stack } from '@mantine/core';
import { Fragment } from 'react';
import { Fragment, useMemo } from 'react';
import { MethodItem } from './MethodItem';
export function MethodList({ data }: { data: (ApiMethodJSON | ApiMethodSignatureJSON)[] }) {
return (
<Stack>
{data.map((method) => (
const methodItems = useMemo(
() =>
data.map((method) => (
<Fragment
key={`${method.name}${method.overloadIndex && method.overloadIndex > 1 ? `:${method.overloadIndex}` : ''}`}
>
<MethodItem data={method} />
<Divider size="md" />
<div className="border-light-900 -mx-8 border-t-2" />
</Fragment>
))}
</Stack>
)),
[data],
);
return <div className="flex flex-col gap-4">{methodItems}</div>;
}

View File

@@ -1,5 +1,5 @@
import type { ParameterDocumentation } from '@discordjs/api-extractor-utils';
import { Box, ScrollArea } from '@mantine/core';
import { useMemo } from 'react';
import { HyperlinkedText } from './HyperlinkedText';
import { Table } from './Table';
import { TSDoc } from './tsdoc/TSDoc';
@@ -10,18 +10,20 @@ const columnStyles = {
};
export function ParameterTable({ data }: { data: ParameterDocumentation[] }) {
const rows = data.map((param) => ({
const rows = useMemo(
() =>
data.map((param) => ({
Name: param.name,
Type: <HyperlinkedText tokens={param.tokens} />,
Optional: param.isOptional ? 'Yes' : 'No',
Description: param.paramCommentBlock ? <TSDoc node={param.paramCommentBlock} /> : 'None',
}));
})),
[data],
);
return (
<Box>
<ScrollArea pb="xs" offsetScrollbars>
<div className="overflow-x-auto">
<Table columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} columnStyles={columnStyles} />
</ScrollArea>
</Box>
</div>
);
}

View File

@@ -1,12 +1,11 @@
import type { ApiPropertyItemJSON } from '@discordjs/api-extractor-utils';
import { Divider, Stack } from '@mantine/core';
import { Fragment } from 'react';
import { Fragment, useMemo } from 'react';
import { CodeListing } from './CodeListing';
export function PropertyList({ data }: { data: ApiPropertyItemJSON[] }) {
return (
<Stack>
{data.map((prop) => (
const propertyItems = useMemo(
() =>
data.map((prop) => (
<Fragment key={prop.name}>
<CodeListing
name={prop.name}
@@ -18,9 +17,11 @@ export function PropertyList({ data }: { data: ApiPropertyItemJSON[] }) {
deprecation={prop.deprecated}
inheritanceData={prop.inheritanceData}
/>
<Divider size="md" />
<div className="border-light-900 -mx-8 border-t-2" />
</Fragment>
))}
</Stack>
)),
[data],
);
return <div className="flex flex-col gap-4">{propertyItems}</div>;
}

View File

@@ -1,25 +0,0 @@
import { startNavigationProgress, resetNavigationProgress, NavigationProgress } from '@mantine/nprogress';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
export function RouterTransition() {
const router = useRouter();
useEffect(() => {
const handleStart = (url: string) => url !== router.asPath && startNavigationProgress();
const handleComplete = () => resetNavigationProgress();
router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleComplete);
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleComplete);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [router.asPath]);
return <NavigationProgress color="blurple" />;
}

View File

@@ -1,37 +1,7 @@
import {
createStyles,
UnstyledButton,
Group,
ThemeIcon,
Collapse,
Box,
Text,
useMantineColorScheme,
} from '@mantine/core';
import { useState, useEffect, type PropsWithChildren } from 'react';
import { Disclosure, DisclosureContent, useDisclosureState } from 'ariakit/disclosure';
import type { PropsWithChildren } from 'react';
import { VscChevronDown } from 'react-icons/vsc';
const useStyles = createStyles((theme, { opened }: { opened: boolean }) => ({
control: {
display: 'block',
width: '100%',
padding: theme.spacing.xs,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![7] : 'transparent',
borderRadius: theme.radius.xs,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![5] : theme.colors.gray![2],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
icon: {
transition: 'transform 150ms ease',
transform: opened ? 'rotate(180deg)' : 'rotate(0deg)',
},
}));
export function Section({
title,
icon,
@@ -46,41 +16,28 @@ export function Section({
padded?: boolean;
title: string;
}>) {
const [opened, setOpened] = useState(!defaultClosed);
const { colorScheme } = useMantineColorScheme();
const { classes } = useStyles({ opened });
useEffect(() => {
setOpened(!defaultClosed);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const disclosure = useDisclosureState({ defaultOpen: !defaultClosed });
return (
<Box sx={{ wordBreak: 'break-all' }}>
<UnstyledButton className={classes.control} onClick={() => setOpened((isOpen) => !isOpen)}>
<Group position="apart">
<Group>
{icon ? (
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
{icon}
</ThemeIcon>
) : null}
<Text weight={600} size="md">
{title}
</Text>
</Group>
<VscChevronDown size={20} className={classes.icon} />
</Group>
</UnstyledButton>
<Collapse in={opened}>
{padded ? (
<Box py={20} px={dense ? 0 : 31} mx={dense ? 10 : 25}>
{children}
</Box>
) : (
children
)}
</Collapse>
</Box>
<div>
<Disclosure
className="bg-light-600 hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 rounded p-3"
state={disclosure}
>
<div className="flex flex-row place-content-between place-items-center">
<div className="flex flex-row place-items-center gap-3">
{icon ?? null}
<span className="font-semibold">{title}</span>
</div>
<VscChevronDown
className={`transform transition duration-150 ease-in-out ${disclosure.open ? 'rotate-180' : 'rotate-0'}`}
size={20}
/>
</div>
</Disclosure>
<DisclosureContent state={disclosure}>
{padded ? <div className={`py-5 ${dense ? 'mx-2 px-0' : 'px-4.5 mx-6.5'}`}>{children}</div> : children}
</DisclosureContent>
</div>
);
}

View File

@@ -4,9 +4,9 @@ import type {
ParameterDocumentation,
ApiConstructorJSON,
} from '@discordjs/api-extractor-utils';
import { Stack, Group, Badge, Title } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { useMemo } from 'react';
import { VscSymbolConstant, VscSymbolMethod, VscSymbolProperty } from 'react-icons/vsc';
import { useMedia } from 'react-use';
import { MethodList } from './MethodList';
import { ParameterTable } from './ParameterTable';
import { PropertyList } from './PropertyList';
@@ -14,7 +14,7 @@ import { Section } from './Section';
import { TSDoc } from './tsdoc/TSDoc';
export function PropertiesSection({ data }: { data: ApiClassJSON['properties'] | ApiInterfaceJSON['properties'] }) {
const matches = useMediaQuery('(max-width: 768px)');
const matches = useMedia('(max-width: 768px)', true);
return data.length ? (
<Section title="Properties" icon={<VscSymbolProperty size={20} />} padded dense={matches}>
@@ -24,7 +24,7 @@ export function PropertiesSection({ data }: { data: ApiClassJSON['properties'] |
}
export function MethodsSection({ data }: { data: ApiClassJSON['methods'] | ApiInterfaceJSON['methods'] }) {
const matches = useMediaQuery('(max-width: 768px)');
const matches = useMedia('(max-width: 768px)', true);
return data.length ? (
<Section title="Methods" icon={<VscSymbolMethod size={20} />} padded dense={matches}>
@@ -34,7 +34,7 @@ export function MethodsSection({ data }: { data: ApiClassJSON['methods'] | ApiIn
}
export function ParametersSection({ data }: { data: ParameterDocumentation[] }) {
const matches = useMediaQuery('(max-width: 768px)');
const matches = useMedia('(max-width: 768px)', true);
return data.length ? (
<Section title="Parameters" icon={<VscSymbolConstant size={20} />} padded dense={matches}>
@@ -44,45 +44,52 @@ export function ParametersSection({ data }: { data: ParameterDocumentation[] })
}
export function ConstructorSection({ data }: { data: ApiConstructorJSON }) {
const matches = useMediaQuery('(max-width: 768px)');
const matches = useMedia('(max-width: 768px)', true);
const getShorthandName = () =>
const getShorthandName = useMemo(
() =>
`constructor(${data.parameters.reduce((prev, cur, index) => {
if (index === 0) {
return `${prev}${cur.isOptional ? `${cur.name}?` : cur.name}`;
}
return `${prev}, ${cur.isOptional ? `${cur.name}?` : cur.name}`;
}, '')})`;
}, '')})`,
[data.parameters],
);
return data.parameters.length ? (
<Section title="Constructor" icon={<VscSymbolMethod size={20} />} padded dense={matches}>
<Stack id={data.name} className="scroll-mt-30" spacing="xs">
<Group>
<Stack>
<Group>
<div id={data.name} className="scroll-mt-30 flex flex-col gap-4">
<div className="flex flex-col">
<div className="flex flex-col gap-2 md:flex-row md:place-items-center">
{data.deprecated || data.protected ? (
<div className="flex flex-row flex-wrap gap-1">
{data.deprecated ? (
<Badge variant="filled" color="red">
<div className="h-5 place-content-center rounded-full bg-red-500 px-3 text-center text-xs font-semibold uppercase text-white">
Deprecated
</Badge>
</div>
) : null}
{data.protected ? <Badge variant="filled">Protected</Badge> : null}
<Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono">
{getShorthandName()}
</Title>
</Group>
</Stack>
</Group>
<Group sx={{ display: data.summary || data.parameters.length ? 'block' : 'none' }} mb="lg">
<Stack>
{data.protected ? (
<div className="bg-blurple h-5 place-content-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white">
Protected
</div>
) : null}
</div>
) : null}
<h4 className="break-all font-mono text-lg font-bold">{getShorthandName}</h4>
</div>
</div>
{data.summary || data.parameters.length ? (
<div className="mb-4 flex flex-col gap-4">
{data.deprecated ? <TSDoc node={data.deprecated} /> : null}
{data.summary ? <TSDoc node={data.summary} /> : null}
{data.remarks ? <TSDoc node={data.remarks} /> : null}
{data.comment ? <TSDoc node={data.comment} /> : null}
{data.parameters.length ? <ParameterTable data={data.parameters} /> : null}
</Stack>
</Group>
</Stack>
</div>
) : null}
</div>
</Section>
) : null;
}

View File

@@ -1,4 +1,3 @@
import { createStyles, Group, Text, NavLink, Box } from '@mantine/core';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { type Dispatch, type SetStateAction, useEffect, useState, useMemo } from 'react';
@@ -66,32 +65,6 @@ function resolveIcon(item: keyof GroupedMembers) {
}
}
const useStyles = createStyles((theme) => ({
link: {
...theme.fn.focusStyles(),
fontWeight: 500,
display: 'block',
width: 'unset',
padding: 5,
paddingLeft: 31,
marginLeft: 25,
fontSize: theme.fontSizes.sm,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.colors.gray![7],
borderLeft: `1px solid ${theme.colorScheme === 'dark' ? theme.colors.dark![4] : theme.colors.gray![3]}`,
'&[data-active]': {
'&:hover': {
color: theme.white,
},
},
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
}));
export function SidebarItems({
members,
setOpened,
@@ -101,7 +74,6 @@ export function SidebarItems({
}) {
const router = useRouter();
const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState('');
const { classes } = useStyles();
const groupItems = useMemo(() => groupMembers(members), [members]);
useEffect(() => {
@@ -109,36 +81,33 @@ export function SidebarItems({
}, [router.asPath]);
return (
<Box sx={(theme) => ({ paddingBottom: 48, [theme.fn.smallerThan('md')]: { paddingBottom: 128 } })}>
<div className="flex flex-col gap-3 p-3 pb-32 lg:pb-12">
{(Object.keys(groupItems) as (keyof GroupedMembers)[])
.filter((group) => groupItems[group].length)
.map((group, idx) => (
<Section key={idx} title={group} icon={resolveIcon(group)}>
{groupItems[group].map((member, index) => (
<Link key={index} href={member.path} passHref prefetch={false}>
<NavLink
className={classes.link}
component="a"
onClick={() => setOpened((isOpened) => !isOpened)}
label={
<Group>
<Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1">
{member.name}
</Text>
<Link key={index} href={member.path} prefetch={false}>
<a
className={`dark:border-dark-100 border-light-800 ml-5 border-l p-[5px] pl-6 ${
asPathWithoutQueryAndAnchor === member.path
? 'bg-blurple text-white'
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
}`}
title={member.name}
onClick={() => setOpened(false)}
>
<div className="flex flex-row place-items-center gap-2 text-sm">
<span className="truncate">{member.name}</span>
{member.overloadIndex && member.overloadIndex > 1 ? (
<Text size="xs" color="dimmed">
{member.overloadIndex}
</Text>
<span className="text-xs">{member.overloadIndex}</span>
) : null}
</Group>
}
active={asPathWithoutQueryAndAnchor === member.path}
variant="filled"
/>
</div>
</a>
</Link>
))}
</Section>
))}
</Box>
</div>
);
}

View File

@@ -1,41 +1,20 @@
import type { getMembers, ApiItemJSON } from '@discordjs/api-extractor-utils';
import {
useMantineTheme,
AppShell,
Navbar,
MediaQuery,
Header,
Burger,
Anchor,
Breadcrumbs,
ScrollArea,
Group,
Text,
ThemeIcon,
Box,
UnstyledButton,
createStyles,
Menu,
ActionIcon,
useMantineColorScheme,
Stack,
Skeleton,
LoadingOverlay,
Container,
Title,
} from '@mantine/core';
import { NextLink } from '@mantine/next';
import { Button } from 'ariakit/button';
import { Menu, MenuButton, MenuItem, useMenuState } from 'ariakit/menu';
import Image from 'next/future/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type { MDXRemoteSerializeResult } from 'next-mdx-remote';
import { type PropsWithChildren, useState, useEffect, useMemo } from 'react';
import { VscChevronDown, VscGithubInverted, VscPackage, VscVersions } from 'react-icons/vsc';
import { WiDaySunny, WiNightClear } from 'react-icons/wi';
import { useTheme } from 'next-themes';
import { type PropsWithChildren, useState, useEffect, useMemo, Fragment } from 'react';
import { Scrollbars } from 'react-custom-scrollbars-2';
import { VscChevronDown, VscColorMode, VscGithubInverted, VscMenu, VscPackage, VscVersions } from 'react-icons/vsc';
import { useMedia /* useLockBodyScroll */ } from 'react-use';
import useSWR from 'swr';
import vercelLogo from '../assets/powered-by-vercel.svg';
import { SidebarItems } from './SidebarItems';
import { PACKAGES } from '~/util/constants';
import type { findMember } from '~/util/model.server';
import { PACKAGES } from '~/util/packages';
const fetcher = async (url: string) => {
const res = await fetch(url);
@@ -66,89 +45,6 @@ export interface GroupedMembers {
Variables: Members;
}
const useStyles = createStyles(
(theme, { openedLib, openedVersion }: { openedLib: boolean; openedVersion: boolean }) => ({
control: {
display: 'block',
width: '100%',
padding: theme.spacing.xs,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![1],
borderRadius: theme.radius.xs,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![5] : theme.colors.gray![2],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
iconLib: {
transition: 'transform 150ms ease',
transform: openedLib ? 'rotate(180deg)' : 'rotate(0deg)',
},
iconVersion: {
transition: 'transform 150ms ease',
transform: openedVersion ? 'rotate(180deg)' : 'rotate(0deg)',
},
content: {
position: 'relative',
minHeight: 'calc(100vh - 50px)',
zIndex: 1,
background: theme.colorScheme === 'dark' ? theme.colors.dark![8] : theme.colors.gray![0],
boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
},
footer: {
position: 'fixed',
bottom: 0,
left: 0,
right: 0,
height: 200,
background: theme.colorScheme === 'dark' ? theme.colors.dark![7] : theme.colors.gray![0],
paddingLeft: 324,
[theme.fn.smallerThan('lg')]: {
paddingRight: 54,
},
[theme.fn.smallerThan('md')]: {
paddingLeft: 24,
},
[theme.fn.smallerThan('sm')]: {
paddingRight: 24,
height: 300,
},
},
links: {
display: 'flex',
justifyContent: 'space-between',
[theme.fn.smallerThan('sm')]: {
flexDirection: 'column',
alignItems: 'center',
gap: 50,
},
},
link: { color: theme.colorScheme === 'dark' ? theme.white : theme.black },
}),
);
const packageMenuItems = PACKAGES.map((pkg) => (
<Menu.Item
key={pkg}
component={NextLink}
href={`/docs/packages/${pkg}/main`}
sx={(theme) => ({ color: theme.colorScheme === 'dark' ? theme.white : theme.black })}
>
{pkg}
</Menu.Item>
));
export function SidebarLayout({
packageName,
branchName,
@@ -157,261 +53,244 @@ export function SidebarLayout({
}: PropsWithChildren<Partial<SidebarLayoutProps>>) {
const router = useRouter();
const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState('');
const { data: versions } = useSWR<string[]>(
`https://docs.discordjs.dev/api/info?package=${packageName ?? 'builders'}`,
fetcher,
);
const theme = useMantineTheme();
// eslint-disable-next-line @typescript-eslint/unbound-method
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const { data: versions } = useSWR<string[]>(`https://docs.discordjs.dev/api/info?package=${packageName}`, fetcher);
const { resolvedTheme, setTheme } = useTheme();
const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
const matches = useMedia('(min-width: 992px)', false);
const [opened, setOpened] = useState(false);
const [openedLibPicker, setOpenedLibPicker] = useState(false);
const [openedVersionPicker, setOpenedVersionPicker] = useState(false);
const packageMenu = useMenuState({ gutter: 8, sameWidth: true });
const versionMenu = useMenuState({ gutter: 8, sameWidth: true });
// useLockBodyScroll(opened);
useEffect(() => {
if (matches) {
setOpened(false);
setOpenedLibPicker(false);
setOpenedVersionPicker(false);
}, []);
}
}, [matches]);
useEffect(() => {
setAsPathWithoutQueryAndAnchor(router.asPath.split('?')[0]?.split('#')[0]?.split(':')[0] ?? '');
}, [router.asPath]);
const { classes } = useStyles({ openedLib: openedLibPicker, openedVersion: openedVersionPicker });
const packageMenuItems = PACKAGES.map((pkg) => (
<Link key={pkg} href={`/docs/packages/${pkg}/main`} passHref prefetch={false}>
<MenuItem
className="hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 rounded bg-white p-3 text-sm"
as="a"
state={packageMenu}
>
{pkg}
</MenuItem>
</Link>
));
const versionMenuItems = useMemo(
() =>
versions?.map((item) => (
<Menu.Item
key={item}
component={NextLink}
href={`/docs/packages/${packageName ?? 'builders'}/${item}`}
sx={(theme) => ({ color: theme.colorScheme === 'dark' ? theme.white : theme.black })}
versions
?.map((item) => (
<Link key={item} href={`/docs/packages/${packageName}/${item}`} passHref prefetch={false}>
<MenuItem
className="hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 rounded bg-white p-3 text-sm"
as="a"
state={versionMenu}
>
{item}
</Menu.Item>
)) ?? [],
</MenuItem>
</Link>
))
.reverse() ?? [],
// eslint-disable-next-line react-hooks/exhaustive-deps
[versions, packageName],
);
const breadcrumbs = useMemo(
const pathElements = useMemo(
() =>
asPathWithoutQueryAndAnchor.split('/').map((path, idx, original) => (
<Link key={idx} href={original.slice(0, idx + 1).join('/')} passHref prefetch={false}>
<Anchor component="a" sx={(theme) => ({ color: theme.colorScheme === 'dark' ? theme.white : theme.black })}>
{path}
</Anchor>
<Link key={idx} href={original.slice(0, idx + 1).join('/')} prefetch={false}>
<a className="hover:underline">{path}</a>
</Link>
)),
[asPathWithoutQueryAndAnchor],
);
const breadcrumbs = useMemo(
() =>
pathElements.flatMap((el, idx, array) => {
if (idx !== array.length - 1) {
return (
<AppShell
sx={(theme) => ({
main: {
background: theme.colorScheme === 'dark' ? theme.colors.dark![8] : theme.colors.gray![0],
overflowX: 'auto',
},
})}
padding={0}
navbarOffsetBreakpoint="md"
asideOffsetBreakpoint="md"
navbar={
<>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<LoadingOverlay
sx={{ position: 'fixed', top: 70, width: 300 }}
visible={router.isFallback}
overlayBlur={2}
/>
</MediaQuery>
<Navbar hiddenBreakpoint="md" hidden={!opened} width={{ md: 300 }}>
{packageName && data ? (
<>
<Navbar.Section p="xs">
<Stack spacing="xs">
<Menu
onOpen={() => setOpenedLibPicker(true)}
onClose={() => setOpenedLibPicker(false)}
radius="sm"
width="target"
>
<Menu.Target>
<UnstyledButton className={classes.control}>
<Group position="apart">
<Group>
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
<VscPackage size={20} />
</ThemeIcon>
<Text weight="600" size="md">
{packageName}
</Text>
</Group>
<VscChevronDown className={classes.iconLib} size={20} />
</Group>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>{packageMenuItems}</Menu.Dropdown>
</Menu>
<Menu
onOpen={() => setOpenedVersionPicker(true)}
onClose={() => setOpenedVersionPicker(false)}
radius="sm"
width="target"
>
<Menu.Target>
<UnstyledButton className={classes.control}>
<Group position="apart">
<Group>
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
<VscVersions size={20} />
</ThemeIcon>
<Text weight="600" size="md">
{branchName}
</Text>
</Group>
<VscChevronDown className={classes.iconVersion} size={20} />
</Group>
</UnstyledButton>
</Menu.Target>
<Menu.Dropdown>{versionMenuItems}</Menu.Dropdown>
</Menu>
</Stack>
</Navbar.Section>
<Navbar.Section px="xs" pb="xs" grow component={ScrollArea}>
<SidebarItems members={data.members} setOpened={setOpened} />
</Navbar.Section>
</>
) : null}
</Navbar>
</>
<Fragment key={idx}>
{el}
<div className="mx-2">/</div>
</Fragment>
);
}
header={
<Header
sx={(theme) => ({
boxShadow:
theme.colorScheme === 'dark'
? '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)'
: 'unset',
})}
height={70}
p="md"
>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: '100%' }}>
<Box>
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
<Burger
opened={opened}
onClick={() => (router.isFallback ? null : setOpened((isOpened) => !isOpened))}
size="sm"
color={theme.colors.gray![6]}
mr="xl"
/>
</MediaQuery>
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
<Skeleton visible={router.isFallback} radius="sm">
<Breadcrumbs>{breadcrumbs}</Breadcrumbs>
</Skeleton>
</MediaQuery>
</Box>
<Group>
<Link href="https://github.com/discordjs/discord.js" passHref prefetch={false}>
<ActionIcon
component="a"
return <Fragment key={idx}>{el}</Fragment>;
}),
[pathElements],
);
return (
<>
<header className="dark:bg-dark-600 dark:border-dark-100 bg-light-600 border-light-800 fixed top-0 left-0 z-20 w-full border-b">
<div className="h-18 block px-6">
<div className="flex h-full flex-row place-content-between place-items-center">
<Button
className="flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline active:translate-y-px lg:hidden"
onClick={() => setOpened((open) => !open)}
>
<VscMenu size={24} />
</Button>
<div className="hidden md:flex md:flex-row">{breadcrumbs}</div>
<div className="flex flex-row gap-4">
<Button
as="a"
className="flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline active:translate-y-px"
href="https://github.com/discordjs/discord.js"
target="_blank"
rel="noopener noreferrer"
variant="transparent"
color="dark"
title="GitHub repository"
>
<VscGithubInverted size={20} />
</ActionIcon>
</Link>
<ActionIcon
variant="subtle"
color={colorScheme === 'dark' ? 'yellow' : 'blue'}
onClick={() => toggleColorScheme()}
title="Toggle color scheme"
radius="sm"
<VscGithubInverted size={24} />
</Button>
<Button
className="flex h-6 w-6 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 bg-transparent p-0 text-sm font-semibold leading-none no-underline active:translate-y-px"
role="button"
onClick={() => toggleTheme()}
>
{colorScheme === 'dark' ? <WiDaySunny size={30} /> : <WiNightClear size={30} />}
</ActionIcon>
</Group>
</Box>
</Header>
}
<VscColorMode size={24} />
</Button>
</div>
</div>
</div>
</header>
<nav
className={`h-[calc(100vh - 73px)] dark:bg-dark-600 dark:border-dark-100 border-light-800 fixed top-[73px] left-0 bottom-0 z-20 w-full border-r bg-white ${
opened ? 'block' : 'hidden'
} lg:w-76 lg:max-w-76 lg:block`}
>
<article>
<Box className={classes.content} p="lg" pb={80}>
{children}
</Box>
<Box sx={(theme) => ({ height: 200, [theme.fn.smallerThan('sm')]: { height: 300 } })} />
<Box
component="footer"
sx={{ paddingRight: data?.member?.kind !== 'Class' && data?.member?.kind !== 'Interface' ? 54 : 324 }}
className={classes.footer}
pt={50}
<Scrollbars
universal
autoHide
hideTracksWhenNotNeeded
renderTrackVertical={(props) => (
<div {...props} className="absolute top-0.5 right-0.5 bottom-0.5 z-30 w-1.5 rounded" />
)}
renderThumbVertical={(props) => <div {...props} className="dark:bg-dark-100 bg-light-900 z-30 rounded" />}
>
<Container>
<Box className={classes.links}>
<Link href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss" prefetch={false}>
<a title="Vercel">
<Image
src="/powered-by-vercel.svg"
alt="Vercel"
width={0}
height={0}
style={{ height: '100%', width: '100%', maxWidth: 200 }}
<div className="flex flex-col gap-3 px-3 pt-3">
<MenuButton
className="bg-light-600 hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 rounded p-3"
state={packageMenu}
>
<div className="flex flex-row place-content-between place-items-center">
<div className="flex flex-row place-items-center gap-3">
<VscPackage size={20} />
<span className="font-semibold">{packageName}</span>
</div>
<VscChevronDown
className={`transform transition duration-150 ease-in-out ${
packageMenu.open ? 'rotate-180' : 'rotate-0'
}`}
size={20}
/>
</div>
</MenuButton>
<Menu
className="dark:bg-dark-600 border-light-800 dark:border-dark-100 z-20 rounded border bg-white p-1"
state={packageMenu}
>
{packageMenuItems}
</Menu>
<MenuButton
className="bg-light-600 hover:bg-light-700 active:bg-light-800 dark:bg-dark-600 dark:hover:bg-dark-500 dark:active:bg-dark-400 rounded p-3"
state={versionMenu}
>
<div className="flex flex-row place-content-between place-items-center">
<div className="flex flex-row place-items-center gap-3">
<VscVersions size={20} />
<span className="font-semibold">{branchName}</span>
</div>
<VscChevronDown
className={`transform transition duration-150 ease-in-out ${
versionMenu.open ? 'rotate-180' : 'rotate-0'
}`}
size={20}
/>
</div>
</MenuButton>
<Menu
className="dark:bg-dark-600 border-light-800 dark:border-dark-100 z-20 rounded border bg-white p-1"
state={versionMenu}
>
{versionMenuItems}
</Menu>
</div>
<SidebarItems members={data?.members ?? []} setOpened={setOpened} />
</Scrollbars>
</nav>
<main
className={`pt-18 lg:pl-76 ${
data?.member?.kind === 'Class' || data?.member?.kind === 'Interface' ? 'xl:pr-64' : ''
}`}
>
<article className="dark:bg-dark-600 bg-light-600">
<div className="min-h-[calc(100vh - 50px)] dark:bg-dark-800 relative z-10 bg-white p-6 pb-20 shadow">
{children}
</div>
<div className="h-76 md:h-52" />
<footer
className={`dark:bg-dark-600 h-76 lg:pl-84 bg-light-600 fixed bottom-0 left-0 right-0 md:h-52 md:pl-4 md:pr-16 ${
data?.member?.kind === 'Class' || data?.member?.kind === 'Interface' ? 'xl:pr-76' : 'xl:pr-16'
}`}
>
<div className="mx-auto flex max-w-6xl flex-col place-items-center gap-12 pt-12 lg:place-content-center">
<div className="flex w-full flex-col place-content-between place-items-center gap-12 md:flex-row md:gap-0">
<a
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
target="_blank"
rel="noopener noreferrer"
title="Vercel"
>
<Image src={vercelLogo} alt="Vercel" />
</a>
</Link>
<Group sx={(theme) => ({ gap: 50, [theme.fn.smallerThan('sm')]: { gap: 25 } })} align="flex-start">
<Stack spacing={8}>
<Title order={4}>Community</Title>
<Stack spacing={0}>
<Link href="https://discord.gg/djs" passHref prefetch={false}>
<Anchor component="a" target="_blank" rel="noopener noreferrer" className={classes.link}>
<div className="flex flex-row gap-6 md:gap-12">
<div className="flex flex-col gap-2">
<h4 className="text-lg font-semibold">Community</h4>
<div className="flex flex-col gap-1">
<a href="https://discord.gg/djs" target="_blank" rel="noopener noreferrer">
Discord
</Anchor>
</Link>
<Link href="https://github.com/discordjs/discord.js/discussions" passHref prefetch={false}>
<Anchor component="a" target="_blank" rel="noopener noreferrer" className={classes.link}>
</a>
<a
href="https://github.com/discordjs/discord.js/discussions"
target="_blank"
rel="noopener noreferrer"
>
GitHub discussions
</Anchor>
</Link>
</Stack>
</Stack>
<Stack spacing={8}>
<Title order={4}>Project</Title>
<Stack spacing={0}>
<Link href="https://github.com/discordjs/discord.js" passHref prefetch={false}>
<Anchor component="a" target="_blank" rel="noopener noreferrer" className={classes.link}>
</a>
</div>
</div>
<div className="flex flex-col gap-2">
<h4 className="text-lg font-semibold">Project</h4>
<div className="flex flex-col gap-1">
<a href="https://github.com/discordjs/discord.js" target="_blank" rel="noopener noreferrer">
discord.js
</Anchor>
</Link>
<Link href="https://discordjs.guide" passHref prefetch={false}>
<Anchor component="a" target="_blank" rel="noopener noreferrer" className={classes.link}>
</a>
<a href="https://discordjs.guide" target="_blank" rel="noopener noreferrer">
discord.js guide
</Anchor>
</Link>
<Link href="https://discord-api-types.dev" passHref prefetch={false}>
<Anchor component="a" target="_blank" rel="noopener noreferrer" className={classes.link}>
</a>
<a href="https://discord-api-types.dev" target="_blank" rel="noopener noreferrer">
discord-api-types
</Anchor>
</Link>
</Stack>
</Stack>
</Group>
</Box>
</Container>
</Box>
</a>
</div>
</div>
</div>
</div>
</div>
</footer>
</article>
</AppShell>
</main>
</>
);
}

View File

@@ -0,0 +1,31 @@
import { PrismAsyncLight } from 'react-syntax-highlighter';
import { vscDarkPlus, prism } from 'react-syntax-highlighter/dist/cjs/styles/prism';
export function SyntaxHighlighter({ language = 'typescript', code }: { code: string; language?: string }) {
return (
<>
<div data-theme="dark">
<PrismAsyncLight
wrapLines
wrapLongLines
language={language}
style={vscDarkPlus}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
>
{code}
</PrismAsyncLight>
</div>
<div data-theme="light">
<PrismAsyncLight
wrapLines
wrapLongLines
language={language}
style={prism}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
>
{code}
</PrismAsyncLight>
</div>
</>
);
}

View File

@@ -1,5 +1,4 @@
import { Table as MantineTable } from '@mantine/core';
import type { ReactNode } from 'react';
import { useMemo, type ReactNode } from 'react';
export function Table({
rows,
@@ -10,28 +9,39 @@ export function Table({
columns: string[];
rows: Record<string, ReactNode>[];
}) {
return (
<MantineTable>
<thead>
<tr>
{columns.map((column) => (
<th key={column} className="break-normal">
const cols = useMemo(
() =>
columns.map((column) => (
<th key={column} className="border-light-900 break-normal border-b px-3 py-2 text-left text-sm">
{column}
</th>
))}
</tr>
</thead>
<tbody>
{rows.map((row, idx) => (
<tr key={idx}>
)),
[columns],
);
const data = useMemo(
() =>
rows.map((row, idx) => (
<tr key={idx} className="[&>td]:last-of-type:border-0">
{Object.entries(row).map(([colName, val]) => (
<td key={colName} className={columnStyles?.[colName] ?? ''}>
<td
key={colName}
className={`border-light-900 border-b px-3 py-2 text-left text-sm ${columnStyles?.[colName] ?? ''}`}
>
{val}
</td>
))}
</tr>
))}
</tbody>
</MantineTable>
)),
[columnStyles, rows],
);
return (
<table className="w-full border-collapse">
<thead>
<tr>{cols}</tr>
</thead>
<tbody>{data}</tbody>
</table>
);
}

View File

@@ -1,31 +1,7 @@
import type { ApiClassJSON, ApiInterfaceJSON } from '@discordjs/api-extractor-utils';
import { createStyles, Group, Text, Box, Stack, ThemeIcon, useMantineColorScheme } from '@mantine/core';
import { useMemo } from 'react';
import { VscListSelection, VscSymbolMethod, VscSymbolProperty } from 'react-icons/vsc';
const useStyles = createStyles((theme) => ({
link: {
...theme.fn.focusStyles(),
fontWeight: 500,
display: 'block',
textDecoration: 'none',
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.colors.gray![7],
lineHeight: 1.2,
fontSize: theme.fontSizes.sm,
padding: 5,
paddingLeft: theme.spacing.md,
marginLeft: 14,
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],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
}));
export function TableOfContentItems({
methods,
properties,
@@ -33,22 +9,19 @@ export function TableOfContentItems({
methods: ApiClassJSON['methods'] | ApiInterfaceJSON['methods'];
properties: ApiClassJSON['properties'] | ApiInterfaceJSON['properties'];
}) {
const { colorScheme } = useMantineColorScheme();
const { classes } = useStyles();
const propertyItems = useMemo(
() =>
properties.map((prop) => (
<Box<'a'> key={prop.name} href={`#${prop.name}`} component="a" className={classes.link}>
<Group>
<Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1">
{prop.name}
</Text>
</Group>
</Box>
<a
key={prop.name}
href={`#${prop.name}`}
title={prop.name}
className="dark:border-dark-100 border-light-800 dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800 pl-6.5 ml-[10px] border-l p-[5px] text-sm"
>
<span className="line-clamp-1">{prop.name}</span>
</a>
)),
// eslint-disable-next-line react-hooks/exhaustive-deps
[properties, colorScheme],
[properties],
);
const methodItems = useMemo(
@@ -59,61 +32,52 @@ export function TableOfContentItems({
}`;
return (
<Box<'a'> key={key} component="a" href={`#${key}`} className={classes.link}>
<Group>
<Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1">
{member.name}
</Text>
<a
key={key}
href={`#${key}`}
title={member.name}
className="dark:border-dark-100 border-light-800 dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800 pl-6.5 ml-[10px] flex flex-row place-items-center gap-2 border-l p-[5px] text-sm"
>
<span className="line-clamp-1">{member.name}</span>
{member.overloadIndex && member.overloadIndex > 1 ? (
<Text size="xs" color="dimmed">
{member.overloadIndex}
</Text>
<span className="text-xs">{member.overloadIndex}</span>
) : null}
</Group>
</Box>
</a>
);
}),
[methods, classes.link],
[methods],
);
return (
<Box sx={{ wordBreak: 'break-all' }} pb="xl">
<Group spacing="xs" mt={6} mb="sm" ml={6}>
<div className="flex flex-col break-all p-3 pb-8">
<div className="mt-4 ml-2 flex flex-row gap-2">
<VscListSelection size={25} />
<Text weight={600}>Table of contents</Text>
</Group>
<Stack spacing={0} mt={26} ml={4}>
<span className="font-semibold">Contents</span>
</div>
<div className="mt-5.5 ml-2 flex flex-col gap-2">
{propertyItems.length ? (
<Box>
<Group spacing="xs">
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
<div className="flex flex-col">
<div className="flex flex-row place-items-center gap-4">
<VscSymbolProperty size={20} />
</ThemeIcon>
<Box p="sm" pl={0}>
<Text weight={600} size="md">
Properties
</Text>
</Box>
</Group>
<div className="p-3 pl-0">
<span className="font-semibold">Properties</span>
</div>
</div>
{propertyItems}
</Box>
</div>
) : null}
{methodItems.length ? (
<Box>
<Group spacing="xs">
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
<div className="flex flex-col">
<div className="flex flex-row place-items-center gap-4">
<VscSymbolMethod size={20} />
</ThemeIcon>
<Box p="sm" pl={0}>
<Text weight={600} size="md">
Methods
</Text>
</Box>
</Group>
<div className="p-3 pl-0">
<span className="font-semibold">Methods</span>
</div>
</div>
{methodItems}
</Box>
</div>
) : null}
</Stack>
</Box>
</div>
</div>
);
}

View File

@@ -1,5 +1,5 @@
import type { TypeParameterData } from '@discordjs/api-extractor-utils';
import { ScrollArea } from '@mantine/core';
import { useMemo } from 'react';
import { HyperlinkedText } from './HyperlinkedText';
import { Table } from './Table';
import { TSDoc } from './tsdoc/TSDoc';
@@ -11,21 +11,25 @@ const rowElements = {
};
export function TypeParamTable({ data }: { data: TypeParameterData[] }) {
const rows = data.map((typeParam) => ({
const rows = useMemo(
() =>
data.map((typeParam) => ({
Name: typeParam.name,
Constraints: <HyperlinkedText tokens={typeParam.constraintTokens} />,
Optional: typeParam.optional ? 'Yes' : 'No',
Default: <HyperlinkedText tokens={typeParam.defaultTokens} />,
Description: typeParam.commentBlock ? <TSDoc node={typeParam.commentBlock} /> : 'None',
}));
})),
[data],
);
return (
<ScrollArea pb="xs" offsetScrollbars>
<div className="overflow-x-auto">
<Table
columns={['Name', 'Constraints', 'Optional', 'Default', 'Description']}
rows={rows}
columnStyles={rowElements}
/>
</ScrollArea>
</div>
);
}

View File

@@ -1,12 +1,8 @@
import type { ApiClassJSON } from '@discordjs/api-extractor-utils';
import { Skeleton } from '@mantine/core';
import { useRouter } from 'next/router';
import { DocContainer } from '../DocContainer';
import { ConstructorSection, MethodsSection, PropertiesSection } from '../Sections';
export function Class({ data }: { data: ApiClassJSON }) {
const router = useRouter();
return (
<DocContainer
name={data.name}
@@ -20,12 +16,8 @@ export function Class({ data }: { data: ApiClassJSON }) {
properties={data.properties}
>
{data.constructor ? <ConstructorSection data={data.constructor} /> : null}
<Skeleton visible={router.isFallback} radius="sm">
<PropertiesSection data={data.properties} />
</Skeleton>
<Skeleton visible={router.isFallback} radius="sm">
<MethodsSection data={data.methods} />
</Skeleton>
</DocContainer>
);
}

View File

@@ -1,21 +1,17 @@
import type { ApiEnumJSON } from '@discordjs/api-extractor-utils';
import { Skeleton, Stack } from '@mantine/core';
import { useMediaQuery } from '@mantine/hooks';
import { useRouter } from 'next/router';
import { VscSymbolEnumMember } from 'react-icons/vsc';
import { useMedia } from 'react-use';
import { CodeListing, CodeListingSeparatorType } from '../CodeListing';
import { DocContainer } from '../DocContainer';
import { Section } from '../Section';
export function Enum({ data }: { data: ApiEnumJSON }) {
const router = useRouter();
const matches = useMediaQuery('(max-width: 768px)');
const matches = useMedia('(max-width: 768px)', true);
return (
<DocContainer name={data.name} kind={data.kind} excerpt={data.excerpt} summary={data.summary}>
<Skeleton visible={router.isFallback} radius="sm">
<Section title="Members" icon={<VscSymbolEnumMember size={20} />} padded dense={matches}>
<Stack>
<div className="flex flex-col gap-4">
{data.members.map((member) => (
<CodeListing
key={member.name}
@@ -25,9 +21,8 @@ export function Enum({ data }: { data: ApiEnumJSON }) {
summary={member.summary}
/>
))}
</Stack>
</div>
</Section>
</Skeleton>
</DocContainer>
);
}

View File

@@ -1,12 +1,8 @@
import type { ApiFunctionJSON } from '@discordjs/api-extractor-utils';
import { Skeleton } from '@mantine/core';
import { useRouter } from 'next/router';
import { DocContainer } from '../DocContainer';
import { ParametersSection } from '../Sections';
export function Function({ data }: { data: ApiFunctionJSON }) {
const router = useRouter();
return (
<DocContainer
name={`${data.name}${data.overloadIndex && data.overloadIndex > 1 ? ` (${data.overloadIndex})` : ''}`}
@@ -15,9 +11,7 @@ export function Function({ data }: { data: ApiFunctionJSON }) {
summary={data.summary}
typeParams={data.typeParameters}
>
<Skeleton visible={router.isFallback} radius="sm">
<ParametersSection data={data.parameters} />
</Skeleton>
</DocContainer>
);
}

View File

@@ -1,12 +1,8 @@
import type { ApiInterfaceJSON } from '@discordjs/api-extractor-utils';
import { Skeleton } from '@mantine/core';
import { useRouter } from 'next/router';
import { DocContainer } from '../DocContainer';
import { MethodsSection, PropertiesSection } from '../Sections';
export function Interface({ data }: { data: ApiInterfaceJSON }) {
const router = useRouter();
return (
<DocContainer
name={data.name}
@@ -17,12 +13,8 @@ export function Interface({ data }: { data: ApiInterfaceJSON }) {
methods={data.methods}
properties={data.properties}
>
<Skeleton visible={router.isFallback} radius="sm">
<PropertiesSection data={data.properties} />
</Skeleton>
<Skeleton visible={router.isFallback} radius="sm">
<MethodsSection data={data.methods} />
</Skeleton>
</DocContainer>
);
}

View File

@@ -1,14 +1,13 @@
import { Alert, Box, Title, Text } from '@mantine/core';
import { StandardTags } from '@microsoft/tsdoc';
import type { PropsWithChildren } from 'react';
import { VscWarning } from 'react-icons/vsc';
export function Block({ children, title }: PropsWithChildren<{ title: string }>) {
return (
<Box>
<Title order={5}>{title}</Title>
<div className="flex flex-col gap-2">
<h5 className="font-bold">{title}</h5>
{children}
</Box>
</div>
);
}
@@ -21,9 +20,17 @@ export function ExampleBlock({
export function DeprecatedBlock({ children }: PropsWithChildren): JSX.Element {
return (
<Alert icon={<VscWarning />} title="Deprecated" variant="outline" color="red" radius="sm">
<div className="rounded border border-red-500 p-4">
<div className="flex flex-row place-items-center gap-4">
<span className="text-red-500">
<VscWarning size={20} />
</span>
<div className="flex flex-col gap-2 text-sm">
<span className="font-semibold text-red-500">Deprecated</span>
{children}
</Alert>
</div>
</div>
</div>
);
}
@@ -54,7 +61,7 @@ export function BlockComment({
return <DefaultValueBlock>{children}</DefaultValueBlock>;
case StandardTags.typeParam.tagNameWithUpperCase:
case StandardTags.param.tagNameWithUpperCase:
return <Text>{children}</Text>;
return <span>{children}</span>;
default: // TODO: Support more blocks in the future.
return <>{children}</>;
}

View File

@@ -7,34 +7,29 @@ import type {
DocBlockJSON,
DocCommentJSON,
} from '@discordjs/api-extractor-utils';
import { Anchor, Box, Code, Text, useMantineColorScheme } from '@mantine/core';
import { DocNodeKind, StandardTags } from '@microsoft/tsdoc';
import Link from 'next/link';
import { Fragment, useCallback, type ReactNode } from 'react';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus, ghcolors } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { SyntaxHighlighter } from '../SyntaxHighlighter';
import { BlockComment } from './BlockComment';
export function TSDoc({ node }: { node: AnyDocNodeJSON }): JSX.Element {
const { colorScheme } = useMantineColorScheme();
const createNode = useCallback(
(node: AnyDocNodeJSON, idx?: number): ReactNode => {
const createNode = useCallback((node: AnyDocNodeJSON, idx?: number): ReactNode => {
let numberOfExamples = 0;
let exampleIndex = 0;
switch (node.kind) {
case DocNodeKind.PlainText:
return (
<Text key={idx} span style={{ wordBreak: 'break-word' }}>
<span key={idx} className="break-words">
{(node as DocPlainTextJSON).text}
</Text>
</span>
);
case DocNodeKind.Paragraph:
return (
<Text key={idx} inline style={{ wordBreak: 'break-word' }}>
<span key={idx} className="break-words leading-relaxed">
{(node as DocNodeContainerJSON).nodes.map((node, idx) => createNode(node, idx))}
</Text>
</span>
);
case DocNodeKind.SoftBreak:
return <Fragment key={idx} />;
@@ -43,20 +38,16 @@ export function TSDoc({ node }: { node: AnyDocNodeJSON }): JSX.Element {
if (codeDestination) {
return (
<Link key={idx} href={codeDestination.path} passHref prefetch={false}>
<Anchor component="a" className="font-mono">
{text ?? codeDestination.name}
</Anchor>
<Link key={idx} href={codeDestination.path} prefetch={false}>
<a className="text-blurple font-mono">{text ?? codeDestination.name}</a>
</Link>
);
}
if (urlDestination) {
return (
<Link key={idx} href={urlDestination} passHref prefetch={false}>
<Anchor component="a" className="font-mono">
{text ?? urlDestination}
</Anchor>
<Link key={idx} href={urlDestination} prefetch={false}>
<a className="text-blurple font-mono">{text ?? urlDestination}</a>
</Link>
);
}
@@ -67,26 +58,15 @@ export function TSDoc({ node }: { node: AnyDocNodeJSON }): JSX.Element {
case DocNodeKind.CodeSpan: {
const { code } = node as DocFencedCodeJSON;
return (
<Code key={idx} sx={{ display: 'inline' }} className="text-sm font-mono">
<code key={idx} className="font-mono text-sm">
{code}
</Code>
</code>
);
}
case DocNodeKind.FencedCode: {
const { language, code } = node as DocFencedCodeJSON;
return (
<SyntaxHighlighter
key={idx}
wrapLines
wrapLongLines
language={language}
style={colorScheme === 'dark' ? vscDarkPlus : ghcolors}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
>
{code}
</SyntaxHighlighter>
);
return <SyntaxHighlighter key={idx} language={language} code={code} />;
}
case DocNodeKind.ParamBlock:
@@ -108,31 +88,32 @@ export function TSDoc({ node }: { node: AnyDocNodeJSON }): JSX.Element {
case DocNodeKind.Comment: {
const comment = node as DocCommentJSON;
if (!comment.customBlocks.length) {
return null;
}
// Cheat a bit by finding out how many comments we have beforehand...
numberOfExamples = comment.customBlocks.filter(
(block) => block.tag.tagName.toUpperCase() === StandardTags.example.tagNameWithUpperCase,
).length;
return <Box key={idx}>{comment.customBlocks.map((node, idx) => createNode(node, idx))}</Box>;
return <div key={idx}>{comment.customBlocks.map((node, idx) => createNode(node, idx))}</div>;
}
default:
console.log(`Captured unknown node kind: ${node.kind}`);
break;
}
// console.log(`Captured unknown node kind: ${node.kind}`);
return null;
},
[colorScheme],
);
}
}, []);
return (
<Box>
<>
{node.kind === 'Paragraph' || node.kind === 'Section' ? (
<>{(node as DocNodeContainerJSON).nodes.map((node, idx) => createNode(node, idx))}</>
) : (
createNode(node)
)}
</Box>
</>
);
}

View File

@@ -1,9 +1,21 @@
import { NextResponse, type NextRequest } from 'next/server';
import { PACKAGES } from './util/constants';
export default async function middleware(request: NextRequest) {
if (PACKAGES.some((pkg) => request.nextUrl.pathname.includes(pkg))) {
const packageName = /\/docs\/packages\/([^/]+)\/.*/.exec(request.nextUrl.pathname)?.[1] ?? 'builders';
const res = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`);
const data: string[] = await res.json();
const latestVersion = data.at(-2);
return NextResponse.redirect(
new URL(request.nextUrl.pathname.replace('stable', latestVersion ?? 'main'), request.url),
);
}
export default function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/docs/packages', request.url));
}
export const config = {
matcher: ['/docs'],
matcher: ['/docs', '/docs/packages/:package/stable/:member*'],
};

View File

@@ -1,41 +1,22 @@
import { Container, Title, Group, Button, Box, createStyles } from '@mantine/core';
import Head from 'next/head';
import Link from 'next/link';
const useStyles = createStyles((theme) => ({
label: {
textAlign: 'center',
fontWeight: 900,
fontSize: 220,
lineHeight: 1,
marginBottom: theme.spacing.xl * 1.5,
color: theme.colorScheme === 'dark' ? theme.colors.dark![4] : theme.colors.gray![2],
[theme.fn.smallerThan('sm')]: {
fontSize: 120,
},
},
}));
export default function FourOhFourPage() {
const { classes } = useStyles();
return (
<>
<Head>
<title key="title">discord.js | 404</title>
<meta key="og_title" property="og:title" content="discord.js | 404" />
</Head>
<Container pt={96} pb={96}>
<Box className={classes.label}>404</Box>
<Title align="center">Not found.</Title>
<Group position="center">
<Link href="/docs/packages" passHref prefetch={false}>
<Button component="a" variant="filled" size="md" mt="xl">
<div className="mx-auto flex h-full max-w-lg flex-col place-content-center place-items-center gap-8 py-16 px-8 lg:py-0 lg:px-6">
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">404</h1>
<h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
<Link href="/docs/packages" prefetch={false}>
<a className="bg-blurple flex h-11 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 px-6 text-base font-semibold leading-none text-white no-underline active:translate-y-px">
Take me back
</Button>
</a>
</Link>
</Group>
</Container>
</div>
</>
);
}

View File

@@ -1,135 +1,31 @@
import { type ColorScheme, ColorSchemeProvider, MantineProvider } from '@mantine/core';
import { useLocalStorage } from '@mantine/hooks';
import { type SpotlightAction, SpotlightProvider } from '@mantine/spotlight';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { type NextRouter, useRouter } from 'next/router';
import { VscPackage } from 'react-icons/vsc';
import { RouterTransition } from '../components/RouterTransition';
import NextProgress from 'next-progress';
import { ThemeProvider } from 'next-themes';
import '@unocss/reset/antfu.css';
import '../styles/unocss.css';
import '../styles/main.css';
import { miniSearch } from '~/util/search';
const actions: (router: NextRouter) => SpotlightAction[] = (router: NextRouter) => [
{
title: 'Home',
description: 'Go to landing page',
onTrigger: () => void router.push('/'),
icon: <VscPackage size={20} />,
},
{
title: 'Packages',
description: 'Go to the package selection',
onTrigger: () => void router.push('/docs/packages'),
icon: <VscPackage size={20} />,
},
{
id: 'packages-builders',
title: 'Builders',
description: 'Go to the @discordjs/builders documentation',
onTrigger: () => void router.push('/docs/packages/builders'),
icon: <VscPackage size={20} />,
},
{
id: 'packages-collection',
title: 'Collection',
description: 'Go to the @discordjs/collection documentation',
onTrigger: () => void router.push('/docs/packages/collection'),
icon: <VscPackage size={20} />,
},
{
id: 'packages-proxy',
title: 'Proxy',
description: 'Go to the @discordjs/proxy documentation',
onTrigger: () => void router.push('/docs/packages/proxy'),
icon: <VscPackage size={20} />,
},
{
id: 'packages-rest',
title: 'REST',
description: 'Go to the @discordjs/rest documentation',
onTrigger: () => void router.push('/docs/packages/voice'),
icon: <VscPackage size={20} />,
},
{
id: 'packages-voice',
title: 'Voice',
description: 'Go to the @discordjs/voice documentation',
onTrigger: () => void router.push('/docs/packages/ws'),
icon: <VscPackage size={20} />,
},
{
id: 'packages-ws',
title: 'WS',
description: 'Go to the @discordjs/ws documentation',
onTrigger: () => void router.push('/docs'),
icon: <VscPackage size={20} />,
},
];
export default function MyApp({ Component, pageProps }: AppProps) {
const router = useRouter();
const [colorScheme, setColorScheme] = useLocalStorage<ColorScheme>({
key: 'theme',
defaultValue: 'dark',
getInitialValueInEffect: true,
});
const toggleColorScheme = (value?: ColorScheme) =>
setColorScheme(value ?? (colorScheme === 'dark' ? 'light' : 'dark'));
return (
<>
<Head>
<title key="title">discord.js</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<meta name="theme-color" content="#5865f2" />
</Head>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}>
<MantineProvider
theme={{
fontFamily: 'Inter var',
fontFamilyMonospace: 'JetBrains Mono',
headings: {
fontFamily: 'Inter var',
},
colorScheme,
colors: {
blurple: [
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
],
},
primaryColor: 'blurple',
<ThemeProvider
attribute="class"
defaultTheme="system"
value={{
light: 'light',
dark: 'dark',
}}
withCSSVariables
withNormalizeCSS
withGlobalStyles
disableTransitionOnChange
>
<SpotlightProvider
shortcut={['mod + P', 'mod + K', '/']}
actions={actions(router)}
limit={7}
filter={(query, actions) => {
if (!query) {
return actions;
}
const search = miniSearch.search(query);
return actions.filter((action) => search.some((res) => res.name === action.title));
}}
>
<RouterTransition />
<NextProgress color="#5865f2" options={{ showSpinner: false }} />
<Component {...pageProps} />
</SpotlightProvider>
</MantineProvider>
</ColorSchemeProvider>
</ThemeProvider>
</>
);
}

View File

@@ -1,12 +1,7 @@
import { createGetInitialProps } from '@mantine/next';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { Html, Head, Main, NextScript } from 'next/document';
import { DESCRIPTION } from '~/util/constants';
const getInitialProps = createGetInitialProps();
export default class _Document extends Document {
public static override getInitialProps = getInitialProps;
public override render() {
export default function Document() {
return (
<Html lang="en">
<Head>
@@ -16,28 +11,23 @@ export default class _Document extends Document {
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#090a16" />
<meta name="color-scheme" content="light dark" />
<meta name="apple-mobile-web-app-title" content="discord.js" />
<meta name="application-name" content="discord.js" />
<meta name="msapplication-TileColor" content="#090a16" />
<meta name="theme-color" content="#1a1b1e" />
<meta
name="description"
content="discord.js is a powerful node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend."
/>
<meta key="description" name="description" content={DESCRIPTION} />
<meta property="og:site_name" content="discord.js" />
<meta property="og:title" content="discord.js" />
<meta
name="og:description"
content="discord.js is a powerful node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend."
/>
<meta property="og:type" content="website" />
<meta key="og_title" property="og:title" content="discord.js" />
<meta key="og_description" name="og:description" content={DESCRIPTION} />
<meta property="og:image" content="https://discordjs.dev/open-graph.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:creator" content="@iCrawlToGo" />
</Head>
<body>
<body className="dark:bg-dark-800 bg-white">
<Main />
<NextScript />
</body>
</Html>
);
}
}

View File

@@ -13,22 +13,23 @@ import {
type ApiEnumJSON,
} from '@discordjs/api-extractor-utils';
import { createApiModel } from '@discordjs/scripts';
import { ActionIcon, Affix, Box, LoadingOverlay, Transition } from '@mantine/core';
import { useMediaQuery, useWindowScroll } from '@mantine/hooks';
import { registerSpotlightActions } from '@mantine/spotlight';
import { ApiFunction, ApiItemKind, type ApiPackage } from '@microsoft/api-extractor-model';
import Head from 'next/head';
import { useRouter } from 'next/router';
import type { GetStaticPaths, GetStaticProps } from 'next/types';
import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import { useEffect } from 'react';
import { VscChevronUp } from 'react-icons/vsc';
import { useMemo } from 'react';
import rehypeIgnore from 'rehype-ignore';
import rehypePrettyCode from 'rehype-pretty-code';
import rehypePrettyCode, { type Options } from 'rehype-pretty-code';
import rehypeRaw from 'rehype-raw';
import rehypeSlug from 'rehype-slug';
import remarkGfm from 'remark-gfm';
import { getHighlighter } from 'shiki';
import shikiLangJavascript from 'shiki/languages/javascript.tmLanguage.json';
import shikiLangTypescript from 'shiki/languages/typescript.tmLanguage.json';
import shikiThemeDarkPlus from 'shiki/themes/dark-plus.json';
import shikiThemeLightPlus from 'shiki/themes/light-plus.json';
import { SidebarLayout, type SidebarLayoutProps } from '~/components/SidebarLayout';
import { Class } from '~/components/model/Class';
import { Enum } from '~/components/model/Enum';
@@ -37,9 +38,9 @@ import { Interface } from '~/components/model/Interface';
import { TypeAlias } from '~/components/model/TypeAlias';
import { Variable } from '~/components/model/Variable';
import { MemberProvider } from '~/contexts/member';
import { PACKAGES } from '~/util/constants';
import { findMember, findMemberByKey } from '~/util/model.server';
import { PACKAGES } from '~/util/packages';
import { miniSearch } from '~/util/search';
// import { miniSearch } from '~/util/search';
export const getStaticPaths: GetStaticPaths = async () => {
const pkgs = (
@@ -54,6 +55,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
} else {
const response = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`);
versions = await response.json();
versions = versions.slice(-2);
for (const version of versions) {
const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${version}.api.json`);
@@ -123,7 +125,13 @@ export const getStaticPaths: GetStaticPaths = async () => {
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const [, packageName = 'builders', branchName = 'main', member] = params!.slug as string[];
const [path, packageName = 'builders', branchName = 'main', member] = params!.slug as string[];
if (path !== 'packages' || !PACKAGES.includes(packageName)) {
return {
notFound: true,
};
}
const [memberName, overloadIndex] = member?.split(':') ?? [];
@@ -134,7 +142,30 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
mdxOptions: {
remarkPlugins: [remarkGfm],
remarkRehypeOptions: { allowDangerousHtml: true },
rehypePlugins: [rehypeRaw, rehypeIgnore, rehypeSlug, [rehypePrettyCode, { theme: 'dark-plus' }]],
rehypePlugins: [
rehypeRaw,
rehypeIgnore,
rehypeSlug,
[
rehypePrettyCode,
{
theme: {
dark: shikiThemeDarkPlus,
light: shikiThemeLightPlus,
},
getHighlighter: async (options?: Partial<Options>) =>
getHighlighter({
...options,
langs: [
// @ts-expect-error: Working as intended
{ id: 'javascript', aliases: ['js'], scopeName: 'source.js', grammar: shikiLangJavascript },
// @ts-expect-error: Working as intended
{ id: 'typescript', aliases: ['ts'], scopeName: 'source.ts', grammar: shikiLangTypescript },
],
}),
},
],
],
format: 'md',
},
});
@@ -186,7 +217,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
props: {
error: error_,
},
revalidate: 3_600,
revalidate: 1,
};
}
};
@@ -206,46 +237,30 @@ const member = (props?: ApiItemJSON | undefined) => {
case 'Enum':
return <Enum data={props as ApiEnumJSON} />;
default:
return <Box>Cannot render that item type</Box>;
return <div>Cannot render that item type</div>;
}
};
export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: string }>) {
const router = useRouter();
const [scroll, scrollTo] = useWindowScroll();
const matches = useMediaQuery('(max-width: 1200px)');
useEffect(() => {
if (props.data?.searchIndex) {
const searchIndex = props.data?.searchIndex.map((idx, index) => ({ id: index, ...idx })) ?? [];
miniSearch.addAll(searchIndex);
registerSpotlightActions(
searchIndex.map((idx) => ({
title: idx.name,
description: idx.summary ?? '',
onTrigger: () => void router.push(idx.path),
})),
const name = useMemo(
() => `discord.js${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`,
[props.data?.member?.name],
);
const ogTitle = useMemo(
() => `${props.packageName ?? 'discord.js'}${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`,
[props.packageName, props.data?.member?.name],
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const name = `discord.js${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`;
if (router.isFallback) {
return (
<SidebarLayout>
<LoadingOverlay visible overlayBlur={2} />
</SidebarLayout>
);
return null;
}
// Just in case
// return <iframe src="https://discord.js.org" style={{ border: 0, height: '100%', width: '100%' }}></iframe>;
return props.error ? (
<Box sx={{ display: 'flex', maxWidth: '100%', height: '100%' }}>{props.error}</Box>
<div className="flex h-full max-h-full w-full max-w-full flex-row">{props.error}</div>
) : (
<MemberProvider member={props.data?.member}>
<SidebarLayout {...props}>
@@ -253,50 +268,20 @@ export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: s
<>
<Head>
<title key="title">{name}</title>
<meta key="og_title" property="og:title" content={ogTitle} />
</Head>
{member(props.data.member)}
</>
) : props.data?.source ? (
<Box
sx={(theme) => ({
a: {
backgroundColor: 'transparent',
color: theme.colors.blurple![0],
textDecoration: 'none',
},
img: {
borderStyle: 'none',
maxWidth: '100%',
boxSizing: 'content-box',
},
})}
px="xl"
>
<div className="prose max-w-none">
<MDXRemote {...props.data.source} />
</Box>
</div>
) : null}
<Affix
position={{
bottom: 20,
right:
matches || (props.data?.member?.kind !== 'Class' && props.data?.member?.kind !== 'Interface') ? 20 : 268,
}}
>
<Transition transition="slide-up" mounted={scroll.y > 200}>
{(transitionStyles) => (
<ActionIcon
variant="filled"
color="blurple"
size={30}
style={transitionStyles}
onClick={() => scrollTo({ y: 0 })}
>
<VscChevronUp size={20} />
</ActionIcon>
)}
</Transition>
</Affix>
</SidebarLayout>
</MemberProvider>
);
}
export const config = {
unstable_includeFiles: ['../{builders,collection,proxy,rest,voice,ws}/README.md'],
};

View File

@@ -1,21 +1,7 @@
import {
Container,
UnstyledButton,
createStyles,
Group,
ThemeIcon,
Text,
Stack,
Box,
Title,
useMantineColorScheme,
Affix,
} from '@mantine/core';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type { GetStaticPaths, GetStaticProps } from 'next/types';
import { VscArrowLeft, VscArrowRight, VscVersions } from 'react-icons/vsc';
import { PACKAGES } from '~/util/packages';
import { PACKAGES } from '~/util/constants';
interface VersionProps {
data: {
@@ -34,20 +20,21 @@ export const getStaticPaths: GetStaticPaths = () => {
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const packageName = params!.package as string | undefined;
const packageName = params!.package as string;
if (!PACKAGES.includes(packageName)) {
return {
notFound: true,
};
}
try {
const res = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName ?? 'builders'}`);
const res = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`);
const data: string[] = await res.json();
if (!data.length) {
console.error('No tags');
return {
props: {
error: 'No tags',
},
revalidate: 3_600,
notFound: true,
};
}
@@ -55,7 +42,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
props: {
packageName,
data: {
versions: data,
versions: data.reverse(),
},
},
revalidate: 3_600,
@@ -68,66 +55,39 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
props: {
error: error_,
},
revalidate: 3_600,
revalidate: 1,
};
}
};
const useStyles = createStyles((theme) => ({
outer: {
display: 'flex',
height: '100%',
alignItems: 'center',
},
control: {
padding: theme.spacing.xs,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
borderRadius: theme.radius.xs,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
},
},
}));
export default function VersionsRoute(props: Partial<VersionProps> & { error?: string }) {
const router = useRouter();
const { classes } = useStyles();
const { colorScheme } = useMantineColorScheme();
return props.error ? (
<Box sx={{ display: 'flex', maxWidth: '100%', height: '100%' }}>{props.error}</Box>
<div className="min-w-xs sm:w-md mx-auto flex h-full flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
{props.error}
</div>
) : (
<Container className={classes.outer} size="xs">
<Stack sx={{ flexGrow: 1 }}>
<Title order={2} ml="xs">
Select a version:
</Title>
<div className="min-w-xs sm:w-md mx-auto flex h-full flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
<div className="flex grow flex-col place-content-center gap-4">
<h1 className="text-2xl font-semibold">Select a version:</h1>
{props.data?.versions.map((version) => (
<Link key={version} href={`/docs/packages/${props.packageName!}/${version}`} passHref prefetch={false}>
<UnstyledButton className={classes.control} component="a">
<Group position="apart">
<Group>
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
<VscVersions size={20} />
</ThemeIcon>
<Text weight={600} size="md">
{version}
</Text>
</Group>
<Link key={version} href={`/docs/packages/${props.packageName}/${version}`} prefetch={false}>
<a className="dark:bg-dark-400 dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 flex flex h-11 transform-gpu cursor-pointer select-none appearance-none flex-col place-content-center rounded border border-neutral-300 bg-transparent p-4 text-base font-semibold leading-none text-black hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white">
<div className="flex flex-row place-content-between place-items-center gap-4">
<div className="flex flex-row place-content-between place-items-center gap-4">
<VscVersions size={25} />
<h2 className="font-semibold">{version}</h2>
</div>
<VscArrowRight size={20} />
</Group>
</UnstyledButton>
</div>
</a>
</Link>
)) ?? null}
</Stack>
<Affix position={{ top: 20, left: 20 }}>
<UnstyledButton onClick={() => void router.push('/docs/packages')}>
<VscArrowLeft size={25} />
</UnstyledButton>
</Affix>
</Container>
<Link href="/docs/packages" prefetch={false}>
<a className="bg-blurple flex h-11 transform-gpu cursor-pointer select-none appearance-none place-items-center gap-2 place-self-center rounded border-0 px-4 text-base font-semibold leading-none text-white no-underline active:translate-y-px">
<VscArrowLeft size={20} /> Go back
</a>
</Link>
</div>
</div>
);
}

View File

@@ -1,95 +1,98 @@
import {
Container,
UnstyledButton,
createStyles,
Group,
ThemeIcon,
Text,
Stack,
Title,
useMantineColorScheme,
Button,
} from '@mantine/core';
import { Button } from 'ariakit/button';
import Link from 'next/link';
import { useRouter } from 'next/router';
import type { MouseEvent } from 'react';
import { VscArrowRight, VscPackage } from 'react-icons/vsc';
import { PACKAGES } from '~/util/packages';
import type { GetStaticProps } from 'next/types';
import { useCallback, type MouseEvent } from 'react';
import { VscArrowLeft, VscArrowRight, VscPackage } from 'react-icons/vsc';
import { PACKAGES } from '~/util/constants';
const useStyles = createStyles((theme) => ({
outer: {
display: 'flex',
height: '100%',
alignItems: 'center',
interface PackageProps {
data: {
versions: { packageName: string; version: string }[];
};
}
export const getStaticProps: GetStaticProps = async () => {
try {
const versions = await Promise.all(
PACKAGES.map(async (pkg) => {
const response = await fetch(`https://docs.discordjs.dev/api/info?package=${pkg}`);
const versions = await response.json();
const latestVersion = versions.at(-2);
return { packageName: pkg, version: latestVersion };
}),
);
return {
props: {
versions,
},
revalidate: 3_600,
};
} catch (error_) {
const error = error_ as Error;
console.error(error);
control: {
padding: theme.spacing.xs,
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black,
borderRadius: theme.radius.xs,
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0],
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
return {
props: {
error: error_,
},
},
}));
revalidate: 1,
};
}
};
export default function PackagesRoute() {
const { classes } = useStyles();
const { colorScheme } = useMantineColorScheme();
export default function PackagesRoute(props: Partial<PackageProps> & { error?: string }) {
const router = useRouter();
const findLatestVersion = useCallback(
(pkg: string) => props.data?.versions.find((version) => version.packageName === pkg),
[props.data?.versions],
);
const handleClick = async (ev: MouseEvent<HTMLDivElement>, packageName: string) => {
ev.stopPropagation();
const res = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName ?? 'builders'}`);
const data: string[] = await res.json();
const latestVersion = data.at(-2);
void router.push(`/docs/packages/${packageName}/${latestVersion}`);
void router.push(`/docs/packages/${packageName}`);
};
return (
<Container className={classes.outer} size="xs">
<Stack sx={{ flexGrow: 1 }}>
<Title order={2} ml="xs">
Select a package:
</Title>
return props.error ? (
<div className="min-w-xs sm:w-md mx-auto flex h-full flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
{props.error}
</div>
) : (
<div className="min-w-xs sm:w-md mx-auto flex h-full flex-row place-content-center place-items-center gap-8 py-0 px-4 lg:py-0 lg:px-6">
<div className="flex grow flex-col place-content-center gap-4">
<h1 className="text-2xl font-semibold">Select a package:</h1>
{PACKAGES.map((pkg) => (
<UnstyledButton
component="div"
key={pkg}
role="link"
className={classes.control}
onClick={(ev: MouseEvent<HTMLDivElement>) => void handleClick(ev, pkg)}
>
<Group position="apart">
<Group sx={{ flexGrow: 1 }} position="apart">
<Group>
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}>
<VscPackage size={20} />
</ThemeIcon>
<Text weight={600} size="md">
{pkg}
</Text>
</Group>
<Link href={`/docs/packages/${pkg}`} passHref prefetch={false}>
<Link key={pkg} href={`/docs/packages/${pkg}/${findLatestVersion(pkg)?.version ?? 'main'}`} prefetch={false}>
<a className="dark:bg-dark-400 dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 flex h-11 transform-gpu cursor-pointer select-none appearance-none place-content-between rounded border border-neutral-300 bg-transparent p-4 text-base font-semibold leading-none text-black hover:bg-neutral-100 active:translate-y-px active:bg-neutral-200 dark:text-white">
<div className="flex grow flex-row place-content-between place-items-center gap-4">
<div className="flex grow flex-row place-content-between place-items-center gap-4">
<div className="flex flex-row place-content-between place-items-center gap-4">
<VscPackage size={25} />
<h2 className="font-semibold">{pkg}</h2>
</div>
<Link href={`/docs/packages/${pkg}`} prefetch={false}>
<Button
component="a"
size="xs"
compact
onClick={(ev: MouseEvent<HTMLAnchorElement>) => ev.stopPropagation()}
as="div"
role="link"
className="bg-blurple flex h-6 transform-gpu cursor-pointer select-none appearance-none place-content-center place-items-center rounded border-0 px-2 text-xs font-semibold leading-none text-white active:translate-y-px"
onClick={async (ev: MouseEvent<HTMLDivElement>) => handleClick(ev, pkg)}
>
Select version
</Button>
</Link>
</Group>
</div>
<VscArrowRight size={20} />
</Group>
</UnstyledButton>
</div>
</a>
</Link>
))}
</Stack>
</Container>
<Link href="/" prefetch={false}>
<a className="bg-blurple flex h-11 transform-gpu cursor-pointer select-none appearance-none place-items-center gap-2 place-self-center rounded border-0 px-4 text-base font-semibold leading-none text-white no-underline active:translate-y-px">
<VscArrowLeft size={20} /> Go back
</a>
</Link>
</div>
</div>
);
}

View File

@@ -1,133 +1,52 @@
import { createStyles, Container, Title, Button, Group, Text, Center, Box, useMantineColorScheme } from '@mantine/core';
import Image from 'next/future/image';
import Link from 'next/link';
import { FiExternalLink } from 'react-icons/fi';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus, ghcolors } from 'react-syntax-highlighter/dist/cjs/styles/prism';
const useStyles = createStyles((theme) => ({
outer: {
display: 'flex',
flexDirection: 'column',
height: '100%',
justifyContent: 'center',
gap: 50,
padding: 32,
[theme.fn.smallerThan('md')]: {
height: 'unset',
},
},
inner: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
[theme.fn.smallerThan('md')]: {
flexDirection: 'column',
gap: 50,
},
},
content: {
maxWidth: 480,
marginRight: theme.spacing.xl,
[theme.fn.smallerThan('md')]: {
marginRight: 0,
},
},
title: {
color: theme.colorScheme === 'dark' ? theme.white : theme.black,
fontSize: 44,
lineHeight: 1.2,
fontWeight: 900,
[theme.fn.smallerThan('xs')]: {
fontSize: 28,
},
},
highlight: {
position: 'relative',
backgroundColor: theme.fn.variant({ variant: 'light', color: theme.primaryColor }).background!,
borderRadius: theme.radius.sm,
padding: '4px 12px',
},
}));
import vercelLogo from '../assets/powered-by-vercel.svg';
import { SyntaxHighlighter } from '~/components/SyntaxHighlighter';
import { CODE_EXAMPLE } from '~/util/constants';
export default function IndexRoute() {
const { classes } = useStyles();
const { colorScheme } = useMantineColorScheme();
return (
<Container className={classes.outer} size="lg">
<Box className={classes.inner}>
<Box className={classes.content}>
<Title className={classes.title}>
The <span className={classes.highlight}>most popular</span> way to build Discord <br /> bots.
</Title>
<Text color="dimmed" mt="md">
<div className="mx-auto flex max-w-6xl flex-col place-items-center gap-12 py-16 px-8 lg:h-full lg:place-content-center lg:py-0 lg:px-6">
<div className="flex flex-col place-items-center gap-10 lg:flex-row lg:gap-6">
<div className="flex max-w-lg flex-col gap-3 lg:mr-8">
<h1 className="text-3xl font-black leading-tight sm:text-5xl sm:leading-tight">
The <span className="bg-blurple relative rounded py-1 px-3 text-white">most popular</span> way to build
Discord <br /> bots.
</h1>
<p className="my-6 leading-normal text-neutral-700 dark:text-neutral-300">
discord.js is a powerful node.js module that allows you to interact with the Discord API very easily. It
takes a much more object-oriented approach than most other JS Discord libraries, making your bot&apos;s code
significantly tidier and easier to comprehend.
</Text>
<Group mt={30}>
<Link href="/docs" passHref prefetch={false}>
<Button component="a" radius="sm" size="md">
</p>
<div className="flex flex-row gap-4">
<Link href="/docs" prefetch={false}>
<a className="bg-blurple flex h-11 transform-gpu cursor-pointer select-none appearance-none place-items-center rounded border-0 px-6 text-base font-semibold leading-none text-white no-underline active:translate-y-px">
Docs
</Button>
</Link>
<Link href="https://discordjs.guide" passHref prefetch={false}>
<Button component="a" variant="default" radius="sm" size="md" rightIcon={<FiExternalLink />}>
Guide
</Button>
</Link>
</Group>
</Box>
<Box pb="xs">
<SyntaxHighlighter
wrapLongLines
language="typescript"
style={colorScheme === 'dark' ? vscDarkPlus : ghcolors}
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }}
>
{`import { Client, GatewayIntentBits } from 'discord.js';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on('ready', () => {
console.log(\`Logged in as \${client.user.tag}!\`);
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
await interaction.reply('Pong!');
}
});
await client.login('token');`}
</SyntaxHighlighter>
</Box>
</Box>
<Center>
<Link href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss" prefetch={false}>
<a title="Vercel">
<Image
src="/powered-by-vercel.svg"
alt="Vercel"
width={0}
height={0}
style={{ height: '100%', width: '100%', maxWidth: 250 }}
/>
</a>
</Link>
</Center>
</Container>
<a
className="dark:bg-dark-400 dark:border-dark-100 dark:hover:bg-dark-300 dark:active:bg-dark-200 border-light-900 hover:bg-light-200 active:bg-light-300 flex h-11 transform-gpu cursor-pointer select-none appearance-none place-items-center gap-2 rounded border bg-transparent px-4 text-base font-semibold leading-none text-black no-underline active:translate-y-px dark:text-white"
href="https://discordjs.guide"
target="_blank"
rel="noopener noreferrer"
>
Guide <FiExternalLink />
</a>
</div>
</div>
<SyntaxHighlighter code={CODE_EXAMPLE} />
</div>
<div className="flex place-content-center">
<a
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
target="_blank"
rel="noopener noreferrer"
title="Vercel"
>
<Image src={vercelLogo} alt="Vercel" />
</a>
</div>
</div>
);
}

View File

@@ -1,29 +1,43 @@
@import url('https://rsms.me/inter/inter.css');
@supports (font-variation-settings: normal) {
html {
font-family: 'Inter var', sans-serif;
:root {
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue;
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
}
@supports (font-variation-settings: normal) {
:root {
font-family: 'Inter var', Arial, Noto Sans, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', Segoe UI Symbol,
'Noto Color Emoji';
}
}
html,
body {
height: 100vh;
margin: 0;
font-family: 'Inter var', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Helvetica Neue, Arial, Noto Sans, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', Segoe UI Symbol,
'Noto Color Emoji';
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
height: 100%;
}
#__next {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
}
.light [data-theme='dark'] {
display: none;
}
.dark [data-theme='light'] {
display: none;
}
pre[data-theme='light'] {
background: #ffffff;
border: 1px solid #dddddd;
}
pre[data-theme='dark'] {
background: #1e1e1e;
}
pre {
font-size: 13px !important;
white-space: pre;
@@ -35,10 +49,9 @@ pre {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
background: #1e1e1e;
border-radius: 4px;
}
code {
font-family: 'JetBrains Mono' !important;
font-family: 'JetBrains Mono', monospace !important;
}

View File

@@ -0,0 +1,22 @@
export const PACKAGES = ['builders', 'collection', 'proxy', 'rest', 'voice', 'ws'];
export const DESCRIPTION =
"discord.js is a powerful node.js module that allows you to interact with the Discord API very easily. It takes a much more object-oriented approach than most other JS Discord libraries, making your bot's code significantly tidier and easier to comprehend.";
export const CODE_EXAMPLE = `import { Client, GatewayIntentBits } from 'discord.js';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.on('ready', () => {
console.log(\`Logged in as \${client.user.tag}!\`);
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
await interaction.reply('Pong!');
}
});
await client.login('token');`;

View File

@@ -1 +0,0 @@
export const PACKAGES = ['builders', 'collection', 'proxy', 'rest', 'voice', 'ws'];

View File

@@ -1,6 +0,0 @@
import MiniSearch from 'minisearch';
export const miniSearch = new MiniSearch({
fields: ['name', 'summary'],
storeFields: ['name', 'summary', 'path'],
});

View File

@@ -1,10 +1,20 @@
import presetWebFonts from '@unocss/preset-web-fonts';
import { defineConfig, presetUno } from 'unocss';
import { defineConfig, presetTypography, presetUno, presetWebFonts } from 'unocss';
export default defineConfig({
theme: {
colors: {
blurple: '#5865F2',
blurple: {
50: '#e0e3ff',
100: '#cdd2ff',
200: '#9ea7ff',
300: '#7782fa',
DEFAULT: '#5865F2',
500: '#3d48c3',
600: '#293294',
700: '#1a2165',
800: '#0e1137',
900: '#020208',
},
},
},
presets: [
@@ -12,7 +22,43 @@ export default defineConfig({
presetWebFonts({
provider: 'google',
fonts: {
mono: ['JetBrains Mono', 'JetBrains Mono:400,600'],
mono: ['JetBrains Mono', 'JetBrains Mono:400,600,700'],
},
}),
presetTypography({
cssExtend: {
pre: {
padding: '1em',
'line-height': '1.5',
'border-radius': '4px',
},
code: {
'font-size': '1em',
'font-weight': 'unset',
},
':where(:not(pre) > code)::before': {
content: '""',
},
':where(:not(pre) > code)::after': {
content: '""',
},
a: {
color: '#5865F2',
'text-decoration': 'none',
},
'a > img': {
display: 'inline-block',
},
h2: {
'margin-top': '1.25em',
},
h3: {
'margin-top': '0.75em',
},
// eslint-disable-next-line id-length
p: {
margin: '.5em 0',
},
},
}),
],

View File

@@ -65,8 +65,8 @@
"@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0",
"@types/node": "^16.11.56",
"@vitest/coverage-c8": "^0.23.0",
"@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.1",
"downlevel-dts": "^0.10.1",
"eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33",
@@ -75,7 +75,7 @@
"tsup": "^6.2.3",
"typescript": "^4.8.2",
"undici": "^5.10.0",
"vitest": "^0.23.0",
"vitest": "^0.23.1",
"zlib-sync": "^0.1.7"
},
"engines": {

1806
yarn.lock

File diff suppressed because it is too large Load Diff