From 96304d7cc8ffe6aafa56d00339500dfbbf5eb5f5 Mon Sep 17 00:00:00 2001 From: iCrawl Date: Fri, 9 Sep 2022 00:08:59 +0200 Subject: [PATCH] feat: command menu --- packages/api-extractor-utils/src/parse.ts | 2 +- packages/scripts/src/generateIndex.ts | 13 +- packages/website/.gitignore | 1 + packages/website/next.config.js | 4 +- packages/website/package.json | 26 +- .../website/scripts/generateAllIndicies.js | 5 + packages/website/src/components/Cmdk.tsx | 225 ++++++++++++++++++ .../website/src/components/SidebarLayout.tsx | 8 +- packages/website/src/pages/_app.tsx | 1 + packages/website/src/pages/docs/[...slug].tsx | 10 +- .../pages/docs/packages/[package]/index.tsx | 2 +- .../website/src/pages/docs/packages/index.tsx | 3 +- packages/website/src/styles/cmdk.css | 3 + packages/website/src/util/fetcher.ts | 4 + 14 files changed, 272 insertions(+), 35 deletions(-) create mode 100644 packages/website/scripts/generateAllIndicies.js create mode 100644 packages/website/src/components/Cmdk.tsx create mode 100644 packages/website/src/styles/cmdk.css create mode 100644 packages/website/src/util/fetcher.ts diff --git a/packages/api-extractor-utils/src/parse.ts b/packages/api-extractor-utils/src/parse.ts index 0938ce8ee..de13e6d34 100644 --- a/packages/api-extractor-utils/src/parse.ts +++ b/packages/api-extractor-utils/src/parse.ts @@ -47,7 +47,7 @@ export function generatePath(items: readonly ApiItem[], version: string) { case ApiItemKind.MethodSignature: case ApiItemKind.PropertySignature: // TODO: Take overloads into account - path += `#${item.displayName}:${item.kind}`; + path += `#${item.displayName}`; break; default: path += `/${item.displayName}:${item.kind}`; diff --git a/packages/scripts/src/generateIndex.ts b/packages/scripts/src/generateIndex.ts index 37fd33fb5..07938abb9 100644 --- a/packages/scripts/src/generateIndex.ts +++ b/packages/scripts/src/generateIndex.ts @@ -8,6 +8,7 @@ import { ApiItem, ApiModel, type ApiPackage, + ApiItemKind, } from '@microsoft/api-extractor-model'; import { DocNodeKind, @@ -102,6 +103,10 @@ export function visitNodes(item: ApiItem, tag: string) { continue; } + if (member.kind === ApiItemKind.Constructor) { + continue; + } + if (ApiItemContainerMixin.isBaseClassOf(member)) { members.push(...visitNodes(member, tag)); } @@ -109,7 +114,7 @@ export function visitNodes(item: ApiItem, tag: string) { members.push({ name: member.displayName, kind: member.kind, - summary: tryResolveSummaryText(member), + summary: tryResolveSummaryText(member) ?? '', path: generatePath(member.getHierarchy(), tag), }); } @@ -123,13 +128,13 @@ export async function generateIndex(model: ApiModel, packageName: string, tag = const dir = 'searchIndex'; try { - (await stat(join(cwd(), dir))).isDirectory(); + (await stat(join(cwd(), 'public', dir))).isDirectory(); } catch { - await mkdir(join(cwd(), dir)); + await mkdir(join(cwd(), 'public', dir)); } await writeFile( - join(cwd(), 'searchIndex', `${packageName}-${tag}-index.json`), + join(cwd(), 'public', dir, `${packageName}-${tag}-index.json`), JSON.stringify(members, undefined, 2), ); } diff --git a/packages/website/.gitignore b/packages/website/.gitignore index ab614dd17..7a01b5c3b 100644 --- a/packages/website/.gitignore +++ b/packages/website/.gitignore @@ -27,3 +27,4 @@ src/styles/unocss.css .tmp/ coverage/ .vercel +public/searchIndex diff --git a/packages/website/next.config.js b/packages/website/next.config.js index 71eb42b67..a27864a51 100644 --- a/packages/website/next.config.js +++ b/packages/website/next.config.js @@ -12,10 +12,8 @@ export default { }, cleanDistDir: true, experimental: { - images: { - allowFutureImage: true, - }, outputFileTracingRoot: fileURLToPath(new URL('../../', import.meta.url)), + fallbackNodePolyfills: true, }, images: { dangerouslyAllowSVG: true, diff --git a/packages/website/package.json b/packages/website/package.json index 434c53cb7..8cc47b7c5 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -5,8 +5,8 @@ "private": true, "scripts": { "test": "vitest run", - "build:local": "NEXT_PUBLIC_LOCAL_DEV=true yarn run --top-level docs --force && yarn build:prod", - "build:prod": "yarn build:css && yarn build:next", + "build:local": "NEXT_PUBLIC_LOCAL_DEV=true yarn build:prod", + "build:prod": "yarn run --top-level docs --force && yarn build:css && yarn build:next && yarn node scripts/generateAllIndicies.js", "build:next": "next build", "build:css": "yarn generate:css", "dev": "yarn run --top-level docs && concurrently 'yarn dev:css' 'yarn dev:next'", @@ -41,15 +41,18 @@ "dependencies": { "@discordjs/api-extractor-utils": "workspace:^", "@discordjs/scripts": "workspace:^", + "@lyrasearch/lyra": "^0.2.3", "@microsoft/api-extractor-model": "7.24.0", "@microsoft/tsdoc": "0.14.1", "@vscode/codicons": "^0.0.32", "ariakit": "^2.0.0-next.41", + "cmdk": "^0.1.20", + "flexsearch": "^0.7.21", "minisearch": "^5.0.0", - "next": "^12.2.5", + "next": "^12.3.0", "next-mdx-remote": "^4.1.0", "next-progress": "^2.2.0", - "next-themes": "^0.2.0", + "next-themes": "^0.2.1", "react": "^18.2.0", "react-custom-scrollbars-2": "^4.5.0", "react-dom": "^18.2.0", @@ -68,22 +71,23 @@ "devDependencies": { "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", - "@types/node": "^16.11.57", + "@types/flexsearch": "^0.7.3", + "@types/node": "^16.11.58", "@types/react-dom": "^18.0.6", "@types/react-syntax-highlighter": "^15.5.5", - "@unocss/cli": "^0.45.15", - "@unocss/reset": "^0.45.15", + "@unocss/cli": "^0.45.18", + "@unocss/reset": "^0.45.18", "@vitejs/plugin-react": "^2.1.0", "@vitest/coverage-c8": "^0.23.1", - "concurrently": "^7.3.0", + "concurrently": "^7.4.0", "eslint": "^8.23.0", "eslint-config-neon": "^0.1.33", "happy-dom": "^6.0.4", "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.13", - "typescript": "^4.8.2", - "unocss": "^0.45.15", - "vercel": "^28.2.2", + "typescript": "^4.8.3", + "unocss": "^0.45.18", + "vercel": "^28.2.3", "vitest": "^0.23.1" }, "engines": { diff --git a/packages/website/scripts/generateAllIndicies.js b/packages/website/scripts/generateAllIndicies.js new file mode 100644 index 000000000..91e908196 --- /dev/null +++ b/packages/website/scripts/generateAllIndicies.js @@ -0,0 +1,5 @@ +import { generateAllIndicies } from '@discordjs/scripts'; + +console.log('Generating all indicies...'); +await generateAllIndicies(); +console.log('Generated all indicies.'); diff --git a/packages/website/src/components/Cmdk.tsx b/packages/website/src/components/Cmdk.tsx new file mode 100644 index 000000000..6243e41e2 --- /dev/null +++ b/packages/website/src/components/Cmdk.tsx @@ -0,0 +1,225 @@ +// import { insertBatch, search as searchDb } from '@lyrasearch/lyra'; +// import type { ApiItemKind } from '@microsoft/api-extractor-model'; +import { Dialog, useDialogState } from 'ariakit/dialog'; +import { Command } from 'cmdk'; +import { useRouter } from 'next/router'; +import { useEffect, useMemo, useState } from 'react'; +import { + VscArrowRight, + VscPackage, + // VscSymbolClass, + // VscSymbolEnum, + // VscSymbolField, + // VscSymbolInterface, + // VscSymbolMethod, + // VscSymbolProperty, + // VscSymbolVariable, + VscVersions, +} from 'react-icons/vsc'; +import { useKey } from 'react-use'; +import useSWR from 'swr'; +import { PACKAGES } from '~/util/constants'; +import { fetcher } from '~/util/fetcher'; + +// function resolveIcon(item: keyof ApiItemKind) { +// switch (item) { +// case 'Class': +// return ; +// case 'Enum': +// return ; +// case 'Interface': +// return ; +// case 'Property': +// return ; +// case 'TypeAlias': +// return ; +// case 'Variables': +// return ; +// default: +// return ; +// } +// } + +// const searchIndex: any[] = []; + +export function CmdkDialog() { + const router = useRouter(); + const dialog = useDialogState(); + const [search, setSearch] = useState(''); + const [page, setPage] = useState(''); + const [packageName, setPackageName] = useState(''); + // const [searchResults, setSearchResults] = useState([]); + + const { data: versions, isValidating } = useSWR( + packageName ? `https://docs.discordjs.dev/api/info?package=${packageName}` : null, + fetcher, + ); + + // const { data: searchIndex } = useSWR( + // packageName ? `http://localhost:3000/searchIndex/${packageName}-main-index.json` : null, + // fetcher, + // ); + + const packageCommandItems = useMemo( + () => + PACKAGES.map((pkg) => ( + { + setPackageName(pkg); + setPage('version'); + setSearch(''); + }} + > +
+
+
+ +

{pkg}

+
+
+ +
+
+ )), + [], + ); + + const versionCommandItems = useMemo( + () => + versions + ?.map((version) => ( + { + void router.push(`/docs/packages/${packageName}/${version}`); + dialog.setOpen(false); + }} + > +
+
+
+ +

{version}

+
+
+ +
+
+ )) + .reverse() ?? [], + // eslint-disable-next-line react-hooks/exhaustive-deps + [packageName], + ); + + // const searchResultItems = useMemo( + // () => + // searchResults?.map((item) => ( + // { + // void router.push(item.path); + // dialog.setOpen(false); + // }} + // > + //
+ //
+ //
+ // {resolveIcon(item.kind)} + //
+ //

