Astro: make search working

This commit is contained in:
Fuma Nama
2025-11-01 17:33:48 +08:00
parent c63a4e651d
commit f56f3e6b7b
14 changed files with 180 additions and 43 deletions
+22 -3
View File
@@ -2,11 +2,30 @@
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwindcss from '@tailwindcss/vite';
import mdx from 'fumadocs-mdx/astro';
import * as SourceConfig from './source.config';
import mdx from '@astrojs/mdx';
import {
rehypeCode,
remarkCodeTab,
remarkHeading,
remarkNpm,
remarkStructure,
} from 'fumadocs-core/mdx-plugins';
export default defineConfig({
integrations: [react(), mdx(SourceConfig)],
integrations: [
react(),
mdx({
extendMarkdownConfig: false,
syntaxHighlight: false,
remarkPlugins: [
remarkHeading,
remarkCodeTab,
remarkNpm,
[remarkStructure, { exportAs: 'structuredData' }],
],
rehypePlugins: [rehypeCode],
}),
],
vite: {
plugins: [tailwindcss()],
},
+1 -1
View File
@@ -9,7 +9,7 @@ Welcome to the docs! You can start writing documents in `/content/docs`.
console.log('Hello World');
```
## What is Next?
## What is `Next`?
<Cards>
<Card title="Learn more about Astro" href="https://astro.build">
+3
View File
@@ -0,0 +1,3 @@
{
"pages": ["..."]
}
+1 -1
View File
@@ -13,9 +13,9 @@
"dependencies": {
"@astrojs/mdx": "^4.3.9",
"@astrojs/react": "^4.4.1",
"@orama/orama": "^3.1.16",
"astro": "^5.6.1",
"fumadocs-core": "workspace:*",
"fumadocs-mdx": "workspace:*",
"fumadocs-ui": "workspace:*",
"react": "^19.2.0",
"react-dom": "^19.2.0",
-5
View File
@@ -1,5 +0,0 @@
import { defineDocs } from 'fumadocs-mdx/config';
export const docs = defineDocs({
dir: 'content/docs',
});
+5 -1
View File
@@ -5,6 +5,7 @@ import type { ReactNode } from 'react';
import { FrameworkProvider } from 'fumadocs-core/framework';
import { navigate } from 'astro:transitions/client';
import { RootProvider } from 'fumadocs-ui/provider/base';
import SearchDialog from './search';
export function Docs({
tree,
@@ -32,9 +33,12 @@ export function Docs({
},
})}
>
<RootProvider theme={{ enabled: false }}>
<RootProvider theme={{ enabled: false }} search={{ SearchDialog }}>
<DocsLayout
tree={tree}
themeSwitch={{
enabled: false,
}}
nav={{
title: 'Fumadocs on Astro',
}}
+1 -1
View File
@@ -8,7 +8,7 @@ import { ClientRouter } from 'astro:transitions';
<ClientRouter />
</head>
<body
class="dark"
class="dark flex flex-col min-h-screen"
transition:persist
transition:animate="none"
transition:name="page-layout"
+51
View File
@@ -0,0 +1,51 @@
'use client';
import {
SearchDialog,
SearchDialogClose,
SearchDialogContent,
SearchDialogHeader,
SearchDialogIcon,
SearchDialogInput,
SearchDialogList,
SearchDialogOverlay,
type SharedProps,
} from 'fumadocs-ui/components/dialog/search';
import { useDocsSearch } from 'fumadocs-core/search/client';
import { create } from '@orama/orama';
import { useI18n } from 'fumadocs-ui/contexts/i18n';
function initOrama() {
return create({
schema: { _: 'string' },
// https://docs.orama.com/docs/orama-js/supported-languages
language: 'english',
});
}
export default function DefaultSearchDialog(props: SharedProps) {
const { locale } = useI18n(); // (optional) for i18n
const { search, setSearch, query } = useDocsSearch({
type: 'static',
initOrama,
locale,
});
return (
<SearchDialog
search={search}
onSearchChange={setSearch}
isLoading={query.isLoading}
{...props}
>
<SearchDialogOverlay />
<SearchDialogContent>
<SearchDialogHeader>
<SearchDialogIcon />
<SearchDialogInput />
<SearchDialogClose />
</SearchDialogHeader>
<SearchDialogList items={query.data !== 'empty' ? query.data : null} />
</SearchDialogContent>
</SearchDialog>
);
}
+12
View File
@@ -6,9 +6,21 @@ const docs = defineCollection({
schema: z.object({
title: z.string(),
description: z.string().optional(),
icon: z.string().optional(),
}),
});
const meta = defineCollection({
loader: glob({ pattern: '**/*.{json,yaml}', base: './content/docs' }),
schema: z.object({
title: z.string().optional(),
description: z.string().optional(),
pages: z.array(z.string()).optional(),
icon: z.string().optional(),
}),
});
export const collections = {
docs,
meta,
};
+40 -20
View File
@@ -1,34 +1,54 @@
import type { MetaData, Source } from 'fumadocs-core/source';
import type { Source } from 'fumadocs-core/source';
import { loader } from 'fumadocs-core/source';
import { type CollectionEntry, getCollection } from 'astro:content';
import * as path from 'node:path';
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
export const source = loader({
source: await createMySource(),
baseUrl: '/',
});
async function createMySource(): Promise<
Source<{
metaData: MetaData;
const docs = import.meta.glob('/content/docs/**/*.{md,mdx}');
export async function getFullExport(entry: CollectionEntry<'docs'>) {
return (await docs['/' + entry.filePath!]()) as {
structuredData: StructuredData;
};
}
async function createMySource() {
const out: Source<{
metaData: CollectionEntry<'meta'>['data'];
pageData: CollectionEntry<'docs'>['data'] & {
_raw: CollectionEntry<'docs'>;
};
}>
> {
const pages = await getCollection('docs');
return {
files: pages.map((page) => {
const virtualPath = path.relative('content/docs', page.filePath!);
return {
type: 'page',
path: virtualPath,
data: {
...page.data,
_raw: page,
},
};
}),
}> = {
files: [],
};
for (const page of await getCollection('docs')) {
const virtualPath = path.relative('content/docs', page.filePath!);
out.files.push({
type: 'page',
path: virtualPath,
data: {
...page.data,
_raw: page,
},
});
}
for (const meta of await getCollection('meta')) {
const virtualPath = path.relative('content/docs', meta.filePath!);
out.files.push({
type: 'meta',
path: virtualPath,
data: meta.data,
});
}
return out;
}
+9 -1
View File
@@ -5,6 +5,7 @@ import defaultMdxComponents from 'fumadocs-ui/mdx';
import '../styles/global.css';
import { source } from '../lib/source';
import Layout from '../components/layout.astro';
import type { TOCItemType } from 'fumadocs-core/toc';
interface Props {
page: CollectionEntry<'docs'>;
@@ -18,7 +19,13 @@ export async function getStaticPaths() {
}
const { page } = Astro.props;
const { Content } = await render(page);
const { Content, headings } = await render(page);
const toc: TOCItemType[] = headings.map((heading) => ({
depth: heading.depth,
title: heading.text,
url: '#' + heading.slug,
}));
---
<Layout>
@@ -29,6 +36,7 @@ const { Content } = await render(page);
tree={source.pageTree}
pathname={Astro.url.pathname}
params={Astro.params as Record<string, string | string[]>}
page={{ toc }}
client:load
>
<h1 class="font-semibold text-3xl">{page.data.title}</h1>
+26
View File
@@ -0,0 +1,26 @@
import type { APIRoute } from 'astro';
import { createFromSource } from 'fumadocs-core/search/server';
import { getFullExport, source } from '../../lib/source';
import { getBreadcrumbItems } from 'fumadocs-core/breadcrumb';
const server = createFromSource(source, {
async buildIndex(page) {
const { structuredData } = await getFullExport(page.data._raw);
console.log(structuredData);
return {
id: page.data._raw.id,
title: page.data.title,
description: page.data.description,
structuredData,
url: page.url,
breadcrumbs: getBreadcrumbItems(page.url, source.pageTree).map((item) =>
String(item.name),
),
};
},
});
export const GET: APIRoute = () => {
return server.staticGET();
};
-4
View File
@@ -1,7 +1,3 @@
@import 'tailwindcss';
@import 'fumadocs-ui/css/neutral.css';
@import 'fumadocs-ui/css/preset.css';
body {
@apply flex flex-col min-h-screen;
}
+9 -6
View File
@@ -306,15 +306,15 @@ importers:
'@astrojs/react':
specifier: ^4.4.1
version: 4.4.1(@types/[email protected])(@types/[email protected](@types/[email protected]))(@types/[email protected])([email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
'@orama/orama':
specifier: ^3.1.16
version: 3.1.16
astro:
specifier: ^5.6.1
version: 5.15.2(@types/[email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])([email protected])
fumadocs-core:
specifier: workspace:*
version: link:../../packages/core
fumadocs-mdx:
specifier: workspace:*
version: link:../../packages/mdx
fumadocs-ui:
specifier: workspace:*
version: link:../../packages/ui
@@ -1516,6 +1516,9 @@ importers:
'@shikijs/transformers':
specifier: ^3.14.0
version: 3.14.0
estree-util-value-to-estree:
specifier: ^3.5.0
version: 3.5.0
github-slugger:
specifier: ^2.0.0
version: 2.0.0
@@ -4250,8 +4253,8 @@ packages:
'@radix-ui/[email protected]':
resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==}
peerDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
@@ -9282,7 +9285,7 @@ packages:
resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==}
engines: {node: '>=10'}
peerDependencies:
'@types/react': 19.2.2
'@types/react': '*'
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':