diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 4874a982200c..fee631a6c57a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -113,6 +113,24 @@ jobs: package: ${{ steps.extract-tag.outputs.package }} version: ${{ steps.extract-tag.outputs.semver }} + - name: Upload split documentation to blob storage + if: ${{ env.REF_TYPE == 'tag' && (!inputs.ref || inputs.ref == 'main') }} + env: + BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} + uses: ./packages/actions/src/uploadSplitDocumentation + with: + package: ${{ steps.extract-tag.outputs.package }} + version: ${{ steps.extract-tag.outputs.semver }} + + - name: Upload split documentation to blob storage + if: ${{ env.REF_TYPE == 'tag' && inputs.ref && inputs.ref != 'main' }} + env: + BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} + uses: ./main/packages/actions/src/uploadSplitDocumentation + with: + package: ${{ steps.extract-tag.outputs.package }} + version: ${{ steps.extract-tag.outputs.semver }} + - name: Move docs to correct directory if: ${{ env.REF_TYPE == 'tag' }} env: @@ -141,6 +159,18 @@ jobs: BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} uses: ./main/packages/actions/src/uploadDocumentation + - name: Upload split documentation to blob storage + if: ${{ env.REF_TYPE == 'branch' && (!inputs.ref || inputs.ref == 'main') }} + env: + BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} + uses: ./packages/actions/src/uploadSplitDocumentation + + - name: Upload split documentation to blob storage + if: ${{ env.REF_TYPE == 'branch' && inputs.ref && inputs.ref != 'main' }} + env: + BLOB_READ_WRITE_TOKEN: ${{ secrets.BLOB_READ_WRITE_TOKEN }} + uses: ./main/packages/actions/src/uploadSplitDocumentation + - name: Move docs to correct directory if: ${{ env.REF_TYPE == 'branch' }} run: | diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 192cc66f4ead..c29baf2a5b47 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -8,7 +8,6 @@ "eamodio.gitlens", "christian-kohler.npm-intellisense", "christian-kohler.path-intellisense", - "antfu.unocss", "unifiedjs.vscode-mdx" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 7f5067db8d03..7f3769bc0fc6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -26,6 +26,7 @@ "npm.packageManager": "pnpm", "typescript.tsdk": "node_modules/typescript/lib", "typescript.enablePromptUseWorkspaceTsdk": true, + "unocss.disable": true, "deno.enable": false, "deno.enablePaths": ["./packages/create-discord-bot/template/Deno"], "deno.lint": false, diff --git a/apps/guide/package.json b/apps/guide/package.json index ad2f82bd0d8c..c58725a211fa 100644 --- a/apps/guide/package.json +++ b/apps/guide/package.json @@ -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" diff --git a/apps/website/.env.development b/apps/website/.env.development index 305279a9d052..0333ed8215fc 100644 --- a/apps/website/.env.development +++ b/apps/website/.env.development @@ -1,2 +1 @@ NEXT_PUBLIC_LOCAL_DEV=true -METADATA_BASE_URL=http://localhost:3000 diff --git a/apps/website/.gitignore b/apps/website/.gitignore index 630f0f3c34cf..529c998d82d5 100644 --- a/apps/website/.gitignore +++ b/apps/website/.gitignore @@ -28,3 +28,5 @@ src/styles/unocss.css lighthouse-results .vercel + +old_src diff --git a/apps/website/.prettierrc.cjs b/apps/website/.prettierrc.cjs index f723230a05d4..f1c65b39d161 100644 --- a/apps/website/.prettierrc.cjs +++ b/apps/website/.prettierrc.cjs @@ -1,2 +1,5 @@ /** @type {import('prettier').Config} */ -module.exports = require('../../.prettierrc.json'); +module.exports = { + ...require('../../.prettierrc.json'), + plugins: ['prettier-plugin-tailwindcss'], +}; diff --git a/apps/website/next.config.mjs b/apps/website/next.config.mjs index a6fe69f2198f..a58114a0b386 100644 --- a/apps/website/next.config.mjs +++ b/apps/website/next.config.mjs @@ -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 [ diff --git a/apps/website/package.json b/apps/website/package.json index 1b9c2cb074ce..3088c38bae70 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -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" diff --git a/apps/website/postcss.config.cjs b/apps/website/postcss.config.cjs index 5d9116500fed..e873f1a4f235 100644 --- a/apps/website/postcss.config.cjs +++ b/apps/website/postcss.config.cjs @@ -1,5 +1,6 @@ module.exports = { plugins: { - '@unocss/postcss': {}, + tailwindcss: {}, + autoprefixer: {}, }, }; diff --git a/apps/website/src/app/_global-error.tsx b/apps/website/src/app/_global-error.tsx deleted file mode 100644 index a1f9bc5c728b..000000000000 --- a/apps/website/src/app/_global-error.tsx +++ /dev/null @@ -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 ( - - - -
-
-

500

-

Error.

-
-
-
- - - - ); -} diff --git a/apps/website/src/app/api/[package]/versions/route.ts b/apps/website/src/app/api/[package]/versions/route.ts deleted file mode 100644 index 53dc59cac4c7..000000000000 --- a/apps/website/src/app/api/[package]/versions/route.ts +++ /dev/null @@ -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)); -} diff --git a/apps/website/src/app/api/dynamic-open-graph.png/route.tsx b/apps/website/src/app/api/dynamic-open-graph.png/route.tsx deleted file mode 100644 index 5b9208f44798..000000000000 --- a/apps/website/src/app/api/dynamic-open-graph.png/route.tsx +++ /dev/null @@ -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 ( - - - - ); - case 'Enum': - return ( - - - - ); - case 'EnumMember': - return ( - - - - ); - case 'Interface': - return ( - - - - ); - case 'TypeAlias': - return ( - - - - ); - case 'Variable': - return ( - - - - ); - case 'Property': - return ( - - - - ); - default: - return ( - - - - ); - } -} - -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( - ( -
-
-
{pkg}
-
-
- {resolveIcon(kind as keyof typeof ApiItemKind)} -

- {name} -

-
-
-
- {props ? ( -
- {resolveIcon('Property', 36)} -
- {props} - Properties -
-
- ) : null} - {methods ? ( -
- {resolveIcon('Method', 36)} -
- {methods} - Methods -
-
- ) : null} - {members ? ( -
- {resolveIcon('EnumMember', 36)} -
- {members} - Members -
-
- ) : null} -
-
- discord.js -
-
-
-
-
- ), - { - 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, - }, - ); -} diff --git a/apps/website/src/app/api/open-graph.png/route.tsx b/apps/website/src/app/api/open-graph.png/route.tsx deleted file mode 100644 index 9ebc85d4fd62..000000000000 --- a/apps/website/src/app/api/open-graph.png/route.tsx +++ /dev/null @@ -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( - ( -
-
-
-
-
-
- The most popular -
- way to build Discord - bots. -
-
-
-
-
- ), - { - width: 1_200, - height: 630, - fonts: [{ name: 'Inter', data: fontData, weight: 900, style: 'normal' }], - }, - ); -} diff --git a/apps/website/src/app/docAPI.ts b/apps/website/src/app/docAPI.ts deleted file mode 100644 index be08ff226764..000000000000 --- a/apps/website/src/app/docAPI.ts +++ /dev/null @@ -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 => { - 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; - } -}; diff --git a/apps/website/src/app/docs/packages/[packageName]/[version]/[item]/opengraph-image.tsx b/apps/website/src/app/docs/packages/[packageName]/[version]/[item]/opengraph-image.tsx new file mode 100644 index 000000000000..c8c16e1f4401 --- /dev/null +++ b/apps/website/src/app/docs/packages/[packageName]/[version]/[item]/opengraph-image.tsx @@ -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( + ( +
+
+
{params.packageName}
+
+
+ {resolveKind(node.kind, 94)} +

+ {node.displayName} +

+
+
+
+ {node.members?.properties?.length ? ( +
+ {resolveKind('Property', 42)} +
+ {node.members.properties.length} + Properties +
+
+ ) : null} + {node.members?.events?.length ? ( +
+ {resolveKind('Method', 42)} +
+ {node.members.events.length} + Events +
+
+ ) : null} + {node.members?.methods?.length ? ( +
+ {resolveKind('Method', 42)} +
+ {node.members.methods.length} + Methods +
+
+ ) : null} + {node.members?.length ? ( +
+ {resolveKind('EnumMember', 42)} +
+ {node.members.length} + Members +
+
+ ) : null} +
+
+ discord.js +
+
+
+
+
+ ), + { + ...size, + }, + ); +} diff --git a/apps/website/src/app/docs/packages/[packageName]/[version]/[item]/page.tsx b/apps/website/src/app/docs/packages/[packageName]/[version]/[item]/page.tsx new file mode 100644 index 000000000000..1999b0fe3cfe --- /dev/null +++ b/apps/website/src/app/docs/packages/[packageName]/[version]/[item]/page.tsx @@ -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 { + 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 ( +
+ +
+ ); +} diff --git a/apps/website/src/app/docs/packages/[packageName]/[version]/layout.tsx b/apps/website/src/app/docs/packages/[packageName]/[version]/layout.tsx new file mode 100644 index 000000000000..2f020d7549d9 --- /dev/null +++ b/apps/website/src/app/docs/packages/[packageName]/[version]/layout.tsx @@ -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 { + 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(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 +
+
+ + + +
+
+ {children} +
+
+
+ + + +
+ +
+ ); +} diff --git a/apps/website/src/app/docs/packages/[packageName]/[version]/page.tsx b/apps/website/src/app/docs/packages/[packageName]/[version]/page.tsx new file mode 100644 index 000000000000..da5a9d94b12e --- /dev/null +++ b/apps/website/src/app/docs/packages/[packageName]/[version]/page.tsx @@ -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 ( +
+ +
+ ); +} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/[item]/loading.tsx b/apps/website/src/app/docs/packages/[package]/[version]/[item]/loading.tsx deleted file mode 100644 index 066281618908..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/[item]/loading.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export default function Loading() { - return ( -
- - - - -
Loading...
-
- ); -} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/[item]/not-found.tsx b/apps/website/src/app/docs/packages/[package]/[version]/[item]/not-found.tsx deleted file mode 100644 index 67ca19a67cd7..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/[item]/not-found.tsx +++ /dev/null @@ -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 ( -
-

404

-

Not found.

- - Take me back - -
- ); -} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx b/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx deleted file mode 100644 index 5503f049ee14..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/[item]/page.tsx +++ /dev/null @@ -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 ; - case 'Function': - return ; - case 'Interface': - return ; - case 'TypeAlias': - return ; - case 'Variable': - return ; - case 'Enum': - return ; - default: - return
Cannot render that item type
; - } -} - -export default async function Page({ params }: { params: ItemRouteParams }) { - const member = await fetchMember(params.package, params.version ?? 'main', params.item); - - if (!member) { - notFound(); - } - - return ( -
- -
- ); -} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/error.tsx b/apps/website/src/app/docs/packages/[package]/[version]/error.tsx deleted file mode 100644 index bcd4b230ba70..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/error.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client'; - -export default function Error({ error }: { readonly error: Error }) { - console.error(error); - - return ( -
-

500

-

Error.

