From 98c4e9183744f015d524caf3e3fd7f0ba4555406 Mon Sep 17 00:00:00 2001 From: Dan Cormier Date: Wed, 8 Jul 2026 18:58:30 -0400 Subject: [PATCH 01/10] feat(docs): replace Algolia search with MiniSearch --- package-lock.json | 17 +- packages/stacks-docs/package.json | 3 +- packages/stacks-docs/src/app.css | 9 - packages/stacks-docs/src/app.d.ts | 9 + .../stacks-docs/src/components/Search.svelte | 247 ++++++++++++++---- .../stacks-docs/src/routes/+layout.server.ts | 37 +++ .../stacks-docs/src/routes/+layout.svelte | 2 +- 7 files changed, 252 insertions(+), 72 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4e63cd1f5..3acfdd1291 100644 --- a/package-lock.json +++ b/package-lock.json @@ -597,14 +597,6 @@ "node": ">=14.17.0" } }, - "node_modules/@docsearch/css": { - "version": "4.6.0", - "license": "MIT" - }, - "node_modules/@docsearch/js": { - "version": "4.6.0", - "license": "MIT" - }, "node_modules/@emnapi/core": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", @@ -9431,6 +9423,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "license": "MIT" + }, "node_modules/mitt": { "version": "3.0.1", "dev": true, @@ -15709,8 +15707,6 @@ "packages/stacks-docs": { "name": "@stackoverflow/stacks-docs", "dependencies": { - "@docsearch/css": "^4.3.2", - "@docsearch/js": "^4.3.2", "@hbsnow/rehype-sectionize": "^1.0.7", "@stackoverflow/stacks": "*", "@stackoverflow/stacks-editor": "*", @@ -15720,6 +15716,7 @@ "@stefanprobst/rehype-extract-toc": "^3.0.0", "better-auth": "^1.6.11", "highlight.js": "^11.11.1", + "minisearch": "^7.2.0", "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", "turndown": "^7.2.2", diff --git a/packages/stacks-docs/package.json b/packages/stacks-docs/package.json index 207acf7f3f..ebdadde79d 100644 --- a/packages/stacks-docs/package.json +++ b/packages/stacks-docs/package.json @@ -23,8 +23,6 @@ "vite": "^7.3.3" }, "dependencies": { - "@docsearch/css": "^4.3.2", - "@docsearch/js": "^4.3.2", "@hbsnow/rehype-sectionize": "^1.0.7", "@stackoverflow/stacks": "*", "@stackoverflow/stacks-editor": "*", @@ -34,6 +32,7 @@ "@stefanprobst/rehype-extract-toc": "^3.0.0", "better-auth": "^1.6.11", "highlight.js": "^11.11.1", + "minisearch": "^7.2.0", "rehype-autolink-headings": "^7.1.0", "rehype-slug": "^6.0.0", "turndown": "^7.2.2", diff --git a/packages/stacks-docs/src/app.css b/packages/stacks-docs/src/app.css index 7d3f7fc100..73276ca4e0 100644 --- a/packages/stacks-docs/src/app.css +++ b/packages/stacks-docs/src/app.css @@ -608,12 +608,3 @@ h1 { .hmn6 .s-editor-resizable { min-height: var(--su640) !important; } - -/* Agolia Docsearch */ - -.DocSearch-Container { - z-index: 9999 !important; -} -.DocSearch-Footer { - margin: 0; -} diff --git a/packages/stacks-docs/src/app.d.ts b/packages/stacks-docs/src/app.d.ts index 8b9d7c7948..78df67cef0 100644 --- a/packages/stacks-docs/src/app.d.ts +++ b/packages/stacks-docs/src/app.d.ts @@ -32,6 +32,14 @@ declare global { [key: string]: unknown; }; + type DocsSearchDocument = { + id: string; + title: string; + description: string; + path: string; + text: string; + }; + namespace App { interface Locals { session?: unknown; @@ -47,6 +55,7 @@ declare global { markdown?: string | null; metadata?: DocsMetadata | null; needsAuth?: boolean; + searchDocuments?: DocsSearchDocument[]; source?: string | null; structure?: { navigation?: DocsNavItem[] }; [key: string]: unknown; diff --git a/packages/stacks-docs/src/components/Search.svelte b/packages/stacks-docs/src/components/Search.svelte index 66e2707fe3..e1ace62d78 100644 --- a/packages/stacks-docs/src/components/Search.svelte +++ b/packages/stacks-docs/src/components/Search.svelte @@ -1,52 +1,199 @@ - -{#if searchEnabled} - -{/if} + + + + + + {#snippet header()} + Search documentation + {/snippet} + + {#snippet body()} +
+ +
+ +
+ {#if results.length} + + {#each results as result, index (result.id)} + handleResultKeydown(event, index)} + > + {result.title} + {#if result.description} + {result.description} + {/if} + {result.path} + + {/each} + + {:else if hasQuery} + + {#snippet description()} + Try a different search term. + {/snippet} + + {:else} +

+ Search components, guidelines, and foundations. +

+ {/if} +
+ {/snippet} +
+ + diff --git a/packages/stacks-docs/src/routes/+layout.server.ts b/packages/stacks-docs/src/routes/+layout.server.ts index df4ac8a57c..481adc209d 100644 --- a/packages/stacks-docs/src/routes/+layout.server.ts +++ b/packages/stacks-docs/src/routes/+layout.server.ts @@ -1,7 +1,17 @@ import type { LayoutServerLoad } from "./$types"; +import type { Component } from "svelte"; +import { render } from "svelte/server"; +import TurndownService from "turndown"; import YAML from "yaml"; import structureRaw from "$src/structure.yaml?raw"; +const mdFiles = import.meta.glob("$docs/public/**/*.md"); + +const turndownService = new TurndownService({ + headingStyle: "atx", + codeBlockStyle: "fenced", +}); + type NavItem = { slug: string; title?: string; @@ -29,6 +39,32 @@ function findByPath( return "slug" in currentLevel ? currentLevel : undefined; } +function getSearchPath(filePath: string): string { + return filePath + .replace("/src/docs/public/", "/") + .replace(/\/index\.md$/, "/") + .replace(/\.md$/, ""); +} + +async function getSearchDocuments(): Promise { + return Promise.all( + Object.entries(mdFiles).map(async ([path, doc]) => { + const page = (await doc()) as { + default: Component; + metadata: DocsMetadata; + }; + + return { + id: path, + title: page.metadata?.title ?? getSearchPath(path), + description: page.metadata?.description ?? "", + path: getSearchPath(path), + text: turndownService.turndown(render(page.default).body), + }; + }) + ); +} + export const load: LayoutServerLoad = async (event) => { // Load the navigation structure from the structure.yaml let structure: Structure = {}; @@ -96,5 +132,6 @@ export const load: LayoutServerLoad = async (event) => { active, breadcrumb, needsAuth, + searchDocuments: await getSearchDocuments(), }; }; diff --git a/packages/stacks-docs/src/routes/+layout.svelte b/packages/stacks-docs/src/routes/+layout.svelte index a1a40c2c8a..63461ede48 100644 --- a/packages/stacks-docs/src/routes/+layout.svelte +++ b/packages/stacks-docs/src/routes/+layout.svelte @@ -39,7 +39,7 @@ - + @@ -81,7 +95,7 @@ @@ -90,7 +104,7 @@ {/snippet} {#snippet body()} -
+
-
+

{resultStatus}

+ +
{#if results.length} -