mirror of
https://github.com/GitbookIO/gitbook.git
synced 2026-09-20 01:25:16 +00:00
Fix schema validation error (#2820)
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Sync tabs across all OpenAPI blocks
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@gitbook/react-openapi': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Support for OpenAPI references
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
"packages/gitbook": {
|
"packages/gitbook": {
|
||||||
"name": "gitbook",
|
"name": "gitbook",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/api": "^0.93.0",
|
"@gitbook/api": "^0.93.0",
|
||||||
"@gitbook/cache-do": "workspace:*",
|
"@gitbook/cache-do": "workspace:*",
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
},
|
},
|
||||||
"packages/gitbook-v2": {
|
"packages/gitbook-v2": {
|
||||||
"name": "gitbook-v2",
|
"name": "gitbook-v2",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/api": "^0.93.0",
|
"@gitbook/api": "^0.93.0",
|
||||||
"next": "canary",
|
"next": "canary",
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
},
|
},
|
||||||
"packages/openapi-parser": {
|
"packages/openapi-parser": {
|
||||||
"name": "@gitbook/openapi-parser",
|
"name": "@gitbook/openapi-parser",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scalar/openapi-parser": "^0.10.4",
|
"@scalar/openapi-parser": "^0.10.4",
|
||||||
"@scalar/openapi-types": "^0.1.6",
|
"@scalar/openapi-types": "^0.1.6",
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
},
|
},
|
||||||
"packages/react-contentkit": {
|
"packages/react-contentkit": {
|
||||||
"name": "@gitbook/react-contentkit",
|
"name": "@gitbook/react-contentkit",
|
||||||
"version": "0.5.1",
|
"version": "0.6.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/api": "^0.93.0",
|
"@gitbook/api": "^0.93.0",
|
||||||
"assert-never": "^1.2.1",
|
"assert-never": "^1.2.1",
|
||||||
@@ -207,7 +207,7 @@
|
|||||||
},
|
},
|
||||||
"packages/react-openapi": {
|
"packages/react-openapi": {
|
||||||
"name": "@gitbook/react-openapi",
|
"name": "@gitbook/react-openapi",
|
||||||
"version": "0.7.1",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gitbook/openapi-parser": "workspace:*",
|
"@gitbook/openapi-parser": "workspace:*",
|
||||||
"@scalar/api-client-react": "1.0.87",
|
"@scalar/api-client-react": "1.0.87",
|
||||||
@@ -217,6 +217,7 @@
|
|||||||
"react-aria": "^3.37.0",
|
"react-aria": "^3.37.0",
|
||||||
"react-aria-components": "^1.6.0",
|
"react-aria-components": "^1.6.0",
|
||||||
"usehooks-ts": "^3.1.0",
|
"usehooks-ts": "^3.1.0",
|
||||||
|
"zustand": "^5.0.3",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "^1.1.20",
|
"bun-types": "^1.1.20",
|
||||||
@@ -3425,6 +3426,8 @@
|
|||||||
|
|
||||||
"zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
"zod": ["zod@3.23.8", "", {}, "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g=="],
|
||||||
|
|
||||||
|
"zustand": ["zustand@5.0.3", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg=="],
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
|
|
||||||
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ export async function createFileSystem(input: {
|
|||||||
const { filesystem } = await load(input.value, {
|
const { filesystem } = await load(input.value, {
|
||||||
plugins: [fetchURLs({ rootURL: input.rootURL })],
|
plugins: [fetchURLs({ rootURL: input.rootURL })],
|
||||||
});
|
});
|
||||||
|
|
||||||
return filesystem;
|
return filesystem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
"flatted": "^3.2.9",
|
"flatted": "^3.2.9",
|
||||||
"react-aria-components": "^1.6.0",
|
"react-aria-components": "^1.6.0",
|
||||||
"react-aria": "^3.37.0",
|
"react-aria": "^3.37.0",
|
||||||
"usehooks-ts": "^3.1.0"
|
"usehooks-ts": "^3.1.0",
|
||||||
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bun-types": "^1.1.20",
|
"bun-types": "^1.1.20",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useCallback, useRef, useState, useSyncExternalStore } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
|
import { mergeProps, useButton, useDisclosure, useFocusRing } from 'react-aria';
|
||||||
import { useDisclosureState } from 'react-stately';
|
import { useDisclosureState } from 'react-stately';
|
||||||
|
|
||||||
@@ -11,30 +11,6 @@ interface InteractiveSectionTab {
|
|||||||
body: React.ReactNode;
|
body: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalState: Record<string, string> = {};
|
|
||||||
const listeners = new Set<() => void>();
|
|
||||||
|
|
||||||
function useSyncedTabsGlobalState() {
|
|
||||||
const subscribe = useCallback((callback: () => void) => {
|
|
||||||
listeners.add(callback);
|
|
||||||
return () => listeners.delete(callback);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const getSnapshot = useCallback(() => globalState, []);
|
|
||||||
|
|
||||||
const setSyncedTabs = useCallback(
|
|
||||||
(updater: (tabs: Record<string, string>) => Record<string, string>) => {
|
|
||||||
globalState = updater(globalState);
|
|
||||||
listeners.forEach((listener) => listener());
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const tabs = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
||||||
|
|
||||||
return [tabs, setSyncedTabs] as const;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* To optimize rendering, most of the components are server-components,
|
* To optimize rendering, most of the components are server-components,
|
||||||
* and the interactiveness is mainly handled by a few key components like this one.
|
* and the interactiveness is mainly handled by a few key components like this one.
|
||||||
@@ -59,8 +35,6 @@ export function InteractiveSection(props: {
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
/** Children to display within the container */
|
/** Children to display within the container */
|
||||||
overlay?: React.ReactNode;
|
overlay?: React.ReactNode;
|
||||||
/** An optional key referencing a value in global state */
|
|
||||||
stateKey?: string;
|
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
@@ -73,16 +47,11 @@ export function InteractiveSection(props: {
|
|||||||
children,
|
children,
|
||||||
overlay,
|
overlay,
|
||||||
toggleIcon = '▶',
|
toggleIcon = '▶',
|
||||||
stateKey,
|
|
||||||
} = props;
|
} = props;
|
||||||
const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState();
|
|
||||||
const tabFromState =
|
const [selectedTabKey, setSelectedTab] = useState(defaultTab);
|
||||||
stateKey && stateKey in syncedTabs
|
|
||||||
? tabs.find((tab) => tab.key === syncedTabs[stateKey])
|
|
||||||
: undefined;
|
|
||||||
const [selectedTabKey, setSelectedTab] = useState(tabFromState?.key ?? defaultTab);
|
|
||||||
const selectedTab: InteractiveSectionTab | undefined =
|
const selectedTab: InteractiveSectionTab | undefined =
|
||||||
tabFromState ?? tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
tabs.find((tab) => tab.key === selectedTabKey) ?? tabs[0];
|
||||||
|
|
||||||
const state = useDisclosureState({
|
const state = useDisclosureState({
|
||||||
defaultExpanded: defaultOpened,
|
defaultExpanded: defaultOpened,
|
||||||
@@ -153,12 +122,6 @@ export function InteractiveSection(props: {
|
|||||||
value={selectedTab?.key ?? ''}
|
value={selectedTab?.key ?? ''}
|
||||||
onChange={(event) => {
|
onChange={(event) => {
|
||||||
setSelectedTab(event.target.value);
|
setSelectedTab(event.target.value);
|
||||||
if (stateKey) {
|
|
||||||
setSyncedTabs((state) => ({
|
|
||||||
...state,
|
|
||||||
[stateKey]: event.target.value,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
state.expand();
|
state.expand();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import { generateMediaTypeExample, generateSchemaExample } from './generateSchem
|
|||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
import { getServersURL } from './OpenAPIServerURL';
|
import { getServersURL } from './OpenAPIServerURL';
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||||
import { noReference } from './utils';
|
import { createStateKey } from './utils';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||||
|
import { checkIsReference } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display code samples to execute the operation.
|
* Display code samples to execute the operation.
|
||||||
@@ -20,24 +21,19 @@ export function OpenAPICodeSample(props: {
|
|||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
const headersObject: { [k: string]: string } = {};
|
const headersObject: { [k: string]: string } = {};
|
||||||
|
|
||||||
data.operation.parameters?.forEach((rawParam) => {
|
data.operation.parameters?.forEach((param) => {
|
||||||
const param = noReference(rawParam);
|
|
||||||
if (!param) {
|
if (!param) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (param.in === 'header' && param.required) {
|
if (param.in === 'header' && param.required) {
|
||||||
const example = param.schema
|
const example = param.schema ? generateSchemaExample(param.schema) : undefined;
|
||||||
? generateSchemaExample(noReference(param.schema))
|
|
||||||
: undefined;
|
|
||||||
if (example !== undefined && param.name) {
|
if (example !== undefined && param.name) {
|
||||||
headersObject[param.name] =
|
headersObject[param.name] =
|
||||||
typeof example !== 'string' ? stringifyOpenAPI(example) : example;
|
typeof example !== 'string' ? stringifyOpenAPI(example) : example;
|
||||||
}
|
}
|
||||||
} else if (param.in === 'query' && param.required) {
|
} else if (param.in === 'query' && param.required) {
|
||||||
const example = param.schema
|
const example = param.schema ? generateSchemaExample(param.schema) : undefined;
|
||||||
? generateSchemaExample(noReference(param.schema))
|
|
||||||
: undefined;
|
|
||||||
if (example !== undefined && param.name) {
|
if (example !== undefined && param.name) {
|
||||||
searchParams.append(
|
searchParams.append(
|
||||||
param.name,
|
param.name,
|
||||||
@@ -47,7 +43,9 @@ export function OpenAPICodeSample(props: {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const requestBody = noReference(data.operation.requestBody);
|
const requestBody = !checkIsReference(data.operation.requestBody)
|
||||||
|
? data.operation.requestBody
|
||||||
|
: undefined;
|
||||||
const requestBodyContentEntries = requestBody?.content
|
const requestBodyContentEntries = requestBody?.content
|
||||||
? Object.entries(requestBody.content)
|
? Object.entries(requestBody.content)
|
||||||
: undefined;
|
: undefined;
|
||||||
@@ -115,7 +113,7 @@ export function OpenAPICodeSample(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPITabs items={samples}>
|
<OpenAPITabs stateKey={createStateKey('codesample')} items={samples}>
|
||||||
<InteractiveSection header={<OpenAPITabsList />} className="openapi-codesample">
|
<InteractiveSection header={<OpenAPITabsList />} className="openapi-codesample">
|
||||||
<OpenAPITabsPanels />
|
<OpenAPITabsPanels />
|
||||||
</InteractiveSection>
|
</InteractiveSection>
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { OpenAPIRootSchema } from './OpenAPISchema';
|
import { OpenAPIRootSchema } from './OpenAPISchema';
|
||||||
import { noReference } from './utils';
|
|
||||||
import type { OpenAPIClientContext } from './types';
|
import type { OpenAPIClientContext } from './types';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
|
import { checkIsReference } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an interactive request body.
|
* Display an interactive request body.
|
||||||
*/
|
*/
|
||||||
export function OpenAPIRequestBody(props: {
|
export function OpenAPIRequestBody(props: {
|
||||||
requestBody: OpenAPIV3.RequestBodyObject;
|
requestBody: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject;
|
||||||
context: OpenAPIClientContext;
|
context: OpenAPIClientContext;
|
||||||
}) {
|
}) {
|
||||||
const { requestBody, context } = props;
|
const { requestBody, context } = props;
|
||||||
|
|
||||||
|
if (checkIsReference(requestBody)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection
|
||||||
header="Body"
|
header="Body"
|
||||||
@@ -24,7 +28,7 @@ export function OpenAPIRequestBody(props: {
|
|||||||
label: contentType,
|
label: contentType,
|
||||||
body: (
|
body: (
|
||||||
<OpenAPIRootSchema
|
<OpenAPIRootSchema
|
||||||
schema={noReference(mediaTypeObject.schema) ?? {}}
|
schema={mediaTypeObject.schema ?? {}}
|
||||||
context={context}
|
context={context}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
||||||
import { checkIsReference, noReference, resolveDescription } from './utils';
|
import { resolveDescription } from './utils';
|
||||||
import type { OpenAPIClientContext } from './types';
|
import type { OpenAPIClientContext } from './types';
|
||||||
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ export function OpenAPIResponse(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { response, context, mediaType } = props;
|
const { response, context, mediaType } = props;
|
||||||
const headers = Object.entries(response.headers ?? {}).map(
|
const headers = Object.entries(response.headers ?? {}).map(
|
||||||
([name, header]) => [name, noReference(header) ?? {}] as const,
|
([name, header]) => [name, header ?? {}] as const,
|
||||||
);
|
);
|
||||||
const content = Object.entries(mediaType.schema ?? {});
|
const content = Object.entries(mediaType.schema ?? {});
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export function OpenAPIResponse(props: {
|
|||||||
<OpenAPISchemaProperties
|
<OpenAPISchemaProperties
|
||||||
properties={headers.map(([name, header]) => ({
|
properties={headers.map(([name, header]) => ({
|
||||||
propertyName: name,
|
propertyName: name,
|
||||||
schema: noReference(header.schema) ?? {},
|
schema: header.schema ?? {},
|
||||||
required: header.required,
|
required: header.required,
|
||||||
}))}
|
}))}
|
||||||
context={context}
|
context={context}
|
||||||
@@ -43,7 +43,7 @@ export function OpenAPIResponse(props: {
|
|||||||
id={`response-${context.blockKey}`}
|
id={`response-${context.blockKey}`}
|
||||||
properties={[
|
properties={[
|
||||||
{
|
{
|
||||||
schema: handleUnresolvedReference(mediaType.schema) ?? {},
|
schema: mediaType.schema ?? {},
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
context={context}
|
context={context}
|
||||||
@@ -52,17 +52,3 @@ export function OpenAPIResponse(props: {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUnresolvedReference(
|
|
||||||
input: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
|
|
||||||
): OpenAPIV3.SchemaObject {
|
|
||||||
const isReference = checkIsReference(input);
|
|
||||||
|
|
||||||
if (isReference || input === undefined) {
|
|
||||||
// If we find a reference that wasn't resolved or needed to be resolved externally, do not try to render it.
|
|
||||||
// Instead we render `any`
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { generateSchemaExample } from './generateSchemaExample';
|
import { generateSchemaExample } from './generateSchemaExample';
|
||||||
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
import type { OpenAPIContextProps, OpenAPIOperationData } from './types';
|
||||||
import { checkIsReference, noReference, resolveDescription } from './utils';
|
import { checkIsReference, createStateKey, resolveDescription } from './utils';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
import { OpenAPITabs, OpenAPITabsList, OpenAPITabsPanels } from './OpenAPITabs';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
@@ -40,7 +40,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
|
|
||||||
const examples = responses
|
const examples = responses
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
const responseObject = noReference(value);
|
const responseObject = value;
|
||||||
const mediaTypeObject = (() => {
|
const mediaTypeObject = (() => {
|
||||||
if (!responseObject.content) {
|
if (!responseObject.content) {
|
||||||
return null;
|
return null;
|
||||||
@@ -68,7 +68,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
const key = Object.keys(examples)[0];
|
const key = Object.keys(examples)[0];
|
||||||
if (key) {
|
if (key) {
|
||||||
// @TODO handle multiple examples
|
// @TODO handle multiple examples
|
||||||
const firstExample = noReference(examples[key]);
|
const firstExample = examples[key];
|
||||||
if (firstExample) {
|
if (firstExample) {
|
||||||
return firstExample;
|
return firstExample;
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
return { value: example };
|
return { value: example };
|
||||||
}
|
}
|
||||||
|
|
||||||
const schema = noReference(mediaTypeObject.schema);
|
const schema = mediaTypeObject.schema;
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -115,7 +115,7 @@ export function OpenAPIResponseExample(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPITabs items={examples}>
|
<OpenAPITabs stateKey={createStateKey('response-example')} items={examples}>
|
||||||
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
<InteractiveSection header={<OpenAPITabsList />} className="openapi-response-example">
|
||||||
<OpenAPITabsPanels />
|
<OpenAPITabsPanels />
|
||||||
</InteractiveSection>
|
</InteractiveSection>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3, OpenAPIV3_1 } from '@gitbook/openapi-parser';
|
||||||
import { createStateKey, resolveDescription } from './utils';
|
|
||||||
import { OpenAPIResponse } from './OpenAPIResponse';
|
import { OpenAPIResponse } from './OpenAPIResponse';
|
||||||
import { OpenAPIClientContext } from './types';
|
import { OpenAPIClientContext } from './types';
|
||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
import { OpenAPIDisclosureGroup } from './OpenAPIDisclosureGroup';
|
import { OpenAPIDisclosureGroup } from './OpenAPIDisclosureGroup';
|
||||||
|
import { Markdown } from './Markdown';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display an interactive response body.
|
* Display an interactive response body.
|
||||||
@@ -15,18 +15,14 @@ export function OpenAPIResponses(props: {
|
|||||||
const { responses, context } = props;
|
const { responses, context } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InteractiveSection
|
<InteractiveSection header="Responses" className="openapi-responses">
|
||||||
stateKey={createStateKey('response', context.blockKey)}
|
|
||||||
header="Responses"
|
|
||||||
className="openapi-responses"
|
|
||||||
>
|
|
||||||
<OpenAPIDisclosureGroup
|
<OpenAPIDisclosureGroup
|
||||||
allowsMultipleExpanded
|
allowsMultipleExpanded
|
||||||
icon={context.icons.chevronRight}
|
icon={context.icons.chevronRight}
|
||||||
groups={Object.entries(responses).map(
|
groups={Object.entries(responses).map(
|
||||||
([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
|
([statusCode, response]: [string, OpenAPIV3.ResponseObject]) => {
|
||||||
const content = Object.entries(response.content ?? {});
|
const content = Object.entries(response.content ?? {});
|
||||||
const description = resolveDescription(response);
|
const description = response.description;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: statusCode,
|
id: statusCode,
|
||||||
@@ -39,9 +35,10 @@ export function OpenAPIResponses(props: {
|
|||||||
{statusCode}
|
{statusCode}
|
||||||
</span>
|
</span>
|
||||||
{description ? (
|
{description ? (
|
||||||
<div className="openapi-markdown openapi-response-description">
|
<Markdown
|
||||||
{htmlToText(description)}
|
source={description}
|
||||||
</div>
|
className="openapi-response-description"
|
||||||
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -64,8 +61,3 @@ export function OpenAPIResponses(props: {
|
|||||||
</InteractiveSection>
|
</InteractiveSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function htmlToText(html: string): string {
|
|
||||||
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
||||||
return doc.body.textContent?.trim() || '';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useId } from 'react';
|
|||||||
import { InteractiveSection } from './InteractiveSection';
|
import { InteractiveSection } from './InteractiveSection';
|
||||||
import { Markdown } from './Markdown';
|
import { Markdown } from './Markdown';
|
||||||
import type { OpenAPIClientContext } from './types';
|
import type { OpenAPIClientContext } from './types';
|
||||||
import { checkIsReference, noReference, resolveDescription } from './utils';
|
import { checkIsReference, resolveDescription } from './utils';
|
||||||
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
import { stringifyOpenAPI } from './stringifyOpenAPI';
|
||||||
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
import { OpenAPISchemaName } from './OpenAPISchemaName';
|
||||||
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
import { OpenAPIDisclosure } from './OpenAPIDisclosure';
|
||||||
@@ -70,7 +70,7 @@ export function OpenAPISchemaProperty(
|
|||||||
<OpenAPISchemaPresentation {...props} />
|
<OpenAPISchemaPresentation {...props} />
|
||||||
{alternatives[0].map((alternative, index) => (
|
{alternatives[0].map((alternative, index) => (
|
||||||
<OpenAPISchemaAlternative
|
<OpenAPISchemaAlternative
|
||||||
key={index}
|
key={`alternative-${index}`}
|
||||||
schema={alternative}
|
schema={alternative}
|
||||||
circularRefs={circularRefs}
|
circularRefs={circularRefs}
|
||||||
context={context}
|
context={context}
|
||||||
@@ -278,9 +278,9 @@ export function OpenAPISchemaPresentation(props: OpenAPISchemaPropertyEntry) {
|
|||||||
function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
|
function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISchemaPropertyEntry[] {
|
||||||
if (schema.allOf) {
|
if (schema.allOf) {
|
||||||
return schema.allOf.reduce((acc, subSchema) => {
|
return schema.allOf.reduce((acc, subSchema) => {
|
||||||
const properties = getSchemaProperties(noReference(subSchema)) ?? [
|
const properties = getSchemaProperties(subSchema) ?? [
|
||||||
{
|
{
|
||||||
schema: noReference(subSchema),
|
schema: subSchema,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
return [...acc, ...properties];
|
return [...acc, ...properties];
|
||||||
@@ -289,7 +289,7 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
|
|||||||
|
|
||||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||||
if (schema.type === 'array' && !!schema.items) {
|
if (schema.type === 'array' && !!schema.items) {
|
||||||
const items = noReference(schema.items);
|
const items = schema.items;
|
||||||
const itemProperties = getSchemaProperties(items);
|
const itemProperties = getSchemaProperties(items);
|
||||||
if (itemProperties) {
|
if (itemProperties) {
|
||||||
return itemProperties;
|
return itemProperties;
|
||||||
@@ -307,12 +307,7 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
|
|||||||
const result: OpenAPISchemaPropertyEntry[] = [];
|
const result: OpenAPISchemaPropertyEntry[] = [];
|
||||||
|
|
||||||
if (schema.properties) {
|
if (schema.properties) {
|
||||||
Object.entries(schema.properties).forEach(([propertyName, rawPropertySchema]) => {
|
Object.entries(schema.properties).forEach(([propertyName, propertySchema]) => {
|
||||||
const isReference = checkIsReference(rawPropertySchema);
|
|
||||||
const propertySchema: OpenAPIV3.SchemaObject = isReference
|
|
||||||
? { propertyName }
|
|
||||||
: rawPropertySchema;
|
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
propertyName,
|
propertyName,
|
||||||
required: Array.isArray(schema.required)
|
required: Array.isArray(schema.required)
|
||||||
@@ -324,7 +319,7 @@ function getSchemaProperties(schema: OpenAPIV3.SchemaObject): null | OpenAPISche
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (schema.additionalProperties) {
|
if (schema.additionalProperties) {
|
||||||
const additionalProperties = noReference(schema.additionalProperties);
|
const additionalProperties = schema.additionalProperties;
|
||||||
|
|
||||||
result.push({
|
result.push({
|
||||||
propertyName: 'Other properties',
|
propertyName: 'Other properties',
|
||||||
@@ -348,17 +343,11 @@ export function getSchemaAlternatives(
|
|||||||
const downAncestors = new Set(ancestors).add(schema);
|
const downAncestors = new Set(ancestors).add(schema);
|
||||||
|
|
||||||
if (schema.anyOf) {
|
if (schema.anyOf) {
|
||||||
return [
|
return [flattenAlternatives('anyOf', schema.anyOf, downAncestors), schema.discriminator];
|
||||||
flattenAlternatives('anyOf', schema.anyOf.map(noReference), downAncestors),
|
|
||||||
noReference(schema.discriminator),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.oneOf) {
|
if (schema.oneOf) {
|
||||||
return [
|
return [flattenAlternatives('oneOf', schema.oneOf, downAncestors), schema.discriminator];
|
||||||
flattenAlternatives('oneOf', schema.oneOf.map(noReference), downAncestors),
|
|
||||||
noReference(schema.discriminator),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.allOf) {
|
if (schema.allOf) {
|
||||||
@@ -396,8 +385,8 @@ export function getSchemaTitle(
|
|||||||
|
|
||||||
// Try using the discriminator
|
// Try using the discriminator
|
||||||
if (discriminator?.propertyName && schema.properties) {
|
if (discriminator?.propertyName && schema.properties) {
|
||||||
const discriminatorProperty = noReference(schema.properties[discriminator.propertyName]);
|
const discriminatorProperty = schema.properties[discriminator.propertyName];
|
||||||
if (discriminatorProperty) {
|
if (discriminatorProperty && !checkIsReference(discriminatorProperty)) {
|
||||||
if (discriminatorProperty.enum) {
|
if (discriminatorProperty.enum) {
|
||||||
return discriminatorProperty.enum.map((value) => value.toString()).join(' | ');
|
return discriminatorProperty.enum.map((value) => value.toString()).join(' | ');
|
||||||
}
|
}
|
||||||
@@ -411,7 +400,7 @@ export function getSchemaTitle(
|
|||||||
type = 'enum';
|
type = 'enum';
|
||||||
// check array AND schema.items as this is sometimes null despite what the type indicates
|
// check array AND schema.items as this is sometimes null despite what the type indicates
|
||||||
} else if (schema.type === 'array' && !!schema.items) {
|
} else if (schema.type === 'array' && !!schema.items) {
|
||||||
type = `${getSchemaTitle(noReference(schema.items))}[]`;
|
type = `${getSchemaTitle(schema.items)}[]`;
|
||||||
} else if (Array.isArray(schema.type)) {
|
} else if (Array.isArray(schema.type)) {
|
||||||
type = schema.type.join(' | ');
|
type = schema.type.join(' | ');
|
||||||
} else if (schema.type || schema.properties) {
|
} else if (schema.type || schema.properties) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { OpenAPIResponses } from './OpenAPIResponses';
|
|||||||
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
import { OpenAPISchemaProperties } from './OpenAPISchema';
|
||||||
import { OpenAPISecurities } from './OpenAPISecurities';
|
import { OpenAPISecurities } from './OpenAPISecurities';
|
||||||
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
import type { OpenAPIClientContext, OpenAPIOperationData } from './types';
|
||||||
import { noReference, resolveDescription } from './utils';
|
import { resolveDescription } from './utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client component to render the spec for the request and response.
|
* Client component to render the spec for the request and response.
|
||||||
@@ -49,7 +49,7 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
|||||||
example: parameter.example,
|
example: parameter.example,
|
||||||
// Deprecated can be defined at the parameter level
|
// Deprecated can be defined at the parameter level
|
||||||
deprecated: parameter.deprecated,
|
deprecated: parameter.deprecated,
|
||||||
...(noReference(parameter.schema) ?? {}),
|
...(parameter.schema ?? {}),
|
||||||
},
|
},
|
||||||
required: parameter.required,
|
required: parameter.required,
|
||||||
};
|
};
|
||||||
@@ -61,13 +61,10 @@ export function OpenAPISpec(props: { data: OpenAPIOperationData; context: OpenAP
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{operation.requestBody ? (
|
{operation.requestBody ? (
|
||||||
<OpenAPIRequestBody
|
<OpenAPIRequestBody requestBody={operation.requestBody} context={context} />
|
||||||
requestBody={noReference(operation.requestBody)}
|
|
||||||
context={context}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
{operation.responses ? (
|
{operation.responses ? (
|
||||||
<OpenAPIResponses responses={noReference(operation.responses)} context={context} />
|
<OpenAPIResponses responses={operation.responses} context={context} />
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createContext, useContext, useMemo, useState } from 'react';
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
import { Key, Tab, TabList, TabPanel, Tabs, TabsProps } from 'react-aria-components';
|
import { Key, Tab, TabList, TabPanel, Tabs, TabsProps } from 'react-aria-components';
|
||||||
import { Markdown } from './Markdown';
|
import { Markdown } from './Markdown';
|
||||||
|
import { useSyncedTabsGlobalState } from './useSyncedTabsGlobalState';
|
||||||
|
import { useIntersectionObserver } from 'usehooks-ts';
|
||||||
|
|
||||||
export type Tab = {
|
export type Tab = {
|
||||||
key: Key;
|
key: Key;
|
||||||
@@ -13,8 +15,7 @@ export type Tab = {
|
|||||||
|
|
||||||
type OpenAPITabsContextData = {
|
type OpenAPITabsContextData = {
|
||||||
items: Tab[];
|
items: Tab[];
|
||||||
selectedKey: Key;
|
selectedTab: Tab;
|
||||||
setSelectedKey: (key: Key) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const OpenAPITabsContext = createContext<OpenAPITabsContextData | null>(null);
|
const OpenAPITabsContext = createContext<OpenAPITabsContextData | null>(null);
|
||||||
@@ -30,25 +31,66 @@ function useOpenAPITabsContext() {
|
|||||||
/**
|
/**
|
||||||
* The OpenAPI Tabs wrapper component.
|
* The OpenAPI Tabs wrapper component.
|
||||||
*/
|
*/
|
||||||
export function OpenAPITabs(props: React.PropsWithChildren<TabsProps & { items: Tab[] }>) {
|
export function OpenAPITabs(
|
||||||
const { children, items } = props;
|
props: React.PropsWithChildren<TabsProps & { items: Tab[]; stateKey?: string }>,
|
||||||
|
) {
|
||||||
const [selectedKey, setSelectedKey] = useState(() => {
|
const { children, items, stateKey } = props;
|
||||||
const firstItem = items[0];
|
const isVisible = stateKey
|
||||||
if (!firstItem) {
|
? useIntersectionObserver({
|
||||||
throw new Error('OpenAPITabs: at least one tab is required');
|
threshold: 0.1,
|
||||||
|
rootMargin: '200px',
|
||||||
|
})
|
||||||
|
: true;
|
||||||
|
const defaultTab = items[0] as Tab;
|
||||||
|
const [syncedTabs, setSyncedTabs] = useSyncedTabsGlobalState<Tab>();
|
||||||
|
const [selectedTabKey, setSelectedTabKey] = useState(() => {
|
||||||
|
if (isVisible && stateKey && syncedTabs && syncedTabs.has(stateKey)) {
|
||||||
|
const tabFromState = syncedTabs.get(stateKey);
|
||||||
|
return tabFromState?.key ?? items[0]?.key;
|
||||||
}
|
}
|
||||||
return firstItem.key;
|
return items[0]?.key;
|
||||||
});
|
});
|
||||||
|
const [selectedTab, setSelectedTab] = useState<Tab>(defaultTab);
|
||||||
|
|
||||||
const contextValue = { items, selectedKey, setSelectedKey };
|
const handleSelectionChange = (key: Key) => {
|
||||||
|
setSelectedTabKey(key);
|
||||||
|
if (stateKey) {
|
||||||
|
const tab = items.find((item) => item.key === key);
|
||||||
|
|
||||||
|
if (!tab) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSyncedTabs((state) => {
|
||||||
|
const newState = new Map(state);
|
||||||
|
newState.set(stateKey, tab);
|
||||||
|
return newState;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVisible && stateKey && syncedTabs && syncedTabs.has(stateKey)) {
|
||||||
|
const tabFromState = syncedTabs.get(stateKey);
|
||||||
|
|
||||||
|
if (!items.some((item) => item.key === tabFromState?.key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabFromState && tabFromState?.key !== selectedTab?.key) {
|
||||||
|
setSelectedTab(tabFromState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [isVisible, stateKey, syncedTabs, selectedTabKey]);
|
||||||
|
|
||||||
|
const contextValue = useMemo(() => ({ items, selectedTab }), [items, selectedTab]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OpenAPITabsContext.Provider value={contextValue}>
|
<OpenAPITabsContext.Provider value={contextValue}>
|
||||||
<Tabs
|
<Tabs
|
||||||
className="openapi-tabs"
|
className="openapi-tabs"
|
||||||
onSelectionChange={setSelectedKey}
|
onSelectionChange={handleSelectionChange}
|
||||||
selectedKey={selectedKey}
|
selectedKey={selectedTab?.key}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -90,23 +132,21 @@ export function OpenAPITabsList() {
|
|||||||
* It renders the content of the selected tab.
|
* It renders the content of the selected tab.
|
||||||
*/
|
*/
|
||||||
export function OpenAPITabsPanels() {
|
export function OpenAPITabsPanels() {
|
||||||
const { selectedKey, items } = useOpenAPITabsContext();
|
const { selectedTab } = useOpenAPITabsContext();
|
||||||
|
|
||||||
const tab = useMemo(() => items.find((tab) => tab.key === selectedKey), [items, selectedKey]);
|
if (!selectedTab) {
|
||||||
|
|
||||||
if (!tab) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TabPanel
|
<TabPanel
|
||||||
key={`TabPanel-${tab.key}`}
|
key={`TabPanel-${selectedTab.key}`}
|
||||||
id={tab.key.toString()}
|
id={selectedTab.key.toString()}
|
||||||
className="openapi-tabs-panel"
|
className="openapi-tabs-panel"
|
||||||
>
|
>
|
||||||
{tab.body}
|
{selectedTab.body}
|
||||||
{tab.description ? (
|
{selectedTab.description ? (
|
||||||
<Markdown source={tab.description} className="openapi-tabs-footer" />
|
<Markdown source={selectedTab.description} className="openapi-tabs-footer" />
|
||||||
) : null}
|
) : null}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
import { noReference } from './utils';
|
|
||||||
import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
import { getExampleFromSchema } from '@scalar/oas-utils/spec-getters';
|
||||||
|
|
||||||
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue };
|
||||||
@@ -50,13 +49,13 @@ export function generateMediaTypeExample(
|
|||||||
if (key) {
|
if (key) {
|
||||||
const example = mediaType.examples[key];
|
const example = mediaType.examples[key];
|
||||||
if (example) {
|
if (example) {
|
||||||
return noReference(example).value;
|
return example.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mediaType.schema) {
|
if (mediaType.schema) {
|
||||||
return generateSchemaExample(noReference(mediaType.schema), options);
|
return generateSchemaExample(mediaType.schema, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
type OpenAPIV3_1,
|
type OpenAPIV3_1,
|
||||||
dereference,
|
dereference,
|
||||||
} from '@gitbook/openapi-parser';
|
} from '@gitbook/openapi-parser';
|
||||||
import { noReference } from './utils';
|
|
||||||
import { OpenAPIOperationData } from './types';
|
import { OpenAPIOperationData } from './types';
|
||||||
|
import { checkIsReference } from './utils';
|
||||||
|
|
||||||
export { toJSON, fromJSON };
|
export { toJSON, fromJSON };
|
||||||
|
|
||||||
@@ -48,8 +48,8 @@ export async function resolveOpenAPIOperation(
|
|||||||
const securityKey = Object.keys(entry)[0];
|
const securityKey = Object.keys(entry)[0];
|
||||||
if (securityKey) {
|
if (securityKey) {
|
||||||
const securityScheme = schema.components?.securitySchemes?.[securityKey];
|
const securityScheme = schema.components?.securitySchemes?.[securityKey];
|
||||||
if (securityScheme) {
|
if (securityScheme && !checkIsReference(securityScheme)) {
|
||||||
securities.push([securityKey, noReference(securityScheme)]);
|
securities.push([securityKey, securityScheme]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,12 +116,13 @@ function getPathObject(
|
|||||||
function getPathObjectParameter(
|
function getPathObjectParameter(
|
||||||
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
schema: OpenAPIV3.Document | OpenAPIV3_1.Document,
|
||||||
path: string,
|
path: string,
|
||||||
): OpenAPIV3.ParameterObject[] | OpenAPIV3_1.ParameterObject[] | null {
|
):
|
||||||
|
| (OpenAPIV3.ReferenceObject | OpenAPIV3.ParameterObject)[]
|
||||||
|
| (OpenAPIV3.ParameterObject | OpenAPIV3_1.ReferenceObject)[]
|
||||||
|
| null {
|
||||||
const pathObject = getPathObject(schema, path);
|
const pathObject = getPathObject(schema, path);
|
||||||
if (pathObject?.parameters) {
|
if (pathObject?.parameters) {
|
||||||
return pathObject.parameters.map(noReference) as
|
return pathObject.parameters;
|
||||||
| OpenAPIV3.ParameterObject[]
|
|
||||||
| OpenAPIV3_1.ParameterObject[];
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface SyncedTabsState<T> {
|
||||||
|
tabs: Map<string, T>;
|
||||||
|
setTabs: (updater: (tabs: Map<string, T>) => Map<string, T>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSyncedTabsStore = create<SyncedTabsState<any>>()((set) => ({
|
||||||
|
tabs: new Map<string, any>(),
|
||||||
|
setTabs: (updater) =>
|
||||||
|
set((state) => ({
|
||||||
|
tabs: updater(new Map(state.tabs)), // Ensure a new Map is created for reactivity
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Selector for better performance - only re-renders when tabs change
|
||||||
|
export function useSyncedTabsGlobalState<T>() {
|
||||||
|
const tabs = useSyncedTabsStore((state) => state.tabs as Map<string, T>);
|
||||||
|
const setTabs = useSyncedTabsStore((state) => state.setTabs as SyncedTabsState<T>['setTabs']);
|
||||||
|
return [tabs, setTabs] as const;
|
||||||
|
}
|
||||||
@@ -1,13 +1,5 @@
|
|||||||
import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
|
import type { AnyObject, OpenAPIV3 } from '@gitbook/openapi-parser';
|
||||||
|
|
||||||
export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
|
|
||||||
if (checkIsReference(input)) {
|
|
||||||
throw new Error('Reference found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
export function checkIsReference(input: unknown): input is OpenAPIV3.ReferenceObject {
|
||||||
return typeof input === 'object' && !!input && '$ref' in input;
|
return typeof input === 'object' && !!input && '$ref' in input;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user