{item.name}

+ // {item.summary} + //
+ //
+ //
+ // + //
+ //
+ // )) ?? [], + // // eslint-disable-next-line react-hooks/exhaustive-deps + // [searchResults], + // ); + + useKey((event) => event.key === 'k' && event.metaKey, dialog.toggle, { event: 'keydown' }, []); + useKey( + (event) => event.key === 'Backspace' && !search, + () => setPage(''), + { event: 'keydown' }, + [], + ); + + useEffect(() => { + if (!dialog.open) { + setSearch(''); + setPage(''); + } + }, [dialog.open]); + + // useEffect(() => { + // if (searchIndex?.length) { + // void insertBatch(db, searchIndex); + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [searchIndex]); + + // useEffect(() => { + // if (search) { + // const results = searchDb(db, { + // term: search, + // properties: ['name', 'kind', 'summary'], + // }); + // setSearchResults(results.hits); + // } + // // eslint-disable-next-line react-hooks/exhaustive-deps + // }, [search]); + + return ( + + + + + No results found + + {isValidating ? ( + +
+ + Loading... +
+
+ ) : null} + + {page /* || search */ ? null : packageCommandItems} + + {page === 'version' /* && !search */ ? versionCommandItems : null} + + {/* {search && !page ? searchResultItems : null} */} +
+
+
+ ); +} diff --git a/packages/website/src/components/SidebarLayout.tsx b/packages/website/src/components/SidebarLayout.tsx index 84bd1a8bb..70a57ee3e 100644 --- a/packages/website/src/components/SidebarLayout.tsx +++ b/packages/website/src/components/SidebarLayout.tsx @@ -12,15 +12,12 @@ import { VscChevronDown, VscColorMode, VscGithubInverted, VscMenu, VscPackage, V import { useMedia /* useLockBodyScroll */ } from 'react-use'; import useSWR from 'swr'; import vercelLogo from '../assets/powered-by-vercel.svg'; +import { CmdkDialog } from './Cmdk'; import { SidebarItems } from './SidebarItems'; import { PACKAGES } from '~/util/constants'; +import { fetcher } from '~/util/fetcher'; import type { findMember } from '~/util/model.server'; -const fetcher = async (url: string) => { - const res = await fetch(url); - return res.json(); -}; - export interface SidebarLayoutProps { branchName: string; data: { @@ -310,6 +307,7 @@ export function SidebarLayout({ + ); } diff --git a/packages/website/src/pages/_app.tsx b/packages/website/src/pages/_app.tsx index 876d659d1..1d2c5dc06 100644 --- a/packages/website/src/pages/_app.tsx +++ b/packages/website/src/pages/_app.tsx @@ -4,6 +4,7 @@ import NextProgress from 'next-progress'; import { ThemeProvider } from 'next-themes'; import '@unocss/reset/tailwind.css'; import '../styles/unocss.css'; +import '../styles/cmdk.css'; import '../styles/main.css'; export default function MyApp({ Component, pageProps }: AppProps) { diff --git a/packages/website/src/pages/docs/[...slug].tsx b/packages/website/src/pages/docs/[...slug].tsx index f69a06c2e..c01423dea 100644 --- a/packages/website/src/pages/docs/[...slug].tsx +++ b/packages/website/src/pages/docs/[...slug].tsx @@ -171,16 +171,9 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { }); let data; - let searchIndex = []; if (process.env.NEXT_PUBLIC_LOCAL_DEV) { const res = await readFile(join(cwd(), '..', packageName, 'docs', 'docs.api.json'), 'utf8'); data = JSON.parse(res); - - const response = await readFile( - join(cwd(), '..', 'scripts', 'searchIndex', `${packageName}-main-index.json`), - 'utf8', - ); - searchIndex = JSON.parse(response); } else { const res = await fetch(`https://docs.discordjs.dev/docs/${packageName}/${branchName}.api.json`); data = await res.json(); @@ -204,7 +197,6 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { member: memberName && containerKey ? findMemberByKey(model, packageName, containerKey, branchName) ?? null : null, source: mdxSource, - searchIndex, }, }, revalidate: 3_600, @@ -215,7 +207,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { return { props: { - error: error_, + error: error.message, }, revalidate: 1, }; diff --git a/packages/website/src/pages/docs/packages/[package]/index.tsx b/packages/website/src/pages/docs/packages/[package]/index.tsx index c7b3dbc7e..809ef5f34 100644 --- a/packages/website/src/pages/docs/packages/[package]/index.tsx +++ b/packages/website/src/pages/docs/packages/[package]/index.tsx @@ -53,7 +53,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { return { props: { - error: error_, + error: error.message, }, revalidate: 1, }; diff --git a/packages/website/src/pages/docs/packages/index.tsx b/packages/website/src/pages/docs/packages/index.tsx index 991a94cd8..f41cb155d 100644 --- a/packages/website/src/pages/docs/packages/index.tsx +++ b/packages/website/src/pages/docs/packages/index.tsx @@ -35,7 +35,7 @@ export const getStaticProps: GetStaticProps = async () => { return { props: { - error: error_, + error: error.message, }, revalidate: 1, }; @@ -44,6 +44,7 @@ export const getStaticProps: GetStaticProps = async () => { export default function PackagesRoute(props: Partial & { error?: string }) { const router = useRouter(); + const findLatestVersion = useCallback( (pkg: string) => props.data?.versions.find((version) => version.packageName === pkg), [props.data?.versions], diff --git a/packages/website/src/styles/cmdk.css b/packages/website/src/styles/cmdk.css new file mode 100644 index 000000000..4aa7cc1a3 --- /dev/null +++ b/packages/website/src/styles/cmdk.css @@ -0,0 +1,3 @@ +[data-backdrop] { + background-color: rgb(0 0 0 / 25%); +} diff --git a/packages/website/src/util/fetcher.ts b/packages/website/src/util/fetcher.ts new file mode 100644 index 000000000..5071b3299 --- /dev/null +++ b/packages/website/src/util/fetcher.ts @@ -0,0 +1,4 @@ +export const fetcher = async (url: string) => { + const res = await fetch(url); + return res.json(); +};