-
- ); -} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/layout.tsx b/apps/website/src/app/docs/packages/[package]/[version]/layout.tsx deleted file mode 100644 index c0278993e4e2..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/layout.tsx +++ /dev/null @@ -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 ( - -
-
-
-
-
- -
- {children} -
-
- - -
-
- -
- ); -} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/page.tsx b/apps/website/src/app/docs/packages/[package]/[version]/page.tsx deleted file mode 100644 index ae328290cca3..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/page.tsx +++ /dev/null @@ -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
{content}
; -} diff --git a/apps/website/src/app/docs/packages/[package]/[version]/providers.tsx b/apps/website/src/app/docs/packages/[package]/[version]/providers.tsx deleted file mode 100644 index 03367f1761a5..000000000000 --- a/apps/website/src/app/docs/packages/[package]/[version]/providers.tsx +++ /dev/null @@ -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 ( - - - - {children} - - - - ); -} diff --git a/apps/website/src/app/docs/packages/[package]/loading.tsx b/apps/website/src/app/docs/packages/[package]/loading.tsx deleted file mode 100644 index bde40d687c23..000000000000 --- a/apps/website/src/app/docs/packages/[package]/loading.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '~/app/loading'; diff --git a/apps/website/src/app/docs/packages/[package]/page.tsx b/apps/website/src/app/docs/packages/[package]/page.tsx deleted file mode 100644 index a92cabe11371..000000000000 --- a/apps/website/src/app/docs/packages/[package]/page.tsx +++ /dev/null @@ -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 ( -
-

Select a version:

-
- {data.map((version, idx) => ( - -
-
- -

{version}

-
- -
- - )) ?? null} -
- - Go back - -
- ); -} diff --git a/apps/website/src/app/docs/packages/loading.tsx b/apps/website/src/app/docs/packages/loading.tsx deleted file mode 100644 index bde40d687c23..000000000000 --- a/apps/website/src/app/docs/packages/loading.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from '~/app/loading'; diff --git a/apps/website/src/app/docs/packages/page.tsx b/apps/website/src/app/docs/packages/page.tsx deleted file mode 100644 index 6eb99fb767fe..000000000000 --- a/apps/website/src/app/docs/packages/page.tsx +++ /dev/null @@ -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 ( -
-

Select a package:

-
- {PACKAGES.map((pkg, idx) => ( - -
-
- -

{pkg}

-
- -
- - ))} - -
-
- -

discord-api-types

-
- -
-
-
- - Go back - -
- ); -} diff --git a/apps/website/src/app/error.tsx b/apps/website/src/app/error.tsx deleted file mode 100644 index 0fb52ed124d9..000000000000 --- a/apps/website/src/app/error.tsx +++ /dev/null @@ -1,12 +0,0 @@ -'use client'; - -export default function Error({ error }: { readonly error: Error }) { - console.error(error); - - return ( -
-

500

-

Error.

-
- ); -} diff --git a/apps/website/src/app/layout.tsx b/apps/website/src/app/layout.tsx index 5c3248efa2ee..82708f046296 100644 --- a/apps/website/src/app/layout.tsx +++ b/apps/website/src/app/layout.tsx @@ -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 ( - - + + + {children} diff --git a/apps/website/src/app/loading.tsx b/apps/website/src/app/loading.tsx deleted file mode 100644 index 8852a454635f..000000000000 --- a/apps/website/src/app/loading.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export default function Loading() { - return ( -
- - - - -
Loading...
-
- ); -} diff --git a/apps/website/src/app/not-found.tsx b/apps/website/src/app/not-found.tsx deleted file mode 100644 index d976c343b605..000000000000 --- a/apps/website/src/app/not-found.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Route } from 'next'; -import Link from 'next/link'; - -export default function NotFound() { - return ( -
-

404

-

Not found.

- - Take me back - -
- ); -} diff --git a/apps/website/src/app/opengraph-image.tsx b/apps/website/src/app/opengraph-image.tsx new file mode 100644 index 000000000000..0429e4eece43 --- /dev/null +++ b/apps/website/src/app/opengraph-image.tsx @@ -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( + ( +
+
+
+
+
+
+ The most popular +
+ way to build Discord + bots. +
+
+
+
+
+ ), + { + ...size, + }, + ); +} diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index 3e5d3d780e2f..d367dbf2b079 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -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 ( -
-
-
-
-

- The most popular way to build - Discord bots. -

-

{DESCRIPTION}

-
- - Docs - - - Guide - - - GitHub - -
- -
-
-
+
+
+

+ The most popular way to build + Discord bots. +

+

{DESCRIPTION}

+ + + + +
+ +
); diff --git a/apps/website/src/app/providers.tsx b/apps/website/src/app/providers.tsx index 4a90036e52eb..fbc910a5d1d5 100644 --- a/apps/website/src/app/providers.tsx +++ b/apps/website/src/app/providers.tsx @@ -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 {children}; + return ( + // eslint-disable-next-line @typescript-eslint/unbound-method + + + {children} + + + ); } diff --git a/apps/website/src/assets/fonts/Inter-Black.ttf b/apps/website/src/assets/fonts/Inter-Black.ttf deleted file mode 100644 index 5aecf7dc414e..000000000000 Binary files a/apps/website/src/assets/fonts/Inter-Black.ttf and /dev/null differ diff --git a/apps/website/src/assets/fonts/Inter-Bold.ttf b/apps/website/src/assets/fonts/Inter-Bold.ttf deleted file mode 100644 index 8e82c70d1081..000000000000 Binary files a/apps/website/src/assets/fonts/Inter-Bold.ttf and /dev/null differ diff --git a/apps/website/src/assets/fonts/Inter-Regular.ttf b/apps/website/src/assets/fonts/Inter-Regular.ttf deleted file mode 100644 index 8d4eebf20665..000000000000 Binary files a/apps/website/src/assets/fonts/Inter-Regular.ttf and /dev/null differ diff --git a/apps/website/src/components/Anchor.tsx b/apps/website/src/components/Anchor.tsx deleted file mode 100644 index b425b8d15e73..000000000000 --- a/apps/website/src/components/Anchor.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { FiLink } from '@react-icons/all-files/fi/FiLink'; - -export function Anchor({ href }: { readonly href: string }) { - return ( - - - - ); -} diff --git a/apps/website/src/components/Badges.tsx b/apps/website/src/components/Badges.tsx index 4da25e88e570..b040aa0ebc1e 100644 --- a/apps/website/src/components/Badges.tsx +++ b/apps/website/src/components/Badges.tsx @@ -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 ( {children} ); } -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 ? ( -
- {isDeprecated ? Deprecated : null} - {isProtected ? Protected : null} - {isStatic ? Static : null} - {isAbstract ? Abstract : null} - {isReadonly ? Readonly : null} +
+ {isDeprecated ? ( + + deprecated + + ) : null} + {isProtected ? protected : null} + {isStatic ? static : null} + {isAbstract ? abstract : null} + {isReadonly ? readonly : null} + {isOptional ? optional : null}
) : null; } diff --git a/apps/website/src/components/CmdK.tsx b/apps/website/src/components/CmdK.tsx deleted file mode 100644 index 05f44a9f77e1..000000000000 --- a/apps/website/src/components/CmdK.tsx +++ /dev/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 ; - case 'Enum': - return ; - case 'Interface': - return ; - case 'Property': - return ; - case 'TypeAlias': - return ; - case 'Variable': - return ; - case 'Event': - return ; - default: - return ; - } -} - -export function CmdKDialog() { - const pathname = usePathname(); - const router = useRouter(); - const dialog = useCmdK(); - const [search, setSearch] = useState(''); - const [searchResults, setSearchResults] = useState([]); - - const packageName = pathname?.split('/').slice(3, 4)[0]; - const branchName = pathname?.split('/').slice(4, 5)[0]; - - const searchResultItems = useMemo( - () => - searchResults?.map((item, idx) => ( - { - router.push(item.path); - dialog!.setOpen(false); - }} - value={`${item.id}`} - > -
-
- {resolveIcon(item.kind)} -
-

{item.name}

-
{item.summary}
-
- {item.path} -
-
-
- -
-
- )) ?? [], - // 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 ( - - - - - No results found - {search ? searchResultItems : null} - - - - ); -} diff --git a/apps/website/src/components/CodeHeading.tsx b/apps/website/src/components/CodeHeading.tsx deleted file mode 100644 index 655de27f3480..000000000000 --- a/apps/website/src/components/CodeHeading.tsx +++ /dev/null @@ -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 ( -
-
- {href ? : null} - {children} -
- {sourceURL ? : null} -
- ); -} diff --git a/apps/website/src/components/ConstructorNode.tsx b/apps/website/src/components/ConstructorNode.tsx new file mode 100644 index 000000000000..3ff4e3c20083 --- /dev/null +++ b/apps/website/src/components/ConstructorNode.tsx @@ -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 ( +
+

+ + Constructors +

+ +
+

+ {/* constructor({parsedContent.constructor.parametersString}) */} + + + + constructor({node.parameters?.length ? : null}) +

+ + + + +
+ + {node.summary?.summarySection.length ? ( + + ) : null} + +
+
+
+
+ ); +} diff --git a/apps/website/src/components/DeprecatedNode.tsx b/apps/website/src/components/DeprecatedNode.tsx new file mode 100644 index 000000000000..414639d1f2ef --- /dev/null +++ b/apps/website/src/components/DeprecatedNode.tsx @@ -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 ( + +

+ +

+
+ ); +} diff --git a/apps/website/src/components/DocItem.tsx b/apps/website/src/components/DocItem.tsx new file mode 100644 index 000000000000..ce4d7b10425d --- /dev/null +++ b/apps/website/src/components/DocItem.tsx @@ -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 ( + + + {node.overloads.map((overload: any) => { + return ( + + Overload {overload.overloadIndex} + + ); + })} + + {node.overloads.map((overload: any) => { + return ( + + + + ); + })} + + ); +} + +export function DocItem({ + node, + packageName, + version, +}: { + readonly node: any; + readonly packageName: string; + readonly version: string; +}) { + if (node.overloads?.length) { + return ; + } + + return ( + <> + + + + + + + {node.summary?.deprecatedBlock.length ? ( + + ) : null} + + {node.summary?.summarySection ? : null} + + {node.summary?.returnsBlock.length ? : null} + + {node.summary?.seeBlocks.length ? : null} + + + + {node.constructor?.parametersString ? : null} + + {node.typeParameters?.length ? ( +
+

+ + Type Parameters +

+ +
+ ) : null} + + {node.parameters?.length ? ( +
+

+ + Parameters +

+ +
+ ) : null} + + {node.members?.properties?.length ? ( + + ) : null} + + {node.members?.events?.length ? ( +
+ +
+ ) : null} + + {node.members?.methods?.length ? ( +
+ +
+ ) : null} + + {node.members?.length ? : null} + + {node.unionMembers?.length ? : null} + + ); +} diff --git a/apps/website/src/components/DocKind.tsx b/apps/website/src/components/DocKind.tsx new file mode 100644 index 000000000000..47ca84732964 --- /dev/null +++ b/apps/website/src/components/DocKind.tsx @@ -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 {node.kind.toLowerCase()}; +} diff --git a/apps/website/src/components/DocNode.tsx b/apps/website/src/components/DocNode.tsx new file mode 100644 index 000000000000..112200c758db --- /dev/null +++ b/apps/website/src/components/DocNode.tsx @@ -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 {node.text}; + case 'LinkTag': { + if (node.resolvedPackage) { + return ( + + {node.text} + + ); + } + + if (node.uri) { + return ( + + {node.text} + + ); + } + + return {node.text}; + } + + case 'CodeSpan': + return ( + + {node.text} + + ); + + case 'FencedCode': { + const { language, text } = node; + + return ( + + + + ); + } + + case 'SoftBreak': + return null; + default: + return null; + } + }; + + return node?.map(createNode) ?? null; +} diff --git a/apps/website/src/components/DocumentationLink.tsx b/apps/website/src/components/DocumentationLink.tsx deleted file mode 100644 index bef2aa71ac38..000000000000 --- a/apps/website/src/components/DocumentationLink.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -export function DocumentationLink({ children, href }: PropsWithChildren<{ readonly href: string }>) { - return ( - - {children} - - ); -} diff --git a/apps/website/src/components/EnumMemberNode.tsx b/apps/website/src/components/EnumMemberNode.tsx new file mode 100644 index 000000000000..06f5f9a13e34 --- /dev/null +++ b/apps/website/src/components/EnumMemberNode.tsx @@ -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 ( +
+

+ + Members +

+ +
+ {node.map((enumMember: any, idx: number) => { + return ( + +
+
+

+ + + + + + {enumMember.displayName} + {enumMember.parameters?.length ? ( + + ) : null} + {enumMember.initializerExcerpt ? ( + <> + {' = '} + + + ) : null} + +

+ + + + +
+ + {enumMember.summary?.deprecatedBlock.length ? ( + + ) : null} + + {enumMember.summary?.summarySection.length ? ( + + ) : null} + + {enumMember.summary?.exampleBlocks.length ? ( + + ) : null} + + {enumMember.summary?.returnsBlock.length ? ( + + ) : null} + + {enumMember.inheritedFrom ? ( + + ) : null} + + {enumMember.summary?.seeBlocks.length ? ( + + ) : null} +
+
+
+
+ + ); + })} +
+
+ ); +} diff --git a/apps/website/src/components/EventNode.tsx b/apps/website/src/components/EventNode.tsx new file mode 100644 index 000000000000..f9a7e109714d --- /dev/null +++ b/apps/website/src/components/EventNode.tsx @@ -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 ( + <> +
+
+

+ {event.displayName} + {event.typeParameters?.length ? ( + <> + {'<'} + + {'>'} + + ) : null} + ({event.parameters?.length ? : null}) +

+ + + + +
+ + {event.summary?.deprecatedBlock.length ? ( + + ) : null} + + {event.summary?.summarySection.length ? ( + + ) : null} + + {event.summary?.exampleBlocks.length ? ( + + ) : null} + + {event.summary?.returnsBlock.length ? ( + + ) : null} + + {event.inheritedFrom ? ( + + ) : null} + + {event.summary?.seeBlocks.length ? : null} +
+
+
+
+ + ); +} + +async function OverloadNode({ + event, + packageName, + version, +}: { + readonly event: any; + readonly packageName: string; + readonly version: string; +}) { + return ( + + + {event.overloads.map((overload: any) => { + return ( + + Overload {overload.overloadIndex} + + ); + })} + + {event.overloads.map((overload: any) => { + return ( + + + + ); + })} + + ); +} + +export async function EventNode({ + node, + packageName, + version, +}: { + readonly node: any; + readonly packageName: string; + readonly version: string; +}) { + return ( + + +

+ Events +

+ + +
+ + +
+ {node.map((event: any) => { + return event.overloads?.length ? ( + + ) : ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/website/src/components/ExampleNode.tsx b/apps/website/src/components/ExampleNode.tsx new file mode 100644 index 000000000000..a8692cc65037 --- /dev/null +++ b/apps/website/src/components/ExampleNode.tsx @@ -0,0 +1,10 @@ +import { DocNode } from './DocNode'; + +export async function ExampleNode({ node, version }: { readonly node: any; readonly version: string }) { + return ( +
+ Examples: + +
+ ); +} diff --git a/apps/website/src/components/ExcerptNode.tsx b/apps/website/src/components/ExcerptNode.tsx new file mode 100644 index 000000000000..adee4a59e9d3 --- /dev/null +++ b/apps/website/src/components/ExcerptNode.tsx @@ -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 ( + + {excerpt.map((excerpt: any, idx: number) => { + if (excerpt.resolvedItem) { + return ( + + {excerpt.text} + + ); + } + + if (excerpt.href) { + return ( + + {excerpt.text} + + ); + } + + if (excerpt.text in BuiltinDocumentationLinks) { + const href = BuiltinDocumentationLinks[excerpt.text as keyof typeof BuiltinDocumentationLinks]; + return ( + + {excerpt.text} + + ); + } + + return {excerpt.text}; + })} + + ); + }; + + return node?.map(createExcerpt) ?? null; +} diff --git a/apps/website/src/components/ExcerptText.tsx b/apps/website/src/components/ExcerptText.tsx deleted file mode 100644 index 64abcea04080..000000000000 --- a/apps/website/src/components/ExcerptText.tsx +++ /dev/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 ( - - {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 ( - - {token.text} - - ); - } - - 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 ( - - {token.text} - - ); - } - - const resolved = token.canonicalReference - ? resolveCanonicalReference(token.canonicalReference, apiPackage) - : null; - - if (!resolved) { - return token.text; - } - - return ( - - {token.text} - - ); - } - - return token.text.replace(/import\("discord-api-types(?:\/v\d+)?"\)\./, ''); - })} - - ); -} diff --git a/apps/website/src/components/Header.tsx b/apps/website/src/components/Header.tsx deleted file mode 100644 index dab90e015b1c..000000000000 --- a/apps/website/src/components/Header.tsx +++ /dev/null @@ -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) => ( - - {path} - - )), - [pathname], - ); - - const breadcrumbs = useMemo( - () => - pathElements.flatMap((el, idx, array) => { - if (idx === 0) { - return ( - -
/
-
{el}
-
/
-
- ); - } - - if (idx !== array.length - 1) { - return ( - -
{el}
-
/
-
- ); - } - - return
{el}
; - }), - [pathElements], - ); - - return ( -
-
-
- -
{breadcrumbs}
- -
- - - -
-
-
-
- ); -} diff --git a/apps/website/src/components/InformationNode.tsx b/apps/website/src/components/InformationNode.tsx new file mode 100644 index 000000000000..9e8852f5a36e --- /dev/null +++ b/apps/website/src/components/InformationNode.tsx @@ -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 ( +
+
+

+ {node.displayName} +

+ {node.implements ? : null} + {node.extends ? : null} + +
+ + + + +
+ ); +} diff --git a/apps/website/src/components/InheritanceNode.tsx b/apps/website/src/components/InheritanceNode.tsx new file mode 100644 index 000000000000..8b2983979492 --- /dev/null +++ b/apps/website/src/components/InheritanceNode.tsx @@ -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 ( +
+

{text}

{' '} + + + +
+ ); +} diff --git a/apps/website/src/components/InheritanceText.tsx b/apps/website/src/components/InheritanceText.tsx deleted file mode 100644 index cacefe71e49d..000000000000 --- a/apps/website/src/components/InheritanceText.tsx +++ /dev/null @@ -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 ( - - Inherited from{' '} - - {parent.displayName} - - - ); -} diff --git a/apps/website/src/components/InheritedFromNode.tsx b/apps/website/src/components/InheritedFromNode.tsx new file mode 100644 index 000000000000..235882cf2372 --- /dev/null +++ b/apps/website/src/components/InheritedFromNode.tsx @@ -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 ( +

+ Inherited from:{' '} + + {node.slice(0, node.indexOf(':'))} + +

+ ); +} diff --git a/apps/website/src/components/InstallButton.tsx b/apps/website/src/components/InstallButton.tsx deleted file mode 100644 index 740cf6dca397..000000000000 --- a/apps/website/src/components/InstallButton.tsx +++ /dev/null @@ -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 ( - - ); -} diff --git a/apps/website/src/components/ItemLink.tsx b/apps/website/src/components/ItemLink.tsx deleted file mode 100644 index d54082be2c8a..000000000000 --- a/apps/website/src/components/ItemLink.tsx +++ /dev/null @@ -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 extends Omit, '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(props: PropsWithChildren>) { - 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 ; -} diff --git a/apps/website/src/components/MethodNode.tsx b/apps/website/src/components/MethodNode.tsx new file mode 100644 index 000000000000..5a9d541fa89d --- /dev/null +++ b/apps/website/src/components/MethodNode.tsx @@ -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 ( + <> +
+
+

