mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core & UI & MDX: Support MDX in codeblock tab value
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@fumadocs/mdx-remote': patch
|
||||
'fumadocs-core': patch
|
||||
'fumadocs-mdx': patch
|
||||
---
|
||||
|
||||
Support MDX in codeblock tab value
|
||||
@@ -41,25 +41,15 @@ export default withContentCollections(config);
|
||||
|
||||
To integrate with Fumadocs, add the following to your `content-collections.ts`.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/content-collections/content-collections.ts",
|
||||
"codeblock": {
|
||||
"meta": "title=\"content-collections.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="content-collections.ts"'>
|
||||
../../examples/content-collections/content-collections.ts
|
||||
</include>
|
||||
|
||||
And pass it to Source API.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/content-collections/lib/source.ts",
|
||||
"codeblock": {
|
||||
"meta": "title=\"lib/source.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="lib/source.ts"'>
|
||||
../../examples/content-collections/lib/source.ts
|
||||
</include>
|
||||
|
||||
Done! You can access the pages and generated page tree from Source API.
|
||||
|
||||
|
||||
@@ -9,15 +9,9 @@ Fumadocs core provides necessary middleware and options for i18n support.
|
||||
|
||||
You can define a config to share between utilities.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/i18n/lib/i18n.ts",
|
||||
"codeblock": {
|
||||
"lang": "ts",
|
||||
"meta": "title=\"lib/i18n.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="lib/i18n.ts"'>
|
||||
../../examples/i18n/lib/i18n.ts
|
||||
</include>
|
||||
|
||||
### Hide Locale Prefix
|
||||
|
||||
@@ -51,14 +45,8 @@ This may cause undesired cache problems, and need to pay extra attention on SEO
|
||||
|
||||
Redirects users to appropriate locale, it can be customised from `i18n.ts`.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/i18n/middleware.ts",
|
||||
"codeblock": {
|
||||
"lang": "ts",
|
||||
"meta": "title=\"middleware.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="middleware.ts"'>
|
||||
../../examples/i18n/middleware.ts
|
||||
</include>
|
||||
|
||||
> When `hideLocale` is enabled, it uses `NextResponse.rewrite` to hide locale prefixes.
|
||||
|
||||
@@ -45,14 +45,9 @@ credentials in environment variables.
|
||||
|
||||
Export the search indexes from Next.js using a route handler, this way we can access the search indexes after production build:
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/app/static.json/algolia.ts",
|
||||
"codeblock": {
|
||||
"meta": "title=\"app/static.json/route.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="app/static.json/route.ts"'>
|
||||
../../examples/next-mdx/app/static.json/algolia.ts
|
||||
</include>
|
||||
|
||||
Make a script to sync search indexes:
|
||||
|
||||
|
||||
@@ -27,26 +27,15 @@ REST API integration requires your docs to upload the indexes.
|
||||
|
||||
3. Then, using the private API key and index ID from dashboard, create a script to sync search indexes.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/scripts/sync-orama-cloud.mjs",
|
||||
"codeblock": {
|
||||
"lang": "js",
|
||||
"meta": "title=\"sync-index.mjs\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="sync-index.mjs"' lang="js">
|
||||
../../examples/next-mdx/scripts/sync-orama-cloud.mjs
|
||||
</include>
|
||||
|
||||
4. Create a route handler in your Next.js app to export search indexes.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/app/static.json/orama-cloud.ts",
|
||||
"codeblock": {
|
||||
"meta": "title=\"app/static.json/route.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="app/static.json/route.ts"'>
|
||||
../../examples/next-mdx/app/static.json/orama-cloud.ts
|
||||
</include>
|
||||
|
||||
5. Run the script after `next build`.
|
||||
|
||||
|
||||
@@ -17,15 +17,9 @@ class-variance-authority lucide-react
|
||||
|
||||
Copy the component:
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "./components/rate.tsx",
|
||||
"codeblock": {
|
||||
"lang": "tsx",
|
||||
"meta": "title=\"components/rate.tsx\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="components/rate.tsx"'>
|
||||
./components/rate.tsx
|
||||
</include>
|
||||
|
||||
The `@/lib/cn` import specifier may be different for your project, change it to import your `cn()` function if needed. (e.g. like `@/lib/utils`)
|
||||
|
||||
@@ -64,15 +58,9 @@ To report your feedback to GitHub Discussion, make a custom `onRateAction`.
|
||||
|
||||
You can copy this example as a starting point:
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "./lib/github.ts",
|
||||
"codeblock": {
|
||||
"lang": "ts",
|
||||
"meta": "title=\"lib/github.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta="lib/github.ts">
|
||||
./lib/github.ts
|
||||
</include>
|
||||
|
||||
- Create your own GitHub App and obtain its app ID and private key.
|
||||
- Fill required environment variables.
|
||||
|
||||
@@ -16,25 +16,39 @@ For docs pages, Fumadocs has a built-in metadata image generator.
|
||||
|
||||
You will need a route handler to get started.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/app/docs-og/[...slug]/route.tsx",
|
||||
"codeblock": {
|
||||
"meta": "title=\"app/docs-og/[...slug]/route.tsx\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="app/docs-og/[...slug]/route.tsx"'>
|
||||
../../examples/next-mdx/app/docs-og/[...slug]/route.tsx
|
||||
</include>
|
||||
|
||||
> We need to append `image.png` to the end of slugs so that we can access it via `/docs-og/my-page/image.png`.
|
||||
|
||||
In your docs page, add the image to metadata.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/app/docs/[[...slug]]/page.with-og-image.tsx",
|
||||
"codeblock": {
|
||||
"meta": "title=\"app/docs/[[...slug]]/page.tsx\""
|
||||
}
|
||||
```tsx title="app/docs/[[...slug]]/page.tsx"
|
||||
import { notFound } from 'next/navigation';
|
||||
import { source } from '@/lib/source';
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const { slug = [] } = await params;
|
||||
const page = source.getPage(slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const image = ['/docs-og', ...slug, 'image.png'].join('/');
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
openGraph: {
|
||||
images: image,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
images: image,
|
||||
},
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ preview: tabs
|
||||
|
||||
Add MDX components.
|
||||
|
||||
```tsx
|
||||
```tsx title="mdx-components.tsx"
|
||||
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||
import * as TabsComponents from 'fumadocs-ui/components/tabs';
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
|
||||
@@ -38,15 +38,9 @@ export const source = loader({
|
||||
|
||||
Create a middleware that redirects users to appropriate locale.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/i18n/middleware.ts",
|
||||
"codeblock": {
|
||||
"lang": "ts",
|
||||
"meta": "title=\"middleware.ts\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="middleware.ts"'>
|
||||
../../examples/i18n/middleware.ts
|
||||
</include>
|
||||
|
||||
<Callout title="Custom Middleware">
|
||||
|
||||
|
||||
@@ -113,25 +113,15 @@ Add the following Tailwind CSS styles to `global.css`.
|
||||
|
||||
Create a `app/layout.config.tsx` file to put the shared options for our layouts.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/app/layout.config.tsx",
|
||||
"codeblock": {
|
||||
"meta": "title=\"app/layout.config.tsx\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="app/layout.config.tsx"'>
|
||||
../../examples/next-mdx/app/layout.config.tsx
|
||||
</include>
|
||||
|
||||
Create a folder `/app/docs` for our docs, and give it a proper layout.
|
||||
|
||||
```json doc-gen:file
|
||||
{
|
||||
"file": "../../examples/next-mdx/app/docs/layout.tsx",
|
||||
"codeblock": {
|
||||
"meta": "title=\"app/docs/layout.tsx\""
|
||||
}
|
||||
}
|
||||
```
|
||||
<include cwd meta='title="app/docs/layout.tsx"'>
|
||||
../../examples/next-mdx/app/docs/layout.tsx
|
||||
</include>
|
||||
|
||||
> `pageTree` refers to Page Tree, it should be provided by your content source.
|
||||
|
||||
|
||||
@@ -283,11 +283,23 @@ return new ResizeObserver(() => {}) // [!code focus]
|
||||
|
||||
#### Tab Groups
|
||||
|
||||
You can use code blocks with the `<Tab />` component.
|
||||
Make sure to add MDX components first:
|
||||
|
||||
```tsx title="mdx-components.tsx"
|
||||
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
||||
import * as TabsComponents from 'fumadocs-ui/components/tabs';
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
|
||||
export function getMDXComponents(components?: MDXComponents): MDXComponents {
|
||||
return {
|
||||
...defaultMdxComponents,
|
||||
...TabsComponents, // [!code ++]
|
||||
...components,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
````mdx
|
||||
import { Tab, Tabs } from 'fumadocs-ui/components/tabs';
|
||||
|
||||
```ts tab="Tab 1"
|
||||
console.log('A');
|
||||
```
|
||||
@@ -297,8 +309,6 @@ console.log('B');
|
||||
```
|
||||
````
|
||||
|
||||
> Note that you can add MDX components instead of importing them in MDX files.
|
||||
|
||||
```ts tab="Tab 1"
|
||||
console.log('A');
|
||||
```
|
||||
@@ -341,3 +351,53 @@ npm i next -D
|
||||
```
|
||||
|
||||
To enable, see [Remark Install](/docs/headless/mdx/install).
|
||||
|
||||
### Tab Groups with MDX
|
||||
|
||||
You can use MDX inside tab values too, enable it the remark plugin:
|
||||
|
||||
```ts tab="Fumadocs MDX" title="source.config.ts"
|
||||
import { defineConfig } from 'fumadocs-mdx/config';
|
||||
|
||||
export default defineConfig({
|
||||
mdxOptions: {
|
||||
remarkCodeTabOptions: {
|
||||
parseMdx: true, // [!code ++]
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```ts tab="MDX Compiler"
|
||||
import { compile } from '@mdx-js/mdx';
|
||||
import { remarkCodeTab } from 'fumadocs-core/mdx-plugins';
|
||||
|
||||
await compile('...', {
|
||||
remarkPlugins: [
|
||||
[
|
||||
remarkCodeTab,
|
||||
{
|
||||
parseMdx: true, // [!code ++]
|
||||
},
|
||||
],
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
````mdx
|
||||
```ts tab="<Building /> Tab 1"
|
||||
console.log('A');
|
||||
```
|
||||
|
||||
```ts tab="<Rocket /> Tab 2"
|
||||
console.log('B');
|
||||
```
|
||||
````
|
||||
|
||||
```ts tab="<Building /> Tab 1"
|
||||
console.log('A');
|
||||
```
|
||||
|
||||
```ts tab="<Rocket /> Tab 2"
|
||||
console.log('B');
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { remark } from 'remark';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import { fileGenerator, remarkDocGen, remarkInstall } from 'fumadocs-docgen';
|
||||
import { remarkInstall } from 'fumadocs-docgen';
|
||||
import remarkMdx from 'remark-mdx';
|
||||
import { remarkAutoTypeTable } from 'fumadocs-typescript';
|
||||
import { remarkInclude } from 'fumadocs-mdx/config';
|
||||
@@ -11,7 +11,6 @@ const processor = remark()
|
||||
.use(remarkInclude)
|
||||
.use(remarkGfm)
|
||||
.use(remarkAutoTypeTable)
|
||||
.use(remarkDocGen, { generators: [fileGenerator()] })
|
||||
.use(remarkInstall);
|
||||
|
||||
export async function getLLMText(page: Page) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@radix-ui/react-slot": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "^1.2.6",
|
||||
"@shikijs/rehype": "^3.4.2",
|
||||
"@shikijs/transformers": "^3.4.2",
|
||||
"ai": "^4.3.15",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"fumadocs-core": "workspace:*",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
import { transformerTwoslash } from 'fumadocs-twoslash';
|
||||
import { createFileSystemTypesCache } from 'fumadocs-twoslash/cache-fs';
|
||||
import remarkMath from 'remark-math';
|
||||
import { fileGenerator, remarkDocGen, remarkInstall } from 'fumadocs-docgen';
|
||||
import { remarkInstall } from 'fumadocs-docgen';
|
||||
import { remarkTypeScriptToJavaScript } from 'fumadocs-docgen/remark-ts2js';
|
||||
import rehypeKatex from 'rehype-katex';
|
||||
import { z } from 'zod';
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
remarkSteps,
|
||||
} from 'fumadocs-core/mdx-plugins';
|
||||
import { remarkAutoTypeTable } from 'fumadocs-typescript';
|
||||
import { transformerRemoveNotationEscape } from '@shikijs/transformers';
|
||||
|
||||
export const docs = defineDocs({
|
||||
docs: {
|
||||
@@ -63,31 +64,17 @@ export default defineConfig({
|
||||
transformerTwoslash({
|
||||
typesCache: createFileSystemTypesCache(),
|
||||
}),
|
||||
{
|
||||
name: 'transformers:remove-notation-escape',
|
||||
code(hast) {
|
||||
for (const line of hast.children) {
|
||||
if (line.type !== 'element') continue;
|
||||
|
||||
const lastSpan = line.children.findLast(
|
||||
(v) => v.type === 'element',
|
||||
);
|
||||
|
||||
const head = lastSpan?.children[0];
|
||||
if (head?.type !== 'text') continue;
|
||||
|
||||
head.value = head.value.replace(/\[\\!code/g, '[!code');
|
||||
}
|
||||
},
|
||||
},
|
||||
transformerRemoveNotationEscape(),
|
||||
],
|
||||
},
|
||||
remarkCodeTabOptions: {
|
||||
parseMdx: true,
|
||||
},
|
||||
remarkPlugins: [
|
||||
remarkSteps,
|
||||
remarkMath,
|
||||
remarkAutoTypeTable,
|
||||
[remarkInstall, { persist: { id: 'package-manager' } }],
|
||||
[remarkDocGen, { generators: [fileGenerator()] }],
|
||||
remarkTypeScriptToJavaScript,
|
||||
],
|
||||
rehypePlugins: (v) => [rehypeKatex, ...v],
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { source } from '@/lib/source';
|
||||
import {
|
||||
DocsBody,
|
||||
DocsDescription,
|
||||
DocsPage,
|
||||
DocsTitle,
|
||||
} from 'fumadocs-ui/page';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { getMDXComponents } from '@/mdx-components';
|
||||
import type { Metadata } from 'next';
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const MDX = page.data.body;
|
||||
|
||||
return (
|
||||
<DocsPage toc={page.data.toc} full={page.data.full}>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
<MDX components={getMDXComponents()} />
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
}
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return source.generateParams();
|
||||
}
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug?: string[] }>;
|
||||
}) {
|
||||
const { slug = [] } = await params;
|
||||
const page = source.getPage(slug);
|
||||
if (!page) notFound();
|
||||
|
||||
const image = ['/docs-og', ...slug, 'image.png'].join('/');
|
||||
return {
|
||||
title: page.data.title,
|
||||
description: page.data.description,
|
||||
openGraph: {
|
||||
images: image,
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary_large_image',
|
||||
images: image,
|
||||
},
|
||||
} satisfies Metadata;
|
||||
}
|
||||
@@ -1,11 +1,64 @@
|
||||
import type { Transformer } from 'unified';
|
||||
import type { Code, Root, RootContent } from 'mdast';
|
||||
import type { Processor, Transformer } from 'unified';
|
||||
import type { Code, Root } from 'mdast';
|
||||
import { visit } from 'unist-util-visit';
|
||||
import type { MdxJsxFlowElement } from 'mdast-util-mdx-jsx';
|
||||
|
||||
const TabRegex = /tab="(.+?)"/;
|
||||
|
||||
function toTab(nodes: Code[]) {
|
||||
const names = nodes.map((node, i) => {
|
||||
export interface RemarkCodeTabOptions {
|
||||
/**
|
||||
* Parse MDX in tab values
|
||||
*
|
||||
* @defaultValue false
|
||||
*/
|
||||
parseMdx?: boolean;
|
||||
}
|
||||
|
||||
export function remarkCodeTab(
|
||||
this: Processor,
|
||||
options: RemarkCodeTabOptions = {},
|
||||
): Transformer<Root, Root> {
|
||||
return (tree) => {
|
||||
visit(tree, (node) => {
|
||||
if (!('children' in node)) return;
|
||||
if (node.type === 'mdxJsxFlowElement' && node.name === 'Tabs') return;
|
||||
let start = -1;
|
||||
let i = 0;
|
||||
|
||||
while (i < node.children.length) {
|
||||
const child = node.children[i];
|
||||
const isSwitcher =
|
||||
child.type === 'code' && child.meta && child.meta.match(TabRegex);
|
||||
|
||||
if (isSwitcher && start === -1) {
|
||||
start = i;
|
||||
}
|
||||
|
||||
// if switcher code blocks terminated, convert them to tabs
|
||||
const isLast = i === node.children.length - 1;
|
||||
if (start !== -1 && (isLast || !isSwitcher)) {
|
||||
const end = isSwitcher ? i + 1 : i;
|
||||
const targets = node.children.slice(start, end) as Code[];
|
||||
|
||||
node.children.splice(
|
||||
start,
|
||||
end - start,
|
||||
options.parseMdx ? toTabMdx(this, targets) : toTab(targets),
|
||||
);
|
||||
|
||||
if (isLast) break;
|
||||
i = start + 1;
|
||||
start = -1;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function processTabValue(nodes: Code[]) {
|
||||
return nodes.map((node, i) => {
|
||||
let title = `Tab ${i + 1}`;
|
||||
|
||||
node.meta = node.meta?.replace(TabRegex, (_, value) => {
|
||||
@@ -15,17 +68,10 @@ function toTab(nodes: Code[]) {
|
||||
|
||||
return title;
|
||||
});
|
||||
}
|
||||
|
||||
const itemsArr = {
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'ArrayExpression',
|
||||
elements: names.map((name) => ({
|
||||
type: 'Literal',
|
||||
value: name,
|
||||
})),
|
||||
},
|
||||
};
|
||||
function toTab(nodes: Code[]): MdxJsxFlowElement {
|
||||
const names = processTabValue(nodes);
|
||||
|
||||
return {
|
||||
type: 'mdxJsxFlowElement',
|
||||
@@ -36,12 +82,24 @@ function toTab(nodes: Code[]) {
|
||||
name: 'items',
|
||||
value: {
|
||||
type: 'mdxJsxAttributeValueExpression',
|
||||
value: names.join(', '),
|
||||
data: {
|
||||
estree: {
|
||||
type: 'Program',
|
||||
sourceType: 'module',
|
||||
comments: [],
|
||||
body: [itemsArr],
|
||||
body: [
|
||||
{
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'ArrayExpression',
|
||||
elements: names.map((name) => ({
|
||||
type: 'Literal',
|
||||
value: name,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -64,42 +122,66 @@ function toTab(nodes: Code[]) {
|
||||
};
|
||||
}
|
||||
|
||||
export function remarkCodeTab(): Transformer<Root, Root> {
|
||||
return (tree) => {
|
||||
visit(tree, (node) => {
|
||||
if (!('children' in node)) return;
|
||||
if (node.type === 'mdxJsxFlowElement' && node.name === 'Tabs') return;
|
||||
let start = -1;
|
||||
let i = 0;
|
||||
function toTabMdx(processor: Processor, nodes: Code[]): MdxJsxFlowElement {
|
||||
const names = processTabValue(nodes);
|
||||
function inline(node: Root) {
|
||||
if (node.type === 'root') {
|
||||
node.children = node.children.flatMap((child) => {
|
||||
if (child.type === 'paragraph') return child.children;
|
||||
|
||||
while (i < node.children.length) {
|
||||
const child = node.children[i];
|
||||
const isSwitcher =
|
||||
child.type === 'code' && child.meta && child.meta.match(TabRegex);
|
||||
return child;
|
||||
});
|
||||
}
|
||||
|
||||
if (isSwitcher && start === -1) {
|
||||
start = i;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// if switcher code blocks terminated, convert them to tabs
|
||||
const isLast = i === node.children.length - 1;
|
||||
if (start !== -1 && (isLast || !isSwitcher)) {
|
||||
const end = isSwitcher ? i + 1 : i;
|
||||
const targets = node.children.slice(start, end);
|
||||
|
||||
node.children.splice(
|
||||
start,
|
||||
end - start,
|
||||
toTab(targets as Code[]) as RootContent,
|
||||
);
|
||||
|
||||
if (isLast) break;
|
||||
i = start;
|
||||
start = -1;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
});
|
||||
return {
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'Tabs',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'defaultValue',
|
||||
value: names[0],
|
||||
},
|
||||
],
|
||||
children: [
|
||||
{
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'TabsList',
|
||||
attributes: [],
|
||||
children: names.map((name) => ({
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'TabsTrigger',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'value',
|
||||
value: name,
|
||||
},
|
||||
],
|
||||
children: [
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- needed
|
||||
inline(processor.parse(name) as Root) as any,
|
||||
],
|
||||
})),
|
||||
},
|
||||
...nodes.map(
|
||||
(node, i) =>
|
||||
({
|
||||
type: 'mdxJsxFlowElement',
|
||||
name: 'TabsContent',
|
||||
attributes: [
|
||||
{
|
||||
type: 'mdxJsxAttribute',
|
||||
name: 'value',
|
||||
value: names[i],
|
||||
},
|
||||
],
|
||||
children: [node],
|
||||
}) satisfies MdxJsxFlowElement,
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
import type {
|
||||
RehypeCodeOptions,
|
||||
RehypeTocOptions,
|
||||
RemarkHeadingOptions,
|
||||
RemarkImageOptions,
|
||||
} from 'fumadocs-core/mdx-plugins';
|
||||
import * as Plugins from 'fumadocs-core/mdx-plugins';
|
||||
import { type CompileOptions, createProcessor } from '@mdx-js/mdx';
|
||||
import type { MDXComponents } from 'mdx/types';
|
||||
@@ -20,10 +14,10 @@ export type MDXOptions = Omit<
|
||||
remarkPlugins?: ResolvePlugins;
|
||||
rehypePlugins?: ResolvePlugins;
|
||||
|
||||
remarkHeadingOptions?: RemarkHeadingOptions | false;
|
||||
rehypeCodeOptions?: RehypeCodeOptions | false;
|
||||
rehypeTocOptions?: RehypeTocOptions | false;
|
||||
remarkCodeTabOptions?: false;
|
||||
remarkHeadingOptions?: Plugins.RemarkHeadingOptions | false;
|
||||
rehypeCodeOptions?: Plugins.RehypeCodeOptions | false;
|
||||
rehypeTocOptions?: Plugins.RehypeTocOptions | false;
|
||||
remarkCodeTabOptions?: Plugins.RemarkCodeTabOptions | false;
|
||||
|
||||
/**
|
||||
* The directory to find image sizes
|
||||
@@ -33,7 +27,7 @@ export type MDXOptions = Omit<
|
||||
*/
|
||||
imageDir?: string;
|
||||
|
||||
remarkImageOptions?: RemarkImageOptions | false;
|
||||
remarkImageOptions?: Plugins.RemarkImageOptions | false;
|
||||
};
|
||||
|
||||
export interface CompileMDXOptions {
|
||||
@@ -64,13 +58,19 @@ export interface CompileMDXResult<TFrontmatter = Record<string, unknown>> {
|
||||
}
|
||||
|
||||
export function createCompiler(mdxOptions?: MDXOptions) {
|
||||
let format = mdxOptions?.format;
|
||||
if (!format || format === 'detect') format = 'mdx';
|
||||
let instance: ReturnType<typeof createProcessor> | undefined;
|
||||
|
||||
const processor = createProcessor({
|
||||
...getCompileOptions(mdxOptions),
|
||||
format,
|
||||
});
|
||||
function getProcessor() {
|
||||
if (instance) return instance;
|
||||
|
||||
let format = mdxOptions?.format;
|
||||
if (!format || format === 'detect') format = 'mdx';
|
||||
|
||||
return (instance = createProcessor({
|
||||
...getCompileOptions(mdxOptions),
|
||||
format,
|
||||
}));
|
||||
}
|
||||
|
||||
return {
|
||||
async render(
|
||||
@@ -90,7 +90,7 @@ export function createCompiler(mdxOptions?: MDXOptions) {
|
||||
* Compile VFile
|
||||
*/
|
||||
async compileFile(from: Compatible): Promise<VFile> {
|
||||
return processor.process(from);
|
||||
return getProcessor().process(from);
|
||||
},
|
||||
async compile<Frontmatter extends object = Record<string, unknown>>(
|
||||
options: Omit<CompileMDXOptions, 'mdxOptions'>,
|
||||
@@ -173,10 +173,12 @@ function getCompileOptions({
|
||||
useImport: false,
|
||||
publicDir: imageDir,
|
||||
...remarkImageOptions,
|
||||
} satisfies RemarkImageOptions,
|
||||
} satisfies Plugins.RemarkImageOptions,
|
||||
]
|
||||
: null,
|
||||
remarkCodeTab && remarkCodeTabOptions !== false ? remarkCodeTab : null,
|
||||
remarkCodeTab && remarkCodeTabOptions !== false
|
||||
? [remarkCodeTab, remarkCodeTabOptions]
|
||||
: null,
|
||||
...v,
|
||||
],
|
||||
options.remarkPlugins,
|
||||
|
||||
@@ -20,7 +20,7 @@ export type DefaultMDXOptions = Omit<
|
||||
remarkStructureOptions?: plugins.StructureOptions | false;
|
||||
remarkHeadingOptions?: plugins.RemarkHeadingOptions;
|
||||
remarkImageOptions?: plugins.RemarkImageOptions | false;
|
||||
remarkCodeTabOptions?: false;
|
||||
remarkCodeTabOptions?: plugins.RemarkCodeTabOptions | false;
|
||||
rehypeCodeOptions?: plugins.RehypeCodeOptions | false;
|
||||
};
|
||||
|
||||
@@ -68,8 +68,10 @@ export function getDefaultMDXOptions({
|
||||
remarkImageOptions !== false && [plugins.remarkImage, remarkImageOptions],
|
||||
// Fumadocs 14 compatibility
|
||||
'remarkCodeTab' in plugins &&
|
||||
remarkCodeTabOptions !== false &&
|
||||
plugins.remarkCodeTab,
|
||||
remarkCodeTabOptions !== false && [
|
||||
plugins.remarkCodeTab,
|
||||
remarkCodeTabOptions,
|
||||
],
|
||||
...v,
|
||||
remarkStructureOptions !== false && [
|
||||
plugins.remarkStructure,
|
||||
|
||||
Generated
+6
-3
Reference in New Issue
Block a user