diff --git a/package-lock.json b/package-lock.json index 8f9b8918..49035323 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "hast-util-from-html": "^2.0.3", "hast-util-select": "^6.0.4", "hastscript": "^9.0.1", + "html-entities": "^2.6.0", "next": "15.3.1", "octokit": "^4.1.3", "react": "^19.0.0", @@ -5485,6 +5486,22 @@ "node": ">=12.0.0" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", diff --git a/package.json b/package.json index 027d0839..e9e6690e 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "hast-util-from-html": "^2.0.3", "hast-util-select": "^6.0.4", "hastscript": "^9.0.1", + "html-entities": "^2.6.0", "next": "15.3.1", "octokit": "^4.1.3", "react": "^19.0.0", diff --git a/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx b/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx index 123a5161..97cf306b 100644 --- a/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx +++ b/src/app/blog/[year]/[month]/[day]/[slug]/page.tsx @@ -30,7 +30,7 @@ export default async function BlogPostPage({ const { frontmatter, content } = getPost(await params); return ( - + {frontmatter.title} diff --git a/src/app/community/page.tsx b/src/app/community/page.tsx index 67c4777b..b9baecd2 100644 --- a/src/app/community/page.tsx +++ b/src/app/community/page.tsx @@ -11,7 +11,13 @@ export default function CommunityPage() { return ( - + Community {content} diff --git a/src/app/docs/layout.tsx b/src/app/docs/layout.tsx index 0e767475..951dc93e 100644 --- a/src/app/docs/layout.tsx +++ b/src/app/docs/layout.tsx @@ -234,6 +234,10 @@ export default function DocsLayout({ const pageSlug = usePathname().replace(/^\/docs\//, ""); const currentPage = docsCollection[pageSlug]; const reinitializeTOCRef = useRef(() => {}); + const pagefindShouldIndex = + currentPage.type === "local-doc" || + (currentPage.version === currentPage.latestVersion && + !currentPage.slug.startsWith(currentPage.versionRoot)); useEffect(() => { reinitializeTOCRef.current(); @@ -329,7 +333,11 @@ export default function DocsLayout({ {/* The main docs page content */} - + {alert} {children} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 07a1ad33..313c8fe0 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -45,15 +45,6 @@ export default function RootLayout({ }>) { const [burgerOpened, { toggle: toggleBurger }] = useDisclosure(); - // useEffect(() => { - // docsearch({ - // container: "#docsearch", - // appId: "prometheus", - // indexName: "prometheus", - // apiKey: "48ac0b7924908a1fd40b1cb18b402ba1", - // }); - // }, []); - return ( @@ -70,7 +61,7 @@ export default function RootLayout({ }} >
- {/*
*/} + console.log("Home"), - leftSection: , - }, - { - id: "dashboard", - label: "Dashboard", - description: "Get full information about current system status", - onClick: () => console.log("Dashboard"), - leftSection: , - }, - { - id: "documentation", - label: "Documentation", - description: "Visit documentation to lean more about all features", - onClick: () => console.log("Documentation"), - leftSection: , - }, -]; - export const Header = ({ burgerOpened, toggleBurger, @@ -191,6 +163,7 @@ export const Header = ({
+ {items} @@ -198,15 +171,6 @@ export const Header = ({ {actionIcons} - , - placeholder: "Search...", - }} - /> ); }; diff --git a/src/components/SpotlightSearch.tsx b/src/components/SpotlightSearch.tsx new file mode 100644 index 00000000..d9e6cb48 --- /dev/null +++ b/src/components/SpotlightSearch.tsx @@ -0,0 +1,206 @@ +"use client"; + +import { Spotlight } from "@mantine/spotlight"; +import { IconSearch } from "@tabler/icons-react"; +import { useRouter } from "next/navigation"; +import { Divider, Group, Highlight, Loader, Space, Text } from "@mantine/core"; +import React, { useState, useEffect } from "react"; +import { decode } from "html-entities"; + +// Extend Window interface to include pagefind +declare global { + interface Window { + /* eslint-disable @typescript-eslint/no-explicit-any */ + pagefind: any; + } +} + +const SearchResult = ({ + query, + result, +}: { + query: string; + result: PagefindResult; +}) => { + const [data, setData] = useState(null); + const router = useRouter(); + + useEffect(() => { + async function fetchData() { + try { + const data = await result.data(); + setData(data); + } catch (error) { + console.error("Error fetching data:", error); + } + } + + fetchData(); + }, [result]); + + if (data === null) { + return ( + + + + ); + } + + return ( + + + {data.sub_results.slice(0, 4).map((subResult, subIdx) => ( + { + router.push( + subResult.url.replace(/(\/[^?#]+)\.html(?=[?#]|$)/, "$1") + ); + }} + > + + + {subResult.title} + + + {decode(subResult.excerpt.replace(/<\/?mark>/g, ""))} + + + + ))} + + ); +}; + +type PagefindSubResult = { + title: string; + url: string; + excerpt: string; + anchor?: { + element: string; + id: string; + text: string; + location: number; + }; +}; + +type PagefindResultData = { + url: string; + content: string; + excerpt: string; + meta: { + title: string; + }; + sub_results: PagefindSubResult[]; +}; + +type PagefindResult = { + id: string; + score: number; + words: string[]; + data: () => Promise; +}; + +export default function SpotlightSearch() { + const [searchInput, setSearchInput] = useState(""); + const [results, setResults] = useState([]); + + useEffect(() => { + async function loadPagefind() { + if (typeof window.pagefind === "undefined") { + try { + window.pagefind = await import( + // @ts-expect-error pagefind.js generated after build + /* webpackIgnore: true */ "/pagefind/pagefind.js" + ); + await window.pagefind.options({ + ranking: { + pageLength: 0, + }, + }); + } catch (e) { + window.pagefind = { + search: () => ({ + results: [ + { + id: "error", + score: 0, + words: [], + data: () => + Promise.resolve({ + url: "", + content: "", + excerpt: "", + meta: { + title: `Error importing pagefind.js`, + }, + sub_results: [ + { + title: "Error", + url: "", + excerpt: `NOTE: Search only works with a static build, not in dev mode. Error: ${e}`, + }, + ], + }), + }, + ], + }), + }; + } + } + } + loadPagefind(); + }, []); + + return ( + { + setSearchInput(query); + console.log("searching for", query); + const search = await window.pagefind.debouncedSearch(query); + if (search === null) { + // A more recent search call has been made, nothing to do. + console.log("search cancelled"); + return; + } + console.log(`Found ${search.results.length} results`); + setResults(search.results as PagefindResult[]); + }} + > + } + /> + + {results.length > 0 ? ( + results.map((result, idx) => ( + + |{result.id}|{idx > 0 && } + + + )) + ) : ( + Nothing found... + )} + + + ); +}