mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
refactor: docs (#10126)
This commit is contained in:
@@ -47,8 +47,8 @@
|
||||
"@code-hike/mdx": "^0.9.0",
|
||||
"@discordjs/ui": "workspace:^",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@vercel/analytics": "^1.1.3",
|
||||
"@vercel/edge-config": "^0.4.1",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@vercel/edge-config": "^1.1.0",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"ariakit": "2.0.0-next.44",
|
||||
"cmdk": "^0.2.1",
|
||||
@@ -70,28 +70,28 @@
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/html-escaper": "^3.0.2",
|
||||
"@types/node": "18.18.8",
|
||||
"@types/react": "^18.2.54",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@unocss/eslint-plugin": "^0.58.5",
|
||||
"@unocss/postcss": "^0.58.5",
|
||||
"@unocss/reset": "^0.58.5",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^1.2.2",
|
||||
"@vitest/coverage-v8": "^1.3.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-neon": "^0.1.58",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.59",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"happy-dom": "^13.3.8",
|
||||
"happy-dom": "^13.6.2",
|
||||
"hast-util-to-string": "^2.0.0",
|
||||
"hastscript": "^8.0.0",
|
||||
"html-escaper": "^3.0.3",
|
||||
"postcss": "^8.4.34",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^3.2.5",
|
||||
"turbo": "^1.12.2",
|
||||
"turbo": "^1.12.4",
|
||||
"typescript": "^5.3.3",
|
||||
"unocss": "^0.58.5",
|
||||
"vercel": "^33.4.1",
|
||||
"vitest": "^1.2.2"
|
||||
"vercel": "^33.5.3",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
NEXT_PUBLIC_LOCAL_DEV=true
|
||||
METADATA_BASE_URL=http://localhost:3000
|
||||
|
||||
2
apps/website/.gitignore
vendored
2
apps/website/.gitignore
vendored
@@ -28,3 +28,5 @@ src/styles/unocss.css
|
||||
lighthouse-results
|
||||
|
||||
.vercel
|
||||
|
||||
old_src
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = require('../../.prettierrc.json');
|
||||
module.exports = {
|
||||
...require('../../.prettierrc.json'),
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import bundleAnalyzer from '@next/bundle-analyzer';
|
||||
import localesPlugin from '@react-aria/optimize-locales-plugin';
|
||||
|
||||
const withBundleAnalyzer = bundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
@@ -6,18 +7,26 @@ const withBundleAnalyzer = bundleAnalyzer({
|
||||
|
||||
export default withBundleAnalyzer({
|
||||
reactStrictMode: true,
|
||||
experimental: {
|
||||
typedRoutes: true,
|
||||
serverComponentsExternalPackages: ['@rushstack/node-core-library', '@discordjs/api-extractor-model', 'jju'],
|
||||
},
|
||||
images: {
|
||||
dangerouslyAllowSVG: true,
|
||||
contentDispositionType: 'attachment',
|
||||
contentSecurityPolicy: "default-src 'self'; frame-src 'none'; sandbox;",
|
||||
},
|
||||
poweredByHeader: false,
|
||||
env: {
|
||||
MAX_FETCH_SIZE: '5',
|
||||
logging: {
|
||||
fetches: {
|
||||
fullUrl: true,
|
||||
},
|
||||
},
|
||||
experimental: {
|
||||
ppr: false,
|
||||
},
|
||||
webpack(config, { isServer }) {
|
||||
if (!isServer) {
|
||||
// Don't include any locale strings in the client JS bundle.
|
||||
config.plugins.push(localesPlugin.webpack({ locales: [] }));
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
async redirects() {
|
||||
return [
|
||||
|
||||
@@ -46,58 +46,60 @@
|
||||
},
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@discordjs/api-extractor-model": "workspace:^",
|
||||
"@discordjs/api-extractor-utils": "workspace:^",
|
||||
"@discordjs/scripts": "workspace:^",
|
||||
"@discordjs/ui": "workspace:^",
|
||||
"@microsoft/tsdoc": "^0.14.2",
|
||||
"@microsoft/tsdoc-config": "0.16.2",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
"@react-icons/all-files": "^4.1.0",
|
||||
"@vercel/analytics": "^1.1.3",
|
||||
"@vercel/edge-config": "^0.4.1",
|
||||
"@vercel/analytics": "^1.2.2",
|
||||
"@vercel/blob": "^0.22.1",
|
||||
"@vercel/edge-config": "^1.1.0",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"@vercel/postgres": "^0.7.2",
|
||||
"ariakit": "2.0.0-next.44",
|
||||
"bright": "^0.8.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"cmdk": "^0.2.1",
|
||||
"geist": "^1.2.2",
|
||||
"jotai": "^2.7.0",
|
||||
"lucide-react": "^0.343.0",
|
||||
"meilisearch": "^0.37.0",
|
||||
"next": "14.1.0",
|
||||
"next": "14.1.1-canary.80",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"next-themes": "^0.2.1",
|
||||
"overlayscrollbars": "^2.5.0",
|
||||
"overlayscrollbars-react": "^0.5.4",
|
||||
"react": "^18.2.0",
|
||||
"react-custom-scrollbars-2": "^4.5.0",
|
||||
"react-aria-components": "^1.1.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-use": "^17.5.0",
|
||||
"rehype-slug": "^5.1.0",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"sharp": "^0.33.2",
|
||||
"swr": "^2.2.4"
|
||||
"usehooks-ts": "^2.15.1",
|
||||
"vaul": "^0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "14.1.0",
|
||||
"@next/bundle-analyzer": "14.1.1-canary.80",
|
||||
"@react-aria/optimize-locales-plugin": "^1.0.2",
|
||||
"@shikijs/rehype": "1.1.7",
|
||||
"@tailwindcss/typography": "^0.5.10",
|
||||
"@testing-library/react": "^14.2.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/node": "18.18.8",
|
||||
"@types/react": "^18.2.54",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@unocss/eslint-plugin": "^0.58.5",
|
||||
"@unocss/postcss": "^0.58.5",
|
||||
"@unocss/reset": "^0.58.5",
|
||||
"@types/react": "^18.2.60",
|
||||
"@types/react-dom": "^18.2.19",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"@vitest/coverage-v8": "^1.2.2",
|
||||
"@vitest/coverage-v8": "^1.3.1",
|
||||
"autoprefixer": "^10.4.17",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-neon": "^0.1.58",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-neon": "^0.1.59",
|
||||
"eslint-formatter-pretty": "^6.0.1",
|
||||
"happy-dom": "^13.3.8",
|
||||
"postcss": "^8.4.34",
|
||||
"happy-dom": "^13.6.2",
|
||||
"postcss": "^8.4.35",
|
||||
"prettier": "^3.2.5",
|
||||
"turbo": "^1.12.2",
|
||||
"prettier-plugin-tailwindcss": "^0.5.11",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-rehype": "^11.1.0",
|
||||
"shiki": "1.1.7",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"turbo": "^1.12.4",
|
||||
"typescript": "^5.3.3",
|
||||
"vercel": "^33.4.1",
|
||||
"vitest": "^1.2.2"
|
||||
"vercel": "^33.5.3",
|
||||
"vitest": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@unocss/postcss': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Analytics } from '@vercel/analytics/react';
|
||||
import { inter } from '~/util/fonts';
|
||||
import { Providers } from './providers';
|
||||
|
||||
import '~/styles/cmdk.css';
|
||||
import '~/styles/main.css';
|
||||
|
||||
export default function GlobalError({ error }: { readonly error: Error }) {
|
||||
console.error(error);
|
||||
|
||||
return (
|
||||
<html className={inter.variable} lang="en" suppressHydrationWarning>
|
||||
<body className="bg-light-600 dark:bg-dark-600 dark:text-light-900">
|
||||
<Providers>
|
||||
<main className="mx-auto max-w-2xl min-h-screen">
|
||||
<div className="mx-auto max-w-lg min-h-screen flex flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">500</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Error.</h2>
|
||||
</div>
|
||||
</main>
|
||||
</Providers>
|
||||
<Analytics />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { fetchVersions } from '~/app/docAPI';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const packageName = req.nextUrl.pathname.split('/').slice(2, 3)[0] ?? 'discord.js';
|
||||
return NextResponse.json(await fetchVersions(packageName));
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
|
||||
import type { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { ImageResponse } from '@vercel/og';
|
||||
import type { NextRequest } from 'next/server';
|
||||
import { resolvePackageName } from '~/util/resolvePackageName';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const fonts = Promise.all([
|
||||
fetch(new URL('../../../assets/fonts/Inter-Regular.ttf', import.meta.url)).then(async (res) => res.arrayBuffer()),
|
||||
fetch(new URL('../../../assets/fonts/Inter-Bold.ttf', import.meta.url)).then(async (res) => res.arrayBuffer()),
|
||||
]);
|
||||
|
||||
function resolveIcon(icon: keyof typeof ApiItemKind, size = 88) {
|
||||
switch (icon) {
|
||||
case 'Class':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.34 9.71h.71l2.67-2.67v-.71L13.38 5h-.7l-1.82 1.81h-5V5.56l1.86-1.85V3l-2-2H5L1 5v.71l2 2h.71l1.14-1.15v5.79l.5.5H10v.52l1.33 1.34h.71l2.67-2.67v-.71L13.37 10h-.7l-1.86 1.85h-5v-4H10v.48l1.34 1.38zm1.69-3.65l.63.63-2 2-.63-.63 2-2zm0 5l.63.63-2 2-.63-.63 2-2zM3.35 6.65l-1.29-1.3 3.29-3.29 1.3 1.29-3.3 3.3z" />
|
||||
</svg>
|
||||
);
|
||||
case 'Enum':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M14 2H8L7 3v3h1V3h6v5h-4v1h4l1-1V3l-1-1zM9 6h4v1H9.41L9 6.59V6zM7 7H2L1 8v5l1 1h6l1-1V8L8 7H7zm1 6H2V8h6v5zM3 9h4v1H3V9zm0 2h4v1H3v-1zm6-7h4v1H9V4z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case 'EnumMember':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M7 3l1-1h6l1 1v5l-1 1h-4V8h4V3H8v3H7V3zm2 6V8L8 7H2L1 8v5l1 1h6l1-1V9zM8 8v5H2V8h6zm1.414-1L9 6.586V6h4v1H9.414zM9 4h4v1H9V4zm-2 6H3v1h4v-1z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case 'Interface':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.496 4a3.49 3.49 0 0 0-3.46 3h-3.1a2 2 0 1 0 0 1h3.1a3.5 3.5 0 1 0 3.46-4zm0 6a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5z" />
|
||||
</svg>
|
||||
);
|
||||
case 'TypeAlias':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14.45 4.5l-5-2.5h-.9l-7 3.5-.55.89v4.5l.55.9 5 2.5h.9l7-3.5.55-.9v-4.5l-.55-.89zm-8 8.64l-4.5-2.25V7.17l4.5 2v3.97zm.5-4.8L2.29 6.23l6.66-3.34 4.67 2.34-6.67 3.11zm7 1.55l-6.5 3.25V9.21l6.5-3v3.68z" />
|
||||
</svg>
|
||||
);
|
||||
case 'Variable':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
clipRule="evenodd"
|
||||
d="M2 5h2V4H1.5l-.5.5v8l.5.5H4v-1H2V5zm12.5-1H12v1h2v7h-2v1h2.5l.5-.5v-8l-.5-.5zm-2.74 2.57L12 7v2.51l-.3.45-4.5 2h-.46l-2.5-1.5-.24-.43v-2.5l.3-.46 4.5-2h.46l2.5 1.5zM5 9.71l1.5.9V9.28L5 8.38v1.33zm.58-2.15l1.45.87 3.39-1.5-1.45-.87-3.39 1.5zm1.95 3.17l3.5-1.56v-1.4l-3.5 1.55v1.41z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
case 'Property':
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.807 14.975a1.75 1.75 0 0 1-1.255-.556 1.684 1.684 0 0 1-.544-1.1A1.72 1.72 0 0 1 1.36 12.1c1.208-1.27 3.587-3.65 5.318-5.345a4.257 4.257 0 0 1 .048-3.078 4.095 4.095 0 0 1 1.665-1.969 4.259 4.259 0 0 1 4.04-.36l.617.268-2.866 2.951 1.255 1.259 2.944-2.877.267.619a4.295 4.295 0 0 1 .04 3.311 4.198 4.198 0 0 1-.923 1.392 4.27 4.27 0 0 1-.743.581 4.217 4.217 0 0 1-3.812.446c-1.098 1.112-3.84 3.872-5.32 5.254a1.63 1.63 0 0 1-1.084.423zm7.938-13.047a3.32 3.32 0 0 0-1.849.557c-.213.13-.412.284-.591.458a3.321 3.321 0 0 0-.657 3.733l.135.297-.233.227c-1.738 1.697-4.269 4.22-5.485 5.504a.805.805 0 0 0 .132 1.05.911.911 0 0 0 .298.22c.1.044.209.069.319.072a.694.694 0 0 0 .45-.181c1.573-1.469 4.612-4.539 5.504-5.44l.23-.232.294.135a3.286 3.286 0 0 0 3.225-.254 3.33 3.33 0 0 0 .591-.464 3.28 3.28 0 0 0 .964-2.358c0-.215-.021-.43-.064-.642L11.43 7.125 8.879 4.578l2.515-2.59a3.286 3.286 0 0 0-.65-.06z" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<svg fill="white" height={size} viewBox="0 0 16 16" width={size} xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13.51 4l-5-3h-1l-5 3-.49.86v6l.49.85 5 3h1l5-3 .49-.85v-6L13.51 4zm-6 9.56l-4.5-2.7V5.7l4.5 2.45v5.41zM3.27 4.7l4.74-2.84 4.74 2.84-4.74 2.59L3.27 4.7zm9.74 6.16l-4.5 2.7V8.15l4.5-2.45v5.16z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const fontData = await fonts;
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
|
||||
const hasPkg = searchParams.has('pkg');
|
||||
const hasKind = searchParams.has('kind');
|
||||
const hasName = searchParams.has('name');
|
||||
const hasMethods = searchParams.has('methods');
|
||||
const hasProps = searchParams.has('props');
|
||||
const hasMembers = searchParams.has('members');
|
||||
const pkg = hasPkg ? resolvePackageName(searchParams.get('pkg')!) : '';
|
||||
const kind = hasKind ? searchParams.get('kind')! : 'Method';
|
||||
const name = hasName ? searchParams.get('name')!.slice(0, 100) : 'My default name which is super long to overflow';
|
||||
const methods = hasMethods ? searchParams.get('methods') : '';
|
||||
const props = hasProps ? searchParams.get('props') : '';
|
||||
const members = hasMembers ? searchParams.get('members') : '';
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'Inter',
|
||||
}}
|
||||
tw="flex flex-row bg-[#181818] h-full w-full p-24"
|
||||
>
|
||||
<div tw="flex flex-col mx-auto h-full text-white">
|
||||
<div tw="flex flex-row text-4xl text-gray-400">{pkg}</div>
|
||||
<div tw="flex flex-col justify-between h-full w-full pt-14">
|
||||
<div tw="flex flex-row items-center max-w-full">
|
||||
<span tw="mr-6">{resolveIcon(kind as keyof typeof ApiItemKind)}</span>
|
||||
<h2
|
||||
style={{
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
tw="text-[5.5rem] font-bold w-full"
|
||||
>
|
||||
{name}
|
||||
</h2>
|
||||
</div>
|
||||
<div tw="flex flex-row w-full justify-between">
|
||||
<div tw="flex flex-row">
|
||||
{props ? (
|
||||
<div tw="flex flex-row mr-12">
|
||||
<span tw="mr-4">{resolveIcon('Property', 36)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{props}</span>
|
||||
<span>Properties</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{methods ? (
|
||||
<div tw="flex flex-row mr-12">
|
||||
<span tw="mr-4">{resolveIcon('Method', 36)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{methods}</span>
|
||||
<span>Methods</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{members ? (
|
||||
<div tw="flex flex-row">
|
||||
<span tw="mr-4">{resolveIcon('EnumMember', 36)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{members}</span>
|
||||
<span>Members</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div tw="flex h-full items-end">
|
||||
<span tw="bg-[#5865f2] text-4xl font-black relative rounded-lg py-4 px-8">discord.js</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1_200,
|
||||
height: 630,
|
||||
fonts: [
|
||||
{ name: 'Inter', data: fontData[0], weight: 500, style: 'normal' },
|
||||
{ name: 'Inter', data: fontData[1], weight: 700, style: 'normal' },
|
||||
],
|
||||
debug: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
|
||||
import { ImageResponse } from '@vercel/og';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const fonts = fetch(new URL('../../../assets/fonts/Inter-Black.ttf', import.meta.url)).then(async (res) =>
|
||||
res.arrayBuffer(),
|
||||
);
|
||||
|
||||
export async function GET() {
|
||||
const fontData = await fonts;
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'Inter',
|
||||
}}
|
||||
tw="flex flex-row bg-[#181818] h-full w-full"
|
||||
>
|
||||
<div tw="mx-auto flex flex-row items-center h-full">
|
||||
<div tw="flex flex-row">
|
||||
<div tw="flex flex-row">
|
||||
<div tw="flex flex-col font-black text-[5.5rem] text-white">
|
||||
<div tw="flex flex-row">
|
||||
The <span tw="bg-[#5865f2] rounded-lg py-1 px-6 ml-4">most popular</span>
|
||||
</div>
|
||||
<span>way to build Discord</span>
|
||||
<span>bots.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 1_200,
|
||||
height: 630,
|
||||
fonts: [{ name: 'Inter', data: fontData, weight: 900, style: 'normal' }],
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { sql } from '@vercel/postgres';
|
||||
|
||||
export const fetchVersions = async (packageName: string): Promise<string[]> => {
|
||||
if (process.env.NEXT_PUBLIC_LOCAL_DEV === 'true' || process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') {
|
||||
return ['main'];
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows } = await sql`select version from documentation where name = ${packageName} order by version desc`;
|
||||
|
||||
return rows.map((row) => row.version);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchModelJSON = async (packageName: string, version: string) => {
|
||||
if (process.env.NEXT_PUBLIC_LOCAL_DEV === 'true') {
|
||||
try {
|
||||
const res = await readFile(
|
||||
join(process.cwd(), '..', '..', 'packages', packageName, 'docs', 'docs.api.json'),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return JSON.parse(res);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') {
|
||||
try {
|
||||
const { rows } = await sql`select url from documentation where name = ${packageName} and version = ${'main'}`;
|
||||
const res = await fetch(rows[0]?.url ?? '');
|
||||
|
||||
return await res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows } = await sql`select url from documentation where name = ${packageName} and version = ${version}`;
|
||||
const res = await fetch(rows[0]?.url ?? '');
|
||||
|
||||
return await res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,98 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
import { ImageResponse } from 'next/og';
|
||||
import { resolveKind } from '~/util/resolveNodeKind';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export const size = {
|
||||
width: 1_200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default async function Image({
|
||||
params,
|
||||
}: {
|
||||
readonly params: { readonly item: string; readonly packageName: string; readonly version: string };
|
||||
}) {
|
||||
const normalizeItem = params.item.split(encodeURIComponent(':')).join('.').toLowerCase();
|
||||
|
||||
const isMainVersion = params.version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${params.packageName}/${params.version}.${normalizeItem}.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
);
|
||||
const node = await fileContent.json();
|
||||
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div tw="flex bg-[#121212] h-full w-full p-14">
|
||||
<div tw="flex flex-col mx-auto h-full text-white">
|
||||
<div tw="flex text-4xl text-gray-400">{params.packageName}</div>
|
||||
<div tw="flex flex-col justify-between h-full w-full pt-14">
|
||||
<div tw="flex items-center max-w-full">
|
||||
<span tw="mr-6">{resolveKind(node.kind, 94)}</span>
|
||||
<h2
|
||||
style={{
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
tw="text-[5.5rem] font-bold w-full"
|
||||
>
|
||||
{node.displayName}
|
||||
</h2>
|
||||
</div>
|
||||
<div tw="flex flex-row w-full justify-between">
|
||||
<div tw="flex flex-row">
|
||||
{node.members?.properties?.length ? (
|
||||
<div tw="flex mr-12">
|
||||
<span tw="mr-4">{resolveKind('Property', 42)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{node.members.properties.length}</span>
|
||||
<span>Properties</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{node.members?.events?.length ? (
|
||||
<div tw="flex mr-12">
|
||||
<span tw="mr-4">{resolveKind('Method', 42)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{node.members.events.length}</span>
|
||||
<span>Events</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{node.members?.methods?.length ? (
|
||||
<div tw="flex mr-12">
|
||||
<span tw="mr-4">{resolveKind('Method', 42)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{node.members.methods.length}</span>
|
||||
<span>Methods</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{node.members?.length ? (
|
||||
<div tw="flex">
|
||||
<span tw="mr-4">{resolveKind('EnumMember', 42)}</span>
|
||||
<div tw="flex flex-col text-4xl">
|
||||
<span tw="mb-4">{node.members.length}</span>
|
||||
<span>Members</span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div tw="flex h-full items-end">
|
||||
<span tw="bg-[#5865f2] text-4xl font-black relative rounded-lg py-4 px-8">discord.js</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// import { readFile } from 'node:fs/promises';
|
||||
// import { join } from 'node:path';
|
||||
// import { inspect } from 'node:util';
|
||||
import type { Metadata } from 'next';
|
||||
import { DocItem } from '~/components/DocItem';
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
readonly params: {
|
||||
readonly item: string;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
};
|
||||
}): Promise<Metadata> {
|
||||
const normalizeItem = params.item.split(encodeURIComponent(':'))[0];
|
||||
|
||||
return {
|
||||
title: `${normalizeItem} (${params.packageName} - ${params.version})`,
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Page({
|
||||
params,
|
||||
}: {
|
||||
readonly params: { readonly item: string; readonly packageName: string; readonly version: string };
|
||||
}) {
|
||||
const normalizeItem = params.item.split(encodeURIComponent(':')).join('.').toLowerCase();
|
||||
|
||||
// const fileContent = await readFile(
|
||||
// join(process.cwd(), `../../packages/${params.packageName}/docs/split/${params.version}.${normalizeItem}.api.json`),
|
||||
// 'utf8',
|
||||
// );
|
||||
// const node = JSON.parse(fileContent);
|
||||
|
||||
const isMainVersion = params.version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${params.packageName}/${params.version}.${normalizeItem}.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
);
|
||||
const node = await fileContent.json();
|
||||
|
||||
// console.log(inspect(node, { depth: 0 }));
|
||||
|
||||
return (
|
||||
<main className="flex w-full flex-col gap-8 pb-12 md:pb-0">
|
||||
<DocItem node={node} packageName={params.packageName} version={params.version} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// import { readFile } from 'node:fs/promises';
|
||||
// import { join } from 'node:path';
|
||||
// import { inspect } from 'node:util';
|
||||
import type { Metadata } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { Navigation } from '~/components/Navigation';
|
||||
import { OverlayScrollbarsComponent } from '~/components/OverlayScrollbars';
|
||||
import { Drawer } from '~/components/ui/Drawer';
|
||||
import { Footer } from '~/components/ui/Footer';
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
const CmdK = dynamic(async () => import('~/components/ui/CmdK').then((mod) => mod.CmdK), { ssr: false });
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
readonly params: { readonly packageName: string; readonly version: string };
|
||||
}): Promise<Metadata> {
|
||||
return {
|
||||
title: {
|
||||
template: '%s | discord.js',
|
||||
default: `${params.packageName} (${params.version})`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default async function Layout({
|
||||
params,
|
||||
children,
|
||||
}: PropsWithChildren<{ readonly params: { readonly packageName: string; readonly version: string } }>) {
|
||||
// const fileContent = await readFile(
|
||||
// join(process.cwd(), `../../packages/${params.packageName}/docs/split/${params.version}.dependencies.api.json`),
|
||||
// 'utf8',
|
||||
// );
|
||||
// const dependencies = JSON.parse(fileContent);
|
||||
|
||||
const isMainVersion = params.version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${params.packageName}/${params.version}.dependencies.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
);
|
||||
const parsedDependencies = await fileContent.json();
|
||||
const dependencies = Object.entries<string>(parsedDependencies)
|
||||
.filter(([key]) => key.startsWith('@discordjs/') && !key.includes('api-extractor'))
|
||||
.map(([key, value]) => `${key.replace('@discordjs/', '').replaceAll('.', '-')}-${value.replaceAll('.', '-')}`);
|
||||
|
||||
// console.log(inspect(dependencies, { depth: 0 }));
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react/no-unknown-property
|
||||
<div vaul-drawer-wrapper="" className="mx-auto flex max-w-screen-xl flex-col gap-12 p-6 md:flex-row">
|
||||
<div className="sticky top-6 hidden flex-shrink-0 self-start md:block">
|
||||
<OverlayScrollbarsComponent
|
||||
className="max-h-[calc(100dvh-48px)]"
|
||||
defer
|
||||
options={{
|
||||
overflow: { x: 'hidden' },
|
||||
scrollbars: {
|
||||
autoHide: 'scroll',
|
||||
autoHideDelay: 500,
|
||||
autoHideSuspend: true,
|
||||
clickScroll: true,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Navigation className="pr-4" packageName={params.packageName} version={params.version} />
|
||||
</OverlayScrollbarsComponent>
|
||||
</div>
|
||||
<div className="pb-12">
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
<div className="fixed bottom-0 left-0 right-0 md:hidden">
|
||||
<Drawer>
|
||||
<Navigation
|
||||
className="max-w-none overflow-auto p-0 lg:max-w-none"
|
||||
packageName={params.packageName}
|
||||
version={params.version}
|
||||
drawer
|
||||
/>
|
||||
</Drawer>
|
||||
</div>
|
||||
<CmdK dependencies={dependencies} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { getHighlighterCore } from 'shiki/core';
|
||||
import getWasm from 'shiki/wasm';
|
||||
|
||||
const highlighter = await getHighlighterCore({
|
||||
themes: [import('shiki/themes/vitesse-light.mjs'), import('shiki/themes/vitesse-dark.mjs')],
|
||||
langs: [
|
||||
import('shiki/langs/typescript.mjs'),
|
||||
import('shiki/langs/javascript.mjs'),
|
||||
import('shiki/langs/shellscript.mjs'),
|
||||
],
|
||||
loadWasm: getWasm,
|
||||
});
|
||||
|
||||
export default async function Page({ params }: { readonly params: { readonly packageName: string } }) {
|
||||
const fileContent = await readFile(
|
||||
join(process.cwd(), `src/assets/readme/${params.packageName}/home-README.md`),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="prose prose-neutral mx-auto max-w-screen-lg dark:prose-invert">
|
||||
<MDXRemote
|
||||
options={{
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [
|
||||
[
|
||||
rehypeShikiFromHighlighter as any,
|
||||
highlighter,
|
||||
{
|
||||
themes: {
|
||||
light: 'vitesse-light',
|
||||
dark: 'vitesse-dark',
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
}}
|
||||
source={fileContent}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="relative top-6 mx-4 min-h-xl flex flex-col items-center justify-center gap-4">
|
||||
<svg
|
||||
className="h-9 w-9 animate-spin text-black dark:text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75 dark:opacity-100"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div className="text-lg font-medium">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { Route } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
export default function NotFound() {
|
||||
const pathname = usePathname();
|
||||
const href = pathname.split('/').slice(0, -1).join('/');
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-lg min-h-screen flex flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">404</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
|
||||
<Link
|
||||
className="h-11 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-items-center border-0 rounded bg-blurple px-6 text-base text-white font-semibold leading-none no-underline outline-none active:translate-y-px focus:ring focus:ring-width-2 focus:ring-white"
|
||||
href={href as Route}
|
||||
>
|
||||
Take me back
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
import type {
|
||||
ApiClass,
|
||||
ApiDeclaredItem,
|
||||
ApiEnum,
|
||||
ApiInterface,
|
||||
ApiItem,
|
||||
ApiItemContainerMixin,
|
||||
ApiMethod,
|
||||
ApiMethodSignature,
|
||||
ApiProperty,
|
||||
ApiPropertySignature,
|
||||
ApiTypeAlias,
|
||||
ApiVariable,
|
||||
ApiFunction,
|
||||
} from '@discordjs/api-extractor-model';
|
||||
import { ApiItemKind, ApiModel, ApiPackage } from '@discordjs/api-extractor-model';
|
||||
import { tryResolveSummaryText } from '@discordjs/scripts';
|
||||
import type { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { fetchModelJSON } from '~/app/docAPI';
|
||||
import { Class } from '~/components/model/Class';
|
||||
import { Interface } from '~/components/model/Interface';
|
||||
import { TypeAlias } from '~/components/model/TypeAlias';
|
||||
import { Variable } from '~/components/model/Variable';
|
||||
import { Enum } from '~/components/model/enum/Enum';
|
||||
import { Function } from '~/components/model/function/Function';
|
||||
import { OVERLOAD_SEPARATOR } from '~/util/constants';
|
||||
import { fetchMember } from '~/util/fetchMember';
|
||||
import { findMember } from '~/util/model';
|
||||
|
||||
export const revalidate = 86_400;
|
||||
|
||||
export interface ItemRouteParams {
|
||||
item: string;
|
||||
package: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
async function fetchHeadMember({ package: packageName, version, item }: ItemRouteParams) {
|
||||
const modelJSON = await fetchModelJSON(packageName, version);
|
||||
|
||||
if (!modelJSON) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const model = new ApiModel();
|
||||
model.addMember(ApiPackage.loadFromJson(modelJSON));
|
||||
const pkg = model.tryGetPackageByName(packageName);
|
||||
const entry = pkg?.entryPoints[0];
|
||||
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const [memberName] = decodeURIComponent(item).split(OVERLOAD_SEPARATOR);
|
||||
return findMember(model, packageName, memberName);
|
||||
}
|
||||
|
||||
function resolveMemberSearchParams(packageName: string, member?: ApiItem) {
|
||||
const params = new URLSearchParams({
|
||||
pkg: packageName,
|
||||
kind: member?.kind ?? '',
|
||||
name: member?.displayName ?? '',
|
||||
});
|
||||
|
||||
switch (member?.kind) {
|
||||
case ApiItemKind.Interface:
|
||||
case ApiItemKind.Class: {
|
||||
const typedMember = member as ApiItemContainerMixin;
|
||||
|
||||
const properties = typedMember.members.filter((member) =>
|
||||
[ApiItemKind.Property, ApiItemKind.PropertySignature].includes(member.kind),
|
||||
) as (ApiProperty | ApiPropertySignature)[];
|
||||
const methods = typedMember.members.filter((member) =>
|
||||
[ApiItemKind.Method, ApiItemKind.Method].includes(member.kind),
|
||||
) as (ApiMethod | ApiMethodSignature)[];
|
||||
|
||||
params.append('methods', methods.length.toString());
|
||||
params.append('props', properties.length.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
case ApiItemKind.Enum: {
|
||||
const typedMember = member as ApiEnum;
|
||||
params.append('members', typedMember.members.length.toString());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: { params: ItemRouteParams }) {
|
||||
const member = await fetchHeadMember(params);
|
||||
const name = `discord.js${member?.displayName ? ` | ${member.displayName}` : ''}`;
|
||||
const ogTitle = `${params.package ?? 'discord.js'}${member?.displayName ? ` | ${member.displayName}` : ''}`;
|
||||
const url = new URL('https://discordjs.dev/api/dynamic-open-graph.png');
|
||||
const searchParams = resolveMemberSearchParams(params.package, member);
|
||||
url.search = searchParams.toString();
|
||||
const ogImage = url.toString();
|
||||
let description;
|
||||
|
||||
if (member) {
|
||||
description = tryResolveSummaryText(member as ApiDeclaredItem);
|
||||
}
|
||||
|
||||
return {
|
||||
title: name,
|
||||
description: description ?? 'Discord.js API Documentation',
|
||||
openGraph: {
|
||||
title: ogTitle,
|
||||
description: description ?? 'Discord.js API Documentation',
|
||||
images: ogImage,
|
||||
},
|
||||
} satisfies Metadata;
|
||||
}
|
||||
|
||||
export async function generateStaticParams({ params: { package: packageName, version } }: { params: ItemRouteParams }) {
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const modelJSON = await fetchModelJSON(packageName, version);
|
||||
|
||||
if (!modelJSON) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const model = new ApiModel();
|
||||
model.addMember(ApiPackage.loadFromJson(modelJSON));
|
||||
|
||||
const pkg = model.tryGetPackageByName(packageName);
|
||||
const entry = pkg?.entryPoints[0];
|
||||
|
||||
if (!entry) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return entry.members.map((member: ApiItem) => ({
|
||||
package: packageName,
|
||||
version,
|
||||
item: `${member.displayName}${OVERLOAD_SEPARATOR}${member.kind}`,
|
||||
}));
|
||||
}
|
||||
|
||||
function Member({ member }: { readonly member?: ApiItem }) {
|
||||
switch (member?.kind) {
|
||||
case 'Class':
|
||||
return <Class clazz={member as ApiClass} />;
|
||||
case 'Function':
|
||||
return <Function item={member as ApiFunction} />;
|
||||
case 'Interface':
|
||||
return <Interface item={member as ApiInterface} />;
|
||||
case 'TypeAlias':
|
||||
return <TypeAlias item={member as ApiTypeAlias} />;
|
||||
case 'Variable':
|
||||
return <Variable item={member as ApiVariable} />;
|
||||
case 'Enum':
|
||||
return <Enum item={member as ApiEnum} />;
|
||||
default:
|
||||
return <div>Cannot render that item type</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default async function Page({ params }: { params: ItemRouteParams }) {
|
||||
const member = await fetchMember(params.package, params.version ?? 'main', params.item);
|
||||
|
||||
if (!member) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Member member={member} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({ error }: { readonly error: Error }) {
|
||||
console.error(error);
|
||||
|
||||
return (
|
||||
<div className="mx-auto h-full max-w-lg flex flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">500</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Error.</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import type { ApiFunction, ApiItem } from '@discordjs/api-extractor-model';
|
||||
import { ApiModel, ApiPackage } from '@discordjs/api-extractor-model';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { notFound } from 'next/navigation';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { fetchModelJSON, fetchVersions } from '~/app/docAPI';
|
||||
import { CmdKDialog } from '~/components/CmdK';
|
||||
import { Nav } from '~/components/Nav';
|
||||
import { Outline } from '~/components/Outline';
|
||||
import type { SidebarSectionItemData } from '~/components/Sidebar';
|
||||
import { resolveItemURI } from '~/components/documentation/util';
|
||||
import { N_RECENT_VERSIONS, PACKAGES } from '~/util/constants';
|
||||
import { Providers } from './providers';
|
||||
|
||||
const Header = dynamic(async () => import('~/components/Header'));
|
||||
const Footer = dynamic(async () => import('~/components/Footer'));
|
||||
|
||||
interface VersionRouteParams {
|
||||
package: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export const generateStaticParams = async () => {
|
||||
if (process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const params: VersionRouteParams[] = [];
|
||||
|
||||
await Promise.all(
|
||||
PACKAGES.map(async (packageName) => {
|
||||
const versions = (await fetchVersions(packageName)).slice(1, N_RECENT_VERSIONS);
|
||||
|
||||
params.push(...versions.map((version) => ({ package: packageName, version })));
|
||||
}),
|
||||
);
|
||||
|
||||
return params;
|
||||
};
|
||||
|
||||
const serializeIntoSidebarItemData = (item: ApiItem) => {
|
||||
return {
|
||||
kind: item.kind,
|
||||
name: item.displayName,
|
||||
href: resolveItemURI(item),
|
||||
overloadIndex: 'overloadIndex' in item ? (item.overloadIndex as number) : undefined,
|
||||
} as SidebarSectionItemData;
|
||||
};
|
||||
|
||||
export default async function PackageLayout({ children, params }: PropsWithChildren<{ params: VersionRouteParams }>) {
|
||||
const modelJSON = await fetchModelJSON(params.package, params.version);
|
||||
|
||||
if (!modelJSON) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const model = new ApiModel();
|
||||
model.addMember(ApiPackage.loadFromJson(modelJSON));
|
||||
|
||||
const pkg = model.tryGetPackageByName(params.package);
|
||||
|
||||
if (!pkg) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const entry = pkg.entryPoints[0];
|
||||
|
||||
if (!entry) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const members = entry.members.filter((member) => {
|
||||
if (member.kind !== 'Function') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (member as ApiFunction).overloadIndex === 1;
|
||||
});
|
||||
|
||||
const versions = await fetchVersions(params.package);
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<main className="mx-auto max-w-7xl px-4 lg:max-w-full">
|
||||
<Header />
|
||||
<div className="relative top-6.5 mx-auto max-w-7xl gap-6 lg:max-w-full lg:flex">
|
||||
<div className="lg:sticky lg:top-23 lg:h-[calc(100vh_-_105px)]">
|
||||
<Nav members={members.map((member) => serializeIntoSidebarItemData(member))} versions={versions} />
|
||||
</div>
|
||||
|
||||
<div className="relative top-4.5 mx-auto max-w-5xl min-w-xs w-full pb-10">
|
||||
{children}
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
<Outline />
|
||||
</div>
|
||||
</main>
|
||||
<CmdKDialog />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { compileMDX } from 'next-mdx-remote/rsc';
|
||||
import { cache } from 'react';
|
||||
import rehypeSlug from 'rehype-slug';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { SyntaxHighlighter } from '~/components/SyntaxHighlighter';
|
||||
|
||||
interface VersionRouteParams {
|
||||
package: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
const loadREADME = cache(async (packageName: string) => {
|
||||
return readFile(join(process.cwd(), 'src', 'assets', 'readme', packageName, 'home-README.md'), 'utf8');
|
||||
});
|
||||
|
||||
export default async function Page({ params }: { params: VersionRouteParams }) {
|
||||
const readmeSource = await loadREADME(params.package);
|
||||
const { content } = await compileMDX({
|
||||
source: readmeSource,
|
||||
// @ts-expect-error SyntaxHighlighter is assignable
|
||||
components: { pre: SyntaxHighlighter },
|
||||
options: {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [rehypeSlug],
|
||||
format: 'mdx',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return <div className="relative top-4 max-w-none prose">{content}</div>;
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { CmdKProvider } from '~/contexts/cmdK';
|
||||
import { MemberProvider } from '~/contexts/member';
|
||||
import { NavProvider } from '~/contexts/nav';
|
||||
import { OutlineProvider } from '~/contexts/outline';
|
||||
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<NavProvider>
|
||||
<OutlineProvider>
|
||||
<MemberProvider>
|
||||
<CmdKProvider>{children}</CmdKProvider>
|
||||
</MemberProvider>
|
||||
</OutlineProvider>
|
||||
</NavProvider>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from '~/app/loading';
|
||||
@@ -1,44 +0,0 @@
|
||||
import { VscArrowLeft } from '@react-icons/all-files/vsc/VscArrowLeft';
|
||||
import { VscArrowRight } from '@react-icons/all-files/vsc/VscArrowRight';
|
||||
import { VscVersions } from '@react-icons/all-files/vsc/VscVersions';
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { fetchVersions } from '~/app/docAPI';
|
||||
import { buttonVariants } from '~/styles/Button';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
|
||||
export const revalidate = 86_400;
|
||||
|
||||
export default async function Page({ params }: { params: { package: string } }) {
|
||||
if (!PACKAGES.includes(params.package)) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const data = await fetchVersions(params.package);
|
||||
|
||||
return (
|
||||
<div className="mx-auto min-h-screen min-w-xs flex flex-col gap-8 px-4 py-6 sm:w-md lg:px-6 lg:py-6">
|
||||
<h1 className="text-2xl font-semibold">Select a version:</h1>
|
||||
<div className="flex flex-col gap-4">
|
||||
{data.map((version, idx) => (
|
||||
<Link
|
||||
className={buttonVariants({ variant: 'secondary' })}
|
||||
href={`/docs/packages/${params.package}/${version}`}
|
||||
key={`${version}-${idx}`}
|
||||
>
|
||||
<div className="flex grow flex-row place-content-between place-items-center gap-4">
|
||||
<div className="flex flex-row place-content-between place-items-center gap-4">
|
||||
<VscVersions size={25} />
|
||||
<h2 className="font-semibold">{version}</h2>
|
||||
</div>
|
||||
<VscArrowRight size={20} />
|
||||
</div>
|
||||
</Link>
|
||||
)) ?? null}
|
||||
</div>
|
||||
<Link className={buttonVariants({ className: 'place-self-center' })} href="/docs/packages">
|
||||
<VscArrowLeft size={20} /> Go back
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from '~/app/loading';
|
||||
@@ -1,44 +0,0 @@
|
||||
import { FiExternalLink } from '@react-icons/all-files/fi/FiExternalLink';
|
||||
import { VscArrowLeft } from '@react-icons/all-files/vsc/VscArrowLeft';
|
||||
import { VscArrowRight } from '@react-icons/all-files/vsc/VscArrowRight';
|
||||
import { VscPackage } from '@react-icons/all-files/vsc/VscPackage';
|
||||
import Link from 'next/link';
|
||||
import { buttonVariants } from '~/styles/Button';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div className="mx-auto min-h-screen min-w-xs flex flex-col gap-8 px-4 py-6 sm:w-md lg:px-6 lg:py-6">
|
||||
<h1 className="text-2xl font-semibold">Select a package:</h1>
|
||||
<div className="flex flex-col gap-4">
|
||||
{PACKAGES.map((pkg, idx) => (
|
||||
<Link
|
||||
className={buttonVariants({ variant: 'secondary' })}
|
||||
href={`/docs/packages/${pkg}`}
|
||||
key={`${pkg}-${idx}`}
|
||||
>
|
||||
<div className="flex grow flex-row place-content-between place-items-center gap-4">
|
||||
<div className="flex flex-row place-content-between place-items-center gap-4">
|
||||
<VscPackage size={25} />
|
||||
<h2 className="font-semibold">{pkg}</h2>
|
||||
</div>
|
||||
<VscArrowRight size={20} />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
<a className={buttonVariants({ variant: 'secondary' })} href="https://discord-api-types.dev/">
|
||||
<div className="flex grow flex-row place-content-between place-items-center gap-4">
|
||||
<div className="flex flex-row place-content-between place-items-center gap-4">
|
||||
<VscPackage size={25} />
|
||||
<h2 className="font-semibold">discord-api-types</h2>
|
||||
</div>
|
||||
<FiExternalLink size={20} />
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<Link className={buttonVariants({ className: 'place-self-center' })} href="/">
|
||||
<VscArrowLeft size={20} /> Go back
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
'use client';
|
||||
|
||||
export default function Error({ error }: { readonly error: Error }) {
|
||||
console.error(error);
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-lg min-h-screen flex flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">500</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Error.</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,26 +1,33 @@
|
||||
import { Analytics } from '@vercel/analytics/react';
|
||||
import { GeistMono } from 'geist/font/mono';
|
||||
import { GeistSans } from 'geist/font/sans';
|
||||
import type { Metadata, Viewport } from 'next';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { LocalizedStringProvider } from 'react-aria-components/i18n';
|
||||
import { DESCRIPTION } from '~/util/constants';
|
||||
import { inter, jetBrainsMono } from '~/util/fonts';
|
||||
import { Providers } from './providers';
|
||||
|
||||
import '~/styles/cmdk.css';
|
||||
import '~/styles/main.css';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
|
||||
export const viewport: Viewport = {
|
||||
themeColor: [
|
||||
{ media: '(prefers-color-scheme: light)', color: '#f1f3f5' },
|
||||
{ media: '(prefers-color-scheme: dark)', color: '#1c1c1e' },
|
||||
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
|
||||
{ media: '(prefers-color-scheme: dark)', color: '#121212' },
|
||||
],
|
||||
colorScheme: 'light dark',
|
||||
};
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL(
|
||||
process.env.METADATA_BASE_URL ? process.env.METADATA_BASE_URL : `http://localhost:${process.env.PORT ?? 3_000}`,
|
||||
process.env.NEXT_PUBLIC_LOCAL_DEV === 'true'
|
||||
? `http://localhost:${process.env.PORT ?? 3_000}`
|
||||
: 'https://discord.js.org',
|
||||
),
|
||||
title: 'discord.js',
|
||||
title: {
|
||||
template: '%s | discord.js',
|
||||
default: 'discord.js',
|
||||
},
|
||||
description: DESCRIPTION,
|
||||
icons: {
|
||||
other: [
|
||||
@@ -66,14 +73,15 @@ export const metadata: Metadata = {
|
||||
},
|
||||
|
||||
other: {
|
||||
'msapplication-TileColor': '#1c1c1e',
|
||||
'msapplication-TileColor': '#121212',
|
||||
},
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: PropsWithChildren) {
|
||||
export default async function RootLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html className={`${inter.variable} ${jetBrainsMono.variable}`} lang="en" suppressHydrationWarning>
|
||||
<body className="bg-light-600 dark:bg-dark-600 dark:text-light-900">
|
||||
<html lang="en" className={`${GeistSans.variable} ${GeistMono.variable} antialiased`} suppressHydrationWarning>
|
||||
<body className="bg-white dark:bg-[#121212]">
|
||||
<LocalizedStringProvider locale="en-US" />
|
||||
<Providers>{children}</Providers>
|
||||
<Analytics />
|
||||
</body>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
export default function Loading() {
|
||||
return (
|
||||
<div className="mx-4 min-h-screen flex flex-col items-center justify-center gap-4">
|
||||
<svg
|
||||
className="h-9 w-9 animate-spin text-black dark:text-white"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75 dark:opacity-100"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div className="text-lg font-medium">Loading...</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { Route } from 'next';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="mx-auto max-w-lg min-h-screen flex flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">404</h1>
|
||||
<h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
|
||||
<Link
|
||||
className="h-11 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-items-center border-0 rounded bg-blurple px-6 text-base text-white font-semibold leading-none no-underline outline-none active:translate-y-px focus:ring focus:ring-width-2 focus:ring-white"
|
||||
href={'/docs' as Route}
|
||||
>
|
||||
Take me back
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
apps/website/src/app/opengraph-image.tsx
Normal file
36
apps/website/src/app/opengraph-image.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
/* eslint-disable react/no-unknown-property */
|
||||
import { ImageResponse } from 'next/og';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export const size = {
|
||||
width: 1_200,
|
||||
height: 630,
|
||||
};
|
||||
|
||||
export const contentType = 'image/png';
|
||||
|
||||
export default async function Image() {
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div tw="flex bg-[#121212] h-full w-full">
|
||||
<div tw="mx-auto flex items-center h-full">
|
||||
<div tw="flex">
|
||||
<div tw="flex">
|
||||
<div tw="flex flex-col font-black text-[5.5rem] text-white">
|
||||
<div tw="flex flex-row">
|
||||
The <span tw="bg-[#5865f2] rounded-lg py-1 px-6 ml-4">most popular</span>
|
||||
</div>
|
||||
<span>way to build Discord</span>
|
||||
<span>bots.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
...size,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -1,83 +1,81 @@
|
||||
import { FiExternalLink } from '@react-icons/all-files/fi/FiExternalLink';
|
||||
import type { Route } from 'next';
|
||||
import { ExternalLink } from 'lucide-react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import vercelLogo from '~/assets/powered-by-vercel.svg';
|
||||
import workersLogo from '~/assets/powered-by-workers.png';
|
||||
import { InstallButton } from '~/components/InstallButton';
|
||||
import { buttonVariants } from '~/styles/Button';
|
||||
import { InstallButton } from '~/components/ui/InstallButton';
|
||||
import { DESCRIPTION } from '~/util/constants';
|
||||
|
||||
export default function Page() {
|
||||
export default async function Page() {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<div className="mx-auto max-w-6xl flex flex-col place-items-center gap-24 px-8 pb-16 pt-12 lg:min-h-[calc(100vh_-_40px)] lg:place-content-center lg:py-10">
|
||||
<div className="flex flex-col place-items-center gap-10 lg:flex-row lg:gap-6">
|
||||
<div className="flex flex-col place-items-center gap-10 text-center">
|
||||
<h1 className="text-3xl font-black leading-tight sm:text-7xl sm:leading-tight">
|
||||
The <span className="relative rounded bg-blurple px-3 py-1 text-white">most popular</span> way to build
|
||||
Discord bots.
|
||||
</h1>
|
||||
<p className="my-6 text-neutral-700 leading-normal dark:text-neutral-300">{DESCRIPTION}</p>
|
||||
<div className="flex flex-wrap place-content-center gap-4 md:flex-row">
|
||||
<Link className={buttonVariants()} href={'/docs' as Route}>
|
||||
Docs
|
||||
</Link>
|
||||
<a
|
||||
className={buttonVariants({ variant: 'secondary' })}
|
||||
href="https://discordjs.guide"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Guide <FiExternalLink />
|
||||
</a>
|
||||
<a
|
||||
className={buttonVariants({ variant: 'secondary' })}
|
||||
href="https://github.com/discordjs/discord.js"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub <FiExternalLink />
|
||||
</a>
|
||||
</div>
|
||||
<InstallButton />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4 md:flex-row">
|
||||
<a
|
||||
className="rounded outline-none focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Vercel"
|
||||
<div className="mx-auto flex min-h-screen w-full max-w-screen-lg flex-col place-content-center place-items-center gap-24 px-8 pb-16 pt-12">
|
||||
<div className="flex flex-col gap-10 text-center">
|
||||
<h1 className="z-10 text-3xl font-black leading-tight sm:text-7xl sm:leading-tight">
|
||||
The <span className="relative rounded bg-blurple px-3 py-1 text-white">most popular</span> way to build
|
||||
Discord bots.
|
||||
</h1>
|
||||
<p className="z-10 leading-normal text-neutral-700 dark:text-neutral-300 md:my-6">{DESCRIPTION}</p>
|
||||
|
||||
<div className="flex flex-wrap place-content-center gap-4 sm:flex-wrap md:flex-row">
|
||||
<Link
|
||||
className="inline-flex rounded-md border border-transparent bg-blurple px-6 py-2 font-medium text-white"
|
||||
href="/docs"
|
||||
>
|
||||
<Image
|
||||
alt="Vercel"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABLCAQAAAA1k5H2AAAAi0lEQVR42u3SMQEAAAgDoC251a3gL2SgmfBYBRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAgwWEOSWBnYbKggAAAABJRU5ErkJggg=="
|
||||
height={44}
|
||||
placeholder="blur"
|
||||
priority
|
||||
src={vercelLogo}
|
||||
width={212}
|
||||
/>
|
||||
Docs
|
||||
</Link>
|
||||
<a
|
||||
className="inline-flex gap-2 rounded-md border border-neutral-300 bg-white px-6 py-2 font-medium hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800"
|
||||
href="https://discordjs.guide"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
Guide <ExternalLink aria-hidden size={20} />
|
||||
</a>
|
||||
<a
|
||||
className="rounded outline-none focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
href="https://www.cloudflare.com"
|
||||
className="inline-flex gap-2 rounded-md border border-neutral-300 bg-white px-6 py-2 font-medium hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800"
|
||||
href="https://github.com/discordjs/discord.js"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Cloudflare Workers"
|
||||
>
|
||||
<Image
|
||||
alt="Cloudflare"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABLCAQAAAA1k5H2AAAAi0lEQVR42u3SMQEAAAgDoC251a3gL2SgmfBYBRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAgwWEOSWBnYbKggAAAABJRU5ErkJggg=="
|
||||
height={44}
|
||||
placeholder="blur"
|
||||
priority
|
||||
src={workersLogo}
|
||||
/>
|
||||
GitHub <ExternalLink aria-hidden size={20} />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<InstallButton className="place-self-center" />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 md:flex-row">
|
||||
<a
|
||||
href="https://vercel.com/?utm_source=discordjs&utm_campaign=oss"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Vercel"
|
||||
>
|
||||
<Image
|
||||
alt="Vercel"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABLCAQAAAA1k5H2AAAAi0lEQVR42u3SMQEAAAgDoC251a3gL2SgmfBYBRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAgwWEOSWBnYbKggAAAABJRU5ErkJggg=="
|
||||
height={44}
|
||||
placeholder="blur"
|
||||
priority
|
||||
src={vercelLogo}
|
||||
width={212}
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="https://www.cloudflare.com"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Cloudflare Workers"
|
||||
>
|
||||
<Image
|
||||
alt="Cloudflare"
|
||||
blurDataURL="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAABLCAQAAAA1k5H2AAAAi0lEQVR42u3SMQEAAAgDoC251a3gL2SgmfBYBRAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARAAARCAgwWEOSWBnYbKggAAAABJRU5ErkJggg=="
|
||||
height={44}
|
||||
placeholder="blur"
|
||||
priority
|
||||
src={workersLogo}
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
'use client';
|
||||
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { RouterProvider } from 'react-aria-components';
|
||||
import { useSystemThemeFallback } from '~/hooks/useSystemThemeFallback';
|
||||
import { useUnregisterServiceWorker } from '~/hooks/useUnregisterServiceWorker';
|
||||
|
||||
export function Providers({ children }: PropsWithChildren) {
|
||||
const router = useRouter();
|
||||
useUnregisterServiceWorker();
|
||||
useSystemThemeFallback();
|
||||
|
||||
return <ThemeProvider attribute="class">{children}</ThemeProvider>;
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
<RouterProvider navigate={router.push}>
|
||||
<JotaiProvider>
|
||||
<ThemeProvider attribute="class">{children}</ThemeProvider>
|
||||
</JotaiProvider>
|
||||
</RouterProvider>
|
||||
);
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,9 +0,0 @@
|
||||
import { FiLink } from '@react-icons/all-files/fi/FiLink';
|
||||
|
||||
export function Anchor({ href }: { readonly href: string }) {
|
||||
return (
|
||||
<a className="mr-1 inline-block rounded outline-none focus:ring focus:ring-width-2 focus:ring-blurple" href={href}>
|
||||
<FiLink size={20} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +1,38 @@
|
||||
import type { ApiDocumentedItem } from '@discordjs/api-extractor-model';
|
||||
import { ApiAbstractMixin, ApiProtectedMixin, ApiReadonlyMixin, ApiStaticMixin } from '@discordjs/api-extractor-model';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export enum BadgeColor {
|
||||
Danger = 'bg-red-500',
|
||||
Primary = 'bg-blurple',
|
||||
Warning = 'bg-yellow-500',
|
||||
}
|
||||
|
||||
export function Badge({
|
||||
children,
|
||||
color = BadgeColor.Primary,
|
||||
}: PropsWithChildren<{ readonly color?: BadgeColor | undefined }>) {
|
||||
export function Badge({ children, className = '' }: PropsWithChildren<{ readonly className?: string }>) {
|
||||
return (
|
||||
<span
|
||||
className={`h-5 flex flex-row place-content-center place-items-center rounded-full px-3 text-center text-xs font-semibold uppercase text-white ${color}`}
|
||||
className={`inline-flex place-items-center gap-1 rounded-full px-2 py-1 font-sans text-sm font-normal leading-none ${className}`}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function Badges({ item }: { readonly item: ApiDocumentedItem }) {
|
||||
const isStatic = ApiStaticMixin.isBaseClassOf(item) && item.isStatic;
|
||||
const isProtected = ApiProtectedMixin.isBaseClassOf(item) && item.isProtected;
|
||||
const isReadonly = ApiReadonlyMixin.isBaseClassOf(item) && item.isReadonly;
|
||||
const isAbstract = ApiAbstractMixin.isBaseClassOf(item) && item.isAbstract;
|
||||
const isDeprecated = Boolean(item.tsdocComment?.deprecatedBlock);
|
||||
export async function Badges({ node }: { readonly node: any }) {
|
||||
const isDeprecated = Boolean(node.summary?.deprecatedBlock?.length);
|
||||
const isProtected = node.isProtected;
|
||||
const isStatic = node.isStatic;
|
||||
const isAbstract = node.isAbstract;
|
||||
const isReadonly = node.isReadonly;
|
||||
const isOptional = node.isOptional;
|
||||
|
||||
const isAny = isStatic || isProtected || isReadonly || isAbstract || isDeprecated;
|
||||
const isAny = isDeprecated || isProtected || isStatic || isAbstract || isReadonly || isOptional;
|
||||
|
||||
return isAny ? (
|
||||
<div className="flex flex-row gap-1 md:ml-7">
|
||||
{isDeprecated ? <Badge color={BadgeColor.Danger}>Deprecated</Badge> : null}
|
||||
{isProtected ? <Badge>Protected</Badge> : null}
|
||||
{isStatic ? <Badge>Static</Badge> : null}
|
||||
{isAbstract ? <Badge>Abstract</Badge> : null}
|
||||
{isReadonly ? <Badge>Readonly</Badge> : null}
|
||||
<div className="mb-1 flex gap-3">
|
||||
{isDeprecated ? (
|
||||
<Badge className="bg-red-500/20 text-red-500">
|
||||
<AlertTriangle aria-hidden size={14} /> deprecated
|
||||
</Badge>
|
||||
) : null}
|
||||
{isProtected ? <Badge className="bg-purple-500/20 text-purple-500">protected</Badge> : null}
|
||||
{isStatic ? <Badge className="bg-purple-500/20 text-purple-500">static</Badge> : null}
|
||||
{isAbstract ? <Badge className="bg-cyan-500/20 text-cyan-500">abstract</Badge> : null}
|
||||
{isReadonly ? <Badge className="bg-purple-500/20 text-purple-500">readonly</Badge> : null}
|
||||
{isOptional ? <Badge className="bg-cyan-500/20 text-cyan-500">optional</Badge> : null}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { VscArrowRight } from '@react-icons/all-files/vsc/VscArrowRight';
|
||||
import { VscSymbolClass } from '@react-icons/all-files/vsc/VscSymbolClass';
|
||||
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';
|
||||
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
||||
import { VscSymbolInterface } from '@react-icons/all-files/vsc/VscSymbolInterface';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty';
|
||||
import { VscSymbolVariable } from '@react-icons/all-files/vsc/VscSymbolVariable';
|
||||
import { Dialog } from 'ariakit/dialog';
|
||||
import { Command } from 'cmdk';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useKey } from 'react-use';
|
||||
import { useCmdK } from '~/contexts/cmdK';
|
||||
import { client } from '~/util/search';
|
||||
|
||||
function resolveIcon(item: keyof typeof ApiItemKind) {
|
||||
switch (item) {
|
||||
case 'Class':
|
||||
return <VscSymbolClass className="shrink-0" size={25} />;
|
||||
case 'Enum':
|
||||
return <VscSymbolEnum className="shrink-0" size={25} />;
|
||||
case 'Interface':
|
||||
return <VscSymbolInterface className="shrink-0" size={25} />;
|
||||
case 'Property':
|
||||
return <VscSymbolProperty className="shrink-0" size={25} />;
|
||||
case 'TypeAlias':
|
||||
return <VscSymbolVariable className="shrink-0" size={25} />;
|
||||
case 'Variable':
|
||||
return <VscSymbolVariable className="shrink-0" size={25} />;
|
||||
case 'Event':
|
||||
return <VscSymbolEvent className="shrink-0" size={25} />;
|
||||
default:
|
||||
return <VscSymbolMethod className="shrink-0" size={25} />;
|
||||
}
|
||||
}
|
||||
|
||||
export function CmdKDialog() {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const dialog = useCmdK();
|
||||
const [search, setSearch] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||
|
||||
const packageName = pathname?.split('/').slice(3, 4)[0];
|
||||
const branchName = pathname?.split('/').slice(4, 5)[0];
|
||||
|
||||
const searchResultItems = useMemo(
|
||||
() =>
|
||||
searchResults?.map((item, idx) => (
|
||||
<Command.Item
|
||||
className="my-1 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-content-center rounded bg-transparent px-4 py-2 text-base text-black font-semibold leading-none outline-none active:translate-y-px dark:border-dark-100 active:bg-neutral-200 hover:bg-neutral-100 dark:text-white [&[aria-selected]]:ring [&[aria-selected]]:ring-width-2 [&[aria-selected]]:ring-blurple dark:active:bg-dark-200 dark:hover:bg-dark-300"
|
||||
key={`${item.id}-${idx}`}
|
||||
onSelect={() => {
|
||||
router.push(item.path);
|
||||
dialog!.setOpen(false);
|
||||
}}
|
||||
value={`${item.id}`}
|
||||
>
|
||||
<div className="flex grow flex-row place-content-between place-items-center gap-4">
|
||||
<div className="flex flex-row place-items-center gap-4">
|
||||
{resolveIcon(item.kind)}
|
||||
<div className="w-50 flex flex-col sm:w-100">
|
||||
<h2 className="font-semibold">{item.name}</h2>
|
||||
<div className="line-clamp-1 text-sm font-normal">{item.summary}</div>
|
||||
<div className="line-clamp-1 hidden text-xs font-light opacity-75 sm:block dark:opacity-50">
|
||||
{item.path}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<VscArrowRight className="shrink-0" size={20} />
|
||||
</div>
|
||||
</Command.Item>
|
||||
)) ?? [],
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[searchResults],
|
||||
);
|
||||
|
||||
useKey(
|
||||
(event) => {
|
||||
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
dialog!.toggle,
|
||||
{ event: 'keydown', options: {} },
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!dialog!.open) {
|
||||
setSearch('');
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dialog!.open]);
|
||||
|
||||
useEffect(() => {
|
||||
const searchDoc = async (searchString: string, version: string) => {
|
||||
const res = await client
|
||||
.index(`${packageName?.replaceAll('.', '-')}-${version}`)
|
||||
.search(searchString, { limit: 5 });
|
||||
setSearchResults(res.hits);
|
||||
};
|
||||
|
||||
if (search && packageName) {
|
||||
void searchDoc(search, branchName?.replaceAll('.', '-') ?? 'main');
|
||||
} else {
|
||||
setSearchResults([]);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<Dialog className="fixed left-1/2 top-1/4 z-50 -translate-x-1/2" state={dialog!}>
|
||||
<Command
|
||||
className="max-w-xs min-w-xs border border-light-900 rounded bg-white/50 shadow backdrop-blur-md sm:max-w-lg sm:min-w-lg dark:border-dark-100 dark:bg-dark/50"
|
||||
label="Command Menu"
|
||||
shouldFilter={false}
|
||||
>
|
||||
<Command.Input
|
||||
className="w-full border-0 border-b border-light-900 rounded rounded-b-0 bg-white/50 p-4 text-lg caret-blurple outline-none dark:border-dark-100 dark:bg-dark/50 placeholder:text-dark-300/75 dark:placeholder:text-white/75"
|
||||
onValueChange={setSearch}
|
||||
placeholder="Quick search..."
|
||||
value={search}
|
||||
/>
|
||||
<Command.List className="pt-0">
|
||||
<Command.Empty className="p-4 text-center">No results found</Command.Empty>
|
||||
{search ? searchResultItems : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { Anchor } from './Anchor';
|
||||
import { SourceLink } from './documentation/SourceLink';
|
||||
|
||||
export interface CodeListingProps {
|
||||
/**
|
||||
* The value of this heading.
|
||||
*/
|
||||
readonly children: ReactNode;
|
||||
/**
|
||||
* Additional class names to apply to the root element.
|
||||
*/
|
||||
readonly className?: string | undefined;
|
||||
/**
|
||||
* The href of this heading.
|
||||
*/
|
||||
readonly href?: string | undefined;
|
||||
/**
|
||||
* The line in the source code where this part is declared
|
||||
*/
|
||||
readonly sourceLine?: number | undefined;
|
||||
/**
|
||||
* The URL of the source code of this code part
|
||||
*/
|
||||
readonly sourceURL?: string | undefined;
|
||||
}
|
||||
|
||||
export function CodeHeading({ href, className, children, sourceURL, sourceLine }: CodeListingProps) {
|
||||
return (
|
||||
<div className="flex flex-row place-items-center justify-between gap-1">
|
||||
<div
|
||||
className={`flex flex-row flex-wrap place-items-center gap-1 break-all font-mono text-lg font-bold ${className}`}
|
||||
>
|
||||
{href ? <Anchor href={href} /> : null}
|
||||
{children}
|
||||
</div>
|
||||
{sourceURL ? <SourceLink className="text-2xl" sourceLine={sourceLine} sourceURL={sourceURL} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
48
apps/website/src/components/ConstructorNode.tsx
Normal file
48
apps/website/src/components/ConstructorNode.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { Code2, LinkIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { ParameterNode } from './ParameterNode';
|
||||
import { SummaryNode } from './SummaryNode';
|
||||
|
||||
export async function ConstructorNode({ node, version }: { readonly node: any; readonly version: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} />
|
||||
Constructors
|
||||
</h2>
|
||||
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<h3 id="constructor" className="group scroll-mt-8 break-words font-mono font-semibold">
|
||||
{/* constructor({parsedContent.constructor.parametersString}) */}
|
||||
<Link href="#constructor" className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block">
|
||||
<LinkIcon aria-hidden size={16} />
|
||||
</Link>
|
||||
constructor({node.parameters?.length ? <ParameterNode node={node.parameters} version={version} /> : null})
|
||||
</h3>
|
||||
|
||||
<a
|
||||
aria-label="Open source file in new tab"
|
||||
className="min-w-min"
|
||||
href={node.sourceLine ? `${node.sourceURL}#L${node.sourceLine}` : node.sourceURL}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Code2
|
||||
aria-hidden
|
||||
size={20}
|
||||
className="text-neutral-500 hover:text-neutral-600 dark:text-neutral-400 dark:hover:text-neutral-300"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{node.summary?.summarySection.length ? (
|
||||
<SummaryNode padding node={node.summary.summarySection} version={version} />
|
||||
) : null}
|
||||
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
apps/website/src/components/DeprecatedNode.tsx
Normal file
18
apps/website/src/components/DeprecatedNode.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { DocNode } from './DocNode';
|
||||
import { Alert } from './ui/Alert';
|
||||
|
||||
export async function DeprecatedNode({
|
||||
deprecatedBlock,
|
||||
version,
|
||||
}: {
|
||||
readonly deprecatedBlock: any;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<Alert title="Deprecated" type="danger">
|
||||
<p className="break-words">
|
||||
<DocNode node={deprecatedBlock} version={version} />
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
134
apps/website/src/components/DocItem.tsx
Normal file
134
apps/website/src/components/DocItem.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { VscSymbolParameter } from '@react-icons/all-files/vsc/VscSymbolParameter';
|
||||
import { ConstructorNode } from './ConstructorNode';
|
||||
import { DeprecatedNode } from './DeprecatedNode';
|
||||
import { EnumMemberNode } from './EnumMemberNode';
|
||||
import { EventNode } from './EventNode';
|
||||
import { InformationNode } from './InformationNode';
|
||||
import { MethodNode } from './MethodNode';
|
||||
import { Outline } from './Outline';
|
||||
import { OverlayScrollbarsComponent } from './OverlayScrollbars';
|
||||
import { ParameterNode } from './ParameterNode';
|
||||
import { PropertyNode } from './PropertyNode';
|
||||
import { ReturnNode } from './ReturnNode';
|
||||
import { SeeNode } from './SeeNode';
|
||||
import { SummaryNode } from './SummaryNode';
|
||||
import { SyntaxHighlighter } from './SyntaxHighlighter';
|
||||
import { TypeParameterNode } from './TypeParameterNode';
|
||||
import { UnionMember } from './UnionMember';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ui/Tabs';
|
||||
|
||||
async function OverloadNode({ node, packageName, version }: { node: any; packageName: string; version: string }) {
|
||||
return (
|
||||
<Tabs className="flex flex-col gap-4">
|
||||
<TabList className="flex gap-2">
|
||||
{node.overloads.map((overload: any) => {
|
||||
return (
|
||||
<Tab
|
||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
||||
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm font-normal leading-none text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
||||
>
|
||||
<span>Overload {overload.overloadIndex}</span>
|
||||
</Tab>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
{node.overloads.map((overload: any) => {
|
||||
return (
|
||||
<TabPanel
|
||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
||||
className="flex flex-col gap-8"
|
||||
>
|
||||
<DocItem node={overload} packageName={packageName} version={version} />
|
||||
</TabPanel>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
export function DocItem({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
if (node.overloads?.length) {
|
||||
return <OverloadNode node={node} packageName={packageName} version={version} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<InformationNode node={node} version={version} />
|
||||
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
options={{
|
||||
overflow: { y: 'hidden' },
|
||||
scrollbars: { autoHide: 'scroll', autoHideDelay: 500, autoHideSuspend: true, clickScroll: true },
|
||||
}}
|
||||
className="rounded-md border border-neutral-300 bg-neutral-100 dark:border-neutral-700 dark:bg-neutral-900"
|
||||
>
|
||||
<SyntaxHighlighter className="py-4 text-sm" lang="typescript" code={node.sourceExcerpt} />
|
||||
</OverlayScrollbarsComponent>
|
||||
|
||||
{node.summary?.deprecatedBlock.length ? (
|
||||
<DeprecatedNode deprecatedBlock={node.summary.deprecatedBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{node.summary?.summarySection ? <SummaryNode node={node.summary.summarySection} version={version} /> : null}
|
||||
|
||||
{node.summary?.returnsBlock.length ? <ReturnNode node={node.summary.returnsBlock} version={version} /> : null}
|
||||
|
||||
{node.summary?.seeBlocks.length ? <SeeNode node={node.summary.seeBlocks} version={version} /> : null}
|
||||
|
||||
<Outline node={node} />
|
||||
|
||||
{node.constructor?.parametersString ? <ConstructorNode node={node.constructor} version={version} /> : null}
|
||||
|
||||
{node.typeParameters?.length ? (
|
||||
<div className="flex flex-col gap-8">
|
||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||
<VscSymbolParameter aria-hidden className="flex-shrink-0" size={24} />
|
||||
Type Parameters
|
||||
</h2>
|
||||
<TypeParameterNode description node={node.typeParameters} version={version} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{node.parameters?.length ? (
|
||||
<div className="flex flex-col gap-8">
|
||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||
<VscSymbolParameter aria-hidden className="flex-shrink-0" size={24} />
|
||||
Parameters
|
||||
</h2>
|
||||
<ParameterNode description node={node.parameters} version={version} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{node.members?.properties?.length ? (
|
||||
<PropertyNode node={node.members.properties} packageName={packageName} version={version} />
|
||||
) : null}
|
||||
|
||||
{node.members?.events?.length ? (
|
||||
<div>
|
||||
<EventNode node={node.members.events} packageName={packageName} version={version} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{node.members?.methods?.length ? (
|
||||
<div>
|
||||
<MethodNode node={node.members.methods} packageName={packageName} version={version} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{node.members?.length ? <EnumMemberNode node={node.members} packageName={packageName} version={version} /> : null}
|
||||
|
||||
{node.unionMembers?.length ? <UnionMember node={node.unionMembers} version={version} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
44
apps/website/src/components/DocKind.tsx
Normal file
44
apps/website/src/components/DocKind.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
export function resolveNodeKind(kind: string) {
|
||||
switch (kind) {
|
||||
case 'Class':
|
||||
return {
|
||||
text: 'text-green-500',
|
||||
background: 'bg-green-500/20',
|
||||
};
|
||||
case 'Interface':
|
||||
return {
|
||||
text: 'text-amber-500',
|
||||
background: 'bg-amber-500/20',
|
||||
};
|
||||
case 'Function':
|
||||
return {
|
||||
text: 'text-blue-500',
|
||||
background: 'bg-blue-500/20',
|
||||
};
|
||||
case 'Enum':
|
||||
return {
|
||||
text: 'text-rose-500',
|
||||
background: 'bg-rose-500/20',
|
||||
};
|
||||
case 'TypeAlias':
|
||||
return {
|
||||
text: 'text-pink-500',
|
||||
background: 'bg-pink-500/20',
|
||||
};
|
||||
case 'Variable':
|
||||
return {
|
||||
text: 'text-purple-500',
|
||||
background: 'bg-purple-500/20',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
text: 'text-gray-500',
|
||||
background: 'bg-gray-500/20',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function DocKind({ background = false, node }: { readonly background?: boolean; readonly node: any }) {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return <span className={background ? `${kind.background} ${kind.text}` : kind.text}>{node.kind.toLowerCase()}</span>;
|
||||
}
|
||||
72
apps/website/src/components/DocNode.tsx
Normal file
72
apps/website/src/components/DocNode.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import Link from 'next/link';
|
||||
import { OverlayScrollbarsComponent } from './OverlayScrollbars';
|
||||
import { SyntaxHighlighter } from './SyntaxHighlighter';
|
||||
|
||||
export async function DocNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
||||
const createNode = (node: any, idx: number) => {
|
||||
switch (node.kind) {
|
||||
case 'PlainText':
|
||||
return <span key={`${node.text}-${idx}`}>{node.text}</span>;
|
||||
case 'LinkTag': {
|
||||
if (node.resolvedPackage) {
|
||||
return (
|
||||
<Link
|
||||
key={`${node.text}-${idx}`}
|
||||
className="font-mono text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={`/docs/packages/${node.resolvedPackage.packageName}/${version}/${node.uri}`}
|
||||
>
|
||||
{node.text}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (node.uri) {
|
||||
return (
|
||||
<a
|
||||
key={`${node.text}-${idx}`}
|
||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={node.uri}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{node.text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return <span key={`${node.text}-${idx}`}>{node.text}</span>;
|
||||
}
|
||||
|
||||
case 'CodeSpan':
|
||||
return (
|
||||
<code key={`${node.text}-${idx}`} className="font-mono text-sm">
|
||||
{node.text}
|
||||
</code>
|
||||
);
|
||||
|
||||
case 'FencedCode': {
|
||||
const { language, text } = node;
|
||||
|
||||
return (
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
options={{
|
||||
overflow: { y: 'hidden' },
|
||||
scrollbars: { autoHide: 'scroll', autoHideDelay: 500, autoHideSuspend: true, clickScroll: true },
|
||||
}}
|
||||
className="my-4 rounded-md border border-neutral-300 bg-neutral-100 dark:border-neutral-700 dark:bg-neutral-900"
|
||||
>
|
||||
<SyntaxHighlighter className="py-4 text-sm " lang={language} code={text} />
|
||||
</OverlayScrollbarsComponent>
|
||||
);
|
||||
}
|
||||
|
||||
case 'SoftBreak':
|
||||
return null;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return node?.map(createNode) ?? null;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export function DocumentationLink({ children, href }: PropsWithChildren<{ readonly href: string }>) {
|
||||
return (
|
||||
<a className="text-blurple" href={href} rel="external noreferrer noopener" target="_blank">
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
109
apps/website/src/components/EnumMemberNode.tsx
Normal file
109
apps/website/src/components/EnumMemberNode.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { VscSymbolEnumMember } from '@react-icons/all-files/vsc/VscSymbolEnumMember';
|
||||
import { Code2, LinkIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import { Badges } from './Badges';
|
||||
import { DeprecatedNode } from './DeprecatedNode';
|
||||
import { ExampleNode } from './ExampleNode';
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
import { InheritedFromNode } from './InheritedFromNode';
|
||||
import { ParameterNode } from './ParameterNode';
|
||||
import { ReturnNode } from './ReturnNode';
|
||||
import { SeeNode } from './SeeNode';
|
||||
import { SummaryNode } from './SummaryNode';
|
||||
|
||||
export async function EnumMemberNode({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||
<VscSymbolEnumMember aria-hidden className="flex-shrink-0" size={24} />
|
||||
Members
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col gap-8">
|
||||
{node.map((enumMember: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${enumMember.displayName}-${idx}`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<h3 id={enumMember.displayName} className="group scroll-mt-8 break-words font-mono font-semibold">
|
||||
<Badges node={enumMember} />
|
||||
<span>
|
||||
<Link
|
||||
href={`#${enumMember.displayName}`}
|
||||
className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block"
|
||||
>
|
||||
<LinkIcon aria-hidden size={16} />
|
||||
</Link>
|
||||
{enumMember.displayName}
|
||||
{enumMember.parameters?.length ? (
|
||||
<ParameterNode node={enumMember.parameters} version={version} />
|
||||
) : null}
|
||||
{enumMember.initializerExcerpt ? (
|
||||
<>
|
||||
{' = '}
|
||||
<ExcerptNode node={enumMember.initializerExcerpt} version={version} />
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<a
|
||||
aria-label="Open source file in new tab"
|
||||
className="min-w-min"
|
||||
href={
|
||||
enumMember.sourceLine ? `${enumMember.sourceURL}#L${enumMember.sourceLine}` : enumMember.sourceURL
|
||||
}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Code2
|
||||
aria-hidden
|
||||
size={20}
|
||||
className="text-neutral-500 hover:text-neutral-600 dark:text-neutral-400 dark:hover:text-neutral-300"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{enumMember.summary?.deprecatedBlock.length ? (
|
||||
<DeprecatedNode deprecatedBlock={enumMember.summary.deprecatedBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{enumMember.summary?.summarySection.length ? (
|
||||
<SummaryNode padding node={enumMember.summary.summarySection} version={version} />
|
||||
) : null}
|
||||
|
||||
{enumMember.summary?.exampleBlocks.length ? (
|
||||
<ExampleNode node={enumMember.summary.exampleBlocks} version={version} />
|
||||
) : null}
|
||||
|
||||
{enumMember.summary?.returnsBlock.length ? (
|
||||
<ReturnNode padding node={enumMember.summary.returnsBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{enumMember.inheritedFrom ? (
|
||||
<InheritedFromNode node={enumMember.inheritedFrom} packageName={packageName} version={version} />
|
||||
) : null}
|
||||
|
||||
{enumMember.summary?.seeBlocks.length ? (
|
||||
<SeeNode padding node={enumMember.summary.seeBlocks} version={version} />
|
||||
) : null}
|
||||
</div>
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
170
apps/website/src/components/EventNode.tsx
Normal file
170
apps/website/src/components/EventNode.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
||||
import { ChevronDown, ChevronUp, Code2 } from 'lucide-react';
|
||||
import { Badges } from './Badges';
|
||||
import { DeprecatedNode } from './DeprecatedNode';
|
||||
import { ExampleNode } from './ExampleNode';
|
||||
import { InheritedFromNode } from './InheritedFromNode';
|
||||
import { ParameterNode } from './ParameterNode';
|
||||
import { ReturnNode } from './ReturnNode';
|
||||
import { SeeNode } from './SeeNode';
|
||||
import { SummaryNode } from './SummaryNode';
|
||||
import { TypeParameterNode } from './TypeParameterNode';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ui/Tabs';
|
||||
|
||||
async function EventBodyNode({
|
||||
event,
|
||||
packageName,
|
||||
version,
|
||||
overload = false,
|
||||
}: {
|
||||
readonly event: any;
|
||||
readonly overload?: boolean;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<h3
|
||||
id={event.displayName}
|
||||
className={`${overload ? 'scroll-mt-16' : 'scroll-mt-8'} break-words font-mono font-semibold`}
|
||||
>
|
||||
<Badges node={event} /> {event.displayName}
|
||||
{event.typeParameters?.length ? (
|
||||
<>
|
||||
{'<'}
|
||||
<TypeParameterNode node={event.typeParameters} version={version} />
|
||||
{'>'}
|
||||
</>
|
||||
) : null}
|
||||
({event.parameters?.length ? <ParameterNode node={event.parameters} version={version} /> : null})
|
||||
</h3>
|
||||
|
||||
<a
|
||||
aria-label="Open source file in new tab"
|
||||
className="min-w-min"
|
||||
href={event.sourceLine ? `${event.sourceURL}#L${event.sourceLine}` : event.sourceURL}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Code2
|
||||
aria-hidden
|
||||
size={20}
|
||||
className="text-neutral-500 hover:text-neutral-600 dark:text-neutral-400 dark:hover:text-neutral-300"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{event.summary?.deprecatedBlock.length ? (
|
||||
<DeprecatedNode deprecatedBlock={event.summary.deprecatedBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{event.summary?.summarySection.length ? (
|
||||
<SummaryNode padding node={event.summary.summarySection} version={version} />
|
||||
) : null}
|
||||
|
||||
{event.summary?.exampleBlocks.length ? (
|
||||
<ExampleNode node={event.summary.exampleBlocks} version={version} />
|
||||
) : null}
|
||||
|
||||
{event.summary?.returnsBlock.length ? (
|
||||
<ReturnNode padding node={event.summary.returnsBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{event.inheritedFrom ? (
|
||||
<InheritedFromNode node={event.inheritedFrom} packageName={packageName} version={version} />
|
||||
) : null}
|
||||
|
||||
{event.summary?.seeBlocks.length ? <SeeNode padding node={event.summary.seeBlocks} version={version} /> : null}
|
||||
</div>
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function OverloadNode({
|
||||
event,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly event: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<Tabs className="flex flex-col gap-4">
|
||||
<TabList className="flex gap-2">
|
||||
{event.overloads.map((overload: any) => {
|
||||
return (
|
||||
<Tab
|
||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
||||
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm font-normal leading-none text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
||||
>
|
||||
<span>Overload {overload.overloadIndex}</span>
|
||||
</Tab>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
{event.overloads.map((overload: any) => {
|
||||
return (
|
||||
<TabPanel
|
||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
||||
className="flex flex-col gap-8"
|
||||
>
|
||||
<EventBodyNode overload event={overload} packageName={packageName} version={version} />
|
||||
</TabPanel>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
export async function EventNode({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscSymbolEvent aria-hidden className="flex-shrink-0" size={24} /> Events
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-8">
|
||||
{node.map((event: any) => {
|
||||
return event.overloads?.length ? (
|
||||
<OverloadNode
|
||||
key={`${event.displayName}-${event.overloadIndex}`}
|
||||
event={event}
|
||||
packageName={packageName}
|
||||
version={version}
|
||||
/>
|
||||
) : (
|
||||
<EventBodyNode
|
||||
key={`${event.displayName}-${event.overloadIndex}`}
|
||||
event={event}
|
||||
packageName={packageName}
|
||||
version={version}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
10
apps/website/src/components/ExampleNode.tsx
Normal file
10
apps/website/src/components/ExampleNode.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { DocNode } from './DocNode';
|
||||
|
||||
export async function ExampleNode({ node, version }: { readonly node: any; readonly version: string }) {
|
||||
return (
|
||||
<div className="break-words pl-4">
|
||||
<span className="font-semibold">Examples:</span>
|
||||
<DocNode node={node} version={version} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
66
apps/website/src/components/ExcerptNode.tsx
Normal file
66
apps/website/src/components/ExcerptNode.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks';
|
||||
|
||||
export async function ExcerptNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
||||
const createExcerpt = (excerpts: any) => {
|
||||
const excerpt = Array.isArray(excerpts) ? excerpts : excerpts.excerpts ?? [excerpts];
|
||||
|
||||
return (
|
||||
<span
|
||||
className={
|
||||
excerpts?.type === 'Extends' || excerpts?.type === 'Implements'
|
||||
? 'after:content-[",_"] last-of-type:after:content-none'
|
||||
: ''
|
||||
}
|
||||
>
|
||||
{excerpt.map((excerpt: any, idx: number) => {
|
||||
if (excerpt.resolvedItem) {
|
||||
return (
|
||||
<Link
|
||||
key={`${excerpt.resolvedItem.displayName}-${idx}`}
|
||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={`/docs/packages/${excerpt.resolvedItem.packageName}/${version}/${excerpt.resolvedItem.uri}`}
|
||||
>
|
||||
{excerpt.text}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
if (excerpt.href) {
|
||||
return (
|
||||
<a
|
||||
key={`${excerpt.text}-${idx}`}
|
||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={excerpt.href}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{excerpt.text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (excerpt.text in BuiltinDocumentationLinks) {
|
||||
const href = BuiltinDocumentationLinks[excerpt.text as keyof typeof BuiltinDocumentationLinks];
|
||||
return (
|
||||
<a
|
||||
key={`${excerpt.text}-${idx}`}
|
||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={href}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
{excerpt.text}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
return <Fragment key={`${excerpt.text}-${idx}`}>{excerpt.text}</Fragment>;
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
return node?.map(createExcerpt) ?? null;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import type { ApiPackage, Excerpt } from '@discordjs/api-extractor-model';
|
||||
import { ExcerptTokenKind } from '@discordjs/api-extractor-model';
|
||||
import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks';
|
||||
import { DISCORD_API_TYPES_DOCS_URL } from '~/util/constants';
|
||||
import { DocumentationLink } from './DocumentationLink';
|
||||
import { ItemLink } from './ItemLink';
|
||||
import { resolveCanonicalReference, resolveItemURI } from './documentation/util';
|
||||
|
||||
export interface ExcerptTextProps {
|
||||
/**
|
||||
* The package this excerpt is referenced from.
|
||||
*/
|
||||
readonly apiPackage: ApiPackage;
|
||||
|
||||
/**
|
||||
* The tokens to render.
|
||||
*/
|
||||
readonly excerpt: Excerpt;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that renders excerpt tokens from an api item.
|
||||
*/
|
||||
export function ExcerptText({ excerpt, apiPackage }: ExcerptTextProps) {
|
||||
return (
|
||||
<span>
|
||||
{excerpt.spannedTokens.map((token, idx) => {
|
||||
if (token.kind === ExcerptTokenKind.Reference) {
|
||||
if (token.text in BuiltinDocumentationLinks) {
|
||||
const href = BuiltinDocumentationLinks[token.text as keyof typeof BuiltinDocumentationLinks];
|
||||
return (
|
||||
<DocumentationLink key={`${token.text}-${idx}`} href={href}>
|
||||
{token.text}
|
||||
</DocumentationLink>
|
||||
);
|
||||
}
|
||||
|
||||
const source = token.canonicalReference?.source;
|
||||
const symbol = token.canonicalReference?.symbol;
|
||||
if (source && 'packageName' in source && source.packageName === 'discord-api-types' && symbol) {
|
||||
const { meaning, componentPath: path } = symbol;
|
||||
let href = DISCORD_API_TYPES_DOCS_URL;
|
||||
|
||||
// dapi-types doesn't have routes for class members
|
||||
// so we can assume this member is for an enum
|
||||
if (meaning === 'member' && path && 'parent' in path) {
|
||||
href += `/enum/${path.parent}#${path.component}`;
|
||||
} else if (meaning === 'type' || meaning === 'var') {
|
||||
href += `#${token.text}`;
|
||||
} else {
|
||||
href += `/${meaning}/${token.text}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<DocumentationLink key={`${token.text}-${idx}`} href={href}>
|
||||
{token.text}
|
||||
</DocumentationLink>
|
||||
);
|
||||
}
|
||||
|
||||
const resolved = token.canonicalReference
|
||||
? resolveCanonicalReference(token.canonicalReference, apiPackage)
|
||||
: null;
|
||||
|
||||
if (!resolved) {
|
||||
return token.text;
|
||||
}
|
||||
|
||||
return (
|
||||
<ItemLink
|
||||
className="text-blurple"
|
||||
itemURI={resolveItemURI(resolved.item)}
|
||||
key={`${resolved.item.displayName}-${resolved.item.containerKey}-${idx}`}
|
||||
packageName={resolved.package}
|
||||
version={resolved.version}
|
||||
>
|
||||
{token.text}
|
||||
</ItemLink>
|
||||
);
|
||||
}
|
||||
|
||||
return token.text.replace(/import\("discord-api-types(?:\/v\d+)?"\)\./, '');
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { FiCommand } from '@react-icons/all-files/fi/FiCommand';
|
||||
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
|
||||
import { VscMenu } from '@react-icons/all-files/vsc/VscMenu';
|
||||
import { VscSearch } from '@react-icons/all-files/vsc/VscSearch';
|
||||
import { Button } from 'ariakit/button';
|
||||
import type { Route } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import { useCmdK } from '~/contexts/cmdK';
|
||||
import { useNav } from '~/contexts/nav';
|
||||
|
||||
const ThemeSwitcher = dynamic(async () => import('./ThemeSwitcher'));
|
||||
|
||||
export default function Header() {
|
||||
const pathname = usePathname();
|
||||
const { setOpened } = useNav();
|
||||
const dialog = useCmdK();
|
||||
|
||||
const pathElements = useMemo(
|
||||
() =>
|
||||
pathname
|
||||
.split('/')
|
||||
.slice(1)
|
||||
.map((path, idx, original) => (
|
||||
<Link
|
||||
className="rounded outline-none hover:underline focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
href={`/${original.slice(0, idx + 1).join('/')}` as Route}
|
||||
key={`${path}-${idx}`}
|
||||
>
|
||||
{path}
|
||||
</Link>
|
||||
)),
|
||||
[pathname],
|
||||
);
|
||||
|
||||
const breadcrumbs = useMemo(
|
||||
() =>
|
||||
pathElements.flatMap((el, idx, array) => {
|
||||
if (idx === 0) {
|
||||
return (
|
||||
<Fragment key={`${el.key}-${idx}`}>
|
||||
<div className="mx-2">/</div>
|
||||
<div>{el}</div>
|
||||
<div className="mx-2">/</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (idx !== array.length - 1) {
|
||||
return (
|
||||
<Fragment key={`${el.key}-${idx}`}>
|
||||
<div>{el}</div>
|
||||
<div className="mx-2">/</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return <div key={`${el.key}-${idx}`}>{el}</div>;
|
||||
}),
|
||||
[pathElements],
|
||||
);
|
||||
|
||||
return (
|
||||
<header className="sticky top-4 z-20 border border-light-900 rounded-md bg-white/75 shadow backdrop-blur-md dark:border-dark-100 dark:bg-dark-600/75">
|
||||
<div className="block h-16 px-6">
|
||||
<div className="h-full flex flex-row place-content-between place-items-center gap-8">
|
||||
<Button
|
||||
aria-label="Menu"
|
||||
className="h-6 w-6 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-items-center border-0 rounded bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-none lg:hidden active:translate-y-px focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
onClick={() => setOpened((open) => !open)}
|
||||
>
|
||||
<VscMenu size={24} />
|
||||
</Button>
|
||||
<div className="hidden lg:flex lg:grow lg:flex-row lg:overflow-hidden">{breadcrumbs}</div>
|
||||
<Button
|
||||
as="div"
|
||||
className="hidden w-56 grow rounded bg-white px-4 py-2.5 outline-none md:block sm:grow-0 dark:bg-dark-800 focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
onClick={() => dialog?.toggle()}
|
||||
>
|
||||
<div className="flex flex-row place-items-center gap-4 md:justify-between">
|
||||
<VscSearch size={18} />
|
||||
<span className="opacity-65">Search...</span>
|
||||
<div className="hidden md:flex md:flex-row md:place-items-center md:gap-2 md:opacity-65">
|
||||
<FiCommand size={18} /> K
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
<div className="flex flex-row place-items-center gap-4">
|
||||
<Button
|
||||
as="div"
|
||||
className="h-6 w-6 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-items-center border-0 rounded bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-none md:hidden active:translate-y-px focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
onClick={() => dialog?.toggle()}
|
||||
>
|
||||
<VscSearch size={24} />
|
||||
</Button>
|
||||
<Button
|
||||
aria-label="GitHub"
|
||||
as="a"
|
||||
className="h-6 w-6 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-items-center border-0 rounded rounded-full bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-none active:translate-y-px focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
href="https://github.com/discordjs/discord.js"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<VscGithubInverted size={24} />
|
||||
</Button>
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
33
apps/website/src/components/InformationNode.tsx
Normal file
33
apps/website/src/components/InformationNode.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { FileCode2 } from 'lucide-react';
|
||||
import { Badges } from './Badges';
|
||||
import { DocKind } from './DocKind';
|
||||
import { InheritanceNode } from './InheritanceNode';
|
||||
|
||||
export async function InformationNode({ node, version }: { readonly node: any; readonly version: string }) {
|
||||
return (
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<div className="flex flex-col gap-1">
|
||||
<h1 className="text-xl">
|
||||
<DocKind node={node} /> <span className="break-words font-bold">{node.displayName}</span>
|
||||
</h1>
|
||||
{node.implements ? <InheritanceNode text="implements" node={node.implements} version={version} /> : null}
|
||||
{node.extends ? <InheritanceNode text="extends" node={node.extends} version={version} /> : null}
|
||||
<Badges node={node} />
|
||||
</div>
|
||||
|
||||
<a
|
||||
aria-label="Open source file in new tab"
|
||||
className="min-w-min"
|
||||
href={node.sourceLine ? `${node.sourceURL}#L${node.sourceLine}` : node.sourceURL}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<FileCode2
|
||||
aria-hidden
|
||||
size={20}
|
||||
className="text-neutral-500 hover:text-neutral-600 dark:text-neutral-400 dark:hover:text-neutral-300"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
20
apps/website/src/components/InheritanceNode.tsx
Normal file
20
apps/website/src/components/InheritanceNode.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
|
||||
export async function InheritanceNode({
|
||||
text,
|
||||
node,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly text: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2 className="inline-block min-w-min text-sm italic text-neutral-500 dark:text-neutral-400">{text}</h2>{' '}
|
||||
<span className="break-words font-mono text-sm">
|
||||
<ExcerptNode node={node} version={version} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import type { ApiDeclaredItem } from '@discordjs/api-extractor-model';
|
||||
import { ItemLink } from './ItemLink';
|
||||
import { resolveItemURI } from './documentation/util';
|
||||
|
||||
export function InheritanceText({ parent }: { readonly parent: ApiDeclaredItem }) {
|
||||
return (
|
||||
<span className="font-semibold">
|
||||
Inherited from{' '}
|
||||
<ItemLink
|
||||
className="rounded text-blurple font-mono outline-none focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
itemURI={resolveItemURI(parent)}
|
||||
>
|
||||
{parent.displayName}
|
||||
</ItemLink>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
23
apps/website/src/components/InheritedFromNode.tsx
Normal file
23
apps/website/src/components/InheritedFromNode.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Link from 'next/link';
|
||||
|
||||
export async function InheritedFromNode({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<p className="break-words pl-4">
|
||||
<span className="font-semibold">Inherited from:</span>{' '}
|
||||
<Link
|
||||
className="font-mono text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
||||
href={`/docs/packages/${packageName}/${version}/${node}`}
|
||||
>
|
||||
{node.slice(0, node.indexOf(':'))}
|
||||
</Link>
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { FiCheck } from '@react-icons/all-files/fi/FiCheck';
|
||||
import { FiCopy } from '@react-icons/all-files/fi/FiCopy';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { buttonVariants } from '~/styles/Button';
|
||||
|
||||
export function InstallButton() {
|
||||
const [interacted, setInteracted] = useState(false);
|
||||
const [state, copyToClipboard] = useCopyToClipboard();
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setInteracted(false), 2_000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [interacted]);
|
||||
|
||||
return (
|
||||
<button
|
||||
className={buttonVariants({
|
||||
variant: 'secondary',
|
||||
className: 'cursor-copy font-mono',
|
||||
})}
|
||||
onClick={() => {
|
||||
setInteracted(true);
|
||||
copyToClipboard('npm install discord.js');
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
<span className="text-blurple font-semibold">{'>'}</span> npm install discord.js{' '}
|
||||
{state.value && interacted ? (
|
||||
<FiCheck className="ml-1 inline-block text-green-500" />
|
||||
) : (
|
||||
<FiCopy className="ml-1 inline-block" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { LinkProps } from 'next/link';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useCurrentPathMeta } from '~/hooks/useCurrentPathMeta';
|
||||
|
||||
export interface ItemLinkProps<Route extends string> extends Omit<LinkProps<Route>, 'href'> {
|
||||
readonly className?: string;
|
||||
/**
|
||||
* The URI of the api item to link to. (e.g. `/RestManager`)
|
||||
*/
|
||||
readonly itemURI: string;
|
||||
|
||||
/**
|
||||
* The name of the package the item belongs to.
|
||||
*/
|
||||
readonly packageName?: string | undefined;
|
||||
|
||||
// TODO: This needs to be properly typed above but monkey-patching it for now.
|
||||
readonly title?: string | undefined;
|
||||
|
||||
/**
|
||||
* The version of the package the item belongs to.
|
||||
*/
|
||||
readonly version?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* A component that renders a link to an api item.
|
||||
*
|
||||
* @remarks
|
||||
* This component only needs the relative path to the item, and will automatically
|
||||
* generate the full path to the item client-side.
|
||||
*/
|
||||
export function ItemLink<Route extends string>(props: PropsWithChildren<ItemLinkProps<Route>>) {
|
||||
const pathname = usePathname();
|
||||
const { packageName, version } = useCurrentPathMeta();
|
||||
|
||||
if (!pathname) {
|
||||
throw new Error('ItemLink must be used inside a Next.js page. (e.g. /docs/packages/foo/main)');
|
||||
}
|
||||
|
||||
const { itemURI, packageName: pkgName, version: pkgVersion, ...linkProps } = props;
|
||||
|
||||
return <Link {...linkProps} href={`/docs/packages/${pkgName ?? packageName}/${pkgVersion ?? version}/${itemURI}`} />;
|
||||
}
|
||||
180
apps/website/src/components/MethodNode.tsx
Normal file
180
apps/website/src/components/MethodNode.tsx
Normal file
@@ -0,0 +1,180 @@
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Badges } from './Badges';
|
||||
import { DeprecatedNode } from './DeprecatedNode';
|
||||
import { ExampleNode } from './ExampleNode';
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
import { InheritedFromNode } from './InheritedFromNode';
|
||||
import { ParameterNode } from './ParameterNode';
|
||||
import { ReturnNode } from './ReturnNode';
|
||||
import { SeeNode } from './SeeNode';
|
||||
import { SummaryNode } from './SummaryNode';
|
||||
import { TypeParameterNode } from './TypeParameterNode';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
||||
import { Tab, TabList, TabPanel, Tabs } from './ui/Tabs';
|
||||
|
||||
async function MethodBodyNode({
|
||||
method,
|
||||
packageName,
|
||||
version,
|
||||
overload = false,
|
||||
}: {
|
||||
readonly method: any;
|
||||
readonly overload?: boolean;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<h3
|
||||
id={method.displayName}
|
||||
className={`${overload ? 'scroll-mt-16' : 'scroll-mt-8'} group break-words font-mono font-semibold`}
|
||||
>
|
||||
<Badges node={method} /> {method.displayName}
|
||||
<span>
|
||||
<Link href={`#${method.displayName}`} className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block">
|
||||
<LinkIcon aria-hidden size={16} />
|
||||
</Link>
|
||||
{method.typeParameters?.length ? (
|
||||
<>
|
||||
{'<'}
|
||||
<TypeParameterNode node={method.typeParameters} version={version} />
|
||||
{'>'}
|
||||
</>
|
||||
) : null}
|
||||
({method.parameters?.length ? <ParameterNode node={method.parameters} version={version} /> : null}
|
||||
) : <ExcerptNode node={method.returnTypeExcerpt} version={version} />
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<a
|
||||
aria-label="Open source file in new tab"
|
||||
className="min-w-min"
|
||||
href={method.sourceLine ? `${method.sourceURL}#L${method.sourceLine}` : method.sourceURL}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Code2
|
||||
aria-hidden
|
||||
size={20}
|
||||
className="text-neutral-500 hover:text-neutral-600 dark:text-neutral-400 dark:hover:text-neutral-300"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{method.summary?.deprecatedBlock.length ? (
|
||||
<DeprecatedNode deprecatedBlock={method.summary.deprecatedBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{method.summary?.summarySection.length ? (
|
||||
<SummaryNode padding node={method.summary.summarySection} version={version} />
|
||||
) : null}
|
||||
|
||||
{method.summary?.exampleBlocks.length ? (
|
||||
<ExampleNode node={method.summary.exampleBlocks} version={version} />
|
||||
) : null}
|
||||
|
||||
{method.summary?.returnsBlock.length ? (
|
||||
<ReturnNode padding node={method.summary.returnsBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{method.inheritedFrom ? (
|
||||
<InheritedFromNode node={method.inheritedFrom} packageName={packageName} version={version} />
|
||||
) : null}
|
||||
|
||||
{method.summary?.seeBlocks.length ? (
|
||||
<SeeNode padding node={method.summary.seeBlocks} version={version} />
|
||||
) : null}
|
||||
</div>
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
async function OverloadNode({
|
||||
method,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly method: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<Tabs className="flex flex-col gap-4">
|
||||
<TabList className="flex gap-2">
|
||||
{method.overloads.map((overload: any) => {
|
||||
return (
|
||||
<Tab
|
||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
||||
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm font-normal leading-none text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
||||
>
|
||||
<span>Overload {overload.overloadIndex}</span>
|
||||
</Tab>
|
||||
);
|
||||
})}
|
||||
</TabList>
|
||||
{method.overloads.map((overload: any) => {
|
||||
return (
|
||||
<TabPanel
|
||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
||||
className="flex flex-col gap-8"
|
||||
>
|
||||
<MethodBodyNode overload method={overload} packageName={packageName} version={version} />
|
||||
</TabPanel>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
export async function MethodNode({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} /> Methods
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-8">
|
||||
{node.map((method: any) => {
|
||||
return method.overloads?.length ? (
|
||||
<OverloadNode
|
||||
key={`${method.displayName}-${method.overloadIndex}`}
|
||||
method={method}
|
||||
packageName={packageName}
|
||||
version={version}
|
||||
/>
|
||||
) : (
|
||||
<MethodBodyNode
|
||||
key={`${method.displayName}-${method.overloadIndex}`}
|
||||
method={method}
|
||||
packageName={packageName}
|
||||
version={version}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Scrollbars } from 'react-custom-scrollbars-2';
|
||||
import { useNav } from '~/contexts/nav';
|
||||
import { Sidebar } from './Sidebar';
|
||||
import type { SidebarSectionItemData } from './Sidebar';
|
||||
|
||||
const PackageSelect = dynamic(async () => import('./PackageSelect'));
|
||||
const VersionSelect = dynamic(async () => import('./VersionSelect'));
|
||||
|
||||
export function Nav({
|
||||
members,
|
||||
versions,
|
||||
}: {
|
||||
readonly members: SidebarSectionItemData[];
|
||||
readonly versions: string[];
|
||||
}) {
|
||||
const { opened } = useNav();
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`dark:bg-dark-600/75 dark:border-dark-100 border-light-900 top-22 fixed bottom-4 left-4 right-4 z-20 mx-auto max-w-5xl rounded-md border bg-white/75 shadow backdrop-blur-md ${
|
||||
opened ? 'block' : 'hidden'
|
||||
} lg:min-w-xs lg:sticky lg:block lg:h-full lg:w-full lg:max-w-xs`}
|
||||
>
|
||||
<Scrollbars
|
||||
autoHide
|
||||
className="[&>div]:overscroll-none"
|
||||
hideTracksWhenNotNeeded
|
||||
renderThumbVertical={(props) => <div {...props} className="z-30 rounded bg-light-900 dark:bg-dark-100" />}
|
||||
renderTrackVertical={(props) => (
|
||||
<div {...props} className="absolute bottom-0.5 right-0.5 top-0.5 z-30 w-1.5 rounded" />
|
||||
)}
|
||||
universal
|
||||
>
|
||||
<div className="flex flex-col gap-4 p-3">
|
||||
<div className="flex flex-col gap-4">
|
||||
<PackageSelect />
|
||||
<VersionSelect versions={versions} />
|
||||
</div>
|
||||
<Sidebar members={members} />
|
||||
</div>
|
||||
</Scrollbars>
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
227
apps/website/src/components/Navigation.tsx
Normal file
227
apps/website/src/components/Navigation.tsx
Normal file
@@ -0,0 +1,227 @@
|
||||
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Link from 'next/link';
|
||||
import { fetchVersions } from '~/util/fetchVersions';
|
||||
import { resolveNodeKind } from './DocKind';
|
||||
import { NavigationItem } from './NavigationItem';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
||||
import { PackageSelect } from './ui/PackageSelect';
|
||||
import { SearchButton } from './ui/SearchButton';
|
||||
import { VersionSelect } from './ui/VersionSelect';
|
||||
|
||||
// eslint-disable-next-line promise/prefer-await-to-then
|
||||
const ThemeSwitch = dynamic(async () => import('~/components/ui/ThemeSwitch').then((mod) => mod.ThemeSwitch), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
export async function Navigation({
|
||||
className = '',
|
||||
packageName,
|
||||
version,
|
||||
drawer = false,
|
||||
}: {
|
||||
readonly className?: string;
|
||||
readonly drawer?: boolean;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
const isMainVersion = version === 'main';
|
||||
const fileContent = await fetch(
|
||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.sitemap.api.json`,
|
||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
||||
);
|
||||
const node = await fileContent.json();
|
||||
|
||||
const versions = await fetchVersions(packageName);
|
||||
|
||||
const groupedNodes = node.reduce((acc: any, node: any) => {
|
||||
(acc[node.kind.toLowerCase()] ||= []).push(node);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return (
|
||||
<aside className={`flex min-w-52 max-w-52 flex-col gap-2 lg:min-w-72 lg:max-w-72 ${className}`}>
|
||||
<div
|
||||
className={`sticky top-0 flex flex-col gap-4 pb-4 ${drawer ? 'bg-neutral-100 dark:bg-neutral-900' : 'bg-white dark:bg-[#121212]'}`}
|
||||
>
|
||||
<div className="flex flex-col gap-2 pt-px">
|
||||
<div className="flex place-content-between place-items-center p-1">
|
||||
<Link href={`/docs/packages/${packageName}/${version}`} className="text-xl font-bold">
|
||||
{packageName}
|
||||
</Link>
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
aria-label="GitHub"
|
||||
className="rounded-full"
|
||||
href="https://github.com/discordjs/discord.js"
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<VscGithubInverted aria-hidden size={24} />
|
||||
</Link>
|
||||
<ThemeSwitch />
|
||||
</div>
|
||||
</div>
|
||||
<PackageSelect packageName={packageName} />
|
||||
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
|
||||
<VersionSelect packageName={packageName} version={version} versions={versions} />
|
||||
</div>
|
||||
|
||||
<SearchButton />
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-col gap-4">
|
||||
{groupedNodes.class?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h4 className="font-semibold">Classes</h4>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{groupedNodes.class.map((node: any, idx: number) => {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return (
|
||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||
{node.kind[0]}
|
||||
</div>{' '}
|
||||
<span className="font-sans">{node.name}</span>
|
||||
</NavigationItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{groupedNodes.function?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h4 className="font-semibold">Functions</h4>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{groupedNodes.function.map((node: any, idx: number) => {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return (
|
||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||
{node.kind[0]}
|
||||
</div>{' '}
|
||||
<span className="font-sans">{node.name}</span>
|
||||
</NavigationItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{groupedNodes.enum?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h4 className="font-semibold">Enums</h4>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{groupedNodes.enum.map((node: any, idx: number) => {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return (
|
||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||
{node.kind[0]}
|
||||
</div>{' '}
|
||||
<span className="font-sans">{node.name}</span>
|
||||
</NavigationItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{groupedNodes.interface?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h4 className="font-semibold">Interfaces</h4>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{groupedNodes.interface.map((node: any, idx: number) => {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return (
|
||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||
{node.kind[0]}
|
||||
</div>{' '}
|
||||
<span className="font-sans">{node.name}</span>
|
||||
</NavigationItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{groupedNodes.typealias?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h4 className="font-semibold">Types</h4>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{groupedNodes.typealias.map((node: any, idx: number) => {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return (
|
||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||
{node.kind[0]}
|
||||
</div>{' '}
|
||||
<span className="font-sans">{node.name}</span>
|
||||
</NavigationItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{groupedNodes.variable?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h4 className="font-semibold">Variables</h4>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
{groupedNodes.variable.map((node: any, idx: number) => {
|
||||
const kind = resolveNodeKind(node.kind);
|
||||
return (
|
||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||
{node.kind[0]}
|
||||
</div>{' '}
|
||||
<span className="font-sans">{node.name}</span>
|
||||
</NavigationItem>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
34
apps/website/src/components/NavigationItem.tsx
Normal file
34
apps/website/src/components/NavigationItem.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
'use client';
|
||||
|
||||
import { useSetAtom } from 'jotai';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { isDrawerOpenAtom } from '~/stores/drawer';
|
||||
|
||||
export function NavigationItem({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}>) {
|
||||
const pathname = usePathname();
|
||||
const setDrawerOpen = useSetAtom(isDrawerOpenAtom);
|
||||
|
||||
const href = `/docs/packages/${packageName}/${version}/${node.href}`;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={`truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1 ${pathname === href ? 'bg-neutral-200 font-medium text-blurple dark:bg-neutral-800' : ''}`}
|
||||
href={href}
|
||||
title={node.name}
|
||||
onClick={() => setDrawerOpen(false)}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -1,32 +1,135 @@
|
||||
'use client';
|
||||
import { VscListSelection } from '@react-icons/all-files/vsc/VscListSelection';
|
||||
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
||||
|
||||
import { useOutline } from '~/contexts/outline';
|
||||
import { Scrollbars } from './Scrollbars';
|
||||
import { TableOfContentItems } from './TableOfContentItems';
|
||||
export async function Outline({ node }: { readonly node: any }) {
|
||||
const hasAny = node.members?.properties?.length || node.members?.events?.length || node.members?.methods?.length;
|
||||
|
||||
export function Outline() {
|
||||
const { members } = useOutline();
|
||||
return hasAny ? (
|
||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscListSelection aria-hidden className="flex-shrink-0" size={24} /> Table of contents
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
if (!members) {
|
||||
return null;
|
||||
}
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{node.members?.properties?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4 px-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscSymbolProperty aria-hidden className="flex-shrink-0" size={24} />
|
||||
Properties
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
return (
|
||||
<div className="lg:sticky lg:top-23 lg:h-[calc(100vh_-_105px)]">
|
||||
<aside className="fixed bottom-4 left-4 right-4 top-22 z-20 mx-auto hidden max-w-5xl border border-light-900 rounded-md bg-white/75 shadow backdrop-blur-md lg:sticky lg:block lg:h-full lg:max-w-xs lg:min-w-xs lg:w-full dark:border-dark-100 dark:bg-dark-600/75">
|
||||
<Scrollbars
|
||||
autoHide
|
||||
className="[&>div]:overscroll-none"
|
||||
hideTracksWhenNotNeeded
|
||||
renderThumbVertical={(props) => <div {...props} className="z-30 rounded bg-light-900 dark:bg-dark-100" />}
|
||||
renderTrackVertical={(props) => (
|
||||
<div {...props} className="absolute bottom-0.5 right-0.5 top-0.5 z-30 w-1.5 rounded" />
|
||||
)}
|
||||
universal
|
||||
>
|
||||
<TableOfContentItems serializedMembers={members} />
|
||||
</Scrollbars>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-2 px-4">
|
||||
{node.members.properties.map((property: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${property.displayName}-${idx}`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<Link
|
||||
href={`#${property.displayName}`}
|
||||
className="grow truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1"
|
||||
>
|
||||
{property.displayName}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{node.members?.events?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4 px-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscSymbolEvent aria-hidden className="flex-shrink-0" size={24} />
|
||||
Events
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-2 px-4">
|
||||
{node.members.events.map((event: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${event.displayName}-${idx}`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<Link
|
||||
href={`#${event.displayName}`}
|
||||
className="grow truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1"
|
||||
>
|
||||
{event.displayName}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
|
||||
{node.members?.methods?.length ? (
|
||||
<Collapsible className="flex flex-col gap-4 px-4" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} />
|
||||
Methods
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-2 px-4">
|
||||
{node.members.methods.map((method: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${method.displayName}-${idx}`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<Link
|
||||
href={`#${method.displayName}`}
|
||||
className="grow truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1"
|
||||
>
|
||||
{method.displayName}
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null}
|
||||
</div>
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
) : null;
|
||||
}
|
||||
|
||||
7
apps/website/src/components/OverlayScrollbars.tsx
Normal file
7
apps/website/src/components/OverlayScrollbars.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { OverlayScrollbars, ClickScrollPlugin } from 'overlayscrollbars';
|
||||
|
||||
OverlayScrollbars.plugin(ClickScrollPlugin);
|
||||
|
||||
export { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
@@ -1,98 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { VscChevronDown } from '@react-icons/all-files/vsc/VscChevronDown';
|
||||
import { VscVersions } from '@react-icons/all-files/vsc/VscVersions';
|
||||
import { Menu, MenuButton, MenuItem, useMenuState } from 'ariakit/menu';
|
||||
import type { PropsWithChildren, ReactNode } from 'react';
|
||||
import { useCallback, useMemo, useState, useEffect } from 'react';
|
||||
|
||||
export interface OverloadSwitcherProps {
|
||||
methodName: string;
|
||||
overloads: ReactNode[];
|
||||
}
|
||||
|
||||
export default function OverloadSwitcher({
|
||||
methodName,
|
||||
overloads,
|
||||
children,
|
||||
}: PropsWithChildren<{
|
||||
readonly methodName: string;
|
||||
readonly overloads: ReactNode[];
|
||||
}>) {
|
||||
const [hash, setHash] = useState(() => (typeof window === 'undefined' ? '' : window.location.hash));
|
||||
const hashChangeHandler = useCallback(() => {
|
||||
setHash(window.location.hash);
|
||||
}, []);
|
||||
const [overloadIndex, setOverloadIndex] = useState(1);
|
||||
const overloadedNode = overloads[overloadIndex - 1]!;
|
||||
const menu = useMenuState({ gutter: 8, sameWidth: true, fitViewport: true });
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('hashchange', hashChangeHandler);
|
||||
return () => {
|
||||
window.removeEventListener('hashchange', hashChangeHandler);
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (hash) {
|
||||
const elementId = hash.replace('#', '');
|
||||
const [name, idx] = elementId.split(':');
|
||||
if (name && methodName === name) {
|
||||
if (idx) {
|
||||
const hashOverload = Number.parseInt(idx, 10);
|
||||
const resolvedOverload = Math.max(Math.min(hashOverload, overloads.length), 1);
|
||||
setOverloadIndex(Number.isNaN(resolvedOverload) ? 1 : resolvedOverload);
|
||||
}
|
||||
|
||||
const element = document.querySelector(`[id^='${name}']`);
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [hash, methodName, overloads.length]);
|
||||
|
||||
const menuItems = useMemo(
|
||||
() =>
|
||||
overloads.map((_, idx) => (
|
||||
<MenuItem
|
||||
className="my-0.5 cursor-pointer rounded bg-white p-3 text-sm outline-none active:bg-light-800 dark:bg-dark-600 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-400 dark:hover:bg-dark-500"
|
||||
key={idx}
|
||||
onClick={() => setOverloadIndex(idx + 1)}
|
||||
>
|
||||
{`Overload ${idx + 1}`}
|
||||
</MenuItem>
|
||||
)),
|
||||
[overloads],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col place-items-start gap-2">
|
||||
<MenuButton
|
||||
className="mb-2 rounded bg-white p-3 outline-none active:bg-light-900 dark:bg-dark-400 hover:bg-light-800 focus:ring focus:ring-width-2 focus:ring-blurple md:-ml-2 dark:active:bg-dark-200 dark:hover:bg-dark-300"
|
||||
state={menu}
|
||||
>
|
||||
<div className="flex flex-row place-content-between place-items-center gap-2">
|
||||
<VscVersions size={20} />
|
||||
<div>
|
||||
<span className="font-semibold">{`Overload ${overloadIndex}`}</span>
|
||||
{` of ${overloads.length}`}
|
||||
</div>
|
||||
<VscChevronDown
|
||||
className={`transform transition duration-150 ease-in-out ${menu.open ? 'rotate-180' : 'rotate-0'}`}
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
</MenuButton>
|
||||
<Menu
|
||||
className="z-20 flex flex-col border border-light-800 rounded bg-white p-1 outline-none dark:border-dark-100 dark:bg-dark-600 focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
state={menu}
|
||||
>
|
||||
{menuItems}
|
||||
</Menu>
|
||||
{children}
|
||||
{overloadedNode}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { VscChevronDown } from '@react-icons/all-files/vsc/VscChevronDown';
|
||||
import { VscPackage } from '@react-icons/all-files/vsc/VscPackage';
|
||||
import { Menu, MenuButton, MenuItem, useMenuState } from 'ariakit/menu';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useMemo } from 'react';
|
||||
import { PACKAGES } from '~/util/constants';
|
||||
|
||||
export default function PackageSelect() {
|
||||
const pathname = usePathname();
|
||||
const packageName = pathname?.split('/').slice(3, 4)[0];
|
||||
|
||||
const packageMenu = useMenuState({
|
||||
gutter: 8,
|
||||
sameWidth: true,
|
||||
fitViewport: true,
|
||||
});
|
||||
|
||||
const packageMenuItems = useMemo(
|
||||
() =>
|
||||
PACKAGES.map((pkg, idx) => (
|
||||
<Link href={`/docs/packages/${pkg}/main`} key={`${pkg}-${idx}`}>
|
||||
<MenuItem
|
||||
className="my-0.5 rounded bg-white p-3 text-sm outline-none active:bg-light-800 dark:bg-dark-600 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-400 dark:hover:bg-dark-500"
|
||||
id={pkg}
|
||||
onClick={() => packageMenu.setOpen(false)}
|
||||
state={packageMenu}
|
||||
>
|
||||
{pkg}
|
||||
</MenuItem>
|
||||
</Link>
|
||||
)),
|
||||
[packageMenu],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuButton
|
||||
className="rounded bg-light-600 p-3 outline-none active:bg-light-800 dark:bg-dark-400 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-400 dark:hover:bg-dark-300"
|
||||
state={packageMenu}
|
||||
>
|
||||
<div className="flex flex-row place-content-between place-items-center">
|
||||
<div className="flex flex-row place-items-center gap-3">
|
||||
<VscPackage size={20} />
|
||||
<span className="font-semibold">{packageName}</span>
|
||||
</div>
|
||||
<VscChevronDown
|
||||
className={`transform transition duration-150 ease-in-out ${packageMenu.open ? 'rotate-180' : 'rotate-0'}`}
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
</MenuButton>
|
||||
<Menu
|
||||
className="z-20 flex flex-col border border-light-800 rounded bg-white p-1 outline-none dark:border-dark-100 dark:bg-dark-600 focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
state={packageMenu}
|
||||
>
|
||||
{packageMenuItems}
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export function Panel({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<div className="border-t-2 border-light-900 dark:border-dark-100" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
49
apps/website/src/components/ParameterNode.tsx
Normal file
49
apps/website/src/components/ParameterNode.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { LinkIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import { Badges } from './Badges';
|
||||
import { DocNode } from './DocNode';
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
|
||||
export async function ParameterNode({
|
||||
description = false,
|
||||
node,
|
||||
version,
|
||||
}: {
|
||||
readonly description?: boolean;
|
||||
readonly node: any;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`${description ? 'flex flex-col gap-8' : 'inline'}`}>
|
||||
{node.map((parameter: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${parameter.name}-${idx}`}>
|
||||
<div className={description ? 'group' : 'inline after:content-[",_"] last-of-type:after:content-none'}>
|
||||
<span className="font-mono font-semibold">
|
||||
{description ? (
|
||||
<Link href={`#${parameter.name}`} className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block">
|
||||
<LinkIcon aria-hidden size={16} />
|
||||
</Link>
|
||||
) : null}
|
||||
{description ? <Badges node={parameter} /> : null}
|
||||
{parameter.name}
|
||||
{parameter.isOptional ? '?' : ''}: <ExcerptNode node={parameter.typeExcerpt} version={version} />
|
||||
</span>
|
||||
{description && parameter.description?.length ? (
|
||||
<div className="mt-4 pl-4">
|
||||
<DocNode node={parameter.description} version={version} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{description ? (
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import type { ApiDocumentedItem, ApiParameterListMixin } from '@discordjs/api-extractor-model';
|
||||
import { useMemo } from 'react';
|
||||
import { resolveParameters } from '~/util/model';
|
||||
import { ExcerptText } from './ExcerptText';
|
||||
import { Table } from './Table';
|
||||
import { TSDoc } from './documentation/tsdoc/TSDoc';
|
||||
|
||||
const columnStyles = {
|
||||
Name: 'font-mono whitespace-nowrap',
|
||||
Type: 'font-mono whitespace-pre-wrap break-normal',
|
||||
};
|
||||
|
||||
export function ParameterTable({ item }: { readonly item: ApiDocumentedItem & ApiParameterListMixin }) {
|
||||
const params = resolveParameters(item);
|
||||
|
||||
const rows = useMemo(
|
||||
() =>
|
||||
params.map((param) => ({
|
||||
Name: param.isRest ? `...${param.name}` : param.name,
|
||||
Type: <ExcerptText excerpt={param.parameterTypeExcerpt} apiPackage={item.getAssociatedPackage()!} />,
|
||||
Optional: param.isOptional ? 'Yes' : 'No',
|
||||
Description: param.description ? <TSDoc item={item} tsdoc={param.description} /> : 'None',
|
||||
})),
|
||||
[item, params],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table columnStyles={columnStyles} columns={['Name', 'Type', 'Optional', 'Description']} rows={rows} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import type {
|
||||
ApiDeclaredItem,
|
||||
ApiItemContainerMixin,
|
||||
ApiProperty,
|
||||
ApiPropertySignature,
|
||||
} from '@discordjs/api-extractor-model';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { Badges } from './Badges';
|
||||
import { CodeHeading } from './CodeHeading';
|
||||
import { ExcerptText } from './ExcerptText';
|
||||
import { InheritanceText } from './InheritanceText';
|
||||
import { TSDoc } from './documentation/tsdoc/TSDoc';
|
||||
|
||||
export function Property({
|
||||
item,
|
||||
children,
|
||||
inheritedFrom,
|
||||
}: PropsWithChildren<{
|
||||
readonly inheritedFrom?: (ApiDeclaredItem & ApiItemContainerMixin) | undefined;
|
||||
readonly item: ApiProperty | ApiPropertySignature;
|
||||
}>) {
|
||||
const hasSummary = Boolean(item.tsdocComment?.summarySection);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col scroll-mt-30 gap-4" id={item.displayName}>
|
||||
<div className="flex flex-col gap-2 md:-ml-9">
|
||||
<Badges item={item} />
|
||||
<CodeHeading
|
||||
href={`#${item.displayName}`}
|
||||
sourceURL={item.sourceLocation.fileUrl}
|
||||
sourceLine={item.sourceLocation.fileLine}
|
||||
>
|
||||
{`${item.displayName}${item.isOptional ? '?' : ''}`}
|
||||
<span>:</span>
|
||||
{item.propertyTypeExcerpt.text ? (
|
||||
<ExcerptText excerpt={item.propertyTypeExcerpt} apiPackage={item.getAssociatedPackage()!} />
|
||||
) : null}
|
||||
</CodeHeading>
|
||||
</div>
|
||||
{hasSummary || inheritedFrom ? (
|
||||
<div className="mb-4 w-full flex flex-col gap-4">
|
||||
{item.tsdocComment ? <TSDoc item={item} tsdoc={item.tsdocComment} /> : null}
|
||||
{inheritedFrom ? <InheritanceText parent={inheritedFrom} /> : null}
|
||||
{children}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
import type {
|
||||
ApiDeclaredItem,
|
||||
ApiItem,
|
||||
ApiItemContainerMixin,
|
||||
ApiProperty,
|
||||
ApiPropertySignature,
|
||||
} from '@discordjs/api-extractor-model';
|
||||
import { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import { resolveMembers } from '~/util/members';
|
||||
import { Property } from './Property';
|
||||
|
||||
export function isPropertyLike(item: ApiItem): item is ApiProperty | ApiPropertySignature {
|
||||
return item.kind === ApiItemKind.Property || item.kind === ApiItemKind.PropertySignature;
|
||||
}
|
||||
|
||||
export function PropertyList({ item }: { readonly item: ApiItemContainerMixin }) {
|
||||
const members = resolveMembers(item, isPropertyLike);
|
||||
|
||||
const propertyItems = useMemo(
|
||||
() =>
|
||||
members.map((prop, idx) => {
|
||||
return (
|
||||
<Fragment key={`${prop.item.displayName}-${idx}`}>
|
||||
<Property
|
||||
inheritedFrom={prop.inherited as ApiDeclaredItem & ApiItemContainerMixin}
|
||||
item={prop.item as ApiProperty}
|
||||
/>
|
||||
<div className="border-t-2 border-light-900 dark:border-dark-100" />
|
||||
</Fragment>
|
||||
);
|
||||
}),
|
||||
[members],
|
||||
);
|
||||
|
||||
return <div className="flex flex-col gap-4">{propertyItems}</div>;
|
||||
}
|
||||
98
apps/website/src/components/PropertyNode.tsx
Normal file
98
apps/website/src/components/PropertyNode.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty';
|
||||
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import { Badges } from './Badges';
|
||||
import { DeprecatedNode } from './DeprecatedNode';
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
import { InheritedFromNode } from './InheritedFromNode';
|
||||
import { SeeNode } from './SeeNode';
|
||||
import { SummaryNode } from './SummaryNode';
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
||||
|
||||
export async function PropertyNode({
|
||||
node,
|
||||
packageName,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly packageName: string;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||
<VscSymbolProperty aria-hidden className="flex-shrink-0" size={24} />
|
||||
Properties
|
||||
</h2>
|
||||
<ChevronDown className='group-data-[state="open"]:hidden' aria-hidden size={24} />
|
||||
<ChevronUp className='group-data-[state="closed"]:hidden' aria-hidden size={24} />
|
||||
</CollapsibleTrigger>
|
||||
|
||||
<CollapsibleContent>
|
||||
<div className="flex flex-col gap-8">
|
||||
{node.map((property: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${property.displayName}-${idx}`}>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex place-content-between place-items-center">
|
||||
<h3
|
||||
id={property.displayName}
|
||||
className="group flex scroll-mt-8 flex-col gap-2 break-words font-mono font-semibold"
|
||||
>
|
||||
<Badges node={property} />
|
||||
<span>
|
||||
<Link
|
||||
href={`#${property.displayName}`}
|
||||
className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block"
|
||||
>
|
||||
<LinkIcon aria-hidden size={16} />
|
||||
</Link>
|
||||
{property.displayName}
|
||||
{property.isOptional ? '?' : ''} : <ExcerptNode node={property.typeExcerpt} version={version} />
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<a
|
||||
aria-label="Open source file in new tab"
|
||||
className="min-w-min"
|
||||
href={property.sourceLine ? `${property.sourceURL}#L${property.sourceLine}` : property.sourceURL}
|
||||
rel="external noreferrer noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<Code2
|
||||
aria-hidden
|
||||
size={20}
|
||||
className="text-neutral-500 hover:text-neutral-600 dark:text-neutral-400 dark:hover:text-neutral-300"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{property.summary?.deprecatedBlock.length ? (
|
||||
<DeprecatedNode deprecatedBlock={property.summary.deprecatedBlock} version={version} />
|
||||
) : null}
|
||||
|
||||
{property.summary?.summarySection.length ? (
|
||||
<SummaryNode padding node={property.summary.summarySection} version={version} />
|
||||
) : null}
|
||||
|
||||
{property.inheritedFrom ? (
|
||||
<InheritedFromNode node={property.inheritedFrom} packageName={packageName} version={version} />
|
||||
) : null}
|
||||
|
||||
{property.summary?.seeBlocks.length ? (
|
||||
<SeeNode padding node={property.summary.seeBlocks} version={version} />
|
||||
) : null}
|
||||
</div>
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
17
apps/website/src/components/ReturnNode.tsx
Normal file
17
apps/website/src/components/ReturnNode.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { DocNode } from './DocNode';
|
||||
|
||||
export async function ReturnNode({
|
||||
padding = false,
|
||||
node,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly padding?: boolean;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={`break-words ${padding ? 'pl-4' : ''}`}>
|
||||
<span className="font-semibold">Returns:</span> <DocNode node={node} version={version} />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { ScrollbarProps } from 'react-custom-scrollbars-2';
|
||||
import { Scrollbars as ReactScrollbars2 } from 'react-custom-scrollbars-2';
|
||||
|
||||
export function Scrollbars(props: ScrollbarProps) {
|
||||
return <ReactScrollbars2 {...props} />;
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Section as DJSSection, type SectionOptions } from '@discordjs/ui';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
export function Section(options: PropsWithChildren<SectionOptions>) {
|
||||
return <DJSSection {...options} />;
|
||||
}
|
||||
17
apps/website/src/components/SeeNode.tsx
Normal file
17
apps/website/src/components/SeeNode.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { DocNode } from './DocNode';
|
||||
|
||||
export async function SeeNode({
|
||||
padding = false,
|
||||
node,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly padding?: boolean;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={`break-words ${padding ? 'pl-4' : ''}`}>
|
||||
<span className="font-semibold">See also:</span> <DocNode node={node} version={version} />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import type { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolClass } from '@react-icons/all-files/vsc/VscSymbolClass';
|
||||
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';
|
||||
import { VscSymbolInterface } from '@react-icons/all-files/vsc/VscSymbolInterface';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { VscSymbolVariable } from '@react-icons/all-files/vsc/VscSymbolVariable';
|
||||
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||
import { useMemo } from 'react';
|
||||
import { useNav } from '~/contexts/nav';
|
||||
import { ItemLink } from './ItemLink';
|
||||
import { Section } from './Section';
|
||||
|
||||
export interface SidebarSectionItemData {
|
||||
href: string;
|
||||
kind: ApiItemKind;
|
||||
name: string;
|
||||
overloadIndex?: number | undefined;
|
||||
}
|
||||
|
||||
interface GroupedMembers {
|
||||
Classes: SidebarSectionItemData[];
|
||||
Enums: SidebarSectionItemData[];
|
||||
Functions: SidebarSectionItemData[];
|
||||
Interfaces: SidebarSectionItemData[];
|
||||
Types: SidebarSectionItemData[];
|
||||
Variables: SidebarSectionItemData[];
|
||||
}
|
||||
|
||||
function groupMembers(members: readonly SidebarSectionItemData[]): GroupedMembers {
|
||||
const Classes: SidebarSectionItemData[] = [];
|
||||
const Enums: SidebarSectionItemData[] = [];
|
||||
const Interfaces: SidebarSectionItemData[] = [];
|
||||
const Types: SidebarSectionItemData[] = [];
|
||||
const Variables: SidebarSectionItemData[] = [];
|
||||
const Functions: SidebarSectionItemData[] = [];
|
||||
|
||||
for (const member of members) {
|
||||
switch (member.kind) {
|
||||
case 'Class':
|
||||
Classes.push(member);
|
||||
break;
|
||||
case 'Enum':
|
||||
Enums.push(member);
|
||||
break;
|
||||
case 'Interface':
|
||||
Interfaces.push(member);
|
||||
break;
|
||||
case 'TypeAlias':
|
||||
Types.push(member);
|
||||
break;
|
||||
case 'Variable':
|
||||
Variables.push(member);
|
||||
break;
|
||||
case 'Function':
|
||||
Functions.push(member);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { Classes, Functions, Enums, Interfaces, Types, Variables };
|
||||
}
|
||||
|
||||
function resolveIcon(item: string) {
|
||||
switch (item) {
|
||||
case 'Classes':
|
||||
return <VscSymbolClass size={20} />;
|
||||
case 'Enums':
|
||||
return <VscSymbolEnum size={20} />;
|
||||
case 'Interfaces':
|
||||
return <VscSymbolInterface size={20} />;
|
||||
case 'Types':
|
||||
case 'Variables':
|
||||
return <VscSymbolVariable size={20} />;
|
||||
default:
|
||||
return <VscSymbolMethod size={20} />;
|
||||
}
|
||||
}
|
||||
|
||||
export function Sidebar({ members }: { readonly members: SidebarSectionItemData[] }) {
|
||||
const segment = useSelectedLayoutSegment();
|
||||
const { setOpened } = useNav();
|
||||
|
||||
const groupItems = useMemo(() => groupMembers(members), [members]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{(Object.keys(groupItems) as (keyof GroupedMembers)[])
|
||||
.filter((group) => groupItems[group].length)
|
||||
.map((group, idx) => (
|
||||
<Section
|
||||
buttonClassName="bg-light-600 hover:bg-light-700 active:bg-light-800 dark:bg-dark-400 dark:hover:bg-dark-300 dark:active:bg-dark-400 focus:ring-width-2 focus:ring-blurple rounded p-3 outline-none focus:ring z-10"
|
||||
icon={resolveIcon(group)}
|
||||
key={`${group}-${idx}`}
|
||||
title={group}
|
||||
>
|
||||
{groupItems[group].map((member, index) => (
|
||||
<ItemLink
|
||||
className={`dark:border-dark-100 border-light-800 focus:ring-width-2 focus:ring-blurple ml-5 flex flex-col border-l first:mt-1 p-[5px] pl-6 outline-none focus:rounded focus:border-0 focus:ring ${
|
||||
decodeURIComponent(segment ?? '') === member.href
|
||||
? 'bg-blurple text-white'
|
||||
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
|
||||
}`}
|
||||
itemURI={member.href}
|
||||
key={`${member.name}-${index}`}
|
||||
onClick={() => setOpened(false)}
|
||||
title={member.name}
|
||||
>
|
||||
<div className="flex flex-row place-items-center gap-2 lg:text-sm">
|
||||
<span className="truncate">{member.name}</span>
|
||||
{member.overloadIndex && member.overloadIndex > 1 ? (
|
||||
<span className="text-xs">{member.overloadIndex}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</ItemLink>
|
||||
))}
|
||||
</Section>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
import type { ApiPackage, Excerpt } from '@discordjs/api-extractor-model';
|
||||
import { ExcerptText } from './ExcerptText';
|
||||
|
||||
export function SignatureText({ excerpt, apiPackage }: { readonly apiPackage: ApiPackage; readonly excerpt: Excerpt }) {
|
||||
return (
|
||||
<h4 className="break-all text-lg font-bold font-mono">
|
||||
<ExcerptText excerpt={excerpt} apiPackage={apiPackage} />
|
||||
</h4>
|
||||
);
|
||||
}
|
||||
17
apps/website/src/components/SummaryNode.tsx
Normal file
17
apps/website/src/components/SummaryNode.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { DocNode } from './DocNode';
|
||||
|
||||
export async function SummaryNode({
|
||||
padding = false,
|
||||
node,
|
||||
version,
|
||||
}: {
|
||||
readonly node: any;
|
||||
readonly padding?: boolean;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<p className={`break-words ${padding ? 'pl-4' : ''}`}>
|
||||
<DocNode node={node} version={version} />
|
||||
</p>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,33 @@
|
||||
import { Code } from 'bright';
|
||||
import { getHighlighterCore } from 'shiki/core';
|
||||
import getWasm from 'shiki/wasm';
|
||||
|
||||
const highlighter = await getHighlighterCore({
|
||||
themes: [import('shiki/themes/github-light.mjs'), import('shiki/themes/github-dark-dimmed.mjs')],
|
||||
langs: [import('shiki/langs/typescript.mjs'), import('shiki/langs/javascript.mjs')],
|
||||
loadWasm: getWasm,
|
||||
});
|
||||
|
||||
export async function SyntaxHighlighter({
|
||||
lang,
|
||||
code,
|
||||
className = '',
|
||||
}: {
|
||||
readonly className?: string;
|
||||
readonly code: string;
|
||||
readonly lang: string;
|
||||
}) {
|
||||
const codeHTML = highlighter.codeToHtml(code.trim(), {
|
||||
lang,
|
||||
themes: {
|
||||
light: 'github-light',
|
||||
dark: 'github-dark-dimmed',
|
||||
},
|
||||
});
|
||||
|
||||
export async function SyntaxHighlighter(props: typeof Code) {
|
||||
return (
|
||||
<>
|
||||
<div data-theme="dark">
|
||||
<Code codeClassName="font-mono" lang={props.lang ?? 'typescript'} {...props} theme="github-dark-dimmed" />
|
||||
</div>
|
||||
<div className="[&_pre]:border [&_pre]:border-gray-300 [&_pre]:rounded-md" data-theme="light">
|
||||
<Code codeClassName="font-mono" lang={props.lang ?? 'typescript'} {...props} theme="min-light" />
|
||||
</div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<div className={className} dangerouslySetInnerHTML={{ __html: codeHTML }} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, type ReactNode } from 'react';
|
||||
|
||||
export function Table({
|
||||
rows,
|
||||
columns,
|
||||
columnStyles,
|
||||
}: {
|
||||
readonly columnStyles?: Record<string, string>;
|
||||
readonly columns: string[];
|
||||
readonly rows: Record<string, ReactNode>[];
|
||||
}) {
|
||||
const cols = useMemo(
|
||||
() =>
|
||||
columns.map((column, idx) => (
|
||||
<th
|
||||
className="break-normal border-b border-light-900 px-3 py-2 text-left text-sm dark:border-dark-100"
|
||||
key={`${column}-${idx}`}
|
||||
>
|
||||
{column}
|
||||
</th>
|
||||
)),
|
||||
[columns],
|
||||
);
|
||||
|
||||
const data = useMemo(
|
||||
() =>
|
||||
rows.map((row, idx) => (
|
||||
<tr className="[&>td]:last-of-type:border-0" key={idx}>
|
||||
{Object.entries(row).map(([colName, val], index) => (
|
||||
<td
|
||||
className={`border-light-900 dark:border-dark-100 border-b px-3 py-2 text-left text-sm ${
|
||||
columnStyles?.[colName] ?? ''
|
||||
}`}
|
||||
key={`${colName}-${index}`}
|
||||
>
|
||||
{val}
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
)),
|
||||
[columnStyles, rows],
|
||||
);
|
||||
|
||||
return (
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr>{cols}</tr>
|
||||
</thead>
|
||||
<tbody>{data}</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { VscListSelection } from '@react-icons/all-files/vsc/VscListSelection';
|
||||
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export interface TableOfContentsSerializedMethod {
|
||||
kind: 'Method' | 'MethodSignature';
|
||||
name: string;
|
||||
overloadIndex?: number;
|
||||
}
|
||||
|
||||
export interface TableOfContentsSerializedProperty {
|
||||
kind: 'Property' | 'PropertySignature';
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TableOfContentsSerializedEvent {
|
||||
kind: 'Event';
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type TableOfContentsSerialized =
|
||||
| TableOfContentsSerializedEvent
|
||||
| TableOfContentsSerializedMethod
|
||||
| TableOfContentsSerializedProperty;
|
||||
|
||||
export interface TableOfContentsItemProps {
|
||||
readonly serializedMembers: TableOfContentsSerialized[];
|
||||
}
|
||||
|
||||
export function TableOfContentsPropertyItem({ property }: { readonly property: TableOfContentsSerializedProperty }) {
|
||||
return (
|
||||
<a
|
||||
className="ml-[10px] border-l border-light-800 p-[5px] pl-6.5 text-sm outline-none focus:border-0 dark:border-dark-100 focus:rounded active:bg-light-800 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-100 dark:hover:bg-dark-200"
|
||||
href={`#${property.name}`}
|
||||
key={`${property.name}-${property.kind}`}
|
||||
title={property.name}
|
||||
>
|
||||
<span className="line-clamp-1">{property.name}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableOfContentsMethodItem({ method }: { readonly method: TableOfContentsSerializedMethod }) {
|
||||
if (method.overloadIndex && method.overloadIndex > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const key = `${method.name}${method.overloadIndex && method.overloadIndex > 1 ? `:${method.overloadIndex}` : ''}`;
|
||||
|
||||
return (
|
||||
<a
|
||||
className="ml-[10px] flex flex-row place-items-center gap-2 border-l border-light-800 p-[5px] pl-6.5 text-sm outline-none focus:border-0 dark:border-dark-100 focus:rounded active:bg-light-800 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-100 dark:hover:bg-dark-200"
|
||||
href={`#${key}`}
|
||||
key={key}
|
||||
title={method.name}
|
||||
>
|
||||
<span className="line-clamp-1">{method.name}</span>
|
||||
{method.overloadIndex && method.overloadIndex > 1 ? (
|
||||
<span className="text-xs">{method.overloadIndex}</span>
|
||||
) : null}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableOfContentsEventItem({ event }: { readonly event: TableOfContentsSerializedEvent }) {
|
||||
return (
|
||||
<a
|
||||
className="ml-[10px] border-l border-light-800 p-[5px] pl-6.5 text-sm outline-none focus:border-0 dark:border-dark-100 focus:rounded active:bg-light-800 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-100 dark:hover:bg-dark-200"
|
||||
href={`#${event.name}`}
|
||||
key={`${event.name}-${event.kind}`}
|
||||
title={event.name}
|
||||
>
|
||||
<span className="line-clamp-1">{event.name}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
export function TableOfContentItems({ serializedMembers }: TableOfContentsItemProps) {
|
||||
const propertyItems = useMemo(
|
||||
() =>
|
||||
serializedMembers
|
||||
.filter(
|
||||
(member): member is TableOfContentsSerializedProperty =>
|
||||
member.kind === 'Property' || member.kind === 'PropertySignature',
|
||||
)
|
||||
.map((prop, idx) => <TableOfContentsPropertyItem key={`${prop.name}-${prop.kind}-${idx}`} property={prop} />),
|
||||
[serializedMembers],
|
||||
);
|
||||
|
||||
const methodItems = useMemo(
|
||||
() =>
|
||||
serializedMembers
|
||||
.filter(
|
||||
(member): member is TableOfContentsSerializedMethod =>
|
||||
member.kind === 'Method' || member.kind === 'MethodSignature',
|
||||
)
|
||||
.map((member, idx) => (
|
||||
<TableOfContentsMethodItem
|
||||
key={`${member.name}${member.overloadIndex ? `:${member.overloadIndex}` : ''}-${idx}`}
|
||||
method={member}
|
||||
/>
|
||||
)),
|
||||
[serializedMembers],
|
||||
);
|
||||
|
||||
const eventItems = useMemo(
|
||||
() =>
|
||||
serializedMembers
|
||||
.filter((member): member is TableOfContentsSerializedEvent => member.kind === 'Event')
|
||||
.map((event, idx) => <TableOfContentsEventItem key={`${event.name}-${event.kind}-${idx}`} event={event} />),
|
||||
[serializedMembers],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col break-all p-3 pb-8">
|
||||
<div className="ml-2 mt-4 flex flex-row gap-2">
|
||||
<VscListSelection size={25} />
|
||||
<span className="font-semibold">Contents</span>
|
||||
</div>
|
||||
<div className="ml-2 mt-5.5 flex flex-col gap-2">
|
||||
{eventItems.length ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row place-items-center gap-4">
|
||||
<VscSymbolEvent size={20} />
|
||||
<div className="p-3 pl-0">
|
||||
<span className="font-semibold">Events</span>
|
||||
</div>
|
||||
</div>
|
||||
{eventItems}
|
||||
</div>
|
||||
) : null}
|
||||
{propertyItems.length ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row place-items-center gap-4">
|
||||
<VscSymbolProperty size={20} />
|
||||
<div className="p-3 pl-0">
|
||||
<span className="font-semibold">Properties</span>
|
||||
</div>
|
||||
</div>
|
||||
{propertyItems}
|
||||
</div>
|
||||
) : null}
|
||||
{methodItems.length ? (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-row place-items-center gap-4">
|
||||
<VscSymbolMethod size={20} />
|
||||
<div className="p-3 pl-0">
|
||||
<span className="font-semibold">Methods</span>
|
||||
</div>
|
||||
</div>
|
||||
{methodItems}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { VscColorMode } from '@react-icons/all-files/vsc/VscColorMode';
|
||||
import { Button } from 'ariakit/button';
|
||||
import { useTheme } from 'next-themes';
|
||||
|
||||
export default function ThemeSwitcher() {
|
||||
const { resolvedTheme, setTheme } = useTheme();
|
||||
const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||
|
||||
return (
|
||||
<Button
|
||||
aria-label="Toggle theme"
|
||||
className="h-6 w-6 flex flex-row transform-gpu cursor-pointer select-none appearance-none place-items-center border-0 rounded rounded-full bg-transparent p-0 text-sm font-semibold leading-none no-underline outline-none active:translate-y-px focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
onClick={() => toggleTheme()}
|
||||
>
|
||||
<VscColorMode size={24} />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
import type { ApiTypeParameterListMixin } from '@discordjs/api-extractor-model';
|
||||
import { useMemo } from 'react';
|
||||
import { ExcerptText } from './ExcerptText';
|
||||
import { Table } from './Table';
|
||||
import { TSDoc } from './documentation/tsdoc/TSDoc';
|
||||
|
||||
const rowElements = {
|
||||
Name: 'font-mono whitespace-nowrap',
|
||||
Constraints: 'font-mono whitespace-pre break-normal',
|
||||
Default: 'font-mono whitespace-pre break-normal',
|
||||
};
|
||||
|
||||
export function TypeParamTable({ item }: { readonly item: ApiTypeParameterListMixin }) {
|
||||
const rows = useMemo(
|
||||
() =>
|
||||
item.typeParameters.map((typeParam) => ({
|
||||
Name: typeParam.name,
|
||||
Constraints: <ExcerptText excerpt={typeParam.constraintExcerpt} apiPackage={item.getAssociatedPackage()!} />,
|
||||
Optional: typeParam.isOptional ? 'Yes' : 'No',
|
||||
Default: <ExcerptText excerpt={typeParam.defaultTypeExcerpt} apiPackage={item.getAssociatedPackage()!} />,
|
||||
Description: typeParam.tsdocTypeParamBlock ? (
|
||||
<TSDoc item={item} tsdoc={typeParam.tsdocTypeParamBlock.content} />
|
||||
) : (
|
||||
'None'
|
||||
),
|
||||
})),
|
||||
[item],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="overflow-x-auto">
|
||||
<Table
|
||||
columnStyles={rowElements}
|
||||
columns={['Name', 'Constraints', 'Optional', 'Default', 'Description']}
|
||||
rows={rows}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
67
apps/website/src/components/TypeParameterNode.tsx
Normal file
67
apps/website/src/components/TypeParameterNode.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { LinkIcon } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { Fragment } from 'react';
|
||||
import { Badges } from './Badges';
|
||||
import { DocNode } from './DocNode';
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
|
||||
export async function TypeParameterNode({
|
||||
description = false,
|
||||
node,
|
||||
version,
|
||||
}: {
|
||||
readonly description?: boolean;
|
||||
readonly node: any;
|
||||
readonly version: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`${description ? 'flex flex-col gap-8' : 'inline-block'}`}>
|
||||
{node.map((typeParameter: any, idx: number) => {
|
||||
return (
|
||||
<Fragment key={`${typeParameter.name}-${idx}`}>
|
||||
<div className={description ? '' : 'inline after:content-[",_"] last-of-type:after:content-none'}>
|
||||
<h3 id={typeParameter.name} className="group inline scroll-mt-8 break-words font-mono font-semibold">
|
||||
{description ? <Badges node={typeParameter} /> : null}
|
||||
<span>
|
||||
{description ? (
|
||||
<Link
|
||||
href={`#${typeParameter.name}`}
|
||||
className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block"
|
||||
>
|
||||
<LinkIcon aria-hidden size={16} />
|
||||
</Link>
|
||||
) : null}
|
||||
{typeParameter.name}
|
||||
{typeParameter.isOptional ? '?' : ''}
|
||||
{typeParameter.constraintsExcerpt.length ? (
|
||||
<>
|
||||
{' extends '}
|
||||
<ExcerptNode node={typeParameter.constraintsExcerpt} version={version} />
|
||||
</>
|
||||
) : null}
|
||||
{typeParameter.defaultExcerpt.length ? (
|
||||
<>
|
||||
{' = '}
|
||||
<ExcerptNode node={typeParameter.defaultExcerpt} version={version} />
|
||||
</>
|
||||
) : null}
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
{description && typeParameter.description?.length ? (
|
||||
<div className="pl-4">
|
||||
<DocNode node={typeParameter.description} version={version} />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{description ? (
|
||||
<div aria-hidden className="px-4">
|
||||
<div role="separator" className="h-[2px] bg-neutral-300 dark:bg-neutral-700" />
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
13
apps/website/src/components/UnionMember.tsx
Normal file
13
apps/website/src/components/UnionMember.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { ExcerptNode } from './ExcerptNode';
|
||||
|
||||
export async function UnionMember({ node, version }: { readonly node: any; readonly version: string }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">Union Members</h2>
|
||||
|
||||
<span className="flex flex-col gap-4 break-words font-mono text-sm">
|
||||
<ExcerptNode node={node} version={version} />
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { VscChevronDown } from '@react-icons/all-files/vsc/VscChevronDown';
|
||||
import { VscVersions } from '@react-icons/all-files/vsc/VscVersions';
|
||||
import { Menu, MenuButton, MenuItem, useMenuState } from 'ariakit/menu';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { useMemo } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
const isDev = process.env.NEXT_PUBLIC_LOCAL_DEV === 'true' ?? process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview';
|
||||
|
||||
export default function VersionSelect({ versions }: { readonly versions: string[] }) {
|
||||
const pathname = usePathname();
|
||||
const packageName = pathname?.split('/').slice(3, 4)[0];
|
||||
const branchName = pathname?.split('/').slice(4, 5)[0];
|
||||
|
||||
const { data } = useSWR<string[]>(packageName ? `/api/${packageName}/versions` : null, {
|
||||
fallbackData: versions,
|
||||
});
|
||||
|
||||
const versionMenu = useMenuState({
|
||||
gutter: 8,
|
||||
sameWidth: true,
|
||||
fitViewport: true,
|
||||
});
|
||||
|
||||
const versionMenuItems = useMemo(
|
||||
() =>
|
||||
data?.map((item, idx) => (
|
||||
<Link href={`/docs/packages/${packageName}/${isDev ? 'main' : item}`} key={`${item}-${idx}`}>
|
||||
<MenuItem
|
||||
className="my-0.5 rounded bg-white p-3 text-sm outline-none active:bg-light-800 dark:bg-dark-600 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-400 dark:hover:bg-dark-500"
|
||||
onClick={() => versionMenu.setOpen(false)}
|
||||
state={versionMenu}
|
||||
>
|
||||
{item}
|
||||
</MenuItem>
|
||||
</Link>
|
||||
)) ?? [],
|
||||
[data, packageName, versionMenu],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuButton
|
||||
className="rounded bg-light-600 p-3 outline-none active:bg-light-800 dark:bg-dark-400 hover:bg-light-700 focus:ring focus:ring-width-2 focus:ring-blurple dark:active:bg-dark-400 dark:hover:bg-dark-300"
|
||||
state={versionMenu}
|
||||
>
|
||||
<div className="flex flex-row place-content-between place-items-center">
|
||||
<div className="flex flex-row place-items-center gap-3">
|
||||
<VscVersions size={20} />
|
||||
<span className="font-semibold">{branchName}</span>
|
||||
</div>
|
||||
<VscChevronDown
|
||||
className={`transform transition duration-150 ease-in-out ${versionMenu.open ? 'rotate-180' : 'rotate-0'}`}
|
||||
size={20}
|
||||
/>
|
||||
</div>
|
||||
</MenuButton>
|
||||
<Menu
|
||||
className="z-20 flex flex-col border border-light-800 rounded bg-white p-1 outline-none dark:border-dark-100 dark:bg-dark-600 focus:ring focus:ring-width-2 focus:ring-blurple"
|
||||
state={versionMenu}
|
||||
>
|
||||
{versionMenuItems}
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
/**
|
||||
* Layout parent of documentation pages.
|
||||
*/
|
||||
export function Documentation({ children }: PropsWithChildren) {
|
||||
return <div className="w-full flex flex-col gap-4">{children}</div>;
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolClass } from '@react-icons/all-files/vsc/VscSymbolClass';
|
||||
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';
|
||||
import { VscSymbolInterface } from '@react-icons/all-files/vsc/VscSymbolInterface';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { VscSymbolVariable } from '@react-icons/all-files/vsc/VscSymbolVariable';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { SourceLink } from './SourceLink';
|
||||
|
||||
function generateIcon(kind: ApiItemKind) {
|
||||
switch (kind) {
|
||||
case ApiItemKind.Class:
|
||||
return <VscSymbolClass />;
|
||||
case ApiItemKind.Function:
|
||||
case ApiItemKind.Method:
|
||||
return <VscSymbolMethod />;
|
||||
case ApiItemKind.Enum:
|
||||
return <VscSymbolEnum />;
|
||||
case ApiItemKind.Interface:
|
||||
return <VscSymbolInterface />;
|
||||
case ApiItemKind.TypeAlias:
|
||||
case ApiItemKind.Variable:
|
||||
return <VscSymbolVariable />;
|
||||
default:
|
||||
return <VscSymbolMethod />;
|
||||
}
|
||||
}
|
||||
|
||||
export function Header({
|
||||
kind,
|
||||
name,
|
||||
sourceURL,
|
||||
sourceLine,
|
||||
}: PropsWithChildren<{
|
||||
readonly kind: ApiItemKind;
|
||||
readonly name: string;
|
||||
readonly sourceLine?: number | undefined;
|
||||
readonly sourceURL?: string | undefined;
|
||||
}>) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<h2 className="flex flex-row place-items-center justify-between gap-2 break-all text-2xl font-bold">
|
||||
<span className="row flex flex place-items-center gap-2">
|
||||
<span>{generateIcon(kind)}</span>
|
||||
{name}
|
||||
</span>
|
||||
{sourceURL ? <SourceLink sourceLine={sourceLine} sourceURL={sourceURL} /> : null}
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
import type { ApiClass, ApiInterface, Excerpt } from '@discordjs/api-extractor-model';
|
||||
import { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { ExcerptText } from '../ExcerptText';
|
||||
|
||||
export function HierarchyText({
|
||||
item,
|
||||
type,
|
||||
}: {
|
||||
readonly item: ApiClass | ApiInterface;
|
||||
readonly type: 'Extends' | 'Implements';
|
||||
}) {
|
||||
if (
|
||||
(item.kind === ApiItemKind.Class &&
|
||||
(item as ApiClass).extendsType === undefined &&
|
||||
(item as ApiClass).implementsTypes.length === 0) ||
|
||||
(item.kind === ApiItemKind.Interface && !(item as ApiInterface).extendsTypes)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let excerpts: Excerpt[];
|
||||
|
||||
if (item.kind === ApiItemKind.Class) {
|
||||
if (type === 'Implements') {
|
||||
if ((item as ApiClass).implementsTypes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
excerpts = (item as ApiClass).implementsTypes.map((typeExcerpt) => typeExcerpt.excerpt);
|
||||
} else {
|
||||
if (!(item as ApiClass).extendsType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
excerpts = [(item as ApiClass).extendsType!.excerpt];
|
||||
}
|
||||
} else {
|
||||
if ((item as ApiInterface).extendsTypes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
excerpts = (item as ApiInterface).extendsTypes.map((typeExcerpt) => typeExcerpt.excerpt);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{excerpts.map((excerpt, idx) => (
|
||||
<div className="flex flex-row place-items-center gap-4" key={`${type}-${idx}`}>
|
||||
<h3 className="text-xl font-bold">{type}</h3>
|
||||
<span className="break-all font-mono space-y-2">
|
||||
<ExcerptText excerpt={excerpt} apiPackage={item.getAssociatedPackage()!} />
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { ApiDeclaredItem, ApiItemContainerMixin } from '@discordjs/api-extractor-model';
|
||||
import { EventsSection } from './section/EventsSection';
|
||||
import { MethodsSection } from './section/MethodsSection';
|
||||
import { PropertiesSection } from './section/PropertiesSection';
|
||||
import { hasEvents, hasProperties, hasMethods } from './util';
|
||||
|
||||
export function Members({ item }: { readonly item: ApiDeclaredItem & ApiItemContainerMixin }) {
|
||||
return (
|
||||
<>
|
||||
{hasEvents(item) ? <EventsSection item={item} /> : null}
|
||||
{hasProperties(item) ? <PropertiesSection item={item} /> : null}
|
||||
{hasMethods(item) ? <MethodsSection item={item} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import type { ApiDeclaredItem } from '@discordjs/api-extractor-model';
|
||||
import { SyntaxHighlighter } from '../SyntaxHighlighter';
|
||||
import { Header } from './Header';
|
||||
import { SummarySection } from './section/SummarySection';
|
||||
|
||||
export interface ObjectHeaderProps {
|
||||
readonly item: ApiDeclaredItem;
|
||||
}
|
||||
|
||||
export function ObjectHeader({ item }: ObjectHeaderProps) {
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
kind={item.kind}
|
||||
name={item.displayName}
|
||||
sourceURL={item.sourceLocation.fileUrl}
|
||||
sourceLine={item.sourceLocation.fileLine}
|
||||
/>
|
||||
{/* @ts-expect-error async component */}
|
||||
<SyntaxHighlighter code={item.excerpt.text} />
|
||||
<SummarySection item={item} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import { VscFileCode } from '@react-icons/all-files/vsc/VscFileCode';
|
||||
|
||||
export function SourceLink({
|
||||
className,
|
||||
sourceURL,
|
||||
sourceLine,
|
||||
}: {
|
||||
readonly className?: string | undefined;
|
||||
readonly sourceLine?: number | undefined;
|
||||
readonly sourceURL?: string | undefined;
|
||||
}) {
|
||||
return (
|
||||
<a
|
||||
className={` text-blurple ${className}`}
|
||||
href={sourceLine ? `${sourceURL}#L${sourceLine}` : sourceURL}
|
||||
rel="external noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<VscFileCode />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { ApiConstructor } from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { CodeHeading } from '~/components/CodeHeading';
|
||||
import { ParameterTable } from '../../ParameterTable';
|
||||
import { TSDoc } from '../tsdoc/TSDoc';
|
||||
import { parametersString } from '../util';
|
||||
import { DocumentationSection } from './DocumentationSection';
|
||||
|
||||
export function ConstructorSection({ item }: { readonly item: ApiConstructor }) {
|
||||
return (
|
||||
<DocumentationSection icon={<VscSymbolMethod size={20} />} padded title="Constructor">
|
||||
<div className="flex flex-col gap-2">
|
||||
<CodeHeading
|
||||
sourceURL={item.sourceLocation.fileUrl}
|
||||
sourceLine={item.sourceLocation.fileLine}
|
||||
>{`constructor(${parametersString(item)})`}</CodeHeading>
|
||||
{item.tsdocComment ? <TSDoc item={item} tsdoc={item.tsdocComment} /> : null}
|
||||
<ParameterTable item={item} />
|
||||
</div>
|
||||
</DocumentationSection>
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { SectionOptions } from '@discordjs/ui';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { Section } from '../../Section';
|
||||
|
||||
export function DocumentationSection(opts: PropsWithChildren<SectionOptions & { separator?: boolean }>) {
|
||||
const { children, separator, ...props } = opts;
|
||||
|
||||
return (
|
||||
<Section {...props}>
|
||||
{children}
|
||||
{separator ? <div className="mt-6 border-t-2 border-light-900 dark:border-dark-100" /> : null}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import {
|
||||
ApiItemKind,
|
||||
type ApiEvent,
|
||||
type ApiItem,
|
||||
type ApiItemContainerMixin,
|
||||
type ApiDeclaredItem,
|
||||
} from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
||||
import { Fragment, useMemo } from 'react';
|
||||
import { Event } from '~/components/model/Event';
|
||||
import { resolveMembers } from '~/util/members';
|
||||
import { DocumentationSection } from './DocumentationSection';
|
||||
|
||||
function isEventLike(item: ApiItem): item is ApiEvent {
|
||||
return item.kind === ApiItemKind.Event;
|
||||
}
|
||||
|
||||
export function EventsSection({ item }: { readonly item: ApiItemContainerMixin }) {
|
||||
const members = resolveMembers(item, isEventLike);
|
||||
|
||||
const eventItems = useMemo(
|
||||
() =>
|
||||
members.map((event, idx) => {
|
||||
return (
|
||||
<Fragment key={`${event.item.displayName}-${idx}`}>
|
||||
<Event
|
||||
inheritedFrom={event.inherited as ApiDeclaredItem & ApiItemContainerMixin}
|
||||
item={event.item as ApiEvent}
|
||||
/>
|
||||
<div className="border-t-2 border-light-900 dark:border-dark-100" />
|
||||
</Fragment>
|
||||
);
|
||||
}),
|
||||
[members],
|
||||
);
|
||||
|
||||
return (
|
||||
<DocumentationSection icon={<VscSymbolEvent size={20} />} padded title="Events">
|
||||
<div className="flex flex-col gap-4">{eventItems}</div>
|
||||
</DocumentationSection>
|
||||
);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import type {
|
||||
ApiDeclaredItem,
|
||||
ApiItem,
|
||||
ApiItemContainerMixin,
|
||||
ApiMethod,
|
||||
ApiMethodSignature,
|
||||
} from '@discordjs/api-extractor-model';
|
||||
import { ApiItemKind } from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||
import { useMemo, Fragment } from 'react';
|
||||
import { resolveMembers } from '~/util/members';
|
||||
import { Method } from '../../model/method/Method';
|
||||
import { DocumentationSection } from './DocumentationSection';
|
||||
|
||||
function isMethodLike(item: ApiItem): item is ApiMethod | ApiMethodSignature {
|
||||
return (
|
||||
item.kind === ApiItemKind.Method ||
|
||||
(item.kind === ApiItemKind.MethodSignature && (item as ApiMethod).overloadIndex <= 1)
|
||||
);
|
||||
}
|
||||
|
||||
export function MethodsSection({ item }: { readonly item: ApiItemContainerMixin }) {
|
||||
const members = resolveMembers(item, isMethodLike);
|
||||
|
||||
const methodItems = useMemo(
|
||||
() =>
|
||||
members.map(({ item: method, inherited }) => (
|
||||
<Fragment
|
||||
key={`${method.displayName}${
|
||||
method.overloadIndex && method.overloadIndex > 1 ? `:${(method as ApiMethod).overloadIndex}` : ''
|
||||
}`}
|
||||
>
|
||||
<Method inheritedFrom={inherited as ApiDeclaredItem & ApiItemContainerMixin} method={method} />
|
||||
<div className="border-t-2 border-light-900 dark:border-dark-100" />
|
||||
</Fragment>
|
||||
)),
|
||||
[members],
|
||||
);
|
||||
|
||||
return (
|
||||
<DocumentationSection icon={<VscSymbolMethod size={20} />} padded title="Methods">
|
||||
<div className="flex flex-col gap-4">{methodItems}</div>
|
||||
</DocumentationSection>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { ApiDocumentedItem, ApiParameterListMixin } from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolParameter } from '@react-icons/all-files/vsc/VscSymbolParameter';
|
||||
import { ParameterTable } from '../../ParameterTable';
|
||||
import { DocumentationSection } from './DocumentationSection';
|
||||
|
||||
export function ParameterSection({ item }: { readonly item: ApiDocumentedItem & ApiParameterListMixin }) {
|
||||
return (
|
||||
<DocumentationSection icon={<VscSymbolParameter size={20} />} padded title="Parameters">
|
||||
<ParameterTable item={item} />
|
||||
</DocumentationSection>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import type { ApiItemContainerMixin } from '@discordjs/api-extractor-model';
|
||||
import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty';
|
||||
import { PropertyList } from '../../PropertyList';
|
||||
import { DocumentationSection } from './DocumentationSection';
|
||||
|
||||
export function PropertiesSection({ item }: { readonly item: ApiItemContainerMixin }) {
|
||||
return (
|
||||
<DocumentationSection icon={<VscSymbolProperty size={20} />} padded title="Properties">
|
||||
<PropertyList item={item} />
|
||||
</DocumentationSection>
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user