mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-26 06:31:49 +00:00
OpenAPI: Support structuredData in virtual file source
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-openapi': patch
|
||||
---
|
||||
|
||||
Support `structuredData` in virtual file source
|
||||
@@ -10,6 +10,9 @@ import type {
|
||||
import type { OpenAPIServer } from '@/server/create';
|
||||
import type { SchemaToPagesOptions } from '@/utils/pages/preset-auto';
|
||||
import type { ApiPageProps } from '@/ui/api-page';
|
||||
import { toStaticData } from '@/utils/pages/to-static-data';
|
||||
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
|
||||
declare module 'fumadocs-core/source' {
|
||||
export interface PageData {
|
||||
@@ -69,13 +72,15 @@ export function openapiPlugin(): LoaderPlugin {
|
||||
|
||||
interface OpenAPIPageData extends PageData {
|
||||
getAPIPageProps: () => ApiPageProps;
|
||||
structuredData: StructuredData;
|
||||
toc: TOCItemType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate virtual pages for Fumadocs Source API
|
||||
*/
|
||||
export async function openapiSource(
|
||||
from: OpenAPIServer,
|
||||
server: OpenAPIServer,
|
||||
options: SchemaToPagesOptions & {
|
||||
baseDir?: string;
|
||||
} = {},
|
||||
@@ -94,27 +99,32 @@ export async function openapiSource(
|
||||
metaData: MetaData;
|
||||
}>[] = [];
|
||||
|
||||
const entries = await fromServer(from, createAutoPreset(options));
|
||||
for (const entry of Object.values(entries).flat()) {
|
||||
files.push({
|
||||
type: 'page',
|
||||
path: `${baseDir}/${entry.path}`,
|
||||
data: {
|
||||
...entry.info,
|
||||
getAPIPageProps() {
|
||||
const props = toBody(entry);
|
||||
props.showDescription ??= true;
|
||||
return props;
|
||||
const entries = await fromServer(server, createAutoPreset(options));
|
||||
for (const [schemaId, list] of Object.entries(entries)) {
|
||||
const dereferenced = (await server.getSchema(schemaId)).dereferenced;
|
||||
for (const entry of list) {
|
||||
const props = toBody(entry);
|
||||
props.showDescription ??= true;
|
||||
|
||||
files.push({
|
||||
type: 'page',
|
||||
path: `${baseDir}/${entry.path}`,
|
||||
data: {
|
||||
...entry.info,
|
||||
getAPIPageProps() {
|
||||
return props;
|
||||
},
|
||||
...toStaticData(props, dereferenced),
|
||||
_openapi: {
|
||||
method:
|
||||
entry.type === 'operation' || entry.type === 'webhook'
|
||||
? entry.item.method
|
||||
: undefined,
|
||||
webhook: entry.type === 'webhook',
|
||||
},
|
||||
},
|
||||
_openapi: {
|
||||
method:
|
||||
entry.type === 'operation' || entry.type === 'webhook'
|
||||
? entry.item.method
|
||||
: undefined,
|
||||
webhook: entry.type === 'webhook',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -102,7 +102,7 @@ export interface CreateAPIPageOptions {
|
||||
* @param generators - codegens for API example usages
|
||||
*/
|
||||
renderAPIExampleUsageTabs?: (
|
||||
generators: CodeUsageGenerator<unknown>[],
|
||||
generators: CodeUsageGenerator[],
|
||||
ctx: RenderContext,
|
||||
) => Awaitable<ReactNode>;
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { NoReference } from '@/utils/schema';
|
||||
import type { Document, OperationObject } from '@/types';
|
||||
import Slugger from 'github-slugger';
|
||||
import { idToTitle } from '@/utils/id-to-title';
|
||||
import type { TOCItemType } from 'fumadocs-core/toc';
|
||||
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
|
||||
import type { ApiPageProps } from '@/ui';
|
||||
|
||||
export function toStaticData(
|
||||
page: ApiPageProps,
|
||||
dereferenced: NoReference<Document>,
|
||||
): {
|
||||
toc: TOCItemType[];
|
||||
structuredData: StructuredData;
|
||||
} {
|
||||
const slugger = new Slugger();
|
||||
const toc: TOCItemType[] = [];
|
||||
const structuredData: StructuredData = { headings: [], contents: [] };
|
||||
|
||||
function pathItem(item: NoReference<OperationObject>, defaultTitle: string) {
|
||||
if (page.showTitle && item.operationId) {
|
||||
const title =
|
||||
item.summary ||
|
||||
(item.operationId ? idToTitle(item.operationId) : defaultTitle);
|
||||
const id = slugger.slug(title);
|
||||
|
||||
toc.push({
|
||||
depth: 2,
|
||||
title,
|
||||
url: `#${id}`,
|
||||
});
|
||||
structuredData.headings.push({
|
||||
content: title,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
if (item.description)
|
||||
structuredData.contents.push({
|
||||
content: item.description,
|
||||
heading: structuredData.headings.at(-1)?.id,
|
||||
});
|
||||
}
|
||||
|
||||
for (const item of page.operations ?? []) {
|
||||
const operation = dereferenced.paths?.[item.path]?.[item.method];
|
||||
if (!operation) continue;
|
||||
|
||||
pathItem(operation, item.path);
|
||||
}
|
||||
|
||||
for (const item of page.webhooks ?? []) {
|
||||
const webhook = dereferenced.webhooks?.[item.name]?.[item.method];
|
||||
if (!webhook) continue;
|
||||
|
||||
pathItem(webhook, item.name);
|
||||
}
|
||||
|
||||
return { toc, structuredData };
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import { idToTitle } from '@/utils/id-to-title';
|
||||
import type { ApiPageProps, OperationItem, WebhookItem } from '@/ui/api-page';
|
||||
import type { ProcessedDocument } from '@/utils/process-document';
|
||||
import type { TableOfContents } from 'fumadocs-core/toc';
|
||||
import type { StructuredData } from 'fumadocs-core/mdx-plugins';
|
||||
import type { Document, TagObject } from '@/types';
|
||||
import type { TagObject } from '@/types';
|
||||
import { dump } from 'js-yaml';
|
||||
import type { NoReference } from '@/utils/schema';
|
||||
import Slugger from 'github-slugger';
|
||||
import { removeUndefined } from '@/utils/remove-undefined';
|
||||
import type { OutputEntry } from '@/utils/pages/builder';
|
||||
import type { InternalOpenAPIMeta } from '@/server/source-api';
|
||||
import { toStaticData } from '@/utils/pages/to-static-data';
|
||||
|
||||
export interface PagesToTextOptions {
|
||||
/**
|
||||
@@ -162,11 +158,6 @@ export function generateDocument(
|
||||
return out.join('\n\n');
|
||||
}
|
||||
|
||||
interface StaticData {
|
||||
toc: TableOfContents;
|
||||
structuredData: StructuredData;
|
||||
}
|
||||
|
||||
export type DocumentContext =
|
||||
| {
|
||||
type: 'tag';
|
||||
@@ -212,7 +203,7 @@ function generatePage(
|
||||
};
|
||||
}
|
||||
|
||||
const data = generateStaticData(processed.dereferenced, page);
|
||||
const data = toStaticData(page, processed.dereferenced);
|
||||
const content: string[] = [];
|
||||
|
||||
if (options.description && includeDescription)
|
||||
@@ -236,45 +227,6 @@ function generatePage(
|
||||
);
|
||||
}
|
||||
|
||||
function generateStaticData(
|
||||
dereferenced: NoReference<Document>,
|
||||
props: ApiPageProps,
|
||||
): StaticData {
|
||||
const slugger = new Slugger();
|
||||
const toc: TableOfContents = [];
|
||||
const structuredData: StructuredData = { headings: [], contents: [] };
|
||||
|
||||
for (const item of props.operations ?? []) {
|
||||
const operation = dereferenced.paths?.[item.path]?.[item.method];
|
||||
if (!operation) continue;
|
||||
|
||||
if (props.showTitle && operation.operationId) {
|
||||
const title =
|
||||
operation.summary ||
|
||||
(operation.operationId ? idToTitle(operation.operationId) : item.path);
|
||||
const id = slugger.slug(title);
|
||||
|
||||
toc.push({
|
||||
depth: 2,
|
||||
title,
|
||||
url: `#${id}`,
|
||||
});
|
||||
structuredData.headings.push({
|
||||
content: title,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
if (operation.description)
|
||||
structuredData.contents.push({
|
||||
content: operation.description,
|
||||
heading: structuredData.headings.at(-1)?.id,
|
||||
});
|
||||
}
|
||||
|
||||
return { toc, structuredData };
|
||||
}
|
||||
|
||||
function pageContent({
|
||||
showTitle,
|
||||
showDescription,
|
||||
|
||||
@@ -40,8 +40,8 @@ export function schemaToString(
|
||||
if ((flags & FormatFlags.UseAlias) === FormatFlags.UseAlias) {
|
||||
if (schema.title) return schema.title;
|
||||
|
||||
const ref = ctx?.getRawRef(schema);
|
||||
if (ref) return ref.split('/').at(-1)!;
|
||||
const ref = ctx?.getRawRef(schema)?.split('/');
|
||||
if (ref && ref.length > 0) return ref[ref.length - 1];
|
||||
}
|
||||
|
||||
if (Array.isArray(schema.type)) {
|
||||
|
||||
Reference in New Issue
Block a user