diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts index df889626..89faa89c 100644 --- a/src/app/api/search/route.ts +++ b/src/app/api/search/route.ts @@ -1,4 +1,24 @@ -import { source } from '@/lib/source'; -import { createFromSource } from 'fumadocs-core/search/server'; +import { programsSource, source } from "@/lib/source"; +import { createSearchAPI } from "fumadocs-core/search/server"; -export const { GET } = createFromSource(source); +export const { GET } = createSearchAPI("advanced", { + language: "english", + indexes: [ + ...source.getPages().map((page) => ({ + title: page.data.title, + description: page.data.description, + url: page.url, + id: page.url, + structuredData: page.data.structuredData, + tag: "docs", + })), + ...programsSource.getPages().map((page) => ({ + title: page.data.title, + description: page.data.description, + url: page.url, + id: page.url, + structuredData: page.data.structuredData, + tag: "programs", + })), + ], +}); diff --git a/src/app/global.css b/src/app/global.css index 369c9291..a073f9da 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -7,7 +7,7 @@ @theme inline { --font-sans: var(--font-inter); - --font-mono: var(--font-inter-mono); + --font-mono: var(--font-jetbrains-mono); --font-funnel-display: var(--font-funnel-display); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); @@ -123,3 +123,7 @@ @apply bg-background text-foreground; } } + +kbd { + @apply font-mono; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8d897ffa..301cc8e7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,19 +1,33 @@ +import SearchDialog from "@/components/search"; import { GoogleAnalytics } from "@next/third-parties/google"; import { RootProvider } from "fumadocs-ui/provider"; import type { Metadata } from "next"; -import { Funnel_Display, Inter } from "next/font/google"; +import { Funnel_Display, Inter, JetBrains_Mono } from "next/font/google"; import type { ReactNode } from "react"; import "./global.css"; const inter = Inter({ subsets: ["latin"], variable: "--font-inter", + weight: "variable", + display: "swap", + preload: true, }); const funnelDisplay = Funnel_Display({ subsets: ["latin", "latin-ext"], weight: "variable", variable: "--font-funnel-display", + display: "swap", + preload: true, +}); + +const jetbrainsMono = JetBrains_Mono({ + subsets: ["latin", "latin-ext"], + weight: "variable", + variable: "--font-jetbrains-mono", + display: "swap", + preload: true, }); export const metadata: Metadata = { @@ -26,13 +40,19 @@ export const metadata: Metadata = { }, }; -export default function Layout({ children }: { children: ReactNode }) { +export default function RootLayout({ children }: { children: ReactNode }) { return ( - {children} + + {children} + diff --git a/src/app/static.json/route.ts b/src/app/static.json/route.ts new file mode 100644 index 00000000..88dfda3a --- /dev/null +++ b/src/app/static.json/route.ts @@ -0,0 +1,72 @@ +import { programsSource, source } from "@/lib/source"; +import { StructuredData } from "fumadocs-core/mdx-plugins"; +import { type DocumentRecord } from "fumadocs-core/search/algolia"; +import { NextResponse } from "next/server"; + +export const revalidate = false; + +// Type for page data with structured data +interface PageWithStructuredData { + url: string; + data: { + title: string; + description?: string; + structuredData: StructuredData; + }; +} + +// Helper function to map pages to DocumentRecord format +function mapPageToDocumentRecord( + page: PageWithStructuredData, + tag: string, +): DocumentRecord { + return { + _id: page.url, + structured: page.data.structuredData, + url: page.url, + title: page.data.title, + description: page.data.description || "", + tag, + }; +} + +// Helper function to safely get pages and map them +function mapPagesToDocuments( + sourceInstance: typeof source, + tag: string, +): DocumentRecord[] { + try { + return sourceInstance + .getPages() + .map((page) => mapPageToDocumentRecord(page, tag)); + } catch (error) { + console.error(`Error mapping ${tag} pages:`, error); + return []; + } +} + +export function GET() { + try { + // Process both sources efficiently with better error handling + const docsPages = mapPagesToDocuments(source, "docs"); + const programPages = mapPagesToDocuments(programsSource, "programs"); + + // Combine arrays efficiently + const results: DocumentRecord[] = [...docsPages, ...programPages]; + + // Add cache headers for better performance + return new NextResponse(JSON.stringify(results), { + status: 200, + headers: { + "Content-Type": "application/json", + "Cache-Control": "public, max-age=3600, stale-while-revalidate=86400", + }, + }); + } catch (error) { + console.error("Error generating static.json:", error); + return NextResponse.json( + { error: "Failed to generate search data" }, + { status: 500 }, + ); + } +} diff --git a/src/components/search.tsx b/src/components/search.tsx new file mode 100644 index 00000000..ebdfbe65 --- /dev/null +++ b/src/components/search.tsx @@ -0,0 +1,100 @@ +"use client"; +import { useDocsSearch } from "fumadocs-core/search/client"; +import { + SearchDialog, + SearchDialogClose, + SearchDialogContent, + SearchDialogFooter, + SearchDialogHeader, + SearchDialogIcon, + SearchDialogInput, + SearchDialogList, + SearchDialogOverlay, + type SharedProps, + TagsList, + TagsListItem, +} from "fumadocs-ui/components/dialog/search"; +import { useI18n } from "fumadocs-ui/contexts/i18n"; +import { ArrowDownIcon, ArrowUpIcon, CornerDownLeftIcon } from "lucide-react"; +import { useState } from "react"; + +const items = [ + { + name: "All", + value: "", + }, + { + name: "Docs", + description: "Only results about documentation", + value: "docs", + }, + { + name: "Programming", + description: "Only results about programming languages", + value: "programs", + }, +]; + +export default function CustomSearchDialog(props: SharedProps) { + const { locale } = useI18n(); // (optional) for i18n + const [tag, setTag] = useState(); + const { search, setSearch, query } = useDocsSearch({ + type: "fetch", + locale, + tag, + delayMs: 500, + }); + + return ( + + + + +
+ + + +
+ + {items.map((item) => ( + + {item.name} + + ))} + +
+ + +
+
+ + + + + + + Navigate +
+
+ + + + Select +
+
+ + ESC + + Close +
+
+
+
+
+ ); +}