feat: add naive client-based search

This commit is contained in:
iCrawl
2022-09-03 00:42:34 +02:00
parent f072d3d916
commit a4777aa9b0
9 changed files with 130 additions and 11 deletions

View File

@@ -47,6 +47,7 @@ export interface SidebarLayoutProps {
data: {
member: ReturnType<typeof findMember>;
members: ReturnType<typeof getMembers>;
searchIndex: any[];
source: MDXRemoteSerializeResult;
};
packageName: string;

View File

@@ -8,6 +8,7 @@ import { VscPackage } from 'react-icons/vsc';
import { RouterTransition } from '../components/RouterTransition';
import '../styles/unocss.css';
import '../styles/main.css';
import { miniSearch } from '~/util/search';
const actions: (router: NextRouter) => SpotlightAction[] = (router: NextRouter) => [
{
@@ -111,7 +112,19 @@ export default function MyApp({ Component, pageProps }: AppProps) {
withNormalizeCSS
withGlobalStyles
>
<SpotlightProvider shortcut={['mod + P', 'mod + K', '/']} actions={actions(router)}>
<SpotlightProvider
shortcut={['mod + P', 'mod + K', '/']}
actions={actions(router)}
limit={7}
filter={(query, actions) => {
if (!query) {
return actions;
}
const search = miniSearch.search(query);
return actions.filter((action) => search.some((res) => res.name === action.title));
}}
>
<RouterTransition />
<Component {...pageProps} />
</SpotlightProvider>

View File

@@ -14,12 +14,14 @@ import {
} from '@discordjs/api-extractor-utils';
import { ActionIcon, Affix, Box, LoadingOverlay, Transition } from '@mantine/core';
import { useMediaQuery, useWindowScroll } from '@mantine/hooks';
import { registerSpotlightActions } from '@mantine/spotlight';
import { ApiFunction, ApiItemKind, type ApiPackage } from '@microsoft/api-extractor-model';
import Head from 'next/head';
import { useRouter } from 'next/router';
import type { GetStaticPaths, GetStaticProps } from 'next/types';
import { MDXRemote } from 'next-mdx-remote';
import { serialize } from 'next-mdx-remote/serialize';
import { useEffect } from 'react';
import { VscChevronUp } from 'react-icons/vsc';
import rehypeIgnore from 'rehype-ignore';
import rehypePrettyCode from 'rehype-pretty-code';
@@ -37,6 +39,7 @@ import { MemberProvider } from '~/contexts/member';
import { createApiModel } from '~/util/api-model.server';
import { findMember, findMemberByKey } from '~/util/model.server';
import { PACKAGES } from '~/util/packages';
import { miniSearch } from '~/util/search';
export const getStaticPaths: GetStaticPaths = async () => {
const pkgs = (
@@ -137,9 +140,16 @@ 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();
@@ -163,6 +173,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
member:
memberName && containerKey ? findMemberByKey(model, packageName, containerKey, branchName) ?? null : null,
source: mdxSource,
searchIndex,
},
},
revalidate: 3_600,
@@ -204,6 +215,22 @@ export default function SlugPage(props: Partial<SidebarLayoutProps & { error?: s
const [scroll, scrollTo] = useWindowScroll();
const matches = useMediaQuery('(max-width: 1200px)');
useEffect(() => {
if (props.data?.searchIndex) {
const searchIndex = props.data?.searchIndex.map((idx, index) => ({ id: index, ...idx })) ?? [];
miniSearch.addAll(searchIndex);
registerSpotlightActions(
searchIndex.map((idx) => ({
title: idx.name,
description: idx.summary ?? '',
onTrigger: () => void router.push(idx.path),
})),
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const name = `discord.js${props.data?.member?.name ? ` | ${props.data.member.name}` : ''}`;
if (router.isFallback) {

View File

@@ -0,0 +1,6 @@
import MiniSearch from 'minisearch';
export const miniSearch = new MiniSearch({
fields: ['name', 'summary'],
storeFields: ['name', 'summary', 'path'],
});