feat: upgrade fumadocs

This commit is contained in:
ReaJason
2026-08-30 21:19:42 +08:00
parent 6c41f208ba
commit e11eafe78b
11 changed files with 489 additions and 922 deletions
+4 -11
View File
@@ -1,5 +1,5 @@
import { create } from "@orama/orama";
import { useDocsSearch } from "fumadocs-core/search/client"; import { useDocsSearch } from "fumadocs-core/search/client";
import { staticClient } from "fumadocs-core/search/client/orama-static";
import { import {
SearchDialog, SearchDialog,
SearchDialogClose, SearchDialogClose,
@@ -13,19 +13,12 @@ import {
} from "fumadocs-ui/components/dialog/search"; } from "fumadocs-ui/components/dialog/search";
import { useI18n } from "fumadocs-ui/contexts/i18n"; import { useI18n } from "fumadocs-ui/contexts/i18n";
function initOrama() {
return create({
schema: { _: "string" },
language: "english",
});
}
export default function DefaultSearchDialog(props: SharedProps) { export default function DefaultSearchDialog(props: SharedProps) {
const { locale } = useI18n(); const { locale } = useI18n();
const { search, setSearch, query } = useDocsSearch({ const { search, setSearch, query } = useDocsSearch({
type: "static", client: staticClient({
initOrama, locale,
locale, }),
}); });
return ( return (
+1 -1
View File
@@ -7,7 +7,7 @@ const EnvSchema = yup.object({
}); });
type EnvSchema = yup.InferType<typeof EnvSchema>; type EnvSchema = yup.InferType<typeof EnvSchema>;
function safeParseYup<T>(schema: yup.ObjectSchema<any>, data: unknown) { function safeParseYup<T>(schema: yup.AnySchema, data: unknown) {
try { try {
const validatedData = schema.validateSync(data, { const validatedData = schema.validateSync(data, {
abortEarly: false, abortEarly: false,
+31 -39
View File
@@ -1,6 +1,5 @@
import type { Route } from "./+types/page"; import type { Route } from "./+types/page";
import browserCollections from "collections/browser";
import { useFumadocsLoader } from "fumadocs-core/source/client"; import { useFumadocsLoader } from "fumadocs-core/source/client";
import { DocsLayout } from "fumadocs-ui/layouts/docs"; import { DocsLayout } from "fumadocs-ui/layouts/docs";
import { import {
@@ -11,13 +10,14 @@ import {
MarkdownCopyButton, MarkdownCopyButton,
ViewOptionsPopover, ViewOptionsPopover,
} from "fumadocs-ui/layouts/docs/page"; } from "fumadocs-ui/layouts/docs/page";
import { use } from "react";
import { useMDXComponents } from "@/components/mdx"; import { useMDXComponents } from "@/components/mdx";
import { baseOptions } from "@/lib/layout.shared"; import { baseOptions } from "@/lib/layout.shared";
import { getPageMarkdownUrl, source } from "@/lib/source"; import { docs, getPageMarkdownUrl, source } from "@/lib/source";
export async function loader({ params }: Route.LoaderArgs) { export async function loader({ params }: Route.LoaderArgs) {
const slugs = params["*"].split("/").filter((v: string | any[]) => v.length > 0); const slugs = params["*"].split("/").filter((v) => v.length > 0);
const page = source.getPage(slugs); const page = source.getPage(slugs);
if (!page) throw new Response("Not found", { status: 404 }); if (!page) throw new Response("Not found", { status: 404 });
@@ -28,48 +28,40 @@ export async function loader({ params }: Route.LoaderArgs) {
}; };
} }
const clientLoader = browserCollections.docs.createClientLoader({ function Content({ path, markdownUrl }: { path: string; markdownUrl: string }) {
component( const page = docs.getPage(path);
{ toc, frontmatter, default: Mdx }, if (!page) throw new Error(`unknown page: ${path}`);
// you can define props for the component
{ // content is loaded lazily, call `page.preload()` in the loader to avoid suspending
markdownUrl, const { toc } = use(page.load());
path, const Mdx = page.body;
}: {
markdownUrl: string; return (
path: string; <DocsPage toc={toc} tableOfContent={{ style: "clerk" }}>
}, <title>{page.title}</title>
) { <meta name="description" content={page.description} />
return ( <DocsTitle>{page.title}</DocsTitle>
<DocsPage toc={toc} tableOfContent={{ style: "clerk" }}> <DocsDescription>{page.description}</DocsDescription>
<title>{frontmatter.title}</title> <div className="-mt-4 flex flex-row items-center gap-2 border-b pb-6">
<meta name="description" content={frontmatter.description} /> <MarkdownCopyButton markdownUrl={markdownUrl} />
<DocsTitle>{frontmatter.title}</DocsTitle> <ViewOptionsPopover
<DocsDescription>{frontmatter.description}</DocsDescription> markdownUrl={markdownUrl}
<div className="-mt-4 flex flex-row items-center gap-2 border-b pb-6"> githubUrl={`https://github.com/ReaJason/MemShellParty/blob/master/web/content/docs/${path}`}
<MarkdownCopyButton markdownUrl={markdownUrl} /> />
<ViewOptionsPopover </div>
markdownUrl={markdownUrl} <DocsBody>
githubUrl={`https://github.com/ReaJason/MemShellParty/blob/master/web/content/docs/${path}`} <Mdx components={useMDXComponents()} />
/> </DocsBody>
</div> </DocsPage>
<DocsBody> );
<Mdx components={useMDXComponents()} /> }
</DocsBody>
</DocsPage>
);
},
});
export default function Page({ loaderData }: Route.ComponentProps) { export default function Page({ loaderData }: Route.ComponentProps) {
const { pageTree, path, markdownUrl } = useFumadocsLoader(loaderData); const { pageTree, path, markdownUrl } = useFumadocsLoader(loaderData);
return ( return (
<DocsLayout {...baseOptions()} tree={pageTree}> <DocsLayout {...baseOptions()} tree={pageTree}>
{clientLoader.useContent(loaderData.path, { <Content path={path} markdownUrl={markdownUrl} />
markdownUrl,
path,
})}
</DocsLayout> </DocsLayout>
); );
} }
+1 -10
View File
@@ -1,17 +1,8 @@
import { stopwords as mandarinStopwords } from "@orama/stopwords/mandarin";
import { createTokenizer } from "@orama/tokenizers/mandarin";
import { createFromSource } from "fumadocs-core/search/server"; import { createFromSource } from "fumadocs-core/search/server";
import { source } from "@/lib/source"; import { source } from "@/lib/source";
const server = createFromSource(source, { const server = createFromSource(source);
components: {
tokenizer: createTokenizer({
language: "mandarin",
stopWords: mandarinStopwords,
}),
},
});
export async function loader() { export async function loader() {
return server.staticGET(); return server.staticGET();
+11 -1
View File
@@ -1,6 +1,16 @@
import { docs } from "collections/server";
import { loader, type InferPageType } from "fumadocs-core/source"; import { loader, type InferPageType } from "fumadocs-core/source";
import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons"; import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons";
import { defineDocs } from "fumadocs-mdx/macro";
export const docs = defineDocs({
dir: "content/docs",
docs: {
async: true,
postprocess: {
includeProcessedMarkdown: true,
},
},
});
export const source = loader({ export const source = loader({
source: docs.toFumadocsSource(), source: docs.toFumadocsSource(),
+2 -5
View File
@@ -53,7 +53,7 @@ const urlPatternIsNeeded = (shellType: string) => {
const isInvalidUrl = (urlPattern: string | undefined) => const isInvalidUrl = (urlPattern: string | undefined) =>
urlPattern === "/" || urlPattern === "/*" || !urlPattern?.startsWith("/") || !urlPattern; urlPattern === "/" || urlPattern === "/*" || !urlPattern?.startsWith("/") || !urlPattern;
export const useYupValidationResolver = (validationSchema: yup.ObjectSchema<any>, t: TFunction) => export const useYupValidationResolver = (validationSchema: yup.AnySchema, t: TFunction) =>
useCallback( useCallback(
async (data: MemShellFormSchema, _context: any): Promise<ValidationResult> => { async (data: MemShellFormSchema, _context: any): Promise<ValidationResult> => {
try { try {
@@ -157,10 +157,7 @@ export const probeShellFormSchema = yup.object().shape({
type ProbeValidationResult = ResolverResult<ProbeShellFormSchema>; type ProbeValidationResult = ResolverResult<ProbeShellFormSchema>;
export const useYupValidationProbeResolver = ( export const useYupValidationProbeResolver = (validationSchema: yup.AnySchema, t: TFunction) =>
validationSchema: yup.ObjectSchema<any>,
t: TFunction,
) =>
useCallback( useCallback(
async (data: ProbeShellFormSchema, _context: any): Promise<ProbeValidationResult> => { async (data: ProbeShellFormSchema, _context: any): Promise<ProbeValidationResult> => {
try { try {
+403 -792
View File
File diff suppressed because it is too large Load Diff
+32 -36
View File
@@ -6,63 +6,59 @@
"build": "react-router build --mode production && bun run copy-build.js", "build": "react-router build --mode production && bun run copy-build.js",
"dev": "react-router dev", "dev": "react-router dev",
"start": "serve ./build/client", "start": "serve ./build/client",
"types:check": "react-router typegen && fumadocs-mdx && tsc --noEmit", "types:check": "react-router typegen && tsc --noEmit",
"postinstall": "fumadocs-mdx",
"lint": "oxlint", "lint": "oxlint",
"lint:fix": "oxlint --fix --fix-suggestions --fix-dangerously .", "lint:fix": "oxlint --fix --fix-suggestions --fix-dangerously .",
"fmt": "oxfmt", "fmt": "oxfmt",
"fmt:check": "oxfmt --check" "fmt:check": "oxfmt --check"
}, },
"dependencies": { "dependencies": {
"@base-ui/react": "^1.6.0", "@base-ui/react": "^1.7.0",
"@hookform/resolvers": "^5.4.0", "@hookform/resolvers": "^5.9.1",
"@orama/orama": "^3.1.18", "@react-router/node": "^8.3.1",
"@orama/stopwords": "^3.1.18",
"@orama/tokenizers": "^3.1.18",
"@react-router/node": "^7.15.1",
"@run-slicer/cfr": "file:./vendor/cfr", "@run-slicer/cfr": "file:./vendor/cfr",
"@tanstack/react-query": "^5.101.2", "@tanstack/react-query": "^5.102.8",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^12.42.0", "framer-motion": "^13.1.1",
"fumadocs-core": "^16.10.6", "fumadocs-core": "^16.15.4",
"fumadocs-mdx": "15.0.12", "fumadocs-mdx": "15.4.0",
"fumadocs-ui": "16.10.5", "fumadocs-ui": "16.15.4",
"i18next": "^26.3.3", "i18next": "^26.4.0",
"isbot": "^5.1.44", "isbot": "^5.2.2",
"lucide-react": "^1.21.0", "lucide-react": "^1.37.0",
"motion": "^12.42.0", "motion": "^13.1.1",
"react": "^19.2.7", "react": "^19.2.8",
"react-copy-to-clipboard": "^5.1.1", "react-copy-to-clipboard": "^5.1.1",
"react-dom": "^19.2.7", "react-dom": "^19.2.8",
"react-hook-form": "^7.80.0", "react-hook-form": "^7.87.0",
"react-i18next": "^17.0.8", "react-i18next": "^17.0.12",
"react-medium-image-zoom": "^5.4.8", "react-medium-image-zoom": "^5.4.9",
"react-syntax-highlighter": "^16.1.1", "react-syntax-highlighter": "^16.1.1",
"sonner": "^2.0.7", "sonner": "^2.0.8",
"tailwind-merge": "^3.6.0", "tailwind-merge": "^3.6.0",
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"yup": "^1.7.1" "yup": "^1.7.1"
}, },
"devDependencies": { "devDependencies": {
"@react-router/dev": "^7.15.1", "@react-router/dev": "^8.3.1",
"@tailwindcss/vite": "^4.3.1", "@tailwindcss/vite": "^4.3.3",
"@types/mdx": "^2.0.14", "@types/mdx": "^2.0.14",
"@types/node": "^26.0.1", "@types/node": "^26.4.0",
"@types/react": "^19.2.17", "@types/react": "^19.2.18",
"@types/react-copy-to-clipboard": "^5.0.7", "@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^19.2.3", "@types/react-dom": "^19.2.5",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"baseline-browser-mapping": "^2.10.40", "baseline-browser-mapping": "^2.11.20",
"oxfmt": "^0.56.0", "oxfmt": "^0.65.0",
"oxlint": "^1.71.0", "oxlint": "^1.80.0",
"react-router-devtools": "^6.2.1", "react-router-devtools": "^6.2.3",
"rimraf": "^6.1.3", "rimraf": "^6.1.3",
"serve": "^14.2.6", "serve": "^14.2.6",
"tailwindcss": "^4.3.1", "tailwindcss": "^4.3.3",
"typescript": "^6.0.3", "typescript": "^7.0.2",
"vite": "^8.1.0", "vite": "^8.2.2",
"vite-plugin-devtools-json": "^1.0.0", "vite-plugin-devtools-json": "^1.1.0",
"vite-tsconfig-paths": "^6.1.1" "vite-tsconfig-paths": "^6.1.1"
} }
} }
-12
View File
@@ -1,12 +0,0 @@
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
export const docs = defineDocs({
dir: "content/docs",
docs: {
postprocess: {
includeProcessedMarkdown: true,
},
},
});
export default defineConfig();
+1 -2
View File
@@ -9,8 +9,7 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"rootDirs": [".", "./.react-router/types"], "rootDirs": [".", "./.react-router/types"],
"paths": { "paths": {
"@/*": ["./app/*"], "@/*": ["./app/*"]
"collections/*": ["./.source/*"]
}, },
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
+3 -13
View File
@@ -1,12 +1,9 @@
import { reactRouter } from "@react-router/dev/vite"; import { reactRouter } from "@react-router/dev/vite";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import mdx from "fumadocs-mdx/vite"; import { fumadocsMdx } from "fumadocs-mdx/vite";
import path from "node:path";
import { defineConfig, loadEnv } from "vite"; import { defineConfig, loadEnv } from "vite";
import devtoolsJson from "vite-plugin-devtools-json"; import devtoolsJson from "vite-plugin-devtools-json";
import * as MdxConfig from "./source.config";
export default defineConfig(({ command, mode }) => { export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd(), "VITE_APP_"); const env = loadEnv(mode, process.cwd(), "VITE_APP_");
const isDev = command === "serve"; const isDev = command === "serve";
@@ -14,17 +11,10 @@ export default defineConfig(({ command, mode }) => {
return { return {
base: isDev || !contextPath ? "/" : `${contextPath}/`, base: isDev || !contextPath ? "/" : `${contextPath}/`,
plugins: [mdx(MdxConfig), tailwindcss(), reactRouter(), devtoolsJson()], plugins: [fumadocsMdx(), tailwindcss(), reactRouter(), devtoolsJson()],
resolve: { resolve: {
tsconfigPaths: true, tsconfigPaths: true,
alias: { alias: !isDev ? { "react-dom/server": "react-dom/server.node" } : {},
"@": path.resolve(__dirname, "./app"),
...(!isDev
? {
"react-dom/server": "react-dom/server.node",
}
: {}),
},
}, },
}; };
}); });