feat(docs): add static generation and search indexes

This commit is contained in:
Maofeng
2026-09-20 16:23:32 +08:00
parent a57fdbe7c6
commit 9635de56e8
19 changed files with 529 additions and 169 deletions
+56 -67
View File
@@ -9,18 +9,8 @@ import { messages as searchMessagesEnUS } from "@workspace/search/locales/en-US"
import { messages as searchMessagesZhHans } from "@workspace/search/locales/zh-Hans"
import React from "react"
import {
getDocHref,
getDocPackages,
getDocSearchText,
getDocSectionLabel,
} from "../content/registry"
import type {
DocLocale,
DocPackage,
DocPage,
DocTocItem,
} from "../content/types"
import { loadDocSearchIndex } from "../content/search-index.client"
import type { DocLocale, DocSearchEntry } from "../content/types"
import { useLocale } from "../lib/locale"
const searchCatalogs = {
@@ -57,69 +47,73 @@ export function DocsSearchProvider({
}
function createDocsSearchAdapter(locale: DocLocale): SearchAdapter {
const docPackages = getDocPackages(locale)
return {
async search({ query, signal }) {
const normalizedQuery = normalize(query)
if (!normalizedQuery) return { groups: [] }
const index = await loadDocSearchIndex(locale)
if (signal.aborted) return { groups: [] }
const terms = normalizedQuery.split(/\s+/).filter(Boolean)
const groups = docPackages.flatMap((docPackage) => {
const items = docPackage.pages
.map((page) => ({
item: createResultItem(docPackage, page, locale),
score: getMatchScore(
normalizedQuery,
terms,
docPackage.label,
page
),
}))
.filter((result) => result.score >= 0)
.sort(
const groups = new Map<
string,
{
id: string
items: Array<{ item: SearchResultItem; score: number }>
label: string
total: number
}
>()
for (const entry of index) {
const score = getMatchScore(normalizedQuery, terms, entry)
if (score < 0) continue
const group = groups.get(entry.packageKey) ?? {
id: entry.packageKey,
items: [],
label: entry.packageLabel,
total: 0,
}
group.items.push({ item: createResultItem(entry), score })
group.total += 1
groups.set(entry.packageKey, group)
}
const results = Array.from(groups.values(), (group) => ({
id: group.id,
label: group.label,
total: group.total,
items: group.items
.toSorted(
(left, right) =>
right.score - left.score ||
left.item.title.localeCompare(right.item.title, locale)
)
.map((result) => result.item)
return items.length > 0
? [
{
id: docPackage.key,
label: docPackage.label,
items,
total: items.length,
},
]
: []
})
.map((result) => result.item),
}))
if (signal.aborted) return { groups: [] }
return { groups }
return { groups: results }
},
}
}
function createResultItem(
docPackage: DocPackage,
page: DocPage,
locale: DocLocale
): SearchResultItem {
function createResultItem(entry: DocSearchEntry): SearchResultItem {
return {
id: `${page.section}/${page.slug}`,
title: page.title,
description: page.description,
id: entry.id,
title: entry.title,
description: entry.description,
keywords: [
docPackage.label,
getDocSectionLabel(page.section, locale),
...page.toc.flatMap(flattenTocTitles),
getDocSearchText(page),
entry.packageLabel,
entry.sectionLabel,
...entry.toc,
entry.body,
],
meta: [getDocSectionLabel(page.section, locale)],
meta: [entry.sectionLabel],
payload: {
href: getDocHref(docPackage, page),
href: entry.href,
} satisfies DocsSearchPayload,
}
}
@@ -127,14 +121,13 @@ function createResultItem(
function getMatchScore(
query: string,
terms: readonly string[],
packageLabel: string,
page: DocPage
entry: DocSearchEntry
) {
const title = normalize(page.title)
const description = normalize(page.description)
const packageName = normalize(packageLabel)
const toc = normalize(page.toc.flatMap(flattenTocTitles).join(" "))
const body = normalize(getDocSearchText(page))
const title = normalize(entry.title)
const description = normalize(entry.description)
const packageName = normalize(entry.packageLabel)
const toc = normalize(entry.toc.join(" "))
const body = normalize(entry.body)
const searchableText = [packageName, title, description, toc, body].join(" ")
if (!terms.every((term) => searchableText.includes(term))) return -1
@@ -147,15 +140,11 @@ function getMatchScore(
else if (packageName.includes(query)) score += 20
if (description.includes(query)) score += 15
if (toc.includes(query)) score += 10
if (page.section === "overview") score += 2
if (entry.id === "overview/overview") score += 2
return score
}
function flattenTocTitles(item: DocTocItem): readonly string[] {
return [item.title, ...(item.items?.flatMap(flattenTocTitles) ?? [])]
}
function normalize(value: string) {
return value.normalize("NFKC").toLocaleLowerCase().trim()
}