diff --git a/client/package.json b/client/package.json index 10a66b1d..860a7f3f 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "@tailwindcss/typography": "^0.4.1", "color": "^4.0.1", "eventemitter3": "^4.0.7", + "flexsearch": "^0.7.21", "gray-matter": "^4.0.3", "lottie-web": "^5.7.13", "match-sorter": "^6.3.1", @@ -43,6 +44,7 @@ }, "devDependencies": { "@types/color": "^3.0.2", + "@types/flexsearch": "^0.7.1", "@types/markdown-it": "^12.2.3", "@types/react": "^17.0.27", "@types/react-dom": "^17.0.9", diff --git a/client/src/docs/index.tsx b/client/src/docs/index.tsx index a06ae98a..4ffc5ff6 100644 --- a/client/src/docs/index.tsx +++ b/client/src/docs/index.tsx @@ -1,14 +1,90 @@ import * as React from "react"; -import {NavLink, Outlet, Route, Routes, useLocation} from "react-router-dom"; +import {Link, NavLink, Route, Routes, useLocation} from "react-router-dom"; import "prismjs/themes/prism-tomorrow.css"; import Menubar from "@thorium/ui/Menubar"; +import {Popover, Transition} from "@headlessui/react"; +import {Index} from "flexsearch"; import "./docs.css"; +const docIndex = new Index(); + +function Search() { + const [results, setResults] = React.useState<{title: string; path: string}[]>( + [] + ); + return ( +
+ + <> + { + const value = e.target.value; + if (value.length > 2) { + const results = docIndex + .search(value) + .map(id => (typeof id === "string" ? JSON.parse(id) : id)); + setResults(results); + } + }} + onChange={e => { + const value = e.target.value; + if (value.length > 2) { + const results = docIndex + .search(value) + .map(id => (typeof id === "string" ? JSON.parse(id) : id)); + setResults(results); + } else { + setResults([]); + } + }} + /> + 0} + > + +
+
+ {results.map(item => ( + { + setResults([]); + }} + key={item.path} + to={item.path} + className="flex items-center px-2 py-1 m-1 transition duration-150 ease-in-out rounded-lg hover:bg-gray-800 focus:outline-none focus-visible:ring focus-visible:ring-orange-500 focus-visible:ring-opacity-50" + > +

{item.title}

+ + ))} +
+
+
+
+ +
+
+ ); +} + const ROUTES = import.meta.globEager("/src/docs/**/*.{tsx,jsx,md,mdx}"); type RouteType = { path: string; component: React.ComponentType; + content: string; section: string; frontmatter: { title: string; @@ -32,12 +108,19 @@ export const routes = Object.keys(ROUTES) return { path: path.toLowerCase().replace(/\s/g, "-"), component: ROUTES[route].default, + content: ROUTES[route].content, section: routeParts[0], frontmatter: ROUTES[route].frontmatter, }; }) .filter(isRoute); +routes.forEach(route => { + docIndex.add( + JSON.stringify({...route.frontmatter, path: route.path}), + route.content + ); +}); type Heading = { title: string; id: string; @@ -228,6 +311,7 @@ export default function DocLayout() {