mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-21 18:13:29 +00:00
Improve OpenAPI schemas block ungrouped style (#3092)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
"@gitbook/react-openapi": patch
|
||||||
|
"gitbook": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Improve OpenAPI schemas block ungrouped style. Classnames have changed, please refer to this PR to update GBX.
|
||||||
+1
-1
@@ -11,7 +11,7 @@ import { useScrollPage } from '@/components/hooks';
|
|||||||
export function PageClientLayout(props: { withSections?: boolean }) {
|
export function PageClientLayout(props: { withSections?: boolean }) {
|
||||||
// We use this hook in the page layout to ensure the elements for the blocks
|
// We use this hook in the page layout to ensure the elements for the blocks
|
||||||
// are rendered before we scroll to a hash or to the top of the page
|
// are rendered before we scroll to a hash or to the top of the page
|
||||||
useScrollPage({ scrollMarginTop: props.withSections ? 50 : undefined });
|
useScrollPage({ scrollMarginTop: props.withSections ? 48 : undefined });
|
||||||
|
|
||||||
useStripFallbackQueryParam();
|
useStripFallbackQueryParam();
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -20,7 +20,15 @@ export function Heading(props: BlockProps<DocumentBlockHeading>) {
|
|||||||
return (
|
return (
|
||||||
<Tag
|
<Tag
|
||||||
id={id}
|
id={id}
|
||||||
className={tcls(textStyle.textSize, 'heading', 'group', 'relative', 'grid', style)}
|
className={tcls(
|
||||||
|
textStyle.textSize,
|
||||||
|
'heading',
|
||||||
|
'group',
|
||||||
|
'relative',
|
||||||
|
'grid',
|
||||||
|
'scroll-m-12',
|
||||||
|
style
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={tcls(
|
className={tcls(
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
import type { JSONDocument } from '@gitbook/api';
|
|
||||||
import { Icon } from '@gitbook/icons';
|
|
||||||
import { OpenAPIOperation as BaseOpenAPIOperation } from '@gitbook/react-openapi';
|
import { OpenAPIOperation as BaseOpenAPIOperation } from '@gitbook/react-openapi';
|
||||||
|
|
||||||
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
|
import { resolveOpenAPIOperationBlock } from '@/lib/openapi/resolveOpenAPIOperationBlock';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
import { PlainCodeBlock } from '../CodeBlock';
|
|
||||||
import { DocumentView } from '../DocumentView';
|
|
||||||
import { Heading } from '../Heading';
|
|
||||||
|
|
||||||
import './scalar.css';
|
import './scalar.css';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
|
import type { AnyOpenAPIOperationsBlock } from '@/lib/openapi/types';
|
||||||
|
import { getOpenAPIContext } from './context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render an openapi block or an openapi-operation block.
|
* Render an openapi block or an openapi-operation block.
|
||||||
@@ -55,56 +51,7 @@ async function OpenAPIOperationBody(props: BlockProps<AnyOpenAPIOperationsBlock>
|
|||||||
return (
|
return (
|
||||||
<BaseOpenAPIOperation
|
<BaseOpenAPIOperation
|
||||||
data={data}
|
data={data}
|
||||||
context={{
|
context={getOpenAPIContext({ props, specUrl })}
|
||||||
specUrl,
|
|
||||||
icons: {
|
|
||||||
chevronDown: <Icon icon="chevron-down" />,
|
|
||||||
chevronRight: <Icon icon="chevron-right" />,
|
|
||||||
plus: <Icon icon="plus" />,
|
|
||||||
},
|
|
||||||
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
|
|
||||||
renderDocument: (documentProps) => (
|
|
||||||
<DocumentView
|
|
||||||
document={documentProps.document as JSONDocument}
|
|
||||||
context={props.context}
|
|
||||||
style="space-y-6"
|
|
||||||
blockStyle="max-w-full"
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
renderHeading: (headingProps) => (
|
|
||||||
<Heading
|
|
||||||
document={props.document}
|
|
||||||
ancestorBlocks={props.ancestorBlocks}
|
|
||||||
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
|
||||||
context={props.context}
|
|
||||||
style={tcls([
|
|
||||||
headingProps.deprecated ? 'line-through' : undefined,
|
|
||||||
headingProps.deprecated || !!headingProps.stability
|
|
||||||
? '[&>div]:mt-0'
|
|
||||||
: undefined,
|
|
||||||
])}
|
|
||||||
block={{
|
|
||||||
object: 'block',
|
|
||||||
key: `${block.key}-heading`,
|
|
||||||
meta: block.meta,
|
|
||||||
data: {},
|
|
||||||
type: 'heading-2',
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
key: `${block.key}-heading-text`,
|
|
||||||
object: 'text',
|
|
||||||
leaves: [
|
|
||||||
{ text: headingProps.title, object: 'leaf', marks: [] },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
defaultInteractiveOpened: context.mode === 'print',
|
|
||||||
id: block.meta?.id,
|
|
||||||
blockKey: block.key,
|
|
||||||
}}
|
|
||||||
className="openapi-block"
|
className="openapi-block"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
|
import { resolveOpenAPISchemasBlock } from '@/lib/openapi/resolveOpenAPISchemasBlock';
|
||||||
import { tcls } from '@/lib/tailwind';
|
import { tcls } from '@/lib/tailwind';
|
||||||
import { Icon } from '@gitbook/icons';
|
|
||||||
import { OpenAPISchemas as BaseOpenAPISchemas } from '@gitbook/react-openapi';
|
import { OpenAPISchemas as BaseOpenAPISchemas } from '@gitbook/react-openapi';
|
||||||
|
|
||||||
import type { BlockProps } from '../Block';
|
import type { BlockProps } from '../Block';
|
||||||
@@ -8,6 +7,7 @@ import type { BlockProps } from '../Block';
|
|||||||
import './scalar.css';
|
import './scalar.css';
|
||||||
import './style.css';
|
import './style.css';
|
||||||
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
|
import type { OpenAPISchemasBlock } from '@/lib/openapi/types';
|
||||||
|
import { getOpenAPIContext } from './context';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render an openapi-schemas block.
|
* Render an openapi-schemas block.
|
||||||
@@ -49,19 +49,9 @@ async function OpenAPISchemasBody(props: BlockProps<OpenAPISchemasBlock>) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<BaseOpenAPISchemas
|
<BaseOpenAPISchemas
|
||||||
data={data}
|
schemas={data.schemas}
|
||||||
grouped={block.data.grouped}
|
grouped={block.data.grouped}
|
||||||
context={{
|
context={getOpenAPIContext({ props, specUrl })}
|
||||||
specUrl,
|
|
||||||
icons: {
|
|
||||||
chevronDown: <Icon icon="chevron-down" />,
|
|
||||||
chevronRight: <Icon icon="chevron-right" />,
|
|
||||||
plus: <Icon icon="plus" />,
|
|
||||||
},
|
|
||||||
defaultInteractiveOpened: context.mode === 'print',
|
|
||||||
id: block.meta?.id,
|
|
||||||
blockKey: block.key,
|
|
||||||
}}
|
|
||||||
className="openapi-block"
|
className="openapi-block"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import type { JSONDocument } from '@gitbook/api';
|
||||||
|
import { Icon } from '@gitbook/icons';
|
||||||
|
import type { OpenAPIContext } from '@gitbook/react-openapi';
|
||||||
|
|
||||||
|
import { tcls } from '@/lib/tailwind';
|
||||||
|
|
||||||
|
import type { BlockProps } from '../Block';
|
||||||
|
import { PlainCodeBlock } from '../CodeBlock';
|
||||||
|
import { DocumentView } from '../DocumentView';
|
||||||
|
import { Heading } from '../Heading';
|
||||||
|
|
||||||
|
import './scalar.css';
|
||||||
|
import './style.css';
|
||||||
|
import type { AnyOpenAPIOperationsBlock, OpenAPISchemasBlock } from '@/lib/openapi/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the OpenAPI context to render a block.
|
||||||
|
*/
|
||||||
|
export function getOpenAPIContext(args: {
|
||||||
|
props: BlockProps<AnyOpenAPIOperationsBlock | OpenAPISchemasBlock>;
|
||||||
|
specUrl: string;
|
||||||
|
}): OpenAPIContext {
|
||||||
|
const { props, specUrl } = args;
|
||||||
|
const { block } = props;
|
||||||
|
return {
|
||||||
|
specUrl,
|
||||||
|
icons: {
|
||||||
|
chevronDown: <Icon icon="chevron-down" />,
|
||||||
|
chevronRight: <Icon icon="chevron-right" />,
|
||||||
|
plus: <Icon icon="plus" />,
|
||||||
|
},
|
||||||
|
renderCodeBlock: (codeProps) => <PlainCodeBlock {...codeProps} />,
|
||||||
|
renderDocument: (documentProps) => (
|
||||||
|
<DocumentView
|
||||||
|
document={documentProps.document as JSONDocument}
|
||||||
|
context={props.context}
|
||||||
|
style="space-y-6"
|
||||||
|
blockStyle="max-w-full"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
renderHeading: (headingProps) => (
|
||||||
|
<Heading
|
||||||
|
document={props.document}
|
||||||
|
ancestorBlocks={props.ancestorBlocks}
|
||||||
|
isEstimatedOffscreen={props.isEstimatedOffscreen}
|
||||||
|
context={props.context}
|
||||||
|
style={tcls([
|
||||||
|
headingProps.deprecated ? 'line-through' : undefined,
|
||||||
|
headingProps.deprecated || !!headingProps.stability
|
||||||
|
? '[&>div]:mt-0'
|
||||||
|
: undefined,
|
||||||
|
])}
|
||||||
|
block={{
|
||||||
|
object: 'block',
|
||||||
|
key: `${block.key}-heading`,
|
||||||
|
meta: block.meta,
|
||||||
|
data: {},
|
||||||
|
type: 'heading-2',
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
key: `${block.key}-heading-text`,
|
||||||
|
object: 'text',
|
||||||
|
leaves: [{ text: headingProps.title, object: 'leaf', marks: [] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
defaultInteractiveOpened: props.context.mode === 'print',
|
||||||
|
id: block.meta?.id,
|
||||||
|
blockKey: block.key,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/* Layout Components */
|
/* Layout Components */
|
||||||
.openapi-operation {
|
.openapi-operation,
|
||||||
|
.openapi-schemas {
|
||||||
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
|
@apply flex-1 flex flex-col gap-8 mb-14 min-w-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.openapi-summary {
|
.openapi-summary {
|
||||||
@apply flex flex-col items-start justify-start gap-3;
|
@apply flex flex-col items-start justify-start gap-3 scroll-m-12;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openapi-summary-tags {
|
.openapi-summary-tags {
|
||||||
@@ -483,12 +484,30 @@
|
|||||||
@apply flex flex-row items-center py-2 px-3 justify-end border-t border-tint-subtle;
|
@apply flex flex-row items-center py-2 px-3 justify-end border-t border-tint-subtle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Response Example */
|
/* Panel */
|
||||||
.openapi-response-example {
|
.openapi-panel {
|
||||||
@apply border rounded bg-tint border-tint-subtle;
|
@apply border rounded bg-tint border-tint-subtle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.openapi-response-example-empty {
|
.openapi-panel-heading {
|
||||||
|
@apply font-medium px-4 py-2 text-xs uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openapi-panel-body {
|
||||||
|
@apply theme-gradient:bg-tint-12/1 relative;
|
||||||
|
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openapi-panel-footer {
|
||||||
|
@apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
|
||||||
|
}
|
||||||
|
|
||||||
|
.openapi-panel-footer .openapi-markdown {
|
||||||
|
@apply text-[0.813rem] text-tint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Example */
|
||||||
|
.openapi-example-empty {
|
||||||
@apply relative text-tint bg-tint min-h-20 flex flex-col justify-center items-center;
|
@apply relative text-tint bg-tint min-h-20 flex flex-col justify-center items-center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,15 +578,6 @@
|
|||||||
|
|
||||||
.openapi-tabs-panel {
|
.openapi-tabs-panel {
|
||||||
@apply flex-1 text-sm relative focus-visible:outline-none;
|
@apply flex-1 text-sm relative focus-visible:outline-none;
|
||||||
@apply before:w-full before:h-px before:absolute before:bg-tint-6 before:-top-px before:z-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openapi-tabs-footer {
|
|
||||||
@apply px-3 py-2 pt-2.5 border-t border-tint-subtle text-[0.813rem] text-tint;
|
|
||||||
}
|
|
||||||
|
|
||||||
.openapi-tabs-footer .openapi-markdown {
|
|
||||||
@apply text-[0.813rem] text-tint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Disclosure group */
|
/* Disclosure group */
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useScrollPage } from '@/components/hooks';
|
|||||||
export function PageClientLayout(props: { withSections?: boolean }) {
|
export function PageClientLayout(props: { withSections?: boolean }) {
|
||||||
// We use this hook in the page layout to ensure the elements for the blocks
|
// We use this hook in the page layout to ensure the elements for the blocks
|
||||||
// are rendered before we scroll to a hash or to the top of the page
|
// are rendered before we scroll to a hash or to the top of the page
|
||||||
useScrollPage({ scrollMarginTop: props.withSections ? 50 : undefined });
|
useScrollPage({ scrollMarginTop: props.withSections ? 48 : undefined });
|
||||||
|
|
||||||
useStripFallbackQueryParam();
|
useStripFallbackQueryParam();
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import type { GitBookAnyContext } from '@v2/lib/context';
|
|||||||
|
|
||||||
import { getNodeText } from './document';
|
import { getNodeText } from './document';
|
||||||
import { resolveOpenAPIOperationBlock } from './openapi/resolveOpenAPIOperationBlock';
|
import { resolveOpenAPIOperationBlock } from './openapi/resolveOpenAPIOperationBlock';
|
||||||
|
import { resolveOpenAPISchemasBlock } from './openapi/resolveOpenAPISchemasBlock';
|
||||||
|
|
||||||
export interface DocumentSection {
|
export interface DocumentSection {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -52,6 +53,26 @@ export async function getDocumentSections(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
block.type === 'openapi-schemas' &&
|
||||||
|
!block.data.grouped &&
|
||||||
|
block.meta?.id &&
|
||||||
|
block.data.schemas.length === 1
|
||||||
|
) {
|
||||||
|
const { data } = await resolveOpenAPISchemasBlock({
|
||||||
|
block,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
const schema = data?.schemas[0];
|
||||||
|
if (schema) {
|
||||||
|
sections.push({
|
||||||
|
id: block.meta.id,
|
||||||
|
title: `The ${schema.name} object`,
|
||||||
|
depth: 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sections;
|
return sections;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { OpenAPIParseError } from '@gitbook/openapi-parser';
|
import { OpenAPIParseError, type OpenAPISchema } from '@gitbook/openapi-parser';
|
||||||
import { type OpenAPISchemasData, resolveOpenAPISchemas } from '@gitbook/react-openapi';
|
import { resolveOpenAPISchemas } from '@gitbook/react-openapi';
|
||||||
import { fetchOpenAPIFilesystem } from './fetch';
|
import { fetchOpenAPIFilesystem } from './fetch';
|
||||||
import type {
|
import type {
|
||||||
OpenAPISchemasBlock,
|
OpenAPISchemasBlock,
|
||||||
@@ -7,7 +7,9 @@ import type {
|
|||||||
ResolveOpenAPIBlockResult,
|
ResolveOpenAPIBlockResult,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|
||||||
type ResolveOpenAPISchemasBlockResult = ResolveOpenAPIBlockResult<OpenAPISchemasData>;
|
type ResolveOpenAPISchemasBlockResult = ResolveOpenAPIBlockResult<{
|
||||||
|
schemas: OpenAPISchema[];
|
||||||
|
}>;
|
||||||
|
|
||||||
const weakmap = new WeakMap<OpenAPISchemasBlock, Promise<ResolveOpenAPISchemasBlockResult>>();
|
const weakmap = new WeakMap<OpenAPISchemasBlock, Promise<ResolveOpenAPISchemasBlockResult>>();
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { StaticSection } from './StaticSection';
|
|||||||
import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
|
import { type CodeSampleGenerator, codeSampleGenerators } from './code-samples';
|
||||||
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
|
import { generateMediaTypeExamples, generateSchemaExample } from './generateSchemaExample';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||||
import { getDefaultServerURL } from './util/server';
|
import { getDefaultServerURL } from './util/server';
|
||||||
import { checkIsReference, createStateKey } from './utils';
|
import { checkIsReference, createStateKey } from './utils';
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ const CUSTOM_CODE_SAMPLES_KEYS = ['x-custom-examples', 'x-code-samples', 'x-code
|
|||||||
*/
|
*/
|
||||||
export function OpenAPICodeSample(props: {
|
export function OpenAPICodeSample(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export function OpenAPICodeSample(props: {
|
|||||||
*/
|
*/
|
||||||
function generateCodeSamples(props: {
|
function generateCodeSamples(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data, context } = props;
|
const { data, context } = props;
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ export interface MediaTypeRenderer {
|
|||||||
function OpenAPICodeSampleFooter(props: {
|
function OpenAPICodeSampleFooter(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
renderers: MediaTypeRenderer[];
|
renderers: MediaTypeRenderer[];
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data, context, renderers } = props;
|
const { data, context, renderers } = props;
|
||||||
const { method, path } = data;
|
const { method, path } = data;
|
||||||
@@ -227,7 +227,7 @@ function OpenAPICodeSampleFooter(props: {
|
|||||||
*/
|
*/
|
||||||
function getCustomCodeSamples(props: {
|
function getCustomCodeSamples(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data, context } = props;
|
const { data, context } = props;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
|
import { generateSchemaExample } from './generateSchemaExample';
|
||||||
|
import { json2xml } from './json2xml';
|
||||||
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
|
import type { OpenAPIContext } from './types';
|
||||||
|
import { checkIsReference } from './utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display an example.
|
||||||
|
*/
|
||||||
|
export function OpenAPIExample(props: {
|
||||||
|
example: OpenAPIV3.ExampleObject;
|
||||||
|
context: OpenAPIContext;
|
||||||
|
syntax: string;
|
||||||
|
}) {
|
||||||
|
const { example, context, syntax } = props;
|
||||||
|
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
||||||
|
|
||||||
|
if (code === null) {
|
||||||
|
return <OpenAPIEmptyExample />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context.renderCodeBlock({ code, syntax });
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean }): string | null {
|
||||||
|
const { example, xml } = args;
|
||||||
|
|
||||||
|
if (!example.value) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof example.value === 'string') {
|
||||||
|
return example.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xml) {
|
||||||
|
return json2xml(example.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringifyOpenAPI(example.value, null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty response example.
|
||||||
|
*/
|
||||||
|
export function OpenAPIEmptyExample() {
|
||||||
|
return (
|
||||||
|
<pre className="openapi-example-empty">
|
||||||
|
<p>No Content</p>
|
||||||
|
</pre>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an example from a reference object.
|
||||||
|
*/
|
||||||
|
export function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
||||||
|
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get examples from a media type object.
|
||||||
|
*/
|
||||||
|
export function getExamplesFromMediaTypeObject(args: {
|
||||||
|
mediaType: string;
|
||||||
|
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||||
|
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
||||||
|
const { mediaTypeObject, mediaType } = args;
|
||||||
|
if (mediaTypeObject.examples) {
|
||||||
|
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
example: checkIsReference(example) ? getExampleFromReference(example) : example,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaTypeObject.example) {
|
||||||
|
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mediaTypeObject.schema) {
|
||||||
|
if (mediaType === 'application/xml') {
|
||||||
|
// @TODO normally we should use the name of the schema but we don't have it
|
||||||
|
// fix it when we got the reference name
|
||||||
|
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'default',
|
||||||
|
example: {
|
||||||
|
value: {
|
||||||
|
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
||||||
|
xml: mediaType === 'application/xml',
|
||||||
|
mode: 'read',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: 'default',
|
||||||
|
example: {
|
||||||
|
value: generateSchemaExample(mediaTypeObject.schema, {
|
||||||
|
mode: 'read',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get example from a schema object.
|
||||||
|
*/
|
||||||
|
export function getExampleFromSchema(args: {
|
||||||
|
schema: OpenAPIV3.SchemaObject;
|
||||||
|
}): OpenAPIV3.ExampleObject {
|
||||||
|
const { schema } = args;
|
||||||
|
|
||||||
|
if (schema.example) {
|
||||||
|
return { value: schema.example };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { value: generateSchemaExample(schema, { mode: 'read' }) };
|
||||||
|
}
|
||||||
@@ -10,7 +10,8 @@ import { OpenAPICodeSample } from './OpenAPICodeSample';
|
|||||||
import { OpenAPIPath } from './OpenAPIPath';
|
import { OpenAPIPath } from './OpenAPIPath';
|
||||||
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
|
||||||
import { OpenAPISpec } from './OpenAPISpec';
|
import { OpenAPISpec } from './OpenAPISpec';
|
||||||
import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import { getOpenAPIClientContext } from './context';
|
||||||
|
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||||
import { resolveDescription } from './utils';
|
import { resolveDescription } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,16 +20,12 @@ import { resolveDescription } from './utils';
|
|||||||
export function OpenAPIOperation(props: {
|
export function OpenAPIOperation(props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { className, data, context } = props;
|
const { className, data, context } = props;
|
||||||
const { operation } = data;
|
const { operation } = data;
|
||||||
|
|
||||||
const clientContext: OpenAPIClientContext = {
|
const clientContext = getOpenAPIClientContext(context);
|
||||||
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
|
||||||
icons: context.icons,
|
|
||||||
blockKey: context.blockKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx('openapi-operation', className)}>
|
<div className={clsx('openapi-operation', className)}>
|
||||||
@@ -79,7 +76,7 @@ export function OpenAPIOperation(props: {
|
|||||||
|
|
||||||
function OpenAPIOperationDescription(props: {
|
function OpenAPIOperationDescription(props: {
|
||||||
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
operation: OpenAPIV3.OperationObject<OpenAPICustomOperationProperties>;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { operation } = props;
|
const { operation } = props;
|
||||||
if (operation['x-gitbook-description-document']) {
|
if (operation['x-gitbook-description-document']) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
import { OpenAPICopyButton } from './OpenAPICopyButton';
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||||
import { getDefaultServerURL } from './util/server';
|
import { getDefaultServerURL } from './util/server';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,7 +7,7 @@ import { getDefaultServerURL } from './util/server';
|
|||||||
*/
|
*/
|
||||||
export function OpenAPIPath(props: {
|
export function OpenAPIPath(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data } = props;
|
const { data } = props;
|
||||||
const { method, path, operation } = data;
|
const { method, path, operation } = data;
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { Markdown } from './Markdown';
|
import { Markdown } from './Markdown';
|
||||||
|
import {
|
||||||
|
OpenAPIEmptyExample,
|
||||||
|
OpenAPIExample,
|
||||||
|
getExampleFromReference,
|
||||||
|
getExamplesFromMediaTypeObject,
|
||||||
|
} from './OpenAPIExample';
|
||||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||||
import { StaticSection } from './StaticSection';
|
import { StaticSection } from './StaticSection';
|
||||||
import { generateSchemaExample } from './generateSchemaExample';
|
import type { OpenAPIContext, OpenAPIOperationData } from './types';
|
||||||
import { json2xml } from './json2xml';
|
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
|
||||||
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -13,7 +16,7 @@ import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
|||||||
*/
|
*/
|
||||||
export function OpenAPIResponseExample(props: {
|
export function OpenAPIResponseExample(props: {
|
||||||
data: OpenAPIOperationData;
|
data: OpenAPIOperationData;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { data, context } = props;
|
const { data, context } = props;
|
||||||
|
|
||||||
@@ -62,7 +65,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
return {
|
return {
|
||||||
key: key,
|
key: key,
|
||||||
label: key,
|
label: key,
|
||||||
body: <OpenAPIEmptyResponseExample />,
|
body: <OpenAPIEmptyExample />,
|
||||||
footer: description ? <Markdown source={description} /> : undefined,
|
footer: description ? <Markdown source={description} /> : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -81,7 +84,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
|
<OpenAPITabs stateKey={createStateKey('response-example')} items={tabs}>
|
||||||
<StaticSection header={<OpenAPITabsList />} className="openapi-response-example">
|
<StaticSection header={<OpenAPITabsList />} className="openapi-panel">
|
||||||
<OpenAPITabsPanels />
|
<OpenAPITabsPanels />
|
||||||
</StaticSection>
|
</StaticSection>
|
||||||
</OpenAPITabs>
|
</OpenAPITabs>
|
||||||
@@ -89,7 +92,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function OpenAPIResponse(props: {
|
function OpenAPIResponse(props: {
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
content: {
|
content: {
|
||||||
[media: string]: OpenAPIV3.MediaTypeObject;
|
[media: string]: OpenAPIV3.MediaTypeObject;
|
||||||
};
|
};
|
||||||
@@ -141,7 +144,7 @@ function OpenAPIResponse(props: {
|
|||||||
function OpenAPIResponseMediaType(props: {
|
function OpenAPIResponseMediaType(props: {
|
||||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
||||||
mediaType: string;
|
mediaType: string;
|
||||||
context: OpenAPIContextProps;
|
context: OpenAPIContext;
|
||||||
}) {
|
}) {
|
||||||
const { mediaTypeObject, mediaType } = props;
|
const { mediaTypeObject, mediaType } = props;
|
||||||
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
const examples = getExamplesFromMediaTypeObject({ mediaTypeObject, mediaType });
|
||||||
@@ -149,7 +152,7 @@ function OpenAPIResponseMediaType(props: {
|
|||||||
const firstExample = examples[0];
|
const firstExample = examples[0];
|
||||||
|
|
||||||
if (!firstExample) {
|
if (!firstExample) {
|
||||||
return <OpenAPIEmptyResponseExample />;
|
return <OpenAPIEmptyExample />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (examples.length === 1) {
|
if (examples.length === 1) {
|
||||||
@@ -184,42 +187,6 @@ function OpenAPIResponseMediaType(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Display an example.
|
|
||||||
*/
|
|
||||||
function OpenAPIExample(props: {
|
|
||||||
example: OpenAPIV3.ExampleObject;
|
|
||||||
context: OpenAPIContextProps;
|
|
||||||
syntax: string;
|
|
||||||
}) {
|
|
||||||
const { example, context, syntax } = props;
|
|
||||||
const code = stringifyExample({ example, xml: syntax === 'xml' });
|
|
||||||
|
|
||||||
if (code === null) {
|
|
||||||
return <OpenAPIEmptyResponseExample />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return context.renderCodeBlock({ code, syntax });
|
|
||||||
}
|
|
||||||
|
|
||||||
function stringifyExample(args: { example: OpenAPIV3.ExampleObject; xml: boolean }): string | null {
|
|
||||||
const { example, xml } = args;
|
|
||||||
|
|
||||||
if (!example.value) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof example.value === 'string') {
|
|
||||||
return example.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xml) {
|
|
||||||
return json2xml(example.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringifyOpenAPI(example.value, null, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the syntax from a media type.
|
* Get the syntax from a media type.
|
||||||
*/
|
*/
|
||||||
@@ -234,75 +201,3 @@ function getSyntaxFromMediaType(mediaType: string): string {
|
|||||||
|
|
||||||
return 'text';
|
return 'text';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get examples from a media type object.
|
|
||||||
*/
|
|
||||||
function getExamplesFromMediaTypeObject(args: {
|
|
||||||
mediaType: string;
|
|
||||||
mediaTypeObject: OpenAPIV3.MediaTypeObject;
|
|
||||||
}): { key: string; example: OpenAPIV3.ExampleObject }[] {
|
|
||||||
const { mediaTypeObject, mediaType } = args;
|
|
||||||
if (mediaTypeObject.examples) {
|
|
||||||
return Object.entries(mediaTypeObject.examples).map(([key, example]) => {
|
|
||||||
return {
|
|
||||||
key,
|
|
||||||
example: checkIsReference(example) ? getExampleFromReference(example) : example,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaTypeObject.example) {
|
|
||||||
return [{ key: 'default', example: { value: mediaTypeObject.example } }];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mediaTypeObject.schema) {
|
|
||||||
if (mediaType === 'application/xml') {
|
|
||||||
// @TODO normally we should use the name of the schema but we don't have it
|
|
||||||
// fix it when we got the reference name
|
|
||||||
const root = mediaTypeObject.schema.xml?.name ?? 'object';
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'default',
|
|
||||||
example: {
|
|
||||||
value: {
|
|
||||||
[root]: generateSchemaExample(mediaTypeObject.schema, {
|
|
||||||
xml: mediaType === 'application/xml',
|
|
||||||
mode: 'read',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key: 'default',
|
|
||||||
example: {
|
|
||||||
value: generateSchemaExample(mediaTypeObject.schema, {
|
|
||||||
mode: 'read',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Empty response example.
|
|
||||||
*/
|
|
||||||
function OpenAPIEmptyResponseExample() {
|
|
||||||
return (
|
|
||||||
<pre className="openapi-response-example-empty">
|
|
||||||
<p>No body</p>
|
|
||||||
</pre>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate an example from a reference object.
|
|
||||||
*/
|
|
||||||
function getExampleFromReference(ref: OpenAPIV3.ReferenceObject): OpenAPIV3.ExampleObject {
|
|
||||||
return { summary: 'Unresolved reference', value: { $ref: ref.$ref } };
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -138,9 +138,9 @@ export function OpenAPITabsPanels() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TabPanel id={key} className="openapi-tabs-panel">
|
<TabPanel id={key} className="openapi-tabs-panel">
|
||||||
<div className="openapi-tabs-body">{selectedTab.body}</div>
|
<div className="openapi-panel-body">{selectedTab.body}</div>
|
||||||
{selectedTab.footer ? (
|
{selectedTab.footer ? (
|
||||||
<div className="openapi-tabs-footer">{selectedTab.footer}</div>
|
<div className="openapi-panel-footer">{selectedTab.footer}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
export interface OpenAPIClientContext {
|
||||||
|
/**
|
||||||
|
* Icons used in the block.
|
||||||
|
*/
|
||||||
|
icons: {
|
||||||
|
chevronDown: React.ReactNode;
|
||||||
|
chevronRight: React.ReactNode;
|
||||||
|
plus: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force all sections to be opened by default.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
defaultInteractiveOpened?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key of the block
|
||||||
|
*/
|
||||||
|
blockKey?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional id attached to the heading and used as an anchor.
|
||||||
|
*/
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OpenAPIContext extends OpenAPIClientContext {
|
||||||
|
/**
|
||||||
|
* Render a code block.
|
||||||
|
*/
|
||||||
|
renderCodeBlock: (props: { code: string; syntax: string }) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the heading of the operation.
|
||||||
|
*/
|
||||||
|
renderHeading: (props: {
|
||||||
|
deprecated: boolean;
|
||||||
|
title: string;
|
||||||
|
stability?: string;
|
||||||
|
}) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the document of the operation.
|
||||||
|
*/
|
||||||
|
renderDocument: (props: { document: object }) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specification URL.
|
||||||
|
*/
|
||||||
|
specUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the client context from the OpenAPI context.
|
||||||
|
*/
|
||||||
|
export function getOpenAPIClientContext(context: OpenAPIContext): OpenAPIClientContext {
|
||||||
|
return {
|
||||||
|
icons: context.icons,
|
||||||
|
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
||||||
|
blockKey: context.blockKey,
|
||||||
|
id: context.id,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,4 +2,4 @@ export * from './schemas';
|
|||||||
export * from './OpenAPIOperation';
|
export * from './OpenAPIOperation';
|
||||||
export * from './OpenAPIOperationContext';
|
export * from './OpenAPIOperationContext';
|
||||||
export * from './resolveOpenAPIOperation';
|
export * from './resolveOpenAPIOperation';
|
||||||
export type { OpenAPISchemasData, OpenAPIOperationData } from './types';
|
export type { OpenAPIOperationData, OpenAPIContext } from './types';
|
||||||
|
|||||||
@@ -1,99 +1,104 @@
|
|||||||
|
import type { OpenAPISchema } from '@gitbook/openapi-parser';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { OpenAPIDisclosureGroup } from '../OpenAPIDisclosureGroup';
|
import { OpenAPIDisclosureGroup } from '../OpenAPIDisclosureGroup';
|
||||||
|
import { OpenAPIExample, getExampleFromSchema } from '../OpenAPIExample';
|
||||||
import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
|
import { OpenAPIRootSchema } from '../OpenAPISchemaServer';
|
||||||
import { Section, SectionBody } from '../StaticSection';
|
import { Section, SectionBody, StaticSection } from '../StaticSection';
|
||||||
import type { OpenAPIClientContext, OpenAPIContextProps, OpenAPISchemasData } from '../types';
|
import { getOpenAPIClientContext } from '../context';
|
||||||
|
import type { OpenAPIContext } from '../types';
|
||||||
type OpenAPISchemasContextProps = Omit<
|
|
||||||
OpenAPIContextProps,
|
|
||||||
'renderCodeBlock' | 'renderHeading' | 'renderDocument'
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display OpenAPI Schemas.
|
* OpenAPI Schemas component.
|
||||||
*/
|
*/
|
||||||
export function OpenAPISchemas(props: {
|
export function OpenAPISchemas(props: {
|
||||||
className?: string;
|
className?: string;
|
||||||
data: OpenAPISchemasData;
|
schemas: OpenAPISchema[];
|
||||||
context: OpenAPISchemasContextProps;
|
context: OpenAPIContext;
|
||||||
/**
|
/**
|
||||||
* Whether to show the schema directly if there is only one.
|
* Whether to show the schema directly if there is only one.
|
||||||
*/
|
*/
|
||||||
grouped?: boolean;
|
grouped?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { className, data, context, grouped } = props;
|
const { schemas, context, grouped, className } = props;
|
||||||
const { schemas } = data;
|
|
||||||
|
|
||||||
const clientContext: OpenAPIClientContext = {
|
const firstSchema = schemas[0];
|
||||||
defaultInteractiveOpened: context.defaultInteractiveOpened,
|
|
||||||
icons: context.icons,
|
|
||||||
blockKey: context.blockKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!schemas.length) {
|
if (!firstSchema) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const clientContext = getOpenAPIClientContext(context);
|
||||||
<div className={clsx('openapi-schemas', className)}>
|
|
||||||
<OpenAPIRootSchemasSchema grouped={grouped} schemas={schemas} context={clientContext} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Root schema for OpenAPI schemas.
|
|
||||||
* It displays a single model or a disclosure group for multiple schemas.
|
|
||||||
*/
|
|
||||||
function OpenAPIRootSchemasSchema(props: {
|
|
||||||
schemas: OpenAPISchemasData['schemas'];
|
|
||||||
context: OpenAPIClientContext;
|
|
||||||
grouped?: boolean;
|
|
||||||
}) {
|
|
||||||
const { schemas, context, grouped } = props;
|
|
||||||
|
|
||||||
// If there is only one model and we are not grouping, we show it directly.
|
// If there is only one model and we are not grouping, we show it directly.
|
||||||
if (schemas.length === 1 && !grouped) {
|
if (schemas.length === 1 && !grouped) {
|
||||||
const schema = schemas?.[0]?.schema;
|
const title = `The ${firstSchema.name} object`;
|
||||||
|
|
||||||
if (!schema) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<div className={clsx('openapi-schemas', className)}>
|
||||||
<SectionBody>
|
<div className="openapi-summary" id={context.id}>
|
||||||
<OpenAPIRootSchema schema={schema} context={context} />
|
{context.renderHeading({
|
||||||
</SectionBody>
|
title,
|
||||||
</Section>
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="openapi-columns">
|
||||||
|
<div className="openapi-column-spec">
|
||||||
|
<StaticSection className="openapi-parameters" header="Attributes">
|
||||||
|
<OpenAPIRootSchema
|
||||||
|
schema={firstSchema.schema}
|
||||||
|
context={clientContext}
|
||||||
|
/>
|
||||||
|
</StaticSection>
|
||||||
|
</div>
|
||||||
|
<div className="openapi-column-preview">
|
||||||
|
<div className="openapi-column-preview-body">
|
||||||
|
<div className="openapi-panel">
|
||||||
|
<h4 className="openapi-panel-heading">{title}</h4>
|
||||||
|
<div className="openapi-panel-body">
|
||||||
|
<OpenAPIExample
|
||||||
|
example={getExampleFromSchema({
|
||||||
|
schema: firstSchema.schema,
|
||||||
|
})}
|
||||||
|
context={context}
|
||||||
|
syntax="json"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are multiple schemas, we use a disclosure group to show them all.
|
// If there are multiple schemas, we use a disclosure group to show them all.
|
||||||
return (
|
return (
|
||||||
<OpenAPIDisclosureGroup
|
<div className={clsx('openapi-schemas', className)}>
|
||||||
allowsMultipleExpanded
|
<OpenAPIDisclosureGroup
|
||||||
icon={context.icons.chevronRight}
|
allowsMultipleExpanded
|
||||||
groups={schemas.map(({ name, schema }) => ({
|
icon={context.icons.chevronRight}
|
||||||
id: name,
|
groups={schemas.map(({ name, schema }) => ({
|
||||||
label: (
|
id: name,
|
||||||
<div className="openapi-response-tab-content" key={`model-${name}`}>
|
label: (
|
||||||
<span className="openapi-response-statuscode">{name}</span>
|
<div className="openapi-response-tab-content" key={`model-${name}`}>
|
||||||
</div>
|
<span className="openapi-response-statuscode">{name}</span>
|
||||||
),
|
</div>
|
||||||
tabs: [
|
),
|
||||||
{
|
tabs: [
|
||||||
id: 'model',
|
{
|
||||||
body: (
|
id: 'model',
|
||||||
<Section className="openapi-section-schemas">
|
body: (
|
||||||
<SectionBody>
|
<Section className="openapi-section-schemas">
|
||||||
<OpenAPIRootSchema schema={schema} context={context} />
|
<SectionBody>
|
||||||
</SectionBody>
|
<OpenAPIRootSchema
|
||||||
</Section>
|
schema={schema}
|
||||||
),
|
context={clientContext}
|
||||||
},
|
/>
|
||||||
],
|
</SectionBody>
|
||||||
}))}
|
</Section>
|
||||||
/>
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import type { Filesystem, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
|
import type { Filesystem, OpenAPISchema, OpenAPIV3xDocument } from '@gitbook/openapi-parser';
|
||||||
import { filterSelectedOpenAPISchemas } from '@gitbook/openapi-parser';
|
import { filterSelectedOpenAPISchemas } from '@gitbook/openapi-parser';
|
||||||
import { dereferenceFilesystem } from '../dereference';
|
import { dereferenceFilesystem } from '../dereference';
|
||||||
import type { OpenAPISchemasData } from '../types';
|
|
||||||
|
|
||||||
//!!TODO: We should return only the schemas that are used in the block. Still a WIP awaiting future work.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve an OpenAPI schemas from a file and compile it to a more usable format.
|
* Resolve an OpenAPI schemas from a file and compile it to a more usable format.
|
||||||
@@ -14,7 +11,9 @@ export async function resolveOpenAPISchemas(
|
|||||||
options: {
|
options: {
|
||||||
schemas: string[];
|
schemas: string[];
|
||||||
}
|
}
|
||||||
): Promise<OpenAPISchemasData | null> {
|
): Promise<{
|
||||||
|
schemas: OpenAPISchema[];
|
||||||
|
} | null> {
|
||||||
const { schemas: selectedSchemas } = options;
|
const { schemas: selectedSchemas } = options;
|
||||||
|
|
||||||
const schema = await dereferenceFilesystem(filesystem);
|
const schema = await dereferenceFilesystem(filesystem);
|
||||||
|
|||||||
@@ -1,33 +1,13 @@
|
|||||||
import type {
|
import type {
|
||||||
OpenAPICustomOperationProperties,
|
OpenAPICustomOperationProperties,
|
||||||
OpenAPICustomSpecProperties,
|
OpenAPICustomSpecProperties,
|
||||||
OpenAPISchema,
|
|
||||||
OpenAPIV3,
|
OpenAPIV3,
|
||||||
} from '@gitbook/openapi-parser';
|
} from '@gitbook/openapi-parser';
|
||||||
|
|
||||||
export interface OpenAPIContextProps extends OpenAPIClientContext {
|
|
||||||
/**
|
|
||||||
* Render a code block.
|
|
||||||
*/
|
|
||||||
renderCodeBlock: (props: { code: string; syntax: string }) => React.ReactNode;
|
|
||||||
/**
|
|
||||||
* Render the heading of the operation.
|
|
||||||
*/
|
|
||||||
renderHeading: (props: {
|
|
||||||
deprecated: boolean;
|
|
||||||
title: string;
|
|
||||||
stability?: string;
|
|
||||||
}) => React.ReactNode;
|
|
||||||
/**
|
|
||||||
* Render the document of the operation.
|
|
||||||
*/
|
|
||||||
renderDocument: (props: { document: object }) => React.ReactNode;
|
|
||||||
|
|
||||||
/** Spec url for the Scalar Api Client */
|
|
||||||
specUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OpenAPIClientContext {
|
export interface OpenAPIClientContext {
|
||||||
|
/**
|
||||||
|
* Icons used in the block.
|
||||||
|
*/
|
||||||
icons: {
|
icons: {
|
||||||
chevronDown: React.ReactNode;
|
chevronDown: React.ReactNode;
|
||||||
chevronRight: React.ReactNode;
|
chevronRight: React.ReactNode;
|
||||||
@@ -39,14 +19,44 @@ export interface OpenAPIClientContext {
|
|||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
defaultInteractiveOpened?: boolean;
|
defaultInteractiveOpened?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The key of the block
|
* The key of the block
|
||||||
*/
|
*/
|
||||||
blockKey?: string;
|
blockKey?: string;
|
||||||
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
|
|
||||||
|
/**
|
||||||
|
* Optional id attached to the heading and used as an anchor.
|
||||||
|
*/
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface OpenAPIContext extends OpenAPIClientContext {
|
||||||
|
/**
|
||||||
|
* Render a code block.
|
||||||
|
*/
|
||||||
|
renderCodeBlock: (props: { code: string; syntax: string }) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the heading of the operation.
|
||||||
|
*/
|
||||||
|
renderHeading: (props: {
|
||||||
|
deprecated?: boolean;
|
||||||
|
title: string;
|
||||||
|
stability?: string;
|
||||||
|
}) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the document of the operation.
|
||||||
|
*/
|
||||||
|
renderDocument: (props: { document: object }) => React.ReactNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specification URL.
|
||||||
|
*/
|
||||||
|
specUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
||||||
path: string;
|
path: string;
|
||||||
method: string;
|
method: string;
|
||||||
@@ -60,8 +70,3 @@ export interface OpenAPIOperationData extends OpenAPICustomSpecProperties {
|
|||||||
/** Securities that should be used for this operation */
|
/** Securities that should be used for this operation */
|
||||||
securities: [string, OpenAPIV3.SecuritySchemeObject][];
|
securities: [string, OpenAPIV3.SecuritySchemeObject][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OpenAPISchemasData {
|
|
||||||
/** Components schemas to be used for schemas */
|
|
||||||
schemas: OpenAPISchema[];
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user