+ {method.displayName} + + + + + {method.typeParameters?.length ? ( + <> + {'<'} + + {'>'} + + ) : null} + ({method.parameters?.length ? : null} + ) : + +

+ + + + +
+ + {method.summary?.deprecatedBlock.length ? ( + + ) : null} + + {method.summary?.summarySection.length ? ( + + ) : null} + + {method.summary?.exampleBlocks.length ? ( + + ) : null} + + {method.summary?.returnsBlock.length ? ( + + ) : null} + + {method.inheritedFrom ? ( + + ) : null} + + {method.summary?.seeBlocks.length ? ( + + ) : null} +
+
+
+
+ + ); +} + +async function OverloadNode({ + method, + packageName, + version, +}: { + readonly method: any; + readonly packageName: string; + readonly version: string; +}) { + return ( + + + {method.overloads.map((overload: any) => { + return ( + + Overload {overload.overloadIndex} + + ); + })} + + {method.overloads.map((overload: any) => { + return ( + + + + ); + })} + + ); +} + +export async function MethodNode({ + node, + packageName, + version, +}: { + readonly node: any; + readonly packageName: string; + readonly version: string; +}) { + return ( + + +

+ Methods +

+ + +
+ + +
+ {node.map((method: any) => { + return method.overloads?.length ? ( + + ) : ( + + ); + })} +
+
+
+ ); +} diff --git a/apps/website/src/components/Nav.tsx b/apps/website/src/components/Nav.tsx deleted file mode 100644 index dbb08aa2ce9c..000000000000 --- a/apps/website/src/components/Nav.tsx +++ /dev/null @@ -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 ( - - ); -} diff --git a/apps/website/src/components/Navigation.tsx b/apps/website/src/components/Navigation.tsx new file mode 100644 index 000000000000..466e6a98c5a0 --- /dev/null +++ b/apps/website/src/components/Navigation.tsx @@ -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 ( + + ); +} diff --git a/apps/website/src/components/NavigationItem.tsx b/apps/website/src/components/NavigationItem.tsx new file mode 100644 index 000000000000..716262897356 --- /dev/null +++ b/apps/website/src/components/NavigationItem.tsx @@ -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 ( + setDrawerOpen(false)} + > + {children} + + ); +} diff --git a/apps/website/src/components/Outline.tsx b/apps/website/src/components/Outline.tsx index f40460e92cde..44fd1d710815 100644 --- a/apps/website/src/components/Outline.tsx +++ b/apps/website/src/components/Outline.tsx @@ -1,32 +1,135 @@ -'use client'; - -import { useOutline } from '~/contexts/outline'; -import { Scrollbars } from './Scrollbars'; -import { TableOfContentItems } from './TableOfContentItems'; - -export function Outline() { - const { members } = useOutline(); - - if (!members) { - return null; - } - - return ( -
- -
- ); +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'; + +export async function Outline({ node }: { readonly node: any }) { + const hasAny = node.members?.properties?.length || node.members?.events?.length || node.members?.methods?.length; + + return hasAny ? ( + + +

+ Table of contents +

+ + +
+ + +
+
+ {node.members?.properties?.length ? ( + + +

+ + Properties +

+ + +
+ + +
+ {node.members.properties.map((property: any, idx: number) => { + return ( + +
+
+ + {property.displayName} + +
+
+
+ ); + })} +
+
+
+ ) : null} + + {node.members?.events?.length ? ( + + +

+ + Events +

+ + +
+ + +
+ {node.members.events.map((event: any, idx: number) => { + return ( + +
+
+ + {event.displayName} + +
+
+
+ ); + })} +
+
+
+ ) : null} + + {node.members?.methods?.length ? ( + + +

+ + Methods +

+ + +
+ + +
+ {node.members.methods.map((method: any, idx: number) => { + return ( + +
+
+ + {method.displayName} + +
+
+
+ ); + })} +
+
+
+ ) : null} +
+
+
+
+
+ + + ) : null; } diff --git a/apps/website/src/components/OverlayScrollbars.tsx b/apps/website/src/components/OverlayScrollbars.tsx new file mode 100644 index 000000000000..0e37086c2edc --- /dev/null +++ b/apps/website/src/components/OverlayScrollbars.tsx @@ -0,0 +1,7 @@ +'use client'; + +import { OverlayScrollbars, ClickScrollPlugin } from 'overlayscrollbars'; + +OverlayScrollbars.plugin(ClickScrollPlugin); + +export { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; diff --git a/apps/website/src/components/OverloadSwitcher.tsx b/apps/website/src/components/OverloadSwitcher.tsx deleted file mode 100644 index 923cb645eb48..000000000000 --- a/apps/website/src/components/OverloadSwitcher.tsx +++ /dev/null @@ -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) => ( - setOverloadIndex(idx + 1)} - > - {`Overload ${idx + 1}`} - - )), - [overloads], - ); - - return ( -
- -
- -
- {`Overload ${overloadIndex}`} - {` of ${overloads.length}`} -
- -
-
- - {menuItems} - - {children} - {overloadedNode} -
- ); -} diff --git a/apps/website/src/components/PackageSelect.tsx b/apps/website/src/components/PackageSelect.tsx deleted file mode 100644 index bae2989ff654..000000000000 --- a/apps/website/src/components/PackageSelect.tsx +++ /dev/null @@ -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) => ( - - packageMenu.setOpen(false)} - state={packageMenu} - > - {pkg} - - - )), - [packageMenu], - ); - - return ( - <> - -
-
- - {packageName} -
- -
-
- - {packageMenuItems} - - - ); -} diff --git a/apps/website/src/components/Panel.tsx b/apps/website/src/components/Panel.tsx deleted file mode 100644 index 50e6a0ed835e..000000000000 --- a/apps/website/src/components/Panel.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -export function Panel({ children }: PropsWithChildren) { - return ( - <> - {children} -
- - ); -} diff --git a/apps/website/src/components/ParameterNode.tsx b/apps/website/src/components/ParameterNode.tsx new file mode 100644 index 000000000000..02f35ec29b83 --- /dev/null +++ b/apps/website/src/components/ParameterNode.tsx @@ -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 ( +
+ {node.map((parameter: any, idx: number) => { + return ( + +
+ + {description ? ( + + + + ) : null} + {description ? : null} + {parameter.name} + {parameter.isOptional ? '?' : ''}: + + {description && parameter.description?.length ? ( +
+ +
+ ) : null} +
+
+ ); + })} + {description ? ( +
+
+
+ ) : null} +
+ ); +} diff --git a/apps/website/src/components/ParameterTable.tsx b/apps/website/src/components/ParameterTable.tsx deleted file mode 100644 index c10c4791bef8..000000000000 --- a/apps/website/src/components/ParameterTable.tsx +++ /dev/null @@ -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: , - Optional: param.isOptional ? 'Yes' : 'No', - Description: param.description ? : 'None', - })), - [item, params], - ); - - return ( -
- - - ); -} diff --git a/apps/website/src/components/Property.tsx b/apps/website/src/components/Property.tsx deleted file mode 100644 index 242d7dad5709..000000000000 --- a/apps/website/src/components/Property.tsx +++ /dev/null @@ -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 ( -
-
- - - {`${item.displayName}${item.isOptional ? '?' : ''}`} - : - {item.propertyTypeExcerpt.text ? ( - - ) : null} - -
- {hasSummary || inheritedFrom ? ( -
- {item.tsdocComment ? : null} - {inheritedFrom ? : null} - {children} -
- ) : null} -
- ); -} diff --git a/apps/website/src/components/PropertyList.tsx b/apps/website/src/components/PropertyList.tsx deleted file mode 100644 index 8e922513ace0..000000000000 --- a/apps/website/src/components/PropertyList.tsx +++ /dev/null @@ -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 ( - - -
- - ); - }), - [members], - ); - - return
{propertyItems}
; -} diff --git a/apps/website/src/components/PropertyNode.tsx b/apps/website/src/components/PropertyNode.tsx new file mode 100644 index 000000000000..60b023be1302 --- /dev/null +++ b/apps/website/src/components/PropertyNode.tsx @@ -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 ( + + +

+ + Properties +

+ + +
+ + +
+ {node.map((property: any, idx: number) => { + return ( + +
+
+

+ + + + + + {property.displayName} + {property.isOptional ? '?' : ''} : + +

+ + + + +
+ + {property.summary?.deprecatedBlock.length ? ( + + ) : null} + + {property.summary?.summarySection.length ? ( + + ) : null} + + {property.inheritedFrom ? ( + + ) : null} + + {property.summary?.seeBlocks.length ? ( + + ) : null} +
+
+
+
+ + ); + })} +
+ + + ); +} diff --git a/apps/website/src/components/ReturnNode.tsx b/apps/website/src/components/ReturnNode.tsx new file mode 100644 index 000000000000..2a18948654eb --- /dev/null +++ b/apps/website/src/components/ReturnNode.tsx @@ -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 ( +

+ Returns: +

+ ); +} diff --git a/apps/website/src/components/Scrollbars.tsx b/apps/website/src/components/Scrollbars.tsx deleted file mode 100644 index 9e76b4923726..000000000000 --- a/apps/website/src/components/Scrollbars.tsx +++ /dev/null @@ -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 ; -} diff --git a/apps/website/src/components/Section.tsx b/apps/website/src/components/Section.tsx deleted file mode 100644 index 722344864a9a..000000000000 --- a/apps/website/src/components/Section.tsx +++ /dev/null @@ -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) { - return ; -} diff --git a/apps/website/src/components/SeeNode.tsx b/apps/website/src/components/SeeNode.tsx new file mode 100644 index 000000000000..4c8565395155 --- /dev/null +++ b/apps/website/src/components/SeeNode.tsx @@ -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 ( +

+ See also: +

+ ); +} diff --git a/apps/website/src/components/Sidebar.tsx b/apps/website/src/components/Sidebar.tsx deleted file mode 100644 index b81fa250f2fb..000000000000 --- a/apps/website/src/components/Sidebar.tsx +++ /dev/null @@ -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 ; - case 'Enums': - return ; - case 'Interfaces': - return ; - case 'Types': - case 'Variables': - return ; - default: - return ; - } -} - -export function Sidebar({ members }: { readonly members: SidebarSectionItemData[] }) { - const segment = useSelectedLayoutSegment(); - const { setOpened } = useNav(); - - const groupItems = useMemo(() => groupMembers(members), [members]); - - return ( -
- {(Object.keys(groupItems) as (keyof GroupedMembers)[]) - .filter((group) => groupItems[group].length) - .map((group, idx) => ( -
- {groupItems[group].map((member, index) => ( - setOpened(false)} - title={member.name} - > -
- {member.name} - {member.overloadIndex && member.overloadIndex > 1 ? ( - {member.overloadIndex} - ) : null} -
-
- ))} -
- ))} -
- ); -} diff --git a/apps/website/src/components/SignatureText.tsx b/apps/website/src/components/SignatureText.tsx deleted file mode 100644 index e381488b9c40..000000000000 --- a/apps/website/src/components/SignatureText.tsx +++ /dev/null @@ -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 ( -

- -

- ); -} diff --git a/apps/website/src/components/SummaryNode.tsx b/apps/website/src/components/SummaryNode.tsx new file mode 100644 index 000000000000..c0c2bffc9c3c --- /dev/null +++ b/apps/website/src/components/SummaryNode.tsx @@ -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 ( +

+ +

+ ); +} diff --git a/apps/website/src/components/SyntaxHighlighter.tsx b/apps/website/src/components/SyntaxHighlighter.tsx index 73f9251dc922..7d020385c024 100644 --- a/apps/website/src/components/SyntaxHighlighter.tsx +++ b/apps/website/src/components/SyntaxHighlighter.tsx @@ -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 ( <> -
- -
-
- -
+ {/* eslint-disable-next-line react/no-danger */} +
); } diff --git a/apps/website/src/components/Table.tsx b/apps/website/src/components/Table.tsx deleted file mode 100644 index 1929d1fe40c3..000000000000 --- a/apps/website/src/components/Table.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client'; - -import { useMemo, type ReactNode } from 'react'; - -export function Table({ - rows, - columns, - columnStyles, -}: { - readonly columnStyles?: Record; - readonly columns: string[]; - readonly rows: Record[]; -}) { - const cols = useMemo( - () => - columns.map((column, idx) => ( -
- )), - [columns], - ); - - const data = useMemo( - () => - rows.map((row, idx) => ( - - {Object.entries(row).map(([colName, val], index) => ( - - ))} - - )), - [columnStyles, rows], - ); - - return ( -
- {column} -
- {val} -
- - {cols} - - {data} -
- ); -} diff --git a/apps/website/src/components/TableOfContentItems.tsx b/apps/website/src/components/TableOfContentItems.tsx deleted file mode 100644 index a1e33316085f..000000000000 --- a/apps/website/src/components/TableOfContentItems.tsx +++ /dev/null @@ -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 ( - - {property.name} - - ); -} - -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 ( - - {method.name} - {method.overloadIndex && method.overloadIndex > 1 ? ( - {method.overloadIndex} - ) : null} - - ); -} - -export function TableOfContentsEventItem({ event }: { readonly event: TableOfContentsSerializedEvent }) { - return ( - - {event.name} - - ); -} - -export function TableOfContentItems({ serializedMembers }: TableOfContentsItemProps) { - const propertyItems = useMemo( - () => - serializedMembers - .filter( - (member): member is TableOfContentsSerializedProperty => - member.kind === 'Property' || member.kind === 'PropertySignature', - ) - .map((prop, idx) => ), - [serializedMembers], - ); - - const methodItems = useMemo( - () => - serializedMembers - .filter( - (member): member is TableOfContentsSerializedMethod => - member.kind === 'Method' || member.kind === 'MethodSignature', - ) - .map((member, idx) => ( - - )), - [serializedMembers], - ); - - const eventItems = useMemo( - () => - serializedMembers - .filter((member): member is TableOfContentsSerializedEvent => member.kind === 'Event') - .map((event, idx) => ), - [serializedMembers], - ); - - return ( -
-
- - Contents -
-
- {eventItems.length ? ( -
-
- -
- Events -
-
- {eventItems} -
- ) : null} - {propertyItems.length ? ( -
-
- -
- Properties -
-
- {propertyItems} -
- ) : null} - {methodItems.length ? ( -
-
- -
- Methods -
-
- {methodItems} -
- ) : null} -
-
- ); -} diff --git a/apps/website/src/components/ThemeSwitcher.tsx b/apps/website/src/components/ThemeSwitcher.tsx deleted file mode 100644 index 38acea06884b..000000000000 --- a/apps/website/src/components/ThemeSwitcher.tsx +++ /dev/null @@ -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 ( - - ); -} diff --git a/apps/website/src/components/TypeParamTable.tsx b/apps/website/src/components/TypeParamTable.tsx deleted file mode 100644 index a16f79d07ee0..000000000000 --- a/apps/website/src/components/TypeParamTable.tsx +++ /dev/null @@ -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: , - Optional: typeParam.isOptional ? 'Yes' : 'No', - Default: , - Description: typeParam.tsdocTypeParamBlock ? ( - - ) : ( - 'None' - ), - })), - [item], - ); - - return ( -
- - - ); -} diff --git a/apps/website/src/components/TypeParameterNode.tsx b/apps/website/src/components/TypeParameterNode.tsx new file mode 100644 index 000000000000..1c0c55764891 --- /dev/null +++ b/apps/website/src/components/TypeParameterNode.tsx @@ -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 ( +
+ {node.map((typeParameter: any, idx: number) => { + return ( + +
+

+ {description ? : null} + + {description ? ( + + + + ) : null} + {typeParameter.name} + {typeParameter.isOptional ? '?' : ''} + {typeParameter.constraintsExcerpt.length ? ( + <> + {' extends '} + + + ) : null} + {typeParameter.defaultExcerpt.length ? ( + <> + {' = '} + + + ) : null} + +

+ + {description && typeParameter.description?.length ? ( +
+ +
+ ) : null} +
+
+ ); + })} + {description ? ( +
+
+
+ ) : null} +
+ ); +} diff --git a/apps/website/src/components/UnionMember.tsx b/apps/website/src/components/UnionMember.tsx new file mode 100644 index 000000000000..25f7989bdce5 --- /dev/null +++ b/apps/website/src/components/UnionMember.tsx @@ -0,0 +1,13 @@ +import { ExcerptNode } from './ExcerptNode'; + +export async function UnionMember({ node, version }: { readonly node: any; readonly version: string }) { + return ( +
+

Union Members

+ + + + +
+ ); +} diff --git a/apps/website/src/components/VersionSelect.tsx b/apps/website/src/components/VersionSelect.tsx deleted file mode 100644 index 72ed52318b7a..000000000000 --- a/apps/website/src/components/VersionSelect.tsx +++ /dev/null @@ -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(packageName ? `/api/${packageName}/versions` : null, { - fallbackData: versions, - }); - - const versionMenu = useMenuState({ - gutter: 8, - sameWidth: true, - fitViewport: true, - }); - - const versionMenuItems = useMemo( - () => - data?.map((item, idx) => ( - - versionMenu.setOpen(false)} - state={versionMenu} - > - {item} - - - )) ?? [], - [data, packageName, versionMenu], - ); - - return ( - <> - -
-
- - {branchName} -
- -
-
- - {versionMenuItems} - - - ); -} diff --git a/apps/website/src/components/documentation/Documentation.tsx b/apps/website/src/components/documentation/Documentation.tsx deleted file mode 100644 index ffddee132463..000000000000 --- a/apps/website/src/components/documentation/Documentation.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -/** - * Layout parent of documentation pages. - */ -export function Documentation({ children }: PropsWithChildren) { - return
{children}
; -} diff --git a/apps/website/src/components/documentation/Header.tsx b/apps/website/src/components/documentation/Header.tsx deleted file mode 100644 index 96dc2f4cc3e4..000000000000 --- a/apps/website/src/components/documentation/Header.tsx +++ /dev/null @@ -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 ; - case ApiItemKind.Function: - case ApiItemKind.Method: - return ; - case ApiItemKind.Enum: - return ; - case ApiItemKind.Interface: - return ; - case ApiItemKind.TypeAlias: - case ApiItemKind.Variable: - return ; - default: - return ; - } -} - -export function Header({ - kind, - name, - sourceURL, - sourceLine, -}: PropsWithChildren<{ - readonly kind: ApiItemKind; - readonly name: string; - readonly sourceLine?: number | undefined; - readonly sourceURL?: string | undefined; -}>) { - return ( -
-

- - {generateIcon(kind)} - {name} - - {sourceURL ? : null} -

-
- ); -} diff --git a/apps/website/src/components/documentation/HierarchyText.tsx b/apps/website/src/components/documentation/HierarchyText.tsx deleted file mode 100644 index 6bf9a5ec7767..000000000000 --- a/apps/website/src/components/documentation/HierarchyText.tsx +++ /dev/null @@ -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 ( -
- {excerpts.map((excerpt, idx) => ( -
-

{type}

- - - -
- ))} -
- ); -} diff --git a/apps/website/src/components/documentation/Members.tsx b/apps/website/src/components/documentation/Members.tsx deleted file mode 100644 index 64a69a4028c6..000000000000 --- a/apps/website/src/components/documentation/Members.tsx +++ /dev/null @@ -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) ? : null} - {hasProperties(item) ? : null} - {hasMethods(item) ? : null} - - ); -} diff --git a/apps/website/src/components/documentation/ObjectHeader.tsx b/apps/website/src/components/documentation/ObjectHeader.tsx deleted file mode 100644 index 198e54ac252e..000000000000 --- a/apps/website/src/components/documentation/ObjectHeader.tsx +++ /dev/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 ( - <> -
- {/* @ts-expect-error async component */} - - - - ); -} diff --git a/apps/website/src/components/documentation/SourceLink.tsx b/apps/website/src/components/documentation/SourceLink.tsx deleted file mode 100644 index 9ecc9115e67e..000000000000 --- a/apps/website/src/components/documentation/SourceLink.tsx +++ /dev/null @@ -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 ( - - - - ); -} diff --git a/apps/website/src/components/documentation/section/ConstructorSection.tsx b/apps/website/src/components/documentation/section/ConstructorSection.tsx deleted file mode 100644 index c06d9e7caf4a..000000000000 --- a/apps/website/src/components/documentation/section/ConstructorSection.tsx +++ /dev/null @@ -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 ( - } padded title="Constructor"> -
- {`constructor(${parametersString(item)})`} - {item.tsdocComment ? : null} - -
-
- ); -} diff --git a/apps/website/src/components/documentation/section/DocumentationSection.tsx b/apps/website/src/components/documentation/section/DocumentationSection.tsx deleted file mode 100644 index e56bd389bd37..000000000000 --- a/apps/website/src/components/documentation/section/DocumentationSection.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SectionOptions } from '@discordjs/ui'; -import type { PropsWithChildren } from 'react'; -import { Section } from '../../Section'; - -export function DocumentationSection(opts: PropsWithChildren) { - const { children, separator, ...props } = opts; - - return ( -
- {children} - {separator ?
: null} -
- ); -} diff --git a/apps/website/src/components/documentation/section/EventsSection.tsx b/apps/website/src/components/documentation/section/EventsSection.tsx deleted file mode 100644 index a91164c4eb21..000000000000 --- a/apps/website/src/components/documentation/section/EventsSection.tsx +++ /dev/null @@ -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 ( - - -
- - ); - }), - [members], - ); - - return ( - } padded title="Events"> -
{eventItems}
-
- ); -} diff --git a/apps/website/src/components/documentation/section/MethodsSection.tsx b/apps/website/src/components/documentation/section/MethodsSection.tsx deleted file mode 100644 index f68479d45487..000000000000 --- a/apps/website/src/components/documentation/section/MethodsSection.tsx +++ /dev/null @@ -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 }) => ( - 1 ? `:${(method as ApiMethod).overloadIndex}` : '' - }`} - > - -
- - )), - [members], - ); - - return ( - } padded title="Methods"> -
{methodItems}
-
- ); -} diff --git a/apps/website/src/components/documentation/section/ParametersSection.tsx b/apps/website/src/components/documentation/section/ParametersSection.tsx deleted file mode 100644 index 71f0d38326fb..000000000000 --- a/apps/website/src/components/documentation/section/ParametersSection.tsx +++ /dev/null @@ -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 ( - } padded title="Parameters"> - - - ); -} diff --git a/apps/website/src/components/documentation/section/PropertiesSection.tsx b/apps/website/src/components/documentation/section/PropertiesSection.tsx deleted file mode 100644 index 999201b97394..000000000000 --- a/apps/website/src/components/documentation/section/PropertiesSection.tsx +++ /dev/null @@ -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 ( - } padded title="Properties"> - - - ); -} diff --git a/apps/website/src/components/documentation/section/SummarySection.tsx b/apps/website/src/components/documentation/section/SummarySection.tsx deleted file mode 100644 index c47287b6a977..000000000000 --- a/apps/website/src/components/documentation/section/SummarySection.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { ApiDeclaredItem } from '@discordjs/api-extractor-model'; -import { VscListSelection } from '@react-icons/all-files/vsc/VscListSelection'; -import { TSDoc } from '../tsdoc/TSDoc'; -import { DocumentationSection } from './DocumentationSection'; - -export function SummarySection({ item }: { readonly item: ApiDeclaredItem }) { - return ( - } padded separator title="Summary"> - {item.tsdocComment?.summarySection ? ( - - ) : ( -

No summary provided.

- )} -
- ); -} diff --git a/apps/website/src/components/documentation/section/TypeParametersSection.tsx b/apps/website/src/components/documentation/section/TypeParametersSection.tsx deleted file mode 100644 index b93246b2313f..000000000000 --- a/apps/website/src/components/documentation/section/TypeParametersSection.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import type { ApiTypeParameterListMixin } from '@discordjs/api-extractor-model'; -import { VscSymbolParameter } from '@react-icons/all-files/vsc/VscSymbolParameter'; -import { TypeParamTable } from '../../TypeParamTable'; -import { DocumentationSection } from './DocumentationSection'; - -export function TypeParameterSection({ item }: { readonly item: ApiTypeParameterListMixin }) { - return ( - } padded title="Type Parameters"> - - - ); -} diff --git a/apps/website/src/components/documentation/section/UnionMembersSection.tsx b/apps/website/src/components/documentation/section/UnionMembersSection.tsx deleted file mode 100644 index 2a736c3de0a5..000000000000 --- a/apps/website/src/components/documentation/section/UnionMembersSection.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Excerpt, type ApiTypeAlias, type ExcerptToken } from '@discordjs/api-extractor-model'; -import { VscSymbolArray } from '@react-icons/all-files/vsc/VscSymbolArray'; -import { useMemo } from 'react'; -import { ExcerptText } from '~/components/ExcerptText'; -import { DocumentationSection } from './DocumentationSection'; - -export type UnionMember = readonly ExcerptToken[]; - -export function UnionMembersSection({ - item, - members, -}: { - readonly item: ApiTypeAlias; - readonly members: UnionMember[]; -}) { - const unionMembers = useMemo( - () => - members.map((member, idx) => ( -
- - - -
- )), - [item, members], - ); - - return ( - } padded title="Union Members"> -
{unionMembers}
-
- ); -} diff --git a/apps/website/src/components/documentation/tsdoc/BlockComment.tsx b/apps/website/src/components/documentation/tsdoc/BlockComment.tsx deleted file mode 100644 index 8d2a055112a3..000000000000 --- a/apps/website/src/components/documentation/tsdoc/BlockComment.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Alert } from '@discordjs/ui'; -import type { PropsWithChildren } from 'react'; - -export function Block({ children, title }: PropsWithChildren<{ readonly title: string }>) { - return ( -
-
{title}
- {children} -
- ); -} - -export function ExampleBlock({ - children, - exampleIndex, -}: PropsWithChildren<{ - readonly exampleIndex?: number | undefined; -}>): JSX.Element { - return {children}; -} - -export function DefaultValueBlock({ children }: PropsWithChildren): JSX.Element { - return {children}; -} - -export function RemarksBlock({ children }: PropsWithChildren): JSX.Element { - return {children}; -} - -export function DeprecatedBlock({ children }: PropsWithChildren): JSX.Element { - return ( - - {children} - - ); -} - -export function SeeBlock({ children }: PropsWithChildren): JSX.Element { - return {children}; -} - -export function ReturnsBlock({ children }: PropsWithChildren): JSX.Element { - return {children}; -} diff --git a/apps/website/src/components/documentation/tsdoc/TSDoc.tsx b/apps/website/src/components/documentation/tsdoc/TSDoc.tsx deleted file mode 100644 index 8f5b007a4873..000000000000 --- a/apps/website/src/components/documentation/tsdoc/TSDoc.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import type { ApiItem } from '@discordjs/api-extractor-model'; -import type { DocComment, DocFencedCode, DocLinkTag, DocNode, DocNodeContainer, DocPlainText } from '@microsoft/tsdoc'; -import { DocNodeKind, StandardTags } from '@microsoft/tsdoc'; -import type { Route } from 'next'; -import Link from 'next/link'; -import { Fragment, useCallback, type ReactNode } from 'react'; -import { DocumentationLink } from '~/components/DocumentationLink'; -import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks'; -import { DISCORD_API_TYPES_DOCS_URL } from '~/util/constants'; -import { ItemLink } from '../../ItemLink'; -import { SyntaxHighlighter } from '../../SyntaxHighlighter'; -import { resolveCanonicalReference, resolveItemURI } from '../util'; -import { DefaultValueBlock, DeprecatedBlock, ExampleBlock, RemarksBlock, ReturnsBlock, SeeBlock } from './BlockComment'; - -export function TSDoc({ item, tsdoc }: { readonly item: ApiItem; readonly tsdoc: DocNode }): JSX.Element { - const createNode = useCallback( - (tsdoc: DocNode, idx?: number): ReactNode => { - switch (tsdoc.kind) { - case DocNodeKind.PlainText: - return ( - - {(tsdoc as DocPlainText).text} - - ); - case DocNodeKind.Section: - case DocNodeKind.Paragraph: - return ( -
- {(tsdoc as DocNodeContainer).nodes.map((node, idx) => createNode(node, idx))} -
- ); - case DocNodeKind.SoftBreak: - return ; - case DocNodeKind.LinkTag: { - const { codeDestination, urlDestination, linkText } = tsdoc as DocLinkTag; - if (codeDestination) { - if ( - !codeDestination.importPath && - !codeDestination.packageName && - codeDestination.memberReferences.length === 1 && - codeDestination.memberReferences[0]!.memberIdentifier && - codeDestination.memberReferences[0]!.memberIdentifier.identifier in BuiltinDocumentationLinks - ) { - const typeName = codeDestination.memberReferences[0]!.memberIdentifier.identifier; - const href = BuiltinDocumentationLinks[typeName as keyof typeof BuiltinDocumentationLinks]; - return ( - - {typeName} - - ); - } - - const declarationReference = item.getAssociatedModel()?.resolveDeclarationReference(codeDestination, item); - const foundItem = declarationReference?.resolvedApiItem; - const resolved = resolveCanonicalReference(codeDestination, item.getAssociatedPackage()); - - if (!foundItem && !resolved) return null; - - if (resolved && resolved.package === 'discord-api-types') { - const { displayName, kind, members, containerKey } = resolved.item; - 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 (kind === 'enum' && members?.[0]) { - href += `/enum/${displayName}#${members[0].displayName}`; - } else if (kind === 'type' || kind === 'var') { - href += `#${displayName}`; - } else { - href += `/${kind}/${displayName}`; - } - - return ( - - {displayName} - {members?.map((member) => `.${member.displayName}`).join('') ?? ''} - - ); - } - - return ( - - {linkText ?? foundItem?.displayName ?? resolved!.item.displayName} - - ); - } - - if (urlDestination) { - return ( - - {linkText ?? urlDestination} - - ); - } - - return null; - } - - case DocNodeKind.CodeSpan: { - const { code } = tsdoc as DocFencedCode; - return ( - - {code} - - ); - } - - case DocNodeKind.FencedCode: { - const { language, code } = tsdoc as DocFencedCode; - // @ts-expect-error async component - return ; - } - - case DocNodeKind.Comment: { - const comment = tsdoc as DocComment; - - const exampleBlocks = comment.customBlocks.filter( - (block) => block.blockTag.tagName.toUpperCase() === StandardTags.example.tagNameWithUpperCase, - ); - - const defaultValueBlock = comment.customBlocks.find( - (block) => block.blockTag.tagName.toUpperCase() === StandardTags.defaultValue.tagNameWithUpperCase, - ); - - return ( -
- {comment.deprecatedBlock ? ( - {createNode(comment.deprecatedBlock.content)} - ) : null} - {comment.summarySection ? createNode(comment.summarySection) : null} - {comment.remarksBlock ? {createNode(comment.remarksBlock.content)} : null} - {defaultValueBlock ? ( - {createNode(defaultValueBlock.content)} - ) : null} - {comment.returnsBlock ? {createNode(comment.returnsBlock.content)} : null} - {exampleBlocks.length - ? exampleBlocks.map((block, idx) => {createNode(block.content)}) - : null} - {comment.seeBlocks.length ? ( - {comment.seeBlocks.map((seeBlock, idx) => createNode(seeBlock.content, idx))} - ) : null} -
- ); - } - - default: - // console.log(`Captured unknown node kind: ${node.kind}`); - return null; - } - }, - [item], - ); - - return ( - <> - {tsdoc.kind === 'Paragraph' || tsdoc.kind === 'Section' ? ( - <>{(tsdoc as DocNodeContainer).nodes.map((node, idx) => createNode(node, idx))} - ) : ( - createNode(tsdoc) - )} - - ); -} diff --git a/apps/website/src/components/documentation/util.ts b/apps/website/src/components/documentation/util.ts deleted file mode 100644 index 165972f2dfb6..000000000000 --- a/apps/website/src/components/documentation/util.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { ApiItemKind, Meaning } from '@discordjs/api-extractor-model'; -import type { - ApiItem, - ApiItemContainerMixin, - ApiMethod, - ApiMethodSignature, - ApiProperty, - ApiPropertySignature, - ApiDocumentedItem, - ApiParameterListMixin, - ApiEvent, - ApiPackage, -} from '@discordjs/api-extractor-model'; -import type { DocDeclarationReference } from '@microsoft/tsdoc'; -import { SelectorKind } from '@microsoft/tsdoc'; -import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference'; -import { METHOD_SEPARATOR, OVERLOAD_SEPARATOR } from '~/util/constants'; -import { resolveMembers } from '~/util/members'; -import { resolveParameters } from '~/util/model'; -import type { TableOfContentsSerialized } from '../TableOfContentItems'; - -export interface ApiItemLike { - containerKey?: string; - displayName: string; - kind: string; - members?: readonly ApiItemLike[]; - parent?: ApiItemLike | undefined; -} - -interface ResolvedCanonicalReference { - item: ApiItemLike; - package: string | undefined; - version: string | undefined; -} - -const kindToMeaning = new Map([ - [ApiItemKind.CallSignature, Meaning.CallSignature], - [ApiItemKind.Class, Meaning.Class], - [ApiItemKind.ConstructSignature, Meaning.ConstructSignature], - [ApiItemKind.Constructor, Meaning.Constructor], - [ApiItemKind.Enum, Meaning.Enum], - [ApiItemKind.Event, Meaning.Event], - [ApiItemKind.Function, Meaning.Function], - [ApiItemKind.IndexSignature, Meaning.IndexSignature], - [ApiItemKind.Interface, Meaning.Interface], - [ApiItemKind.Property, Meaning.Member], - [ApiItemKind.Namespace, Meaning.Namespace], - [ApiItemKind.None, Meaning.ComplexType], - [ApiItemKind.TypeAlias, Meaning.TypeAlias], - [ApiItemKind.Variable, Meaning.Variable], -]); - -export function hasProperties(item: ApiItemContainerMixin) { - return resolveMembers(item, memberPredicate).some( - ({ item: member }) => member.kind === ApiItemKind.Property || member.kind === ApiItemKind.PropertySignature, - ); -} - -export function hasMethods(item: ApiItemContainerMixin) { - return resolveMembers(item, memberPredicate).some( - ({ item: member }) => member.kind === ApiItemKind.Method || member.kind === ApiItemKind.MethodSignature, - ); -} - -export function hasEvents(item: ApiItemContainerMixin) { - return resolveMembers(item, memberPredicate).some(({ item: member }) => member.kind === ApiItemKind.Event); -} - -export function resolveItemURI(item: ApiItemLike): string { - return !item.parent || item.parent.kind === ApiItemKind.EntryPoint - ? `${item.displayName}${OVERLOAD_SEPARATOR}${item.kind}` - : `${item.parent.displayName}${OVERLOAD_SEPARATOR}${item.parent.kind}${METHOD_SEPARATOR}${item.displayName}`; -} - -export function resolveCanonicalReference( - canonicalReference: DeclarationReference | DocDeclarationReference, - apiPackage: ApiPackage | undefined, -): ResolvedCanonicalReference | null { - if ( - 'source' in canonicalReference && - canonicalReference.source && - 'packageName' in canonicalReference.source && - canonicalReference.symbol?.componentPath && - canonicalReference.symbol.meaning - ) - return { - package: canonicalReference.source.unscopedPackageName, - item: { - kind: mapMeaningToKind(canonicalReference.symbol.meaning as unknown as Meaning), - displayName: canonicalReference.symbol.componentPath.component.toString(), - containerKey: `|${ - canonicalReference.symbol.meaning - }|${canonicalReference.symbol.componentPath.component.toString()}`, - }, - // eslint-disable-next-line unicorn/better-regex - version: apiPackage?.dependencies?.[canonicalReference.source.packageName]?.replace(/[~^]/, ''), - }; - else if ( - 'memberReferences' in canonicalReference && - canonicalReference.memberReferences.length && - canonicalReference.memberReferences[0]?.memberIdentifier && - canonicalReference.memberReferences[0]?.selector?.selectorKind === SelectorKind.System - ) { - const member = canonicalReference.memberReferences[0]!; - return { - package: canonicalReference.packageName?.replace('@discordjs/', ''), - item: { - kind: member.selector!.selector, - displayName: member.memberIdentifier!.identifier, - containerKey: `|${member.selector!.selector}|${member.memberIdentifier!.identifier}`, - members: canonicalReference.memberReferences - .slice(1) - .map((member) => ({ kind: member.kind, displayName: member.memberIdentifier!.identifier! })), - }, - // eslint-disable-next-line unicorn/better-regex - version: apiPackage?.dependencies?.[canonicalReference.packageName ?? '']?.replace(/[~^]/, ''), - }; - } - - return null; -} - -export function mapMeaningToKind(meaning: Meaning): ApiItemKind { - return [...kindToMeaning.entries()].find((mapping) => mapping[1] === meaning)?.[0] ?? ApiItemKind.None; -} - -export function mapKindToMeaning(kind: ApiItemKind): Meaning { - return kindToMeaning.get(kind) ?? Meaning.Variable; -} - -export function memberPredicate( - item: ApiItem, -): item is ApiEvent | ApiMethod | ApiMethodSignature | ApiProperty | ApiPropertySignature { - return ( - item.kind === ApiItemKind.Property || - item.kind === ApiItemKind.PropertySignature || - item.kind === ApiItemKind.Method || - item.kind === ApiItemKind.MethodSignature || - item.kind === ApiItemKind.Event - ); -} - -export function serializeMembers(clazz: ApiItemContainerMixin): TableOfContentsSerialized[] { - return resolveMembers(clazz, memberPredicate).map(({ item: member }) => { - if (member.kind === 'Method' || member.kind === 'MethodSignature') { - return { - kind: member.kind as 'Method' | 'MethodSignature', - name: member.displayName, - overloadIndex: (member as ApiMethod | ApiMethodSignature).overloadIndex, - }; - } else if (member.kind === 'Event') { - return { - kind: member.kind as 'Event', - name: member.displayName, - }; - } else { - return { - kind: member.kind as 'Property' | 'PropertySignature', - name: member.displayName, - }; - } - }); -} - -export function parametersString(item: ApiDocumentedItem & ApiParameterListMixin) { - return resolveParameters(item).reduce((prev, cur, index) => { - if (index === 0) { - return `${prev}${cur.isRest ? '...' : ''}${cur.isOptional ? `${cur.name}?` : cur.name}`; - } - - return `${prev}, ${cur.isRest ? '...' : ''}${cur.isOptional ? `${cur.name}?` : cur.name}`; - }, ''); -} diff --git a/apps/website/src/components/model/Class.tsx b/apps/website/src/components/model/Class.tsx deleted file mode 100644 index af293ccd376f..000000000000 --- a/apps/website/src/components/model/Class.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { ApiClass, ApiConstructor } from '@discordjs/api-extractor-model'; -import { ApiItemKind } from '@discordjs/api-extractor-model'; -import { Badges } from '../Badges'; -import { Documentation } from '../documentation/Documentation'; -import { HierarchyText } from '../documentation/HierarchyText'; -import { Members } from '../documentation/Members'; -import { ObjectHeader } from '../documentation/ObjectHeader'; -import { ConstructorSection } from '../documentation/section/ConstructorSection'; -import { TypeParameterSection } from '../documentation/section/TypeParametersSection'; -import { serializeMembers } from '../documentation/util'; -import { OutlineSetter } from './OutlineSetter'; - -export function Class({ clazz }: { readonly clazz: ApiClass }) { - const constructor = clazz.members.find((member) => member.kind === ApiItemKind.Constructor) as - | ApiConstructor - | undefined; - - const outlineMembers = serializeMembers(clazz); - - return ( - - - - - - {clazz.typeParameters.length ? : null} - {constructor ? : null} - - - - ); -} diff --git a/apps/website/src/components/model/Event.tsx b/apps/website/src/components/model/Event.tsx deleted file mode 100644 index 8fed2da56cb2..000000000000 --- a/apps/website/src/components/model/Event.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { ApiDeclaredItem, ApiItemContainerMixin, ApiEvent } from '@discordjs/api-extractor-model'; -import { Badges } from '../Badges'; -import { CodeHeading } from '../CodeHeading'; -import { InheritanceText } from '../InheritanceText'; -import { ParameterTable } from '../ParameterTable'; -import { TSDoc } from '../documentation/tsdoc/TSDoc'; - -export function Event({ - item, - inheritedFrom, -}: { - readonly inheritedFrom?: (ApiDeclaredItem & ApiItemContainerMixin) | undefined; - readonly item: ApiEvent; -}) { - const hasSummary = Boolean(item.tsdocComment?.summarySection); - - return ( -
-
- - - {item.name} - -
- {hasSummary || inheritedFrom ? ( -
- {item.tsdocComment ? : null} - {item.parameters.length ? : null} - {inheritedFrom ? : null} -
- ) : null} -
- ); -} diff --git a/apps/website/src/components/model/Interface.tsx b/apps/website/src/components/model/Interface.tsx deleted file mode 100644 index d981487405ac..000000000000 --- a/apps/website/src/components/model/Interface.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { ApiInterface } from '@discordjs/api-extractor-model'; -import { Documentation } from '../documentation/Documentation'; -import { HierarchyText } from '../documentation/HierarchyText'; -import { Members } from '../documentation/Members'; -import { ObjectHeader } from '../documentation/ObjectHeader'; -import { TypeParameterSection } from '../documentation/section/TypeParametersSection'; -import { serializeMembers } from '../documentation/util'; -import { OutlineSetter } from './OutlineSetter'; - -export function Interface({ item }: { readonly item: ApiInterface }) { - const outlineMembers = serializeMembers(item); - - return ( - - - - {item.typeParameters.length ? : null} - - - - ); -} diff --git a/apps/website/src/components/model/OutlineSetter.tsx b/apps/website/src/components/model/OutlineSetter.tsx deleted file mode 100644 index d07aaa127237..000000000000 --- a/apps/website/src/components/model/OutlineSetter.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; - -import { useEffect, type PropsWithChildren } from 'react'; -import { useOutline } from '~/contexts/outline'; -import type { TableOfContentsSerialized } from '../TableOfContentItems'; - -export function OutlineSetter({ members }: PropsWithChildren<{ readonly members: TableOfContentsSerialized[] }>) { - const { setMembers } = useOutline(); - - useEffect(() => { - setMembers(members); - - return () => { - setMembers(null); - }; - }, [members, setMembers]); - - return null; -} diff --git a/apps/website/src/components/model/TypeAlias.tsx b/apps/website/src/components/model/TypeAlias.tsx deleted file mode 100644 index d15f861b4a52..000000000000 --- a/apps/website/src/components/model/TypeAlias.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { ExcerptTokenKind, type ApiTypeAlias, ExcerptToken } from '@discordjs/api-extractor-model'; -import { useMemo } from 'react'; -import { SyntaxHighlighter } from '../SyntaxHighlighter'; -import { Documentation } from '../documentation/Documentation'; -import { Header } from '../documentation/Header'; -import { SummarySection } from '../documentation/section/SummarySection'; -import { UnionMembersSection } from '../documentation/section/UnionMembersSection'; - -export function TypeAlias({ item }: { readonly item: ApiTypeAlias }) { - const union = useMemo(() => { - const union: ExcerptToken[][] = []; - let currentUnionMember: ExcerptToken[] = []; - let depth = 0; - for (const token of item.typeExcerpt.spannedTokens) { - if (token.text.includes('?')) { - return [item.typeExcerpt.spannedTokens]; - } - - depth += token.text.split('<').length - token.text.split('>').length; - - if (token.text.trim() === '|' && depth === 0) { - if (currentUnionMember.length) { - union.push(currentUnionMember); - currentUnionMember = []; - } - } else if (depth === 0 && token.kind === ExcerptTokenKind.Content && token.text.includes('|')) { - for (const [idx, tokenpart] of token.text.split('|').entries()) { - if (currentUnionMember.length && depth === 0 && idx === 0) { - currentUnionMember.push(new ExcerptToken(ExcerptTokenKind.Content, tokenpart)); - union.push(currentUnionMember); - currentUnionMember = []; - } else if (currentUnionMember.length && depth === 0) { - union.push(currentUnionMember); - currentUnionMember = [new ExcerptToken(ExcerptTokenKind.Content, tokenpart)]; - } else if (tokenpart.length) { - currentUnionMember.push(new ExcerptToken(ExcerptTokenKind.Content, tokenpart)); - } - } - } else { - currentUnionMember.push(token); - } - } - - if (currentUnionMember.length) { - union.push(currentUnionMember); - } - - return union; - }, [item]); - - return ( - -
- {/* @ts-expect-error async component */} - - - {union.length ? : null} - - ); -} diff --git a/apps/website/src/components/model/Variable.tsx b/apps/website/src/components/model/Variable.tsx deleted file mode 100644 index 7a260ad74b10..000000000000 --- a/apps/website/src/components/model/Variable.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { ApiVariable } from '@discordjs/api-extractor-model'; -import { Documentation } from '../documentation/Documentation'; -import { ObjectHeader } from '../documentation/ObjectHeader'; - -export function Variable({ item }: { readonly item: ApiVariable }) { - return ( - - - - ); -} diff --git a/apps/website/src/components/model/enum/Enum.tsx b/apps/website/src/components/model/enum/Enum.tsx deleted file mode 100644 index 32240fa7f2da..000000000000 --- a/apps/website/src/components/model/enum/Enum.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { ApiEnum } from '@discordjs/api-extractor-model'; -import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum'; -import { Panel } from '../../Panel'; -import { Documentation } from '../../documentation/Documentation'; -import { ObjectHeader } from '../../documentation/ObjectHeader'; -import { DocumentationSection } from '../../documentation/section/DocumentationSection'; -import { EnumMember } from './EnumMember'; - -export function Enum({ item }: { readonly item: ApiEnum }) { - return ( - - - } padded title="Members"> -
- {item.members.map((member, idx) => ( - - - - ))} -
-
-
- ); -} diff --git a/apps/website/src/components/model/enum/EnumMember.tsx b/apps/website/src/components/model/enum/EnumMember.tsx deleted file mode 100644 index 21bb67defac7..000000000000 --- a/apps/website/src/components/model/enum/EnumMember.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { ApiEnumMember } from '@discordjs/api-extractor-model'; -import { CodeHeading } from '~/components/CodeHeading'; -import { SignatureText } from '../../SignatureText'; -import { TSDoc } from '../../documentation/tsdoc/TSDoc'; - -export function EnumMember({ member }: { readonly member: ApiEnumMember }) { - return ( -
- - {member.name} - = - {member.initializerExcerpt ? ( - - ) : null} - - {member.tsdocComment ? : null} -
- ); -} diff --git a/apps/website/src/components/model/function/Function.tsx b/apps/website/src/components/model/function/Function.tsx deleted file mode 100644 index 7edaaef4ca61..000000000000 --- a/apps/website/src/components/model/function/Function.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { ApiFunction } from '@discordjs/api-extractor-model'; -import dynamic from 'next/dynamic'; -import { Documentation } from '~/components/documentation/Documentation'; -import { ObjectHeader } from '~/components/documentation/ObjectHeader'; -import { FunctionBody } from './FunctionBody'; - -const OverloadSwitcher = dynamic(async () => import('../../OverloadSwitcher')); - -export function Function({ item }: { readonly item: ApiFunction }) { - if (item.getMergedSiblings().length > 1) { - const overloads = item.getMergedSiblings().map((sibling, idx) => ( - - - - - )); - - return ; - } - - return ( - - - - - ); -} diff --git a/apps/website/src/components/model/function/FunctionBody.tsx b/apps/website/src/components/model/function/FunctionBody.tsx deleted file mode 100644 index 4b809da0301b..000000000000 --- a/apps/website/src/components/model/function/FunctionBody.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { ApiFunction } from '@discordjs/api-extractor-model'; -import { ParameterSection } from '../../documentation/section/ParametersSection'; -import { TypeParameterSection } from '../../documentation/section/TypeParametersSection'; - -export interface FunctionBodyProps { - mergedSiblingCount: number; - overloadDocumentation: React.ReactNode[]; -} - -export function FunctionBody({ item }: { readonly item: ApiFunction }) { - return ( - <> - {item.typeParameters.length ? : null} - {item.parameters.length ? : null} - - ); -} diff --git a/apps/website/src/components/model/method/Method.tsx b/apps/website/src/components/model/method/Method.tsx deleted file mode 100644 index c42158403b09..000000000000 --- a/apps/website/src/components/model/method/Method.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { - ApiItemKind, - type ApiDeclaredItem, - type ApiItemContainerMixin, - type ApiMethod, - type ApiMethodSignature, -} from '@discordjs/api-extractor-model'; -import dynamic from 'next/dynamic'; -import { Fragment } from 'react'; -import { MethodDocumentation } from './MethodDocumentation'; -import { MethodHeader } from './MethodHeader'; - -const OverloadSwitcher = dynamic(async () => import('../../OverloadSwitcher')); - -export function Method({ - method, - inheritedFrom, -}: { - readonly inheritedFrom?: (ApiDeclaredItem & ApiItemContainerMixin) | undefined; - readonly method: ApiMethod | ApiMethodSignature; -}) { - if ( - method - .getMergedSiblings() - .filter((sibling) => sibling.kind === ApiItemKind.Method || sibling.kind === ApiItemKind.MethodSignature).length > - 1 - ) { - // We have overloads, use the overload switcher, but render - // each overload node on the server. - const overloads = method - .getMergedSiblings() - .filter((sibling) => sibling.kind === ApiItemKind.Method || sibling.kind === ApiItemKind.MethodSignature) - .map((sibling, idx) => ( - - - - - )); - - return ; - } - - // We have just a single method, render it on the server. - return ( - <> - - - - ); -} diff --git a/apps/website/src/components/model/method/MethodDocumentation.tsx b/apps/website/src/components/model/method/MethodDocumentation.tsx deleted file mode 100644 index b64f68417637..000000000000 --- a/apps/website/src/components/model/method/MethodDocumentation.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { - ApiItemKind, - type ApiDeclaredItem, - type ApiItemContainerMixin, - type ApiMethod, - type ApiMethodSignature, -} from '@discordjs/api-extractor-model'; -import { ParameterSection } from '~/components/documentation/section/ParametersSection'; -import { TypeParameterSection } from '~/components/documentation/section/TypeParametersSection'; -import { InheritanceText } from '../../InheritanceText'; -import { TSDoc } from '../../documentation/tsdoc/TSDoc'; - -export interface MethodDocumentationProps { - readonly inheritedFrom?: (ApiDeclaredItem & ApiItemContainerMixin) | undefined; - readonly method: ApiMethod | ApiMethodSignature; -} - -export function MethodDocumentation({ method, inheritedFrom }: MethodDocumentationProps) { - const parent = method.parent as ApiDeclaredItem; - const firstOverload = method - .getMergedSiblings() - .find( - (meth): meth is ApiMethod => meth.kind === ApiItemKind.Method && (meth as ApiMethod).overloadIndex === 1, - )?.tsdocComment; - - if (!(method.tsdocComment?.summarySection || firstOverload?.summarySection || method.parameters.length > 0)) { - return null; - } - - return ( -
- {method.tsdocComment || firstOverload ? ( - - ) : null} - {method.typeParameters.length ? : null} - {method.parameters.length ? : null} - {inheritedFrom && parent ? : null} -
- ); -} diff --git a/apps/website/src/components/model/method/MethodHeader.tsx b/apps/website/src/components/model/method/MethodHeader.tsx deleted file mode 100644 index 973773cc0f7a..000000000000 --- a/apps/website/src/components/model/method/MethodHeader.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { ApiMethod, ApiMethodSignature } from '@discordjs/api-extractor-model'; -import { useMemo } from 'react'; -import { Badges } from '~/components/Badges'; -import { CodeHeading } from '~/components/CodeHeading'; -import { ExcerptText } from '~/components/ExcerptText'; -import { parametersString } from '~/components/documentation/util'; - -export function MethodHeader({ method }: { readonly method: ApiMethod | ApiMethodSignature }) { - const key = useMemo( - () => `${method.displayName}${method.overloadIndex && method.overloadIndex > 1 ? `:${method.overloadIndex}` : ''}`, - [method.displayName, method.overloadIndex], - ); - - return ( -
-
- - - {`${method.name}(${parametersString(method)})`} - : - - -
-
- ); -} diff --git a/apps/website/src/components/ui/Alert.tsx b/apps/website/src/components/ui/Alert.tsx new file mode 100644 index 000000000000..276d21368e85 --- /dev/null +++ b/apps/website/src/components/ui/Alert.tsx @@ -0,0 +1,67 @@ +import { VscFlame } from '@react-icons/all-files/vsc/VscFlame'; +import { VscInfo } from '@react-icons/all-files/vsc/VscInfo'; +import { VscWarning } from '@react-icons/all-files/vsc/VscWarning'; +import type { PropsWithChildren } from 'react'; + +interface IAlert { + readonly title?: string | undefined; + readonly type: 'danger' | 'info' | 'success' | 'warning'; +} + +function resolveType(type: IAlert['type']) { + switch (type) { + case 'danger': { + return { + text: 'text-red-500', + border: 'border-red-500', + icon: , + }; + } + + case 'info': { + return { + text: 'text-blue-500', + border: 'border-blue-500', + icon: , + }; + } + + case 'success': { + return { + text: 'text-green-500', + border: 'border-green-500', + icon: , + }; + } + + case 'warning': { + return { + text: 'text-yellow-500', + border: 'border-yellow-500', + icon: , + }; + } + } +} + +export async function Alert({ title, type, children }: PropsWithChildren) { + const { text, border, icon } = resolveType(type); + + return ( +
+
+
{children}
+
+
+
+
+ {icon} + {title ? {title} : null} +
+
+
+
+
+
+ ); +} diff --git a/apps/website/src/components/ui/Button.tsx b/apps/website/src/components/ui/Button.tsx new file mode 100644 index 000000000000..f469c6a53ef2 --- /dev/null +++ b/apps/website/src/components/ui/Button.tsx @@ -0,0 +1,3 @@ +'use client'; + +export { Button } from 'react-aria-components'; diff --git a/apps/website/src/components/ui/CmdK.tsx b/apps/website/src/components/ui/CmdK.tsx new file mode 100644 index 000000000000..5b5eb48e46a0 --- /dev/null +++ b/apps/website/src/components/ui/CmdK.tsx @@ -0,0 +1,148 @@ +'use client'; + +import { Command } from 'cmdk'; +import { useAtom, useSetAtom } from 'jotai'; +import { ArrowRight } from 'lucide-react'; +import MeiliSearch from 'meilisearch'; +import { usePathname, useRouter } from 'next/navigation'; +import { useEffect, useMemo, useState } from 'react'; +import { useDebounceValue } from 'usehooks-ts'; +import { isCmdKOpenAtom } from '~/stores/cmdk'; +import { isDrawerOpenAtom } from '~/stores/drawer'; +import { resolveKind } from '~/util/resolveNodeKind'; +import { OverlayScrollbarsComponent } from '../OverlayScrollbars'; + +const client = new MeiliSearch({ + host: 'https://search.discordjs.dev', + apiKey: 'b51923c6abb574b1e97be9a03dc6414b6c69fb0c5696d0ef01a82b0f77d223db', +}); + +export function CmdK({ dependencies }: { readonly dependencies: string[] }) { + const pathname = usePathname(); + const router = useRouter(); + const [open, setOpen] = useAtom(isCmdKOpenAtom); + const setDrawerOpen = useSetAtom(isDrawerOpenAtom); + const [search, setSearch] = useDebounceValue('', 250); + const [searchResults, setSearchResults] = useState([]); + + const packageName = useMemo(() => pathname?.split('/').slice(3, 4)[0], [pathname]); + const branchName = useMemo(() => pathname?.split('/').slice(4, 5)[0], [pathname]); + + const searchResultItems = useMemo( + () => + searchResults?.map((item, idx) => ( + { + router.push(item.path); + setOpen(false); + }} + value={item.id} + > + {resolveKind(item.kind)} +
+ {item.name} + {item.summary} + {item.path} +
+ +
+ )) ?? [], + [router, searchResults, setOpen], + ); + + // Toggle the menu when ⌘K is pressed + useEffect(() => { + const down = (event: KeyboardEvent) => { + if (event.key === 'k' && (event.metaKey || event.ctrlKey)) { + event.preventDefault(); + setOpen((open) => !open); + } + }; + + document.addEventListener('keydown', down); + return () => { + document.removeEventListener('keydown', down); + }; + }, [setOpen]); + + useEffect(() => { + if (open) { + setDrawerOpen(false); + setSearch(''); + } + + return () => { + document.body.style.pointerEvents = 'auto'; + }; + }, [open, setDrawerOpen, setSearch]); + + useEffect(() => { + // const searchDoc = async (searchString: string, version: string) => { + // console.log(dependencies); + // const res = await client + // .index(`${packageName?.replaceAll('.', '-')}-${version}`) + // .search(searchString, { limit: 25 }); + // setSearchResults(res.hits); + // }; + + const searchDoc = async (searchString: string, version: string) => { + const result = await client.multiSearch({ + queries: [`${packageName?.replaceAll('.', '-')}-${version}`, ...dependencies].map((dep) => ({ + indexUid: dep, + // eslint-disable-next-line id-length + q: searchString, + limit: 25, + attributesToSearchOn: ['name'], + })), + }); + setSearchResults(result.results.flatMap((res) => res.hits)); + }; + + if (search && packageName) { + void searchDoc(search, branchName?.replaceAll('.', '-') ?? 'main'); + } else { + setSearchResults([]); + } + }, [branchName, dependencies, packageName, search]); + + return ( + + + + + {search && searchResultItems.length ? ( + searchResultItems + ) : ( +
+ No results found. +
+ )} +
+
+
+ ); +} diff --git a/apps/website/src/components/ui/Collapsible.tsx b/apps/website/src/components/ui/Collapsible.tsx new file mode 100644 index 000000000000..5d19873e940c --- /dev/null +++ b/apps/website/src/components/ui/Collapsible.tsx @@ -0,0 +1,3 @@ +'use client'; + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@radix-ui/react-collapsible'; diff --git a/apps/website/src/components/ui/Drawer.tsx b/apps/website/src/components/ui/Drawer.tsx new file mode 100644 index 000000000000..48979e391458 --- /dev/null +++ b/apps/website/src/components/ui/Drawer.tsx @@ -0,0 +1,37 @@ +'use client'; + +import { useAtom } from 'jotai'; +import { ChevronUp } from 'lucide-react'; +import { useEffect, type PropsWithChildren } from 'react'; +import { useMediaQuery } from 'usehooks-ts'; +import { Drawer as Vaul } from 'vaul'; +import { isDrawerOpenAtom } from '~/stores/drawer'; + +export function Drawer({ children }: PropsWithChildren) { + const [open, setOpen] = useAtom(isDrawerOpenAtom); + const isMedium = useMediaQuery('(min-width: 768px)'); + + useEffect(() => { + if (isMedium) { + setOpen(false); + } + }, [isMedium, setOpen]); + + return ( + + + + + + + +
+ {children} + + + + ); +} diff --git a/apps/website/src/components/Footer.tsx b/apps/website/src/components/ui/Footer.tsx similarity index 70% rename from apps/website/src/components/Footer.tsx rename to apps/website/src/components/ui/Footer.tsx index b07ec0494fef..26a79cf0efea 100644 --- a/apps/website/src/components/Footer.tsx +++ b/apps/website/src/components/ui/Footer.tsx @@ -2,13 +2,13 @@ import Image from 'next/image'; import vercelLogo from '~/assets/powered-by-vercel.svg'; import workersLogo from '~/assets/powered-by-workers.png'; -export default function Footer() { +export function Footer() { return (