1
0
mirror of https://github.com/prometheus/docs.git synced 2026-02-05 06:45:01 +01:00

Add Pagefind-based search

Signed-off-by: Julius Volz <julius.volz@gmail.com>
This commit is contained in:
Julius Volz
2025-05-20 22:03:11 +02:00
parent c83808a7e8
commit c65a69a24b
8 changed files with 246 additions and 53 deletions

17
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -30,7 +30,7 @@ export default async function BlogPostPage({
const { frontmatter, content } = getPost(await params);
return (
<Box className="markdown-content">
<Box className="markdown-content" data-pagefind-body>
<Title order={2} mt={0} mb="xs">
{frontmatter.title}
</Title>

View File

@@ -11,7 +11,13 @@ export default function CommunityPage() {
return (
<Group wrap="nowrap" align="flex-start" pos="relative">
<Box pos="sticky" top={0} w="fit-content" className="markdown-content">
<Box
pos="sticky"
top={0}
w="fit-content"
className="markdown-content"
data-pagefind-body
>
<Title order={1}>Community</Title>
<PromMarkdown>{content}</PromMarkdown>
</Box>

View File

@@ -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({
</Box>
{/* The main docs page content */}
<Box miw={0} className="markdown-content">
<Box
miw={0}
className="markdown-content"
data-pagefind-body={pagefindShouldIndex ? "true" : undefined}
>
{alert}
{children}

View File

@@ -45,15 +45,6 @@ export default function RootLayout({
}>) {
const [burgerOpened, { toggle: toggleBurger }] = useDisclosure();
// useEffect(() => {
// docsearch({
// container: "#docsearch",
// appId: "prometheus",
// indexName: "prometheus",
// apiKey: "48ac0b7924908a1fd40b1cb18b402ba1",
// });
// }, []);
return (
<html lang="en" {...mantineHtmlProps} className={interFont.variable}>
<head>
@@ -70,7 +61,7 @@ export default function RootLayout({
}}
>
<Header burgerOpened={burgerOpened} toggleBurger={toggleBurger} />
{/* <div id="docsearch" /> */}
<AppShell.Main px={{ base: 0, sm: "md" }}>
<Container
size="xl"

View File

@@ -9,19 +9,15 @@ import {
AppShell,
} from "@mantine/core";
import Image from "next/image";
import {
IconDashboard,
IconFileText,
IconHome,
IconSearch,
} from "@tabler/icons-react";
import { IconSearch } from "@tabler/icons-react";
import prometheusLogo from "../assets/prometheus-logo.svg";
import classes from "./Header.module.css";
import githubLogo from "../assets/github-logo.svg";
import Link from "next/link";
import { ThemeSelector } from "./ThemeSelector";
import { Spotlight, SpotlightActionData, spotlight } from "@mantine/spotlight";
import { usePathname } from "next/navigation";
import { spotlight } from "@mantine/spotlight";
import SpotlightSearch from "./SpotlightSearch";
const links = [
{
@@ -35,30 +31,6 @@ const links = [
{ link: "/blog", label: "Blog" },
];
const actions: SpotlightActionData[] = [
{
id: "home",
label: "Home",
description: "Get to home page",
onClick: () => console.log("Home"),
leftSection: <IconHome size={24} stroke={1.5} />,
},
{
id: "dashboard",
label: "Dashboard",
description: "Get full information about current system status",
onClick: () => console.log("Dashboard"),
leftSection: <IconDashboard size={24} stroke={1.5} />,
},
{
id: "documentation",
label: "Documentation",
description: "Visit documentation to lean more about all features",
onClick: () => console.log("Documentation"),
leftSection: <IconFileText size={24} stroke={1.5} />,
},
];
export const Header = ({
burgerOpened,
toggleBurger,
@@ -191,6 +163,7 @@ export const Header = ({
</Group>
</div>
</Container>
<SpotlightSearch />
</AppShell.Header>
<AppShell.Navbar p="lg">
{items}
@@ -198,15 +171,6 @@ export const Header = ({
{actionIcons}
</Group>
</AppShell.Navbar>
<Spotlight
actions={actions}
nothingFound="Nothing found..."
highlightQuery
searchProps={{
leftSection: <IconSearch size={20} stroke={1.5} />,
placeholder: "Search...",
}}
/>
</>
);
};

View File

@@ -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<PagefindResultData | null>(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 (
<Group justify="center" my="sm">
<Loader size="sm" color="gray" variant="dots" />
</Group>
);
}
return (
<Spotlight.ActionsGroup label={data.meta.title}>
<Space h="xs" />
{data.sub_results.slice(0, 4).map((subResult, subIdx) => (
<Spotlight.Action
key={`${result.id}-${subIdx}`}
id={`${result.id}-${subIdx}`}
label={subResult.title}
description={subResult.excerpt}
onClick={() => {
router.push(
subResult.url.replace(/(\/[^?#]+)\.html(?=[?#]|$)/, "$1")
);
}}
>
<Group wrap="nowrap" gap="xs" align="flex-start" w="100%">
<Highlight
highlight={query}
fw="bold"
fz="sm"
w="30%"
display="block"
>
{subResult.title}
</Highlight>
<Highlight
highlight={query}
size="xs"
display="block"
opacity={0.7}
flex="1"
>
{decode(subResult.excerpt.replace(/<\/?mark>/g, ""))}
</Highlight>
</Group>
</Spotlight.Action>
))}
</Spotlight.ActionsGroup>
);
};
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<PagefindResultData>;
};
export default function SpotlightSearch() {
const [searchInput, setSearchInput] = useState("");
const [results, setResults] = useState<PagefindResult[]>([]);
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 (
<Spotlight.Root
size="xl"
maxHeight="90vh"
scrollable
onQueryChange={async (query) => {
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[]);
}}
>
<Spotlight.Search
placeholder="Search..."
leftSection={<IconSearch stroke={1.5} />}
/>
<Spotlight.ActionsList>
{results.length > 0 ? (
results.map((result, idx) => (
<React.Fragment key={result.id}>
|{result.id}|{idx > 0 && <Divider my="xs" />}
<SearchResult query={searchInput} result={result} />
</React.Fragment>
))
) : (
<Spotlight.Empty>Nothing found...</Spotlight.Empty>
)}
</Spotlight.ActionsList>
</Spotlight.Root>
);
}