Core & OpenAPI: Improve Fumadocs OpenAPI support (TOC & Structured Data plugins)

This commit is contained in:
fuma-nama
2024-08-13 18:06:51 +08:00
parent 758013f37b
commit 61b91fa2a0
14 changed files with 44 additions and 182 deletions
+7
View File
@@ -0,0 +1,7 @@
---
'fumadocs-openapi': patch
'fumadocs-core': patch
'fumadocs-mdx': patch
---
Improve Fumadocs OpenAPI support
-2
View File
@@ -34,5 +34,3 @@ curl -XPOST "https://api.unkey.dev/v1/keys.createKey" \
-H "Content-Type: application/json" \
-d '{"apiId": "api_123", "name": "My Key"}'
```
![img](https://miro.medium.com/v2/resize:fit:4800/format:webp/1*qHtOm0c0IXsefOd2W7VECg.png)
@@ -40,6 +40,19 @@ export function remarkHeading({
const toc: TOCItemType[] = [];
slugger.reset();
// Fumadocs OpenAPI Generated TOC
if (file.data.frontmatter) {
const frontmatter = file.data.frontmatter as {
_openapi?: {
toc?: TOCItemType[];
};
};
if (frontmatter._openapi?.toc) {
toc.push(...frontmatter._openapi.toc);
}
}
visit(root, 'heading', (heading) => {
heading.data ||= {};
heading.data.hProperties ||= {};
@@ -45,6 +45,20 @@ export function remarkStructure({
const data: StructuredData = { contents: [], headings: [] };
let lastHeading: string | undefined = '';
// Fumadocs OpenAPI Generated Structured Data
if (file.data.frontmatter) {
const frontmatter = file.data.frontmatter as {
_openapi?: {
structuredData?: StructuredData;
};
};
if (frontmatter._openapi?.structuredData) {
data.headings.push(...frontmatter._openapi.structuredData.headings);
data.contents.push(...frontmatter._openapi.structuredData.contents);
}
}
visit(node, types, (element) => {
if (element.type === 'root') return;
const content = flattenNode(element).trim();
@@ -18,8 +18,6 @@ export const attachFile: BuildPageTreeOptions['attachFile'] = (node, file) => {
};
method = meta.method;
} else if ('method' in data && typeof data.method === 'string') {
method = data.method;
}
if (method) {
@@ -64,7 +64,7 @@ export function generateDocument(
out.push(imports);
}
out.push(pageContent(data, options.page));
out.push(pageContent(options.page));
return out.join('\n\n');
}
@@ -108,24 +108,6 @@ function generateStaticData(
return { toc, structuredData };
}
function pageContent(data: StaticData, props: ApiPageProps): string {
// modify toc and structured data if possible
// it may not be compatible with other content sources except Fumadocs MDX
// TODO: Maybe add to frontmatter and let developers to handle them?
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(props.operations)}} hasHead={${JSON.stringify(props.hasHead)}} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
toc.push(...${JSON.stringify(data.toc)})
}
if (typeof structuredData !== 'undefined') {
structuredData.headings = ${JSON.stringify(data.structuredData.headings)}
structuredData.contents = ${JSON.stringify(data.structuredData.contents)}
}
}
{startup()}`;
function pageContent(props: ApiPageProps): string {
return `<APIPage document={${JSON.stringify(props.document)}} operations={${JSON.stringify(props.operations)}} hasHead={${JSON.stringify(props.hasHead)}} />`;
}
+1 -16
View File
@@ -46,19 +46,4 @@ _openapi:
heading: delete-special-event
---
<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} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
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"}])
}
if (typeof structuredData !== 'undefined') {
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"}]
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"}]
}
}
{startup()}
<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} />
@@ -16,19 +16,4 @@ _openapi:
heading: get-museum-hours
---
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"get","path":"/museum-hours"}]} hasHead={true} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
toc.push(...[{"depth":2,"title":"Get museum hours","url":"#get-museum-hours"}])
}
if (typeof structuredData !== 'undefined') {
structuredData.headings = [{"content":"Get museum hours","id":"get-museum-hours"}]
structuredData.contents = [{"content":"Get upcoming museum operating hours","heading":"get-museum-hours"}]
}
}
{startup()}
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"get","path":"/museum-hours"}]} hasHead={true} />
+1 -16
View File
@@ -25,19 +25,4 @@ _openapi:
heading: get-ticket-qr-code
---
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"post","path":"/tickets"},{"method":"get","path":"/tickets/{ticketId}/qr"}]} hasHead={true} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
toc.push(...[{"depth":2,"title":"Buy museum tickets","url":"#buy-museum-tickets"},{"depth":2,"title":"Get ticket QR code","url":"#get-ticket-qr-code"}])
}
if (typeof structuredData !== 'undefined') {
structuredData.headings = [{"content":"Buy museum tickets","id":"buy-museum-tickets"},{"content":"Get ticket QR code","id":"get-ticket-qr-code"}]
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"}]
}
}
{startup()}
<APIPage document={"./fixtures/museum.yaml"} operations={[{"method":"post","path":"/tickets"},{"method":"get","path":"/tickets/{ticketId}/qr"}]} hasHead={true} />
+1 -16
View File
@@ -23,19 +23,4 @@ _openapi:
contents: []
---
<APIPage document={"./fixtures/petstore.yaml"} operations={[{"method":"get","path":"/pets"},{"method":"post","path":"/pets"},{"method":"get","path":"/pets/{petId}"}]} hasHead={true} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
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"}])
}
if (typeof structuredData !== 'undefined') {
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"}]
structuredData.contents = []
}
}
{startup()}
<APIPage document={"./fixtures/petstore.yaml"} operations={[{"method":"get","path":"/pets"},{"method":"post","path":"/pets"},{"method":"get","path":"/pets/{petId}"}]} hasHead={true} />
+1 -16
View File
@@ -28,19 +28,4 @@ _openapi:
contents: []
---
<APIPage document={"./fixtures/unkey.json"} operations={[{"method":"get","path":"/v1/apis.getApi"},{"method":"post","path":"/v1/apis.createApi"},{"method":"get","path":"/v1/apis.listKeys"},{"method":"post","path":"/v1/apis.deleteApi"}]} hasHead={true} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
toc.push(...[{"depth":2,"title":"Get Api","url":"#get-api"},{"depth":2,"title":"Create Api","url":"#create-api"},{"depth":2,"title":"List Keys","url":"#list-keys"},{"depth":2,"title":"Delete Api","url":"#delete-api"}])
}
if (typeof structuredData !== 'undefined') {
structuredData.headings = [{"content":"Get Api","id":"get-api"},{"content":"Create Api","id":"create-api"},{"content":"List Keys","id":"list-keys"},{"content":"Delete Api","id":"delete-api"}]
structuredData.contents = []
}
}
{startup()}
<APIPage document={"./fixtures/unkey.json"} operations={[{"method":"get","path":"/v1/apis.getApi"},{"method":"post","path":"/v1/apis.createApi"},{"method":"get","path":"/v1/apis.listKeys"},{"method":"post","path":"/v1/apis.deleteApi"}]} hasHead={true} />
+1 -16
View File
@@ -43,19 +43,4 @@ _openapi:
contents: []
---
<APIPage document={"./fixtures/unkey.json"} operations={[{"method":"get","path":"/v1/keys.getKey"},{"method":"post","path":"/v1/keys.deleteKey"},{"method":"post","path":"/v1/keys.createKey"},{"method":"post","path":"/v1/keys.verifyKey"},{"method":"post","path":"/v1/keys.updateKey"},{"method":"post","path":"/v1/keys.updateRemaining"},{"method":"get","path":"/v1/keys.getVerifications"}]} hasHead={true} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
toc.push(...[{"depth":2,"title":"Get Key","url":"#get-key"},{"depth":2,"title":"Delete Key","url":"#delete-key"},{"depth":2,"title":"Create Key","url":"#create-key"},{"depth":2,"title":"Verify Key","url":"#verify-key"},{"depth":2,"title":"Update Key","url":"#update-key"},{"depth":2,"title":"Update Remaining","url":"#update-remaining"},{"depth":2,"title":"Get Verifications","url":"#get-verifications"}])
}
if (typeof structuredData !== 'undefined') {
structuredData.headings = [{"content":"Get Key","id":"get-key"},{"content":"Delete Key","id":"delete-key"},{"content":"Create Key","id":"create-key"},{"content":"Verify Key","id":"verify-key"},{"content":"Update Key","id":"update-key"},{"content":"Update Remaining","id":"update-remaining"},{"content":"Get Verifications","id":"get-verifications"}]
structuredData.contents = []
}
}
{startup()}
<APIPage document={"./fixtures/unkey.json"} operations={[{"method":"get","path":"/v1/keys.getKey"},{"method":"post","path":"/v1/keys.deleteKey"},{"method":"post","path":"/v1/keys.createKey"},{"method":"post","path":"/v1/keys.verifyKey"},{"method":"post","path":"/v1/keys.updateKey"},{"method":"post","path":"/v1/keys.updateRemaining"},{"method":"get","path":"/v1/keys.getVerifications"}]} hasHead={true} />
+1 -16
View File
@@ -13,19 +13,4 @@ _openapi:
contents: []
---
<APIPage document={"./fixtures/unkey.json"} operations={[{"method":"get","path":"/v1/liveness"}]} hasHead={true} />
export function startup() {
if (typeof toc !== 'undefined') {
// toc might be immutable
while (toc.length > 0) toc.pop()
toc.push(...[{"depth":2,"title":"Check Liveness","url":"#check-liveness"}])
}
if (typeof structuredData !== 'undefined') {
structuredData.headings = [{"content":"Check Liveness","id":"check-liveness"}]
structuredData.contents = []
}
}
{startup()}
<APIPage document={"./fixtures/unkey.json"} operations={[{"method":"get","path":"/v1/liveness"}]} hasHead={true} />
-45
View File
@@ -698,9 +698,6 @@ importers:
react:
specifier: '>= 18'
version: 18.3.1
rehype-img-size:
specifier: ^1.0.1
version: 1.0.1
ws:
specifier: ^8.18.0
version: 8.18.0([email protected])
@@ -4587,11 +4584,6 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
[email protected]:
resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==}
engines: {node: '>=14.0.0'}
hasBin: true
[email protected]:
resolution: {integrity: sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==}
engines: {node: '>=16.x'}
@@ -5936,10 +5928,6 @@ packages:
resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==}
hasBin: true
[email protected]:
resolution: {integrity: sha512-+rLkxF2H3mQULAg3iA2Z2spJQlBcCpApG8sHC47bc0p33ol+ddz+O3gyUcTgk5xX5jGaj1oQOBs/cBy8nIIhoQ==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
[email protected]:
resolution: {integrity: sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==}
@@ -6648,9 +6636,6 @@ packages:
[email protected]:
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
[email protected]:
resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==}
[email protected]:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
@@ -6666,15 +6651,9 @@ packages:
[email protected]:
resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
[email protected]:
resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
[email protected]:
resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
[email protected]:
resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
[email protected]:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
@@ -10930,10 +10909,6 @@ snapshots:
[email protected]: {}
[email protected]:
dependencies:
queue: 6.0.2
[email protected]:
dependencies:
queue: 6.0.2
@@ -12529,11 +12504,6 @@ snapshots:
dependencies:
jsesc: 0.5.0
[email protected]:
dependencies:
image-size: 1.0.2
unist-util-visit: 4.1.2
[email protected]:
dependencies:
'@types/hast': 3.0.4
@@ -13371,10 +13341,6 @@ snapshots:
'@types/unist': 3.0.2
unist-util-is: 6.0.0
[email protected]:
dependencies:
'@types/unist': 2.0.8
[email protected]:
dependencies:
'@types/unist': 3.0.2
@@ -13396,22 +13362,11 @@ snapshots:
dependencies:
'@types/unist': 3.0.2
[email protected]:
dependencies:
'@types/unist': 2.0.8
unist-util-is: 5.2.1
[email protected]:
dependencies:
'@types/unist': 3.0.2
unist-util-is: 6.0.0
[email protected]:
dependencies:
'@types/unist': 2.0.8
unist-util-is: 5.2.1
unist-util-visit-parents: 5.1.3
[email protected]:
dependencies:
'@types/unist': 3.0.2