From 12553da1351dc06fd05768b1a32447a8f68a89aa Mon Sep 17 00:00:00 2001 From: Suneet Tipirneni <77477100+suneettipirneni@users.noreply.github.com> Date: Sun, 27 Nov 2022 10:24:34 -0500 Subject: [PATCH] feat(website): add `app` dir (#8869) Co-authored-by: iCrawl --- apps/website/.gitignore | 1 + apps/website/next.config.js | 1 + .../website/src/app/docs/[...slug]/layout.tsx | 14 + .../docs/[...slug]/not-found.tsx} | 9 +- apps/website/src/app/docs/[...slug]/page.tsx | 394 ++++++++++++++++++ .../src/app/docs/[...slug]/providers.tsx | 13 + .../docs/packages/[package]/page.tsx} | 82 +--- .../index.tsx => app/docs/packages/page.tsx} | 81 +--- apps/website/src/app/head.tsx | 28 ++ apps/website/src/app/layout.tsx | 19 + .../src/{pages/index.tsx => app/page.tsx} | 3 +- apps/website/src/app/providers.tsx | 20 + apps/website/src/components/CmdK.tsx | 24 +- apps/website/src/components/CodeListing.tsx | 2 + apps/website/src/components/DocContainer.tsx | 2 + apps/website/src/components/Header.tsx | 123 ++++++ .../src/components/HyperlinkedText.tsx | 3 +- .../src/components/InheritanceText.tsx | 3 +- apps/website/src/components/MDXRemote.tsx | 3 + apps/website/src/components/MethodItem.tsx | 4 +- apps/website/src/components/MethodList.tsx | 2 + apps/website/src/components/Nav.tsx | 37 ++ apps/website/src/components/PackageSelect.tsx | 67 +++ .../website/src/components/ParameterTable.tsx | 2 + apps/website/src/components/PropertyList.tsx | 2 + apps/website/src/components/Sections.tsx | 2 + apps/website/src/components/SidebarItems.tsx | 40 +- apps/website/src/components/SidebarLayout.tsx | 376 ----------------- .../src/components/SyntaxHighlighter.tsx | 2 + apps/website/src/components/Table.tsx | 2 + .../src/components/TableOfContentItems.tsx | 2 + .../website/src/components/TypeParamTable.tsx | 2 + apps/website/src/components/VersionSelect.tsx | 60 +++ apps/website/src/components/model/Class.tsx | 2 + apps/website/src/components/model/Enum.tsx | 2 + .../website/src/components/model/Function.tsx | 4 +- .../src/components/model/Interface.tsx | 2 + .../src/components/model/TypeAlias.tsx | 2 + .../website/src/components/model/Variable.tsx | 2 + .../src/components/tsdoc/BlockComment.tsx | 2 + apps/website/src/components/tsdoc/TSDoc.tsx | 4 +- apps/website/src/contexts/cmdK.tsx | 8 +- apps/website/src/contexts/member.tsx | 6 +- apps/website/src/contexts/nav.tsx | 19 + apps/website/src/pages/_app.tsx | 33 -- apps/website/src/pages/_document.tsx | 32 -- apps/website/src/pages/docs/[...slug].tsx | 345 --------------- apps/website/src/styles/main.css | 4 + apps/website/tsconfig.json | 2 +- 49 files changed, 941 insertions(+), 953 deletions(-) create mode 100644 apps/website/src/app/docs/[...slug]/layout.tsx rename apps/website/src/{pages/404.tsx => app/docs/[...slug]/not-found.tsx} (87%) create mode 100644 apps/website/src/app/docs/[...slug]/page.tsx create mode 100644 apps/website/src/app/docs/[...slug]/providers.tsx rename apps/website/src/{pages/docs/packages/[package]/index.tsx => app/docs/packages/[package]/page.tsx} (53%) rename apps/website/src/{pages/docs/packages/index.tsx => app/docs/packages/page.tsx} (63%) create mode 100644 apps/website/src/app/head.tsx create mode 100644 apps/website/src/app/layout.tsx rename apps/website/src/{pages/index.tsx => app/page.tsx} (98%) create mode 100644 apps/website/src/app/providers.tsx create mode 100644 apps/website/src/components/Header.tsx create mode 100644 apps/website/src/components/MDXRemote.tsx create mode 100644 apps/website/src/components/Nav.tsx create mode 100644 apps/website/src/components/PackageSelect.tsx delete mode 100644 apps/website/src/components/SidebarLayout.tsx create mode 100644 apps/website/src/components/VersionSelect.tsx create mode 100644 apps/website/src/contexts/nav.tsx delete mode 100644 apps/website/src/pages/_app.tsx delete mode 100644 apps/website/src/pages/_document.tsx delete mode 100644 apps/website/src/pages/docs/[...slug].tsx diff --git a/apps/website/.gitignore b/apps/website/.gitignore index 9c93f933b..5e42e5ba4 100644 --- a/apps/website/.gitignore +++ b/apps/website/.gitignore @@ -27,3 +27,4 @@ src/styles/unocss.css coverage/ .vercel public/searchIndex +.vscode diff --git a/apps/website/next.config.js b/apps/website/next.config.js index b870121bf..0c25edbf6 100644 --- a/apps/website/next.config.js +++ b/apps/website/next.config.js @@ -12,6 +12,7 @@ export default { cleanDistDir: true, experimental: { appDir: true, + serverComponentsExternalPackages: ['@microsoft/api-extractor-model', 'jju', 'shiki'], outputFileTracingRoot: fileURLToPath(new URL('../../', import.meta.url)), fallbackNodePolyfills: false, }, diff --git a/apps/website/src/app/docs/[...slug]/layout.tsx b/apps/website/src/app/docs/[...slug]/layout.tsx new file mode 100644 index 000000000..b6b566c27 --- /dev/null +++ b/apps/website/src/app/docs/[...slug]/layout.tsx @@ -0,0 +1,14 @@ +import type { PropsWithChildren } from 'react'; +import { Providers } from './providers'; +import { CmdKDialog } from '~/components/CmdK'; +import { Header } from '~/components/Header'; + +export default function SidebarLayout({ children }: PropsWithChildren) { + return ( + +
+ <>{children} + + + ); +} diff --git a/apps/website/src/pages/404.tsx b/apps/website/src/app/docs/[...slug]/not-found.tsx similarity index 87% rename from apps/website/src/pages/404.tsx rename to apps/website/src/app/docs/[...slug]/not-found.tsx index 4c8a4d9f6..07232c348 100644 --- a/apps/website/src/pages/404.tsx +++ b/apps/website/src/app/docs/[...slug]/not-found.tsx @@ -1,20 +1,19 @@ -import Head from 'next/head'; +// import Head from 'next/head'; import Link from 'next/link'; -export default function FourOhFourPage() { +export default function NotFound() { return ( <> - + {/* discord.js | 404 - + */}

