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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -59,14 +59,14 @@
"ws": "^8.8.1" "ws": "^8.8.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.18.13", "@babel/core": "^7.19.0",
"@babel/preset-env": "^7.18.10", "@babel/preset-env": "^7.19.0",
"@babel/preset-typescript": "^7.18.6", "@babel/preset-typescript": "^7.18.6",
"@discordjs/docgen": "workspace:^", "@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7", "@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0", "@microsoft/api-extractor": "^7.30.0",
"@types/jest": "^29.0.0", "@types/jest": "^29.0.0",
"@types/node": "^16.11.56", "@types/node": "^16.11.57",
"downlevel-dts": "^0.10.1", "downlevel-dts": "^0.10.1",
"eslint": "^8.23.0", "eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33", "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} * @type {import('next').NextConfig}
*/ */
@@ -12,6 +15,7 @@ export default {
images: { images: {
allowFutureImage: true, allowFutureImage: true,
}, },
outputFileTracingRoot: fileURLToPath(new URL('../../', import.meta.url)),
}, },
images: { images: {
dangerouslyAllowSVG: true, dangerouslyAllowSVG: true,

View File

@@ -41,52 +41,50 @@
"dependencies": { "dependencies": {
"@discordjs/api-extractor-utils": "workspace:^", "@discordjs/api-extractor-utils": "workspace:^",
"@discordjs/scripts": "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/api-extractor-model": "7.24.0",
"@microsoft/tsdoc": "0.14.1", "@microsoft/tsdoc": "0.14.1",
"@vscode/codicons": "^0.0.32", "@vscode/codicons": "^0.0.32",
"ariakit": "^2.0.0-next.41",
"minisearch": "^5.0.0", "minisearch": "^5.0.0",
"next": "^12.2.5", "next": "^12.2.5",
"next-mdx-remote": "^4.1.0", "next-mdx-remote": "^4.1.0",
"next-progress": "^2.2.0",
"next-themes": "^0.2.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-custom-scrollbars-2": "^4.5.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-icons": "^4.4.0", "react-icons": "^4.4.0",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.5.0",
"react-use": "^17.4.0",
"rehype-ignore": "^1.0.1", "rehype-ignore": "^1.0.1",
"rehype-pretty-code": "^0.3.2", "rehype-pretty-code": "^0.3.2",
"rehype-raw": "^6.1.1", "rehype-raw": "^6.1.1",
"rehype-slug": "^5.0.1", "rehype-slug": "^5.0.1",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"sharp": "^0.30.7", "sharp": "^0.31.0",
"shiki": "^0.11.1", "shiki": "^0.11.1",
"swr": "^1.3.0" "swr": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3", "@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-dom": "^18.0.6",
"@types/react-syntax-highlighter": "^15.5.5", "@types/react-syntax-highlighter": "^15.5.5",
"@unocss/cli": "^0.45.15", "@unocss/cli": "^0.45.15",
"@unocss/preset-web-fonts": "^0.45.15",
"@unocss/reset": "^0.45.15", "@unocss/reset": "^0.45.15",
"@vitejs/plugin-react": "^2.0.1", "@vitejs/plugin-react": "^2.1.0",
"@vitest/coverage-c8": "^0.23.0", "@vitest/coverage-c8": "^0.23.1",
"concurrently": "^7.3.0", "concurrently": "^7.3.0",
"eslint": "^8.23.0", "eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33", "eslint-config-neon": "^0.1.33",
"happy-dom": "^6.0.4", "happy-dom": "^6.0.4",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"prettier-plugin-tailwindcss": "^0.1.13",
"typescript": "^4.8.2", "typescript": "^4.8.2",
"unocss": "^0.45.15", "unocss": "^0.45.15",
"vercel": "^28.2.2", "vercel": "^28.2.2",
"vitest": "^0.23.0" "vitest": "^0.23.1"
}, },
"engines": { "engines": {
"node": ">=16.9.0" "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 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 type { PropsWithChildren } from 'react';
import { FiLink } from 'react-icons/fi'; import { FiLink } from 'react-icons/fi';
import { HyperlinkedText } from './HyperlinkedText'; import { HyperlinkedText } from './HyperlinkedText';
@@ -12,19 +10,6 @@ export enum CodeListingSeparatorType {
Value = '=', Value = '=',
} }
const useStyles = createStyles((theme) => ({
outer: {
display: 'flex',
alignItems: 'center',
gap: 16,
[theme.fn.smallerThan('sm')]: {
flexDirection: 'column',
alignItems: 'unset',
},
},
}));
export function CodeListing({ export function CodeListing({
name, name,
separator = CodeListingSeparatorType.Type, separator = CodeListingSeparatorType.Type,
@@ -47,48 +32,51 @@ export function CodeListing({
summary?: ApiItemJSON['summary']; summary?: ApiItemJSON['summary'];
typeTokens: TokenDocumentation[]; typeTokens: TokenDocumentation[];
}>) { }>) {
const { classes } = useStyles();
const matches = useMediaQuery('(max-width: 768px)');
return ( return (
<Stack id={name} className="scroll-mt-30" spacing="xs"> <div id={name} className="scroll-mt-30 flex flex-col gap-4">
<Box className={classes.outer} ml={matches ? 0 : -45}> <div className={`md:-ml-8.5 flex flex-col gap-0.5 md:flex-row md:place-items-center md:gap-2`}>
<MediaQuery smallerThan="sm" styles={{ display: 'none' }}> <a className="hidden md:inline-block" href={`#${name}`}>
<ActionIcon component="a" href={`#${name}`} variant="transparent"> <FiLink size={20} />
<FiLink size={20} /> </a>
</ActionIcon>
</MediaQuery>
{deprecation || readonly || optional ? ( {deprecation || readonly || optional ? (
<Group spacing={10} noWrap> <div className="flex flex-row flex-wrap gap-1">
{deprecation ? ( {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 Deprecated
</Badge> </div>
) : null} ) : null}
{readonly ? <Badge variant="filled">Readonly</Badge> : null} {readonly ? (
{optional ? <Badge variant="filled">Optional</Badge> : null} <div className="bg-blurple h-5 place-content-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white">
</Group> Readonly
</div>
) : null}
{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} ) : null}
<Group spacing={10}> <div className="flex flex-row flex-wrap place-items-center gap-1">
<Title order={4} className="font-mono"> <h4 className="break-all font-mono text-lg font-bold">
{name} {name}
{optional ? '?' : ''} {optional ? '?' : ''}
</Title> </h4>
<Title order={4}>{separator}</Title> <h4 className="font-mono text-lg font-bold">{separator}</h4>
<Title sx={{ wordBreak: 'break-all' }} order={4} className="font-mono"> <h4 className="break-all font-mono text-lg font-bold">
<HyperlinkedText tokens={typeTokens} /> <HyperlinkedText tokens={typeTokens} />
</Title> </h4>
</Group> </div>
</Box> </div>
<Group> {summary || inheritanceData ? (
<Stack> <div className="flex flex-col gap-4">
{deprecation ? <TSDoc node={deprecation} /> : null} {deprecation ? <TSDoc node={deprecation} /> : null}
{summary && <TSDoc node={summary} />} {summary ? <TSDoc node={summary} /> : null}
{comment && <TSDoc node={comment} />} {comment ? <TSDoc node={comment} /> : null}
{inheritanceData ? <InheritanceText data={inheritanceData} /> : null} {inheritanceData ? <InheritanceText data={inheritanceData} /> : null}
{children} {children}
</Stack> </div>
</Group> ) : null}
</Stack> </div>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,19 +1,20 @@
import type { ApiMethodJSON, ApiMethodSignatureJSON } from '@discordjs/api-extractor-utils'; import type { ApiMethodJSON, ApiMethodSignatureJSON } from '@discordjs/api-extractor-utils';
import { Divider, Stack } from '@mantine/core'; import { Fragment, useMemo } from 'react';
import { Fragment } from 'react';
import { MethodItem } from './MethodItem'; import { MethodItem } from './MethodItem';
export function MethodList({ data }: { data: (ApiMethodJSON | ApiMethodSignatureJSON)[] }) { export function MethodList({ data }: { data: (ApiMethodJSON | ApiMethodSignatureJSON)[] }) {
return ( const methodItems = useMemo(
<Stack> () =>
{data.map((method) => ( data.map((method) => (
<Fragment <Fragment
key={`${method.name}${method.overloadIndex && method.overloadIndex > 1 ? `:${method.overloadIndex}` : ''}`} key={`${method.name}${method.overloadIndex && method.overloadIndex > 1 ? `:${method.overloadIndex}` : ''}`}
> >
<MethodItem data={method} /> <MethodItem data={method} />
<Divider size="md" /> <div className="border-light-900 -mx-8 border-t-2" />
</Fragment> </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 type { ParameterDocumentation } from '@discordjs/api-extractor-utils';
import { Box, ScrollArea } from '@mantine/core'; import { useMemo } from 'react';
import { HyperlinkedText } from './HyperlinkedText'; import { HyperlinkedText } from './HyperlinkedText';
import { Table } from './Table'; import { Table } from './Table';
import { TSDoc } from './tsdoc/TSDoc'; import { TSDoc } from './tsdoc/TSDoc';
@@ -10,18 +10,20 @@ const columnStyles = {
}; };
export function ParameterTable({ data }: { data: ParameterDocumentation[] }) { export function ParameterTable({ data }: { data: ParameterDocumentation[] }) {
const rows = data.map((param) => ({ const rows = useMemo(
Name: param.name, () =>
Type: <HyperlinkedText tokens={param.tokens} />, data.map((param) => ({
Optional: param.isOptional ? 'Yes' : 'No', Name: param.name,
Description: param.paramCommentBlock ? <TSDoc node={param.paramCommentBlock} /> : 'None', Type: <HyperlinkedText tokens={param.tokens} />,
})); Optional: param.isOptional ? 'Yes' : 'No',
Description: param.paramCommentBlock ? <TSDoc node={param.paramCommentBlock} /> : 'None',
})),
[data],
);
return ( return (
<Box> <div className="overflow-x-auto">
<ScrollArea pb="xs" offsetScrollbars> <Table columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} columnStyles={columnStyles} />
<Table columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} columnStyles={columnStyles} /> </div>
</ScrollArea>
</Box>
); );
} }

View File

@@ -1,12 +1,11 @@
import type { ApiPropertyItemJSON } from '@discordjs/api-extractor-utils'; import type { ApiPropertyItemJSON } from '@discordjs/api-extractor-utils';
import { Divider, Stack } from '@mantine/core'; import { Fragment, useMemo } from 'react';
import { Fragment } from 'react';
import { CodeListing } from './CodeListing'; import { CodeListing } from './CodeListing';
export function PropertyList({ data }: { data: ApiPropertyItemJSON[] }) { export function PropertyList({ data }: { data: ApiPropertyItemJSON[] }) {
return ( const propertyItems = useMemo(
<Stack> () =>
{data.map((prop) => ( data.map((prop) => (
<Fragment key={prop.name}> <Fragment key={prop.name}>
<CodeListing <CodeListing
name={prop.name} name={prop.name}
@@ -18,9 +17,11 @@ export function PropertyList({ data }: { data: ApiPropertyItemJSON[] }) {
deprecation={prop.deprecated} deprecation={prop.deprecated}
inheritanceData={prop.inheritanceData} inheritanceData={prop.inheritanceData}
/> />
<Divider size="md" /> <div className="border-light-900 -mx-8 border-t-2" />
</Fragment> </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 { import { Disclosure, DisclosureContent, useDisclosureState } from 'ariakit/disclosure';
createStyles, import type { PropsWithChildren } from 'react';
UnstyledButton,
Group,
ThemeIcon,
Collapse,
Box,
Text,
useMantineColorScheme,
} from '@mantine/core';
import { useState, useEffect, type PropsWithChildren } from 'react';
import { VscChevronDown } from 'react-icons/vsc'; 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({ export function Section({
title, title,
icon, icon,
@@ -46,41 +16,28 @@ export function Section({
padded?: boolean; padded?: boolean;
title: string; title: string;
}>) { }>) {
const [opened, setOpened] = useState(!defaultClosed); const disclosure = useDisclosureState({ defaultOpen: !defaultClosed });
const { colorScheme } = useMantineColorScheme();
const { classes } = useStyles({ opened });
useEffect(() => {
setOpened(!defaultClosed);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return ( return (
<Box sx={{ wordBreak: 'break-all' }}> <div>
<UnstyledButton className={classes.control} onClick={() => setOpened((isOpen) => !isOpen)}> <Disclosure
<Group position="apart"> 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"
<Group> state={disclosure}
{icon ? ( >
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}> <div className="flex flex-row place-content-between place-items-center">
{icon} <div className="flex flex-row place-items-center gap-3">
</ThemeIcon> {icon ?? null}
) : null} <span className="font-semibold">{title}</span>
<Text weight={600} size="md"> </div>
{title} <VscChevronDown
</Text> className={`transform transition duration-150 ease-in-out ${disclosure.open ? 'rotate-180' : 'rotate-0'}`}
</Group> size={20}
<VscChevronDown size={20} className={classes.icon} /> />
</Group> </div>
</UnstyledButton> </Disclosure>
<Collapse in={opened}> <DisclosureContent state={disclosure}>
{padded ? ( {padded ? <div className={`py-5 ${dense ? 'mx-2 px-0' : 'px-4.5 mx-6.5'}`}>{children}</div> : children}
<Box py={20} px={dense ? 0 : 31} mx={dense ? 10 : 25}> </DisclosureContent>
{children} </div>
</Box>
) : (
children
)}
</Collapse>
</Box>
); );
} }

View File

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

View File

@@ -1,4 +1,3 @@
import { createStyles, Group, Text, NavLink, Box } from '@mantine/core';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { type Dispatch, type SetStateAction, useEffect, useState, useMemo } from 'react'; 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({ export function SidebarItems({
members, members,
setOpened, setOpened,
@@ -101,7 +74,6 @@ export function SidebarItems({
}) { }) {
const router = useRouter(); const router = useRouter();
const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState(''); const [asPathWithoutQueryAndAnchor, setAsPathWithoutQueryAndAnchor] = useState('');
const { classes } = useStyles();
const groupItems = useMemo(() => groupMembers(members), [members]); const groupItems = useMemo(() => groupMembers(members), [members]);
useEffect(() => { useEffect(() => {
@@ -109,36 +81,33 @@ export function SidebarItems({
}, [router.asPath]); }, [router.asPath]);
return ( 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)[]) {(Object.keys(groupItems) as (keyof GroupedMembers)[])
.filter((group) => groupItems[group].length) .filter((group) => groupItems[group].length)
.map((group, idx) => ( .map((group, idx) => (
<Section key={idx} title={group} icon={resolveIcon(group)}> <Section key={idx} title={group} icon={resolveIcon(group)}>
{groupItems[group].map((member, index) => ( {groupItems[group].map((member, index) => (
<Link key={index} href={member.path} passHref prefetch={false}> <Link key={index} href={member.path} prefetch={false}>
<NavLink <a
className={classes.link} className={`dark:border-dark-100 border-light-800 ml-5 border-l p-[5px] pl-6 ${
component="a" asPathWithoutQueryAndAnchor === member.path
onClick={() => setOpened((isOpened) => !isOpened)} ? 'bg-blurple text-white'
label={ : 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
<Group> }`}
<Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1"> title={member.name}
{member.name} onClick={() => setOpened(false)}
</Text> >
{member.overloadIndex && member.overloadIndex > 1 ? ( <div className="flex flex-row place-items-center gap-2 text-sm">
<Text size="xs" color="dimmed"> <span className="truncate">{member.name}</span>
{member.overloadIndex} {member.overloadIndex && member.overloadIndex > 1 ? (
</Text> <span className="text-xs">{member.overloadIndex}</span>
) : null} ) : null}
</Group> </div>
} </a>
active={asPathWithoutQueryAndAnchor === member.path}
variant="filled"
/>
</Link> </Link>
))} ))}
</Section> </Section>
))} ))}
</Box> </div>
); );
} }

View File

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

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

View File

@@ -1,31 +1,7 @@
import type { ApiClassJSON, ApiInterfaceJSON } from '@discordjs/api-extractor-utils'; 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 { useMemo } from 'react';
import { VscListSelection, VscSymbolMethod, VscSymbolProperty } from 'react-icons/vsc'; 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({ export function TableOfContentItems({
methods, methods,
properties, properties,
@@ -33,22 +9,19 @@ export function TableOfContentItems({
methods: ApiClassJSON['methods'] | ApiInterfaceJSON['methods']; methods: ApiClassJSON['methods'] | ApiInterfaceJSON['methods'];
properties: ApiClassJSON['properties'] | ApiInterfaceJSON['properties']; properties: ApiClassJSON['properties'] | ApiInterfaceJSON['properties'];
}) { }) {
const { colorScheme } = useMantineColorScheme();
const { classes } = useStyles();
const propertyItems = useMemo( const propertyItems = useMemo(
() => () =>
properties.map((prop) => ( properties.map((prop) => (
<Box<'a'> key={prop.name} href={`#${prop.name}`} component="a" className={classes.link}> <a
<Group> key={prop.name}
<Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1"> href={`#${prop.name}`}
{prop.name} title={prop.name}
</Text> 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"
</Group> >
</Box> <span className="line-clamp-1">{prop.name}</span>
</a>
)), )),
// eslint-disable-next-line react-hooks/exhaustive-deps [properties],
[properties, colorScheme],
); );
const methodItems = useMemo( const methodItems = useMemo(
@@ -59,61 +32,52 @@ export function TableOfContentItems({
}`; }`;
return ( return (
<Box<'a'> key={key} component="a" href={`#${key}`} className={classes.link}> <a
<Group> key={key}
<Text sx={{ textOverflow: 'ellipsis', overflow: 'hidden' }} className="line-clamp-1"> href={`#${key}`}
{member.name} title={member.name}
</Text> 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"
{member.overloadIndex && member.overloadIndex > 1 ? ( >
<Text size="xs" color="dimmed"> <span className="line-clamp-1">{member.name}</span>
{member.overloadIndex} {member.overloadIndex && member.overloadIndex > 1 ? (
</Text> <span className="text-xs">{member.overloadIndex}</span>
) : null} ) : null}
</Group> </a>
</Box>
); );
}), }),
[methods, classes.link], [methods],
); );
return ( return (
<Box sx={{ wordBreak: 'break-all' }} pb="xl"> <div className="flex flex-col break-all p-3 pb-8">
<Group spacing="xs" mt={6} mb="sm" ml={6}> <div className="mt-4 ml-2 flex flex-row gap-2">
<VscListSelection size={25} /> <VscListSelection size={25} />
<Text weight={600}>Table of contents</Text> <span className="font-semibold">Contents</span>
</Group> </div>
<Stack spacing={0} mt={26} ml={4}> <div className="mt-5.5 ml-2 flex flex-col gap-2">
{propertyItems.length ? ( {propertyItems.length ? (
<Box> <div className="flex flex-col">
<Group spacing="xs"> <div className="flex flex-row place-items-center gap-4">
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}> <VscSymbolProperty size={20} />
<VscSymbolProperty size={20} /> <div className="p-3 pl-0">
</ThemeIcon> <span className="font-semibold">Properties</span>
<Box p="sm" pl={0}> </div>
<Text weight={600} size="md"> </div>
Properties
</Text>
</Box>
</Group>
{propertyItems} {propertyItems}
</Box> </div>
) : null} ) : null}
{methodItems.length ? ( {methodItems.length ? (
<Box> <div className="flex flex-col">
<Group spacing="xs"> <div className="flex flex-row place-items-center gap-4">
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}> <VscSymbolMethod size={20} />
<VscSymbolMethod size={20} /> <div className="p-3 pl-0">
</ThemeIcon> <span className="font-semibold">Methods</span>
<Box p="sm" pl={0}> </div>
<Text weight={600} size="md"> </div>
Methods
</Text>
</Box>
</Group>
{methodItems} {methodItems}
</Box> </div>
) : null} ) : null}
</Stack> </div>
</Box> </div>
); );
} }

View File

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

View File

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

View File

@@ -1,33 +1,28 @@
import type { ApiEnumJSON } from '@discordjs/api-extractor-utils'; 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 { VscSymbolEnumMember } from 'react-icons/vsc';
import { useMedia } from 'react-use';
import { CodeListing, CodeListingSeparatorType } from '../CodeListing'; import { CodeListing, CodeListingSeparatorType } from '../CodeListing';
import { DocContainer } from '../DocContainer'; import { DocContainer } from '../DocContainer';
import { Section } from '../Section'; import { Section } from '../Section';
export function Enum({ data }: { data: ApiEnumJSON }) { export function Enum({ data }: { data: ApiEnumJSON }) {
const router = useRouter(); const matches = useMedia('(max-width: 768px)', true);
const matches = useMediaQuery('(max-width: 768px)');
return ( return (
<DocContainer name={data.name} kind={data.kind} excerpt={data.excerpt} summary={data.summary}> <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}>
<Section title="Members" icon={<VscSymbolEnumMember size={20} />} padded dense={matches}> <div className="flex flex-col gap-4">
<Stack> {data.members.map((member) => (
{data.members.map((member) => ( <CodeListing
<CodeListing key={member.name}
key={member.name} name={member.name}
name={member.name} separator={CodeListingSeparatorType.Value}
separator={CodeListingSeparatorType.Value} typeTokens={member.initializerTokens}
typeTokens={member.initializerTokens} summary={member.summary}
summary={member.summary} />
/> ))}
))} </div>
</Stack> </Section>
</Section>
</Skeleton>
</DocContainer> </DocContainer>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,21 @@
import { NextResponse, type NextRequest } from 'next/server'; 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)); return NextResponse.redirect(new URL('/docs/packages', request.url));
} }
export const config = { 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 Head from 'next/head';
import Link from 'next/link'; 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() { export default function FourOhFourPage() {
const { classes } = useStyles();
return ( return (
<> <>
<Head> <Head>
<title key="title">discord.js | 404</title> <title key="title">discord.js | 404</title>
<meta key="og_title" property="og:title" content="discord.js | 404" />
</Head> </Head>
<Container pt={96} pb={96}> <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">
<Box className={classes.label}>404</Box> <h1 className="text-[9rem] font-black leading-none md:text-[12rem]">404</h1>
<Title align="center">Not found.</Title> <h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
<Group position="center"> <Link href="/docs/packages" prefetch={false}>
<Link href="/docs/packages" passHref 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">
<Button component="a" variant="filled" size="md" mt="xl"> Take me back
Take me back </a>
</Button> </Link>
</Link> </div>
</Group>
</Container>
</> </>
); );
} }

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 type { AppProps } from 'next/app';
import Head from 'next/head'; import Head from 'next/head';
import { type NextRouter, useRouter } from 'next/router'; import NextProgress from 'next-progress';
import { VscPackage } from 'react-icons/vsc'; import { ThemeProvider } from 'next-themes';
import { RouterTransition } from '../components/RouterTransition'; import '@unocss/reset/antfu.css';
import '../styles/unocss.css'; import '../styles/unocss.css';
import '../styles/main.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) { 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 ( return (
<> <>
<Head> <Head>
<title key="title">discord.js</title> <title key="title">discord.js</title>
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<meta name="theme-color" content="#5865f2" />
</Head> </Head>
<ColorSchemeProvider colorScheme={colorScheme} toggleColorScheme={toggleColorScheme}> <ThemeProvider
<MantineProvider attribute="class"
theme={{ defaultTheme="system"
fontFamily: 'Inter var', value={{
fontFamilyMonospace: 'JetBrains Mono', light: 'light',
headings: { dark: 'dark',
fontFamily: 'Inter var', }}
}, disableTransitionOnChange
colorScheme, >
colors: { <NextProgress color="#5865f2" options={{ showSpinner: false }} />
blurple: [ <Component {...pageProps} />
'#5865f2', </ThemeProvider>
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
'#5865f2',
],
},
primaryColor: 'blurple',
}}
withCSSVariables
withNormalizeCSS
withGlobalStyles
>
<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 />
<Component {...pageProps} />
</SpotlightProvider>
</MantineProvider>
</ColorSchemeProvider>
</> </>
); );
} }

View File

@@ -1,43 +1,33 @@
import { createGetInitialProps } from '@mantine/next'; import { Html, Head, Main, NextScript } from 'next/document';
import Document, { Html, Head, Main, NextScript } from 'next/document'; import { DESCRIPTION } from '~/util/constants';
const getInitialProps = createGetInitialProps(); export default function Document() {
return (
<Html lang="en">
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#090a16" />
export default class _Document extends Document { <meta name="color-scheme" content="light dark" />
public static override getInitialProps = getInitialProps; <meta name="apple-mobile-web-app-title" content="discord.js" />
<meta name="application-name" content="discord.js" />
public override render() { <meta name="msapplication-TileColor" content="#090a16" />
return ( <meta key="description" name="description" content={DESCRIPTION} />
<Html lang="en"> <meta property="og:site_name" content="discord.js" />
<Head> <meta property="og:type" content="website" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" /> <meta key="og_title" property="og:title" content="discord.js" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> <meta key="og_description" name="og:description" content={DESCRIPTION} />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> <meta property="og:image" content="https://discordjs.dev/open-graph.png" />
<link rel="manifest" href="/site.webmanifest" /> <meta name="twitter:card" content="summary_large_image" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#090a16" /> <meta name="twitter:creator" content="@iCrawlToGo" />
</Head>
<meta name="apple-mobile-web-app-title" content="discord.js" /> <body className="dark:bg-dark-800 bg-white">
<meta name="application-name" content="discord.js" /> <Main />
<meta name="msapplication-TileColor" content="#090a16" /> <NextScript />
<meta name="theme-color" content="#1a1b1e" /> </body>
<meta </Html>
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 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:image" content="https://discordjs.dev/open-graph.png" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
} }

View File

@@ -13,22 +13,23 @@ import {
type ApiEnumJSON, type ApiEnumJSON,
} from '@discordjs/api-extractor-utils'; } from '@discordjs/api-extractor-utils';
import { createApiModel } from '@discordjs/scripts'; 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 { ApiFunction, ApiItemKind, type ApiPackage } from '@microsoft/api-extractor-model';
import Head from 'next/head'; import Head from 'next/head';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { GetStaticPaths, GetStaticProps } from 'next/types'; import type { GetStaticPaths, GetStaticProps } from 'next/types';
import { MDXRemote } from 'next-mdx-remote'; import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize'; import { serialize } from 'next-mdx-remote/serialize';
import { useEffect } from 'react'; import { useMemo } from 'react';
import { VscChevronUp } from 'react-icons/vsc';
import rehypeIgnore from 'rehype-ignore'; 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 rehypeRaw from 'rehype-raw';
import rehypeSlug from 'rehype-slug'; import rehypeSlug from 'rehype-slug';
import remarkGfm from 'remark-gfm'; 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 { SidebarLayout, type SidebarLayoutProps } from '~/components/SidebarLayout';
import { Class } from '~/components/model/Class'; import { Class } from '~/components/model/Class';
import { Enum } from '~/components/model/Enum'; import { Enum } from '~/components/model/Enum';
@@ -37,9 +38,9 @@ import { Interface } from '~/components/model/Interface';
import { TypeAlias } from '~/components/model/TypeAlias'; import { TypeAlias } from '~/components/model/TypeAlias';
import { Variable } from '~/components/model/Variable'; import { Variable } from '~/components/model/Variable';
import { MemberProvider } from '~/contexts/member'; import { MemberProvider } from '~/contexts/member';
import { PACKAGES } from '~/util/constants';
import { findMember, findMemberByKey } from '~/util/model.server'; 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 () => { export const getStaticPaths: GetStaticPaths = async () => {
const pkgs = ( const pkgs = (
@@ -54,6 +55,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
} else { } else {
const response = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`); const response = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`);
versions = await response.json(); versions = await response.json();
versions = versions.slice(-2);
for (const version of versions) { for (const version of versions) {
const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${version}.api.json`); 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 }) => { 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(':') ?? []; const [memberName, overloadIndex] = member?.split(':') ?? [];
@@ -134,7 +142,30 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
mdxOptions: { mdxOptions: {
remarkPlugins: [remarkGfm], remarkPlugins: [remarkGfm],
remarkRehypeOptions: { allowDangerousHtml: true }, 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', format: 'md',
}, },
}); });
@@ -186,7 +217,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
props: { props: {
error: error_, error: error_,
}, },
revalidate: 3_600, revalidate: 1,
}; };
} }
}; };
@@ -206,46 +237,30 @@ const member = (props?: ApiItemJSON | undefined) => {
case 'Enum': case 'Enum':
return <Enum data={props as ApiEnumJSON} />; return <Enum data={props as ApiEnumJSON} />;
default: 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 }>) { export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: string }>) {
const router = useRouter(); const router = useRouter();
const [scroll, scrollTo] = useWindowScroll(); const name = useMemo(
const matches = useMediaQuery('(max-width: 1200px)'); () => `discord.js${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`,
[props.data?.member?.name],
useEffect(() => { );
if (props.data?.searchIndex) { const ogTitle = useMemo(
const searchIndex = props.data?.searchIndex.map((idx, index) => ({ id: index, ...idx })) ?? []; () => `${props.packageName ?? 'discord.js'}${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`,
miniSearch.addAll(searchIndex); [props.packageName, props.data?.member?.name],
);
registerSpotlightActions(
searchIndex.map((idx) => ({
title: idx.name,
description: idx.summary ?? '',
onTrigger: () => void router.push(idx.path),
})),
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const name = `discord.js${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`;
if (router.isFallback) { if (router.isFallback) {
return ( return null;
<SidebarLayout>
<LoadingOverlay visible overlayBlur={2} />
</SidebarLayout>
);
} }
// Just in case // Just in case
// return <iframe src="https://discord.js.org" style={{ border: 0, height: '100%', width: '100%' }}></iframe>; // return <iframe src="https://discord.js.org" style={{ border: 0, height: '100%', width: '100%' }}></iframe>;
return props.error ? ( 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}> <MemberProvider member={props.data?.member}>
<SidebarLayout {...props}> <SidebarLayout {...props}>
@@ -253,50 +268,20 @@ export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: s
<> <>
<Head> <Head>
<title key="title">{name}</title> <title key="title">{name}</title>
<meta key="og_title" property="og:title" content={ogTitle} />
</Head> </Head>
{member(props.data.member)} {member(props.data.member)}
</> </>
) : props.data?.source ? ( ) : props.data?.source ? (
<Box <div className="prose max-w-none">
sx={(theme) => ({
a: {
backgroundColor: 'transparent',
color: theme.colors.blurple![0],
textDecoration: 'none',
},
img: {
borderStyle: 'none',
maxWidth: '100%',
boxSizing: 'content-box',
},
})}
px="xl"
>
<MDXRemote {...props.data.source} /> <MDXRemote {...props.data.source} />
</Box> </div>
) : null} ) : 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> </SidebarLayout>
</MemberProvider> </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 Link from 'next/link';
import { useRouter } from 'next/router';
import type { GetStaticPaths, GetStaticProps } from 'next/types'; import type { GetStaticPaths, GetStaticProps } from 'next/types';
import { VscArrowLeft, VscArrowRight, VscVersions } from 'react-icons/vsc'; import { VscArrowLeft, VscArrowRight, VscVersions } from 'react-icons/vsc';
import { PACKAGES } from '~/util/packages'; import { PACKAGES } from '~/util/constants';
interface VersionProps { interface VersionProps {
data: { data: {
@@ -34,20 +20,21 @@ export const getStaticPaths: GetStaticPaths = () => {
}; };
export const getStaticProps: GetStaticProps = async ({ params }) => { 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 { 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(); const data: string[] = await res.json();
if (!data.length) { if (!data.length) {
console.error('No tags');
return { return {
props: { notFound: true,
error: 'No tags',
},
revalidate: 3_600,
}; };
} }
@@ -55,7 +42,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
props: { props: {
packageName, packageName,
data: { data: {
versions: data, versions: data.reverse(),
}, },
}, },
revalidate: 3_600, revalidate: 3_600,
@@ -68,66 +55,39 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
props: { props: {
error: error_, 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 }) { export default function VersionsRoute(props: Partial<VersionProps> & { error?: string }) {
const router = useRouter();
const { classes } = useStyles();
const { colorScheme } = useMantineColorScheme();
return props.error ? ( 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"> <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">
<Stack sx={{ flexGrow: 1 }}> <div className="flex grow flex-col place-content-center gap-4">
<Title order={2} ml="xs"> <h1 className="text-2xl font-semibold">Select a version:</h1>
Select a version:
</Title>
{props.data?.versions.map((version) => ( {props.data?.versions.map((version) => (
<Link key={version} href={`/docs/packages/${props.packageName!}/${version}`} passHref prefetch={false}> <Link key={version} href={`/docs/packages/${props.packageName}/${version}`} prefetch={false}>
<UnstyledButton className={classes.control} component="a"> <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">
<Group position="apart"> <div className="flex flex-row place-content-between place-items-center gap-4">
<Group> <div className="flex flex-row place-content-between place-items-center gap-4">
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}> <VscVersions size={25} />
<VscVersions size={20} /> <h2 className="font-semibold">{version}</h2>
</ThemeIcon> </div>
<Text weight={600} size="md">
{version}
</Text>
</Group>
<VscArrowRight size={20} /> <VscArrowRight size={20} />
</Group> </div>
</UnstyledButton> </a>
</Link> </Link>
)) ?? null} )) ?? null}
</Stack> <Link href="/docs/packages" prefetch={false}>
<Affix position={{ top: 20, left: 20 }}> <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">
<UnstyledButton onClick={() => void router.push('/docs/packages')}> <VscArrowLeft size={20} /> Go back
<VscArrowLeft size={25} /> </a>
</UnstyledButton> </Link>
</Affix> </div>
</Container> </div>
); );
} }

View File

@@ -1,95 +1,98 @@
import { import { Button } from 'ariakit/button';
Container,
UnstyledButton,
createStyles,
Group,
ThemeIcon,
Text,
Stack,
Title,
useMantineColorScheme,
Button,
} from '@mantine/core';
import Link from 'next/link'; import Link from 'next/link';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { MouseEvent } from 'react'; import type { GetStaticProps } from 'next/types';
import { VscArrowRight, VscPackage } from 'react-icons/vsc'; import { useCallback, type MouseEvent } from 'react';
import { PACKAGES } from '~/util/packages'; import { VscArrowLeft, VscArrowRight, VscPackage } from 'react-icons/vsc';
import { PACKAGES } from '~/util/constants';
const useStyles = createStyles((theme) => ({ interface PackageProps {
outer: { data: {
display: 'flex', versions: { packageName: string; version: string }[];
height: '100%', };
alignItems: 'center', }
},
control: { export const getStaticProps: GetStaticProps = async () => {
padding: theme.spacing.xs, try {
color: theme.colorScheme === 'dark' ? theme.colors.dark![0] : theme.black, const versions = await Promise.all(
borderRadius: theme.radius.xs, 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 };
}),
);
'&:hover': { return {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark![6] : theme.colors.gray![0], props: {
color: theme.colorScheme === 'dark' ? theme.white : theme.black, versions,
}, },
}, revalidate: 3_600,
})); };
} catch (error_) {
const error = error_ as Error;
console.error(error);
export default function PackagesRoute() { return {
const { classes } = useStyles(); props: {
const { colorScheme } = useMantineColorScheme(); error: error_,
},
revalidate: 1,
};
}
};
export default function PackagesRoute(props: Partial<PackageProps> & { error?: string }) {
const router = useRouter(); 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) => { const handleClick = async (ev: MouseEvent<HTMLDivElement>, packageName: string) => {
ev.stopPropagation(); ev.stopPropagation();
void router.push(`/docs/packages/${packageName}`);
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}`);
}; };
return ( return props.error ? (
<Container className={classes.outer} size="xs"> <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">
<Stack sx={{ flexGrow: 1 }}> {props.error}
<Title order={2} ml="xs"> </div>
Select a package: ) : (
</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 package:</h1>
{PACKAGES.map((pkg) => ( {PACKAGES.map((pkg) => (
<UnstyledButton <Link key={pkg} href={`/docs/packages/${pkg}/${findLatestVersion(pkg)?.version ?? 'main'}`} prefetch={false}>
component="div" <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">
key={pkg} <div className="flex grow flex-row place-content-between place-items-center gap-4">
role="link" <div className="flex grow flex-row place-content-between place-items-center gap-4">
className={classes.control} <div className="flex flex-row place-content-between place-items-center gap-4">
onClick={(ev: MouseEvent<HTMLDivElement>) => void handleClick(ev, pkg)} <VscPackage size={25} />
> <h2 className="font-semibold">{pkg}</h2>
<Group position="apart"> </div>
<Group sx={{ flexGrow: 1 }} position="apart"> <Link href={`/docs/packages/${pkg}`} prefetch={false}>
<Group> <Button
<ThemeIcon variant={colorScheme === 'dark' ? 'filled' : 'outline'} radius="sm" size={30}> as="div"
<VscPackage size={20} /> role="link"
</ThemeIcon> 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"
<Text weight={600} size="md"> onClick={async (ev: MouseEvent<HTMLDivElement>) => handleClick(ev, pkg)}
{pkg} >
</Text> Select version
</Group> </Button>
<Link href={`/docs/packages/${pkg}`} passHref prefetch={false}> </Link>
<Button </div>
component="a" <VscArrowRight size={20} />
size="xs" </div>
compact </a>
onClick={(ev: MouseEvent<HTMLAnchorElement>) => ev.stopPropagation()} </Link>
>
Select version
</Button>
</Link>
</Group>
<VscArrowRight size={20} />
</Group>
</UnstyledButton>
))} ))}
</Stack> <Link href="/" prefetch={false}>
</Container> <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 Image from 'next/future/image';
import Link from 'next/link'; import Link from 'next/link';
import { FiExternalLink } from 'react-icons/fi'; import { FiExternalLink } from 'react-icons/fi';
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'; import vercelLogo from '../assets/powered-by-vercel.svg';
import { vscDarkPlus, ghcolors } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import { SyntaxHighlighter } from '~/components/SyntaxHighlighter';
import { CODE_EXAMPLE } from '~/util/constants';
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',
},
}));
export default function IndexRoute() { export default function IndexRoute() {
const { classes } = useStyles();
const { colorScheme } = useMantineColorScheme();
return ( return (
<Container className={classes.outer} size="lg"> <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">
<Box className={classes.inner}> <div className="flex flex-col place-items-center gap-10 lg:flex-row lg:gap-6">
<Box className={classes.content}> <div className="flex max-w-lg flex-col gap-3 lg:mr-8">
<Title className={classes.title}> <h1 className="text-3xl font-black leading-tight sm:text-5xl sm:leading-tight">
The <span className={classes.highlight}>most popular</span> way to build Discord <br /> bots. The <span className="bg-blurple relative rounded py-1 px-3 text-white">most popular</span> way to build
</Title> Discord <br /> bots.
<Text color="dimmed" mt="md"> </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 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 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. significantly tidier and easier to comprehend.
</Text> </p>
<div className="flex flex-row gap-4">
<Group mt={30}> <Link href="/docs" prefetch={false}>
<Link href="/docs" passHref 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">
<Button component="a" radius="sm" size="md">
Docs Docs
</Button> </a>
</Link> </Link>
<Link href="https://discordjs.guide" passHref prefetch={false}> <a
<Button component="a" variant="default" radius="sm" size="md" rightIcon={<FiExternalLink />}> 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"
Guide href="https://discordjs.guide"
</Button> target="_blank"
</Link> rel="noopener noreferrer"
</Group> >
</Box> Guide <FiExternalLink />
<Box pb="xs"> </a>
<SyntaxHighlighter </div>
wrapLongLines </div>
language="typescript" <SyntaxHighlighter code={CODE_EXAMPLE} />
style={colorScheme === 'dark' ? vscDarkPlus : ghcolors} </div>
codeTagProps={{ style: { fontFamily: 'JetBrains Mono' } }} <div className="flex place-content-center">
> <a
{`import { Client, GatewayIntentBits } from 'discord.js'; href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
target="_blank"
const client = new Client({ intents: [GatewayIntentBits.Guilds] }); rel="noopener noreferrer"
title="Vercel"
client.on('ready', () => { >
console.log(\`Logged in as \${client.user.tag}!\`); <Image src={vercelLogo} alt="Vercel" />
}); </a>
</div>
client.on('interactionCreate', async (interaction) => { </div>
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>
); );
} }

View File

@@ -1,29 +1,43 @@
@import url('https://rsms.me/inter/inter.css'); @import url('https://rsms.me/inter/inter.css');
: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) { @supports (font-variation-settings: normal) {
html { :root {
font-family: 'Inter var', sans-serif; font-family: 'Inter var', Arial, Noto Sans, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', Segoe UI Symbol,
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; 'Noto Color Emoji';
} }
} }
html, html,
body { body {
height: 100vh; height: 100%;
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';
} }
#__next { #__next {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 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 { pre {
font-size: 13px !important; font-size: 13px !important;
white-space: pre; white-space: pre;
@@ -35,10 +49,9 @@ pre {
padding: 1em; padding: 1em;
margin: 0.5em 0; margin: 0.5em 0;
overflow: auto; overflow: auto;
background: #1e1e1e;
border-radius: 4px; border-radius: 4px;
} }
code { 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, presetTypography, presetUno, presetWebFonts } from 'unocss';
import { defineConfig, presetUno } from 'unocss';
export default defineConfig({ export default defineConfig({
theme: { theme: {
colors: { 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: [ presets: [
@@ -12,7 +22,43 @@ export default defineConfig({
presetWebFonts({ presetWebFonts({
provider: 'google', provider: 'google',
fonts: { 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:^", "@discordjs/docgen": "workspace:^",
"@favware/cliff-jumper": "^1.8.7", "@favware/cliff-jumper": "^1.8.7",
"@microsoft/api-extractor": "^7.30.0", "@microsoft/api-extractor": "^7.30.0",
"@types/node": "^16.11.56", "@types/node": "^16.11.57",
"@vitest/coverage-c8": "^0.23.0", "@vitest/coverage-c8": "^0.23.1",
"downlevel-dts": "^0.10.1", "downlevel-dts": "^0.10.1",
"eslint": "^8.23.0", "eslint": "^8.23.0",
"eslint-config-neon": "^0.1.33", "eslint-config-neon": "^0.1.33",
@@ -75,7 +75,7 @@
"tsup": "^6.2.3", "tsup": "^6.2.3",
"typescript": "^4.8.2", "typescript": "^4.8.2",
"undici": "^5.10.0", "undici": "^5.10.0",
"vitest": "^0.23.0", "vitest": "^0.23.1",
"zlib-sync": "^0.1.7" "zlib-sync": "^0.1.7"
}, },
"engines": { "engines": {

1810
yarn.lock

File diff suppressed because it is too large Load Diff