Skip to content

Commit 61b91fa

Browse files
committed
Core & OpenAPI: Improve Fumadocs OpenAPI support (TOC & Structured Data plugins)
1 parent 758013f commit 61b91fa

File tree

14 files changed

+44
-182
lines changed

14 files changed

+44
-182
lines changed

.changeset/honest-readers-guess.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'fumadocs-openapi': patch
3+
'fumadocs-core': patch
4+
'fumadocs-mdx': patch
5+
---
6+
7+
Improve Fumadocs OpenAPI support

examples/openapi/content/docs/index.mdx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,3 @@ curl -XPOST "https://api.unkey.dev/v1/keys.createKey" \
3434
-H "Content-Type: application/json" \
3535
-d '{"apiId": "api_123", "name": "My Key"}'
3636
```
37-
38-
![img](https://miro.medium.com/v2/resize:fit:4800/format:webp/1*qHtOm0c0IXsefOd2W7VECg.png)

packages/core/src/mdx-plugins/remark-heading.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,19 @@ export function remarkHeading({
4040
const toc: TOCItemType[] = [];
4141
slugger.reset();
4242

43+
// Fumadocs OpenAPI Generated TOC
44+
if (file.data.frontmatter) {
45+
const frontmatter = file.data.frontmatter as {
46+
_openapi?: {
47+
toc?: TOCItemType[];
48+
};
49+
};
50+
51+
if (frontmatter._openapi?.toc) {
52+
toc.push(...frontmatter._openapi.toc);
53+
}
54+
}
55+
4356
visit(root, 'heading', (heading) => {
4457
heading.data ||= {};
4558
heading.data.hProperties ||= {};

packages/core/src/mdx-plugins/remark-structure.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,20 @@ export function remarkStructure({
4545
const data: StructuredData = { contents: [], headings: [] };
4646
let lastHeading: string | undefined = '';
4747

48+
// Fumadocs OpenAPI Generated Structured Data
49+
if (file.data.frontmatter) {
50+
const frontmatter = file.data.frontmatter as {
51+
_openapi?: {
52+
structuredData?: StructuredData;
53+
};
54+
};
55+
56+
if (frontmatter._openapi?.structuredData) {
57+
data.headings.push(...frontmatter._openapi.structuredData.headings);
58+
data.contents.push(...frontmatter._openapi.structuredData.contents);
59+
}
60+
}
61+
4862
visit(node, types, (element) => {
4963
if (element.type === 'root') return;
5064
const content = flattenNode(element).trim();

packages/openapi/src/server/source-api.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ export const attachFile: BuildPageTreeOptions['attachFile'] = (node, file) => {
1818
};
1919

2020
method = meta.method;
21-
} else if ('method' in data && typeof data.method === 'string') {
22-
method = data.method;
2321
}
2422

2523
if (method) {

packages/openapi/src/utils/generate-document.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export function generateDocument(
6464
out.push(imports);
6565
}
6666

67-
out.push(pageContent(data, options.page));
67+
out.push(pageContent(options.page));
6868

6969
return out.join('\n\n');
7070
}
@@ -108,24 +108,6 @@ function generateStaticData(
108108
return { toc, structuredData };
109109
}
110110

111-
function pageContent(data: StaticData, props: ApiPageProps): string {
112-
// modify toc and structured data if possible
113-
// it may not be compatible with other content sources except Fumadocs MDX
114-
// TODO: Maybe add to frontmatter and let developers to handle them?
115-
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(props.operations)}} hasHead={${JSON.stringify(props.hasHead)}} />
116-
117-
export function startup() {
118-
if (typeof toc !== 'undefined') {
119-
// toc might be immutable
120-
while (toc.length > 0) toc.pop()
121-
toc.push(...${JSON.stringify(data.toc)})
122-
}
123-
124-
if (typeof structuredData !== 'undefined') {
125-
structuredData.headings = ${JSON.stringify(data.structuredData.headings)}
126-
structuredData.contents = ${JSON.stringify(data.structuredData.contents)}
127-
}
128-
}
129-
130-
{startup()}`;
111+
function pageContent(props: ApiPageProps): string {
112+
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(props.operations)}} hasHead={${JSON.stringify(props.hasHead)}} />`;
131113
}

packages/openapi/test/out/museum/events.mdx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,4 @@ _openapi:
4646
heading: delete-special-event
4747
---
4848

49-
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"get","path":"/special-events"},{"method":"post","path":"/special-events"},{"method":"get","path":"/special-events/{eventId}"},{"method":"patch","path":"/special-events/{eventId}"},{"method":"delete","path":"/special-events/{eventId}"}]} hasHead={true} />
50-
51-
export function startup() {
52-
if (typeof toc !== 'undefined') {
53-
// toc might be immutable
54-
while (toc.length > 0) toc.pop()
55-
toc.push(...[{"depth":2,"title":"List special events","url":"#list-special-events"},{"depth":2,"title":"Create special events","url":"#create-special-events"},{"depth":2,"title":"Get special event","url":"#get-special-event"},{"depth":2,"title":"Update special event","url":"#update-special-event"},{"depth":2,"title":"Delete special event","url":"#delete-special-event"}])
56-
}
57-
58-
if (typeof structuredData !== 'undefined') {
59-
structuredData.headings = [{"content":"List special events","id":"list-special-events"},{"content":"Create special events","id":"create-special-events"},{"content":"Get special event","id":"get-special-event"},{"content":"Update special event","id":"update-special-event"},{"content":"Delete special event","id":"delete-special-event"}]
60-
structuredData.contents = [{"content":"Return a list of upcoming special events at the museum.","heading":"list-special-events"},{"content":"Creates a new special event for the museum.","heading":"create-special-events"},{"content":"Get details about a special event.","heading":"get-special-event"},{"content":"Update the details of a special event","heading":"update-special-event"},{"content":"Delete a special event from the collection. Allows museum to cancel planned events.","heading":"delete-special-event"}]
61-
}
62-
}
63-
64-
{startup()}
49+
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"get","path":"/special-events"},{"method":"post","path":"/special-events"},{"method":"get","path":"/special-events/{eventId}"},{"method":"patch","path":"/special-events/{eventId}"},{"method":"delete","path":"/special-events/{eventId}"}]} hasHead={true} />

packages/openapi/test/out/museum/operations.mdx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,4 @@ _openapi:
1616
heading: get-museum-hours
1717
---
1818

19-
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"get","path":"/museum-hours"}]} hasHead={true} />
20-
21-
export function startup() {
22-
if (typeof toc !== 'undefined') {
23-
// toc might be immutable
24-
while (toc.length > 0) toc.pop()
25-
toc.push(...[{"depth":2,"title":"Get museum hours","url":"#get-museum-hours"}])
26-
}
27-
28-
if (typeof structuredData !== 'undefined') {
29-
structuredData.headings = [{"content":"Get museum hours","id":"get-museum-hours"}]
30-
structuredData.contents = [{"content":"Get upcoming museum operating hours","heading":"get-museum-hours"}]
31-
}
32-
}
33-
34-
{startup()}
19+
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"get","path":"/museum-hours"}]} hasHead={true} />

packages/openapi/test/out/museum/tickets.mdx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,4 @@ _openapi:
2525
heading: get-ticket-qr-code
2626
---
2727

28-
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"post","path":"/tickets"},{"method":"get","path":"/tickets/{ticketId}/qr"}]} hasHead={true} />
29-
30-
export function startup() {
31-
if (typeof toc !== 'undefined') {
32-
// toc might be immutable
33-
while (toc.length > 0) toc.pop()
34-
toc.push(...[{"depth":2,"title":"Buy museum tickets","url":"#buy-museum-tickets"},{"depth":2,"title":"Get ticket QR code","url":"#get-ticket-qr-code"}])
35-
}
36-
37-
if (typeof structuredData !== 'undefined') {
38-
structuredData.headings = [{"content":"Buy museum tickets","id":"buy-museum-tickets"},{"content":"Get ticket QR code","id":"get-ticket-qr-code"}]
39-
structuredData.contents = [{"content":"Purchase museum tickets for general entry or special events.","heading":"buy-museum-tickets"},{"content":"Return an image of your ticket with scannable QR code. Used for event entry.","heading":"get-ticket-qr-code"}]
40-
}
41-
}
42-
43-
{startup()}
28+
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"post","path":"/tickets"},{"method":"get","path":"/tickets/{ticketId}/qr"}]} hasHead={true} />

packages/openapi/test/out/petstore.mdx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,4 @@ _openapi:
2323
contents: []
2424
---
2525

26-
<APIPage document={"./fixtures/petstore.yaml"} operations={[{"method":"get","path":"/pets"},{"method":"post","path":"/pets"},{"method":"get","path":"/pets/{petId}"}]} hasHead={true} />
27-
28-
export function startup() {
29-
if (typeof toc !== 'undefined') {
30-
// toc might be immutable
31-
while (toc.length > 0) toc.pop()
32-
toc.push(...[{"depth":2,"title":"List all pets","url":"#list-all-pets"},{"depth":2,"title":"Create a pet","url":"#create-a-pet"},{"depth":2,"title":"Info for a specific pet","url":"#info-for-a-specific-pet"}])
33-
}
34-
35-
if (typeof structuredData !== 'undefined') {
36-
structuredData.headings = [{"content":"List all pets","id":"list-all-pets"},{"content":"Create a pet","id":"create-a-pet"},{"content":"Info for a specific pet","id":"info-for-a-specific-pet"}]
37-
structuredData.contents = []
38-
}
39-
}
40-
41-
{startup()}
26+
<APIPage document={"./fixtures/petstore.yaml"} operations={[{"method":"get","path":"/pets"},{"method":"post","path":"/pets"},{"method":"get","path":"/pets/{petId}"}]} hasHead={true} />

0 commit comments

Comments
 (0)