refactor(docs) parse and render docs mdx files by fumadocs

This commit is contained in:
javayhu 2025-06-16 00:51:36 +08:00
parent 3b741b3b98
commit f4d8a09ab6
10 changed files with 379 additions and 281 deletions

3
.gitignore vendored
View File

@ -43,6 +43,9 @@ next-env.d.ts
# content collections
.content-collections
# fumadocs
.source
# OpenNext build output
.open-next

View File

@ -1,11 +1,7 @@
import path from 'path';
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
import { defineCollection, defineConfig } from '@content-collections/core';
import {
createDocSchema,
createMetaSchema,
transformMDX,
} from '@fumadocs/content-collections/configuration';
import { transformMDX } from '@fumadocs/content-collections/configuration';
/**
* 1. Content Collections documentation
@ -16,25 +12,25 @@ import {
* 2. Use Content Collections for Fumadocs
* https://fumadocs.dev/docs/headless/content-collections
*/
const docs = defineCollection({
name: 'docs',
directory: 'content/docs',
include: '**/*.mdx',
schema: (z) => ({
...createDocSchema(z),
preview: z.string().optional(),
index: z.boolean().default(false),
}),
transform: transformMDX,
});
// const docs = defineCollection({
// name: 'docs',
// directory: 'content/docs',
// include: '**/*.mdx',
// schema: (z) => ({
// ...createDocSchema(z),
// preview: z.string().optional(),
// index: z.boolean().default(false),
// }),
// transform: transformMDX,
// });
const metas = defineCollection({
name: 'meta',
directory: 'content/docs',
include: '**/meta**.json',
parser: 'json',
schema: createMetaSchema,
});
// const metas = defineCollection({
// name: 'meta',
// directory: 'content/docs',
// include: '**/meta**.json',
// parser: 'json',
// schema: createMetaSchema,
// });
/**
* Blog Author collection
@ -324,5 +320,5 @@ function extractLocaleAndBase(fileName: string): {
}
export default defineConfig({
collections: [docs, metas, authors, categories, posts, pages, releases],
collections: [authors, categories, posts, pages, releases],
});

View File

@ -1,4 +1,4 @@
import { withContentCollections } from '@content-collections/next';
import { createMDX } from 'fumadocs-mdx/next';
import type { NextConfig } from 'next';
import createNextIntlPlugin from 'next-intl/plugin';
@ -69,8 +69,9 @@ const nextConfig: NextConfig = {
const withNextIntl = createNextIntlPlugin();
/**
* withContentCollections must be the outermost plugin
*
* https://www.content-collections.dev/docs/quickstart/next
* https://fumadocs.dev/docs/ui/manual-installation
* https://fumadocs.dev/docs/mdx/plugin
*/
export default withContentCollections(withNextIntl(nextConfig));
const withMDX = createMDX();
export default withMDX(withNextIntl(nextConfig));

View File

@ -6,6 +6,7 @@
"dev": "concurrently \"content-collections watch\" \"next dev\"",
"build": "content-collections build && next build",
"start": "next start",
"postinstall": "fumadocs-mdx",
"lint": "biome check --write .",
"lint:fix": "biome check --fix --unsafe .",
"format": "biome format --write .",

510
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

27
source.config.ts Normal file
View File

@ -0,0 +1,27 @@
import { defineDocs, frontmatterSchema, metaSchema } from 'fumadocs-mdx/config';
import { z } from 'zod';
const customDocsSchema = frontmatterSchema.extend({
preview: z.string().optional(),
index: z.boolean().default(false),
});
const customMetaSchema = metaSchema.extend({
description: z.string().optional(),
});
/**
* frontmatterSchema.extend causes error: Type instantiation is excessively deep,
* so we define the schema manually.
*
* https://fumadocs.dev/docs/mdx/collections#schema-1
*/
export const docs = defineDocs({
dir: 'content/docs',
docs: {
schema: customDocsSchema,
},
meta: {
schema: customMetaSchema,
},
});

View File

@ -1,5 +1,5 @@
import { getMDXComponents } from '@/components/custom/mdx-components';
import * as Preview from '@/components/docs';
import { CustomMDXContent } from '@/components/shared/custom-mdx-content';
import {
HoverCard,
HoverCardContent,
@ -87,6 +87,8 @@ export default async function DocPage({ params }: DocPageProps) {
const preview = page.data.preview;
const MDX = page.data.body;
return (
<DocsPage
toc={page.data.toc}
@ -102,9 +104,8 @@ export default async function DocPage({ params }: DocPageProps) {
{preview ? <PreviewRenderer preview={preview} /> : null}
{/* MDX Content */}
<CustomMDXContent
code={page.data.body}
customComponents={{
<MDX
components={getMDXComponents({
a: ({ href, ...props }: { href?: string; [key: string]: any }) => {
const found = source.getPageByHref(href ?? '', {
dir: page.file.dirname,
@ -133,7 +134,7 @@ export default async function DocPage({ params }: DocPageProps) {
</HoverCard>
);
},
}}
})}
/>
</DocsBody>
</DocsPage>

View File

@ -0,0 +1,45 @@
import { ImageWrapper } from '@/components/docs/image-wrapper';
import { Wrapper } from '@/components/docs/wrapper';
import { YoutubeVideo } from '@/components/docs/youtube-video';
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
import { Callout } from 'fumadocs-ui/components/callout';
import { File, Files, Folder } from 'fumadocs-ui/components/files';
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
import { TypeTable } from 'fumadocs-ui/components/type-table';
import defaultMdxComponents from 'fumadocs-ui/mdx';
import * as LucideIcons from 'lucide-react';
import type { MDXComponents } from 'mdx/types';
import type { ComponentProps, FC } from 'react';
/**
* Enhanced MDX Content component that includes commonly used MDX components
* It can be used for blog posts, documentation, and custom pages
*/
export function getMDXComponents(components?: MDXComponents): MDXComponents {
// Start with default components
const baseComponents: Record<string, any> = {
...defaultMdxComponents,
...LucideIcons,
// ...((await import('lucide-react')) as unknown as MDXComponents),
YoutubeVideo,
Tabs,
Tab,
TypeTable,
Accordion,
Accordions,
Steps,
Step,
Wrapper,
File,
Folder,
Files,
blockquote: Callout as unknown as FC<ComponentProps<'blockquote'>>,
img: ImageWrapper,
};
return {
...baseComponents,
...components,
};
}

View File

@ -28,7 +28,7 @@ export default function Example() {
className="bg-transparent px-4 py-2 text-sm focus-visible:outline-none"
/>
</div>
<DynamicCodeBlock lang={lang} code={code} options={{}} />
<DynamicCodeBlock lang={lang} code={code} />
</div>
);
}

View File

@ -1,12 +1,12 @@
import { createMDXSource } from '@fumadocs/content-collections';
import { allDocs, allMetas } from 'content-collections';
import { loader } from 'fumadocs-core/source';
import * as LucideIcons from 'lucide-react';
import { createElement } from 'react';
import { docs } from '../../../.source';
import { docsI18nConfig } from './i18n';
/**
* Turn a content source into a unified interface
* .source folder is generated by `fumadocs-mdx`
*
* https://fumadocs.dev/docs/headless/source-api
* https://fumadocs.dev/docs/headless/content-collections
@ -14,7 +14,7 @@ import { docsI18nConfig } from './i18n';
export const source = loader({
baseUrl: '/docs',
i18n: docsI18nConfig,
source: createMDXSource(allDocs, allMetas),
source: docs.toFumadocsSource(),
icon(iconName) {
if (!iconName) {
return undefined;