404

Not found.

Take me back diff --git a/apps/website/src/app/docs/[...slug]/page.tsx b/apps/website/src/app/docs/[...slug]/page.tsx new file mode 100644 index 000000000..560d90eaf --- /dev/null +++ b/apps/website/src/app/docs/[...slug]/page.tsx @@ -0,0 +1,394 @@ +/* eslint-disable no-case-declarations */ +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +// eslint-disable-next-line n/prefer-global/process +import process, { cwd } from 'node:process'; +import { + findPackage, + getMembers, + type ApiItemJSON, + type ApiClassJSON, + type ApiFunctionJSON, + type ApiInterfaceJSON, + type ApiTypeAliasJSON, + type ApiVariableJSON, + type ApiEnumJSON, +} from '@discordjs/api-extractor-utils'; +import { createApiModel } from '@discordjs/scripts'; +import { ApiFunction, ApiItemKind, type ApiPackage } from '@microsoft/api-extractor-model'; +import Image from 'next/image'; +// import Head from 'next/head'; +import { notFound } from 'next/navigation'; +import { serialize } from 'next-mdx-remote/serialize'; +import rehypeIgnore from 'rehype-ignore'; +import rehypePrettyCode, { type Options } from 'rehype-pretty-code'; +import rehypeRaw from 'rehype-raw'; +import rehypeSlug from 'rehype-slug'; +import remarkGfm from 'remark-gfm'; +import { getHighlighter } from 'shiki'; +import shikiLangJavascript from 'shiki/languages/javascript.tmLanguage.json'; +import shikiLangTypescript from 'shiki/languages/typescript.tmLanguage.json'; +import shikiThemeDarkPlus from 'shiki/themes/dark-plus.json'; +import shikiThemeLightPlus from 'shiki/themes/light-plus.json'; +import vercelLogo from '../../../assets/powered-by-vercel.svg'; +import { MDXRemote } from '~/components/MDXRemote'; +import { Nav } from '~/components/Nav'; +import { Class } from '~/components/model/Class'; +import { Enum } from '~/components/model/Enum'; +import { Function } from '~/components/model/Function'; +import { Interface } from '~/components/model/Interface'; +import { TypeAlias } from '~/components/model/TypeAlias'; +import { Variable } from '~/components/model/Variable'; +import { MemberProvider } from '~/contexts/member'; +import { DESCRIPTION, PACKAGES } from '~/util/constants'; +import { findMember, findMemberByKey } from '~/util/model.server'; +import { tryResolveDescription } from '~/util/summary'; + +export async function generateStaticParams() { + return ( + await Promise.all( + PACKAGES.map(async (packageName) => { + try { + let data: any[] = []; + let versions: string[] = []; + if (process.env.NEXT_PUBLIC_LOCAL_DEV) { + const res = await readFile( + join(cwd(), '..', '..', 'packages', packageName, 'docs', 'docs.api.json'), + 'utf8', + ); + data = JSON.parse(res); + } else { + const response = await fetch(`https://docs.discordjs.dev/api/info?package=${packageName}`); + versions = await response.json(); + versions = versions.slice(-2); + + for (const version of versions) { + const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${version}.api.json`); + data = [...data, await res.json()]; + } + } + + if (Array.isArray(data)) { + const models = data.map((innerData) => createApiModel(innerData)); + const pkgs = models.map((model) => findPackage(model, packageName)) as ApiPackage[]; + + return [ + ...versions.map((version) => ({ slug: ['packages', packageName, version] })), + ...pkgs.flatMap((pkg, idx) => + getMembers(pkg, versions[idx] ?? 'main').map((member) => { + if (member.kind === ApiItemKind.Function && member.overloadIndex && member.overloadIndex > 1) { + return { + slug: [ + 'packages', + packageName, + versions[idx] ?? 'main', + `${member.name}:${member.overloadIndex}:${member.kind}`, + ], + }; + } + + return { + slug: ['packages', packageName, versions[idx] ?? 'main', `${member.name}:${member.kind}`], + }; + }), + ), + ]; + } + + const model = createApiModel(data); + const pkg = findPackage(model, packageName)!; + + return [ + { slug: ['packages', packageName, 'main'] }, + ...getMembers(pkg, 'main').map((member) => { + if (member.kind === ApiItemKind.Function && member.overloadIndex && member.overloadIndex > 1) { + return { + slug: ['packages', packageName, 'main', `${member.name}:${member.overloadIndex}:${member.kind}`], + }; + } + + return { slug: ['packages', packageName, 'main', `${member.name}:${member.kind}`] }; + }), + ]; + } catch { + return { slug: [] }; + } + }), + ) + ).flat(); +} + +async function getData(slug: string[]) { + const [path, packageName = 'builders', branchName = 'main', member] = slug; + + if (path !== 'packages' || !PACKAGES.includes(packageName)) { + notFound(); + } + + const [memberName, overloadIndex] = member?.split('%3A') ?? []; + + const readme = await readFile(join(cwd(), '..', '..', 'packages', packageName, 'README.md'), 'utf8'); + + const mdxSource = await serialize(readme, { + mdxOptions: { + remarkPlugins: [remarkGfm], + remarkRehypeOptions: { allowDangerousHtml: true }, + rehypePlugins: [ + rehypeRaw, + rehypeIgnore, + rehypeSlug, + [ + rehypePrettyCode, + { + theme: { + dark: shikiThemeDarkPlus, + light: shikiThemeLightPlus, + }, + getHighlighter: async (options?: Partial) => + getHighlighter({ + ...options, + langs: [ + // @ts-expect-error: Working as intended + { id: 'javascript', aliases: ['js'], scopeName: 'source.js', grammar: shikiLangJavascript }, + // @ts-expect-error: Working as intended + { id: 'typescript', aliases: ['ts'], scopeName: 'source.ts', grammar: shikiLangTypescript }, + ], + }), + }, + ], + ], + format: 'md', + }, + }); + + let data; + if (process.env.NEXT_PUBLIC_LOCAL_DEV) { + const res = await readFile(join(cwd(), '..', '..', 'packages', packageName, 'docs', 'docs.api.json'), 'utf8'); + data = JSON.parse(res); + } else { + const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${branchName}.api.json`, { + next: { revalidate: 3_600 }, + }); + data = await res.json(); + } + + const model = createApiModel(data); + const pkg = findPackage(model, packageName); + + // eslint-disable-next-line prefer-const + let { containerKey, name } = findMember(model, packageName, memberName, branchName) ?? {}; + if (name && overloadIndex && !Number.isNaN(Number.parseInt(overloadIndex, 10))) { + containerKey = ApiFunction.getContainerKey(name, Number.parseInt(overloadIndex, 10)); + } + + const members = pkg + ? getMembers(pkg, branchName).filter((item) => item.overloadIndex === null || item.overloadIndex <= 1) + : []; + const foundMember = + memberName && containerKey ? findMemberByKey(model, packageName, containerKey, branchName) ?? null : null; + const description = foundMember ? tryResolveDescription(foundMember) ?? DESCRIPTION : DESCRIPTION; + + return { + packageName, + branchName, + data: { + members, + member: foundMember, + description, + source: mdxSource, + }, + }; +} + +// function resolveMember(packageName?: string | undefined, member?: SidebarLayoutProps['data']['member']) { +// switch (member?.kind) { +// case 'Class': { +// const typedMember = member as ApiClassJSON; +// return `?pkg=${packageName}&kind=${typedMember.kind}&name=${typedMember.name}&methods=${typedMember.methods.length}&props=${typedMember.properties.length}`; +// } + +// case 'Function': { +// const typedMember = member as ApiFunctionJSON; +// return `?pkg=${packageName}&kind=${typedMember.kind}&name=${typedMember.name}`; +// } + +// case 'Interface': { +// const typedMember = member as ApiInterfaceJSON; +// return `?pkg=${packageName}&kind=${typedMember.kind}&name=${typedMember.name}&methods=${typedMember.methods.length}&props=${typedMember.properties.length}`; +// } + +// case 'TypeAlias': { +// const typedMember = member as ApiTypeAliasJSON; +// return `?pkg=${packageName}&kind=${typedMember.kind}&name=${typedMember.name}`; +// } + +// case 'Variable': { +// const typedMember = member as ApiVariableJSON; +// return `?pkg=${packageName}&kind=${typedMember.kind}&name=${typedMember.name}`; +// } + +// case 'Enum': { +// const typedMember = member as ApiEnumJSON; +// return `?pkg=${packageName}&kind=${typedMember.kind}&name=${typedMember.name}&members=${typedMember.members.length}`; +// } + +// default: { +// return `?pkg=${packageName}&kind=${member?.kind}&name=${member?.name}`; +// } +// } +// } + +function member(props?: ApiItemJSON | undefined) { + switch (props?.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: { slug: string[] } }) { + const data = await getData(params.slug); + + // const name = useMemo( + // () => `discord.js${params.data?.member?.name ? ` | ${params.data.member.name}` : ''}`, + // [params.data?.member?.name], + // ); + // const ogTitle = useMemo( + // () => `${params.packageName ?? 'discord.js'}${params.data?.member?.name ? ` | ${params.data.member.name}` : ''}`, + // [params.packageName, params.data?.member?.name], + // ); + // const ogImage = useMemo( + // () => resolveMember(params.packageName, params.data?.member), + // [params.packageName, params.data?.member], + // ); + + // Just in case + // return ; + + return ( + +