Compare commits

...

66 Commits

Author SHA1 Message Date
Brett Jephson d9ee1a7ac4 Merge branch 'main' into brett/RND-2311-openapi-enum 2024-10-21 19:33:26 +01:00
Brett Jephson 846de0e1a9 Merge branch 'main' into brett/RND-2311-openapi-enum 2024-10-18 09:57:06 +01:00
Brett Jephson 3d9aefaf25 Merge remote-tracking branch 'origin/main' into brett/RND-2311-openapi-enum 2024-10-14 11:39:00 +01:00
Brett Jephson 5beb2dbd69 Tidy 2024-10-09 14:00:37 +01:00
Brett Jephson d19e3507cc Merge branch 'brett/RND-2311-openapi-enum' of github.com:GitbookIO/gitbook into brett/RND-2311-openapi-enum 2024-10-09 13:58:56 +01:00
Brett Jephson 152aad2c8f Update comment 2024-10-09 13:57:51 +01:00
Brett Jephson 3ee149d87d Remove unused imports 2024-10-09 13:57:51 +01:00
Brett Jephson 0ccc5034c4 Formatting 2024-10-09 13:57:51 +01:00
Brett Jephson 95e8772eab Refactor - naming and comments 2024-10-09 13:57:48 +01:00
Brett Jephson 9675290e61 Don't make server url editable if no update function or context 2024-10-09 13:56:55 +01:00
Brett Jephson c7ca921d7f Self review changes 2024-10-09 13:56:55 +01:00
Brett Jephson 2d4ddc9d95 Formatting 2024-10-09 13:53:57 +01:00
Brett Jephson 65d77295e5 Revised client state with context 2024-10-09 13:51:39 +01:00
Brett Jephson 662df6f9aa Format 2024-10-09 13:46:25 +01:00
Brett Jephson e866080867 Update server choice based on selection 2024-10-09 13:45:06 +01:00
Brett Jephson dce39c4c0d Update server url based on variable selection 2024-10-09 13:41:51 +01:00
Brett Jephson 7b02f72660 Update icon 2024-10-09 13:38:28 +01:00
Brett Jephson 74392eccee Formatting 2024-10-09 13:38:27 +01:00
Brett Jephson 184d616701 Add server url cache to update request code sample 2024-10-09 13:38:27 +01:00
Brett Jephson 9b78692124 Formatting 2024-10-09 13:38:27 +01:00
Brett Jephson f67b143851 Fix type 2024-10-09 13:38:27 +01:00
Brett Jephson f65e3f5fbb Add icons for editing url 2024-10-09 13:38:27 +01:00
Brett Jephson ac492e094d Revised client state with context 2024-10-09 13:38:27 +01:00
Brett Jephson f6823e92bb Changeset 2024-10-09 13:38:27 +01:00
Brett Jephson 9437aa25e8 Format 2024-10-09 13:38:27 +01:00
Brett Jephson 77d64d06b9 Update server choice based on selection 2024-10-09 13:38:27 +01:00
Brett Jephson 1ae1715993 Update server url based on variable selection 2024-10-09 13:38:27 +01:00
Brett Jephson c1193fd2cd Select variables in server url 2024-10-09 13:38:27 +01:00
Brett Jephson 60a9b95a25 Merge branch 'main' into brett/RND-2311-openapi-enum 2024-10-03 13:35:47 +01:00
Brett Jephson 586e66d03a Update comment 2024-10-02 17:32:55 +01:00
Brett Jephson 39d88867ae Formatting 2024-10-02 13:19:06 +01:00
Brett Jephson f98b7b2b00 Merge branch 'main' into brett/RND-2311-openapi-enum 2024-10-02 11:53:49 +01:00
Brett Jephson abf7ec35a6 Remove unused imports 2024-10-02 11:52:56 +01:00
Brett Jephson cb3c1f9f78 Formatting 2024-10-02 11:50:18 +01:00
Brett Jephson ce890c609c Refactor - naming and comments 2024-10-02 11:44:10 +01:00
Brett Jephson 5b933777e9 Merge remote-tracking branch 'origin/main' into brett/RND-2311-openapi-enum-og 2024-10-01 16:43:41 +01:00
Brett Jephson 8d1c349ed1 Don't make server url editable if no update function or context 2024-09-25 09:36:37 +01:00
Brett Jephson 4026fc1339 Merge branch 'main' into brett/RND-2311-openapi-enum 2024-09-25 09:17:21 +01:00
Brett Jephson 4b9f206e50 Merge branch 'main' into brett/RND-2311-openapi-enum 2024-09-25 08:35:18 +01:00
Brett Jephson f6b75e7dbd Self review changes 2024-09-24 16:35:31 +01:00
Brett Jephson 949f25cfa6 Merge branch 'brett/RND-2311-openapi-enum' of github.com:GitbookIO/gitbook into brett/RND-2311-openapi-enum 2024-09-24 16:26:52 +01:00
Brett Jephson 0b44528a1c Merge branch 'main' into brett/RND-2311-openapi-enum 2024-09-24 16:26:13 +01:00
Brett Jephson ea4f70ec88 Update icon 2024-09-24 16:25:35 +01:00
Brett Jephson 768b555f66 Formatting 2024-09-24 16:25:35 +01:00
Brett Jephson 96d5537087 Add server url cache to update request code sample 2024-09-24 16:25:35 +01:00
Brett Jephson 28c59d81e7 Formatting 2024-09-24 16:25:35 +01:00
Brett Jephson 3d147902d6 Fix type 2024-09-24 16:25:35 +01:00
Brett Jephson a6adfaaf27 Add icons for editing url 2024-09-24 16:25:35 +01:00
Brett Jephson d29a7a6f15 Revised client state with context 2024-09-24 16:25:35 +01:00
Brett Jephson 7244a0419a Changeset 2024-09-24 16:25:35 +01:00
Brett Jephson c338bca2ba Format 2024-09-24 16:25:35 +01:00
Brett Jephson 4f59d18bfc Update server choice based on selection 2024-09-24 16:25:35 +01:00
Brett Jephson b87ec84fd5 Update server url based on variable selection 2024-09-24 16:25:35 +01:00
Brett Jephson e793a177f6 Select variables in server url 2024-09-24 16:25:35 +01:00
Brett Jephson 9573535eba Update icon 2024-09-24 15:37:31 +01:00
Brett Jephson 087a783574 Formatting 2024-09-24 15:37:31 +01:00
Brett Jephson 3b0f5d82d5 Add server url cache to update request code sample 2024-09-24 15:37:31 +01:00
Brett Jephson 69414bdc52 Formatting 2024-09-24 15:37:31 +01:00
Brett Jephson adaf0deb5b Fix type 2024-09-24 15:37:31 +01:00
Brett Jephson d919d2ccfa Add icons for editing url 2024-09-24 15:37:31 +01:00
Brett Jephson 2d15453e51 Revised client state with context 2024-09-24 15:37:31 +01:00
Brett Jephson ca4518d068 Changeset 2024-09-24 15:37:31 +01:00
Brett Jephson cb454a3c4e Format 2024-09-24 15:37:31 +01:00
Brett Jephson e852206573 Update server choice based on selection 2024-09-24 15:37:31 +01:00
Brett Jephson 7e79e14a62 Update server url based on variable selection 2024-09-24 15:37:31 +01:00
Brett Jephson 4d476a8cbf Select variables in server url 2024-09-24 15:37:31 +01:00
19 changed files with 519 additions and 86 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@gitbook/react-openapi': minor
'gitbook': minor
---
Allow selection of server url
@@ -3,6 +3,7 @@ import { Metadata, Viewport } from 'next';
import { notFound, redirect } from 'next/navigation';
import React from 'react';
import { serverUrlCache } from '@/components/DocumentView/OpenAPI/ServerUrlCache';
import { PageAside } from '@/components/PageAside';
import { PageBody, PageCover } from '@/components/PageBody';
import { PageHrefContext, absoluteHref, pageHref } from '@/lib/links';
@@ -26,6 +27,8 @@ export default async function Page(props: {
}) {
const { params, searchParams } = props;
serverUrlCache.parse(searchParams);
const {
content: contentPointer,
contentTarget,
@@ -7,6 +7,8 @@ import { LoadingPane } from '@/components/primitives';
import { fetchOpenAPIBlock } from '@/lib/openapi';
import { tcls } from '@/lib/tailwind';
import OpenAPIClientStateContainer from './OpenAPIClientStateContainer';
import { serverUrlCache } from './ServerUrlCache';
import { BlockProps } from '../Block';
import { PlainCodeBlock } from '../CodeBlock';
@@ -17,7 +19,7 @@ import './scalar.css';
* Render an OpenAPI block.
*/
export async function OpenAPI(props: BlockProps<DocumentBlockSwagger>) {
const { block, style } = props;
const { style } = props;
return (
<div className={tcls('w-full', 'flex', 'flex-row', style, 'max-w-full')}>
<React.Suspense fallback={<OpenAPIFallback />}>
@@ -45,21 +47,30 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
return null;
}
// To update the code sample we need to re-render the server component
// so reading the cached value from search params
const serverUrl = serverUrlCache.get('serverUrl');
return (
<OpenAPIOperation
data={data}
context={{
icons: {
chevronDown: <Icon icon="chevron-down" />,
chevronRight: <Icon icon="chevron-right" />,
},
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
id: block.meta?.id,
blockKey: block.key,
}}
className="openapi-block"
/>
<OpenAPIClientStateContainer block={block} servers={data.servers}>
<OpenAPIOperation
data={data}
context={{
icons: {
chevronDown: <Icon icon="chevron-down" />,
chevronRight: <Icon icon="chevron-right" />,
edit: <Icon icon="edit" />,
editDone: <Icon icon="check" />,
},
CodeBlock: PlainCodeBlock,
defaultInteractiveOpened: context.mode === 'print',
id: block.meta?.id,
blockKey: block.key,
serverUrl,
}}
className="openapi-block"
/>
</OpenAPIClientStateContainer>
);
}
@@ -0,0 +1,42 @@
'use client';
import { DocumentBlock } from '@gitbook/api';
import { OpenAPIClientState } from '@gitbook/react-openapi/client';
import { useRouter, useSearchParams } from 'next/navigation';
import { OpenAPIV3 } from 'openapi-types';
import * as React from 'react';
/**
* Client component that wraps `OpenAPIClientState` so we can
* use some hooks (e.g. useRouter) in the `onUpdate` callback.
*/
export default function OpenAPIClientStateContainer(props: {
children: React.ReactNode;
block: DocumentBlock;
servers: OpenAPIV3.ServerObject[];
}) {
const { block, children, servers } = props;
const [isPending, startTransition] = React.useTransition();
const router = useRouter();
const searchParams = useSearchParams();
return (
<OpenAPIClientState
isPending={isPending}
servers={servers}
params={
searchParams.get('block') === block.key
? Object.fromEntries(searchParams.entries())
: undefined
}
onUpdate={async (params) => {
startTransition(() => {
const queryParams = new URLSearchParams(params ?? '');
router.replace(`?${queryParams}`, { scroll: false });
});
}}
>
{children}
</OpenAPIClientState>
);
}
@@ -0,0 +1,5 @@
import { createSearchParamsCache, parseAsString } from 'nuqs/server';
export const serverUrlCache = createSearchParamsCache({
serverUrl: parseAsString,
});
@@ -57,7 +57,7 @@
/** URL */
.openapi-url {
@apply font-mono text-sm text-dark/8 dark:text-light/8;
@apply flex items-center font-mono text-sm text-dark/8 dark:text-light/8;
}
.openapi-url-var {
@@ -364,3 +364,24 @@
.openapi-markdown > *:last-child {
@apply mb-0;
}
.openapi-select-button,
.openapi-edit-button {
@apply p-0.5 inline-flex text-dark/6 dark:text-light/6 hover:opacity-8;
}
.openapi-edit-button {
@apply p-0.5 ml-4 size-4;
}
.openapi-edit-button > * {
@apply size-full;
}
.openapi-select-button {
@apply leading-[1cap] disabled:opacity-5;
}
.openapi-pending {
@apply opacity-5;
}
@@ -50,7 +50,6 @@ export function PageBody(props: {
const language = getSpaceLanguage(customization);
const updatedAt = page.updatedAt ?? page.createdAt;
const shouldHighlightCode = createHighlightingContext();
return (
<>
<main
+2 -1
View File
@@ -6,7 +6,8 @@
"types": "./dist/index.d.ts",
"development": "./src/index.ts",
"default": "./dist/index.js"
}
},
"./client": "./src/client.ts"
},
"version": "0.6.0",
"dependencies": {
@@ -0,0 +1,92 @@
'use client';
import * as React from 'react';
import { getServersURL } from './utils';
import { OpenAPIV3 } from 'openapi-types';
type OpenAPIClientStateContextProps = {
/**
* Whether client state updates are in a pending state,
* i.e. is a transition in progress.
*/
isPending?: boolean;
/**
* The server url
*/
serverUrl?: string;
/**
* The current state
*/
state?: Record<string, string> | null;
/**
* Callback for when the client state is updated
*/
onUpdate: (params: Record<string, string> | null) => void;
};
const OpenAPIClientStateContext = React.createContext<OpenAPIClientStateContextProps | null>(null);
export function useOpenAPIClientState() {
return React.useContext(OpenAPIClientStateContext);
}
/**
* Control client state for an OpenAPI operation
*/
export function OpenAPIClientState(props: {
children: React.ReactNode;
servers: OpenAPIV3.ServerObject[];
isPending?: boolean;
params?: Record<string, string>;
onUpdate: OpenAPIClientStateContextProps['onUpdate'];
}) {
const { children, servers, isPending, params, onUpdate } = props;
const clientState = React.useMemo(() => {
if (!params) {
return null;
}
return parseClientStateModifiers(servers, params);
}, [servers, params]);
const serverUrl = getServersURL(servers, clientState ?? undefined);
return (
<OpenAPIClientStateContext.Provider
value={{
isPending,
state: clientState,
serverUrl,
onUpdate,
}}
>
{children}
</OpenAPIClientStateContext.Provider>
);
}
function parseClientStateModifiers(
servers: OpenAPIV3.ServerObject[],
params: Record<string, string>,
) {
if (!servers) {
return null;
}
const serverQueryParam = params['server'];
const serverIndex =
serverQueryParam && !isNaN(Number(serverQueryParam))
? Math.max(0, Math.min(Number(serverQueryParam), servers.length - 1))
: 0;
const server = servers[serverIndex];
return server
? Object.keys(server.variables ?? {}).reduce<Record<string, string>>(
(result, key) => {
const selection = params[key];
if (!isNaN(Number(selection))) {
result[key] = selection;
}
return result;
},
{ server: `${serverIndex}`, edit: params['edit'] },
)
: null;
}
@@ -4,10 +4,9 @@ import { CodeSampleInput, codeSampleGenerators } from './code-samples';
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
import { InteractiveSection } from './InteractiveSection';
import { getServersURL } from './OpenAPIServerURL';
import { ScalarApiButton } from './ScalarApiButton';
import { OpenAPIContextProps } from './types';
import { noReference } from './utils';
import { getServersURL, noReference } from './utils';
/**
* Display code samples to execute the operation.
@@ -49,14 +48,11 @@ export function OpenAPICodeSample(props: {
}
});
const serverUrl = context.serverUrl ?? getServersURL(data.servers);
const requestBody = noReference(data.operation.requestBody);
const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined;
const input: CodeSampleInput = {
url:
getServersURL(data.servers) +
data.path +
(searchParams.size ? `?${searchParams.toString()}` : ''),
url: serverUrl + data.path + (searchParams.size ? `?${searchParams.toString()}` : ''),
method: data.method,
body: requestBodyContent
? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true })
@@ -37,7 +37,7 @@ export function OpenAPIOperation(props: {
{operation.description ? (
<Markdown className="openapi-description" source={operation.description} />
) : null}
<div className="openapi-target">
<div className="openapi-target flex items-center">
<span
className={classNames(
'openapi-method',
@@ -47,8 +47,11 @@ export function OpenAPIOperation(props: {
{method.toUpperCase()}
</span>
<span className="openapi-url">
<OpenAPIServerURL servers={servers} />
{path}
<OpenAPIServerURL
servers={servers}
context={clientContext}
path={path}
/>
</span>
</div>
</div>
+49 -53
View File
@@ -1,66 +1,62 @@
'use client';
import * as React from 'react';
import { OpenAPIV3 } from 'openapi-types';
import classNames from 'classnames';
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
import { OpenAPIClientContext } from './types';
import { ServerURLForm } from './OpenAPIServerURLForm';
import { useOpenAPIClientState } from './OpenAPIClientStateContext';
import { parseServerURL } from './utils';
/**
* Show the url of the server with variables replaced by their default values.
* Show the url of the server, where there are variables they are replaced by their default values.
* If a stateContext is provided with an `onUpdate` callback then the variables can be edited and
* if there are multiple servers available they can be selected.
*/
export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) {
const { servers } = props;
const server = servers[0];
export function OpenAPIServerURL(props: {
servers: OpenAPIV3.ServerObject[];
context: OpenAPIClientContext;
path?: string;
}) {
const { path, servers, context } = props;
const stateContext = useOpenAPIClientState();
const serverIndex = !isNaN(Number(stateContext?.state?.server))
? Number(stateContext?.state?.server)
: 0;
const server = servers[serverIndex];
const parts = parseServerURL(server?.url ?? '');
if (!server) {
return null;
}
return (
<span>
{parts.map((part, i) => {
if (part.kind === 'text') {
return <span key={i}>{part.text}</span>;
} else {
if (!server.variables?.[part.name]) {
return <span key={i}>{`{${part.name}}`}</span>;
}
<ServerURLForm context={context} servers={servers} serverIndex={serverIndex}>
<span className={classNames(stateContext?.isPending && 'openapi-pending')}>
{parts.map((part, i) => {
if (part.kind === 'text') {
return <span key={i}>{part.text}</span>;
} else {
if (!server.variables?.[part.name]) {
return <span key={i}>{`{${part.name}}`}</span>;
}
return (
<OpenAPIServerURLVariable
key={i}
name={part.name}
variable={server.variables[part.name]}
/>
);
}
})}
</span>
return (
<OpenAPIServerURLVariable
key={i}
name={part.name}
variable={server.variables[part.name]}
selectionIndex={Number(stateContext?.state?.[part.name])}
selectable={Boolean(stateContext?.state?.edit)}
/>
);
}
})}
{path}
</span>
</ServerURLForm>
);
}
/**
* Get the default URL for the server.
*/
export function getServersURL(servers: OpenAPIV3.ServerObject[]): string {
const server = servers[0];
const parts = parseServerURL(server?.url ?? '');
return parts
.map((part) => {
if (part.kind === 'text') {
return part.text;
} else {
return server.variables?.[part.name]?.default ?? `{${part.name}}`;
}
})
.join('');
}
function parseServerURL(url: string) {
const parts = url.split(/{([^}]+)}/g);
const result: Array<{ kind: 'variable'; name: string } | { kind: 'text'; text: string }> = [];
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 0) {
result.push({ kind: 'text', text: parts[i] });
} else {
result.push({ kind: 'variable', name: parts[i] });
}
}
return result;
}
@@ -0,0 +1,101 @@
'use client';
import * as React from 'react';
import { OpenAPIClientContext } from './types';
import { OpenAPIV3 } from 'openapi-types';
import { ServerSelector } from './ServerSelector';
import { useOpenAPIClientState } from './OpenAPIClientStateContext';
import { getServersURL } from './utils';
export function ServerURLForm(props: {
children: React.ReactNode;
context: OpenAPIClientContext;
servers: OpenAPIV3.ServerObject[];
serverIndex: number;
}) {
const { children, context, servers, serverIndex } = props;
const stateContext = useOpenAPIClientState();
const server = servers[serverIndex];
const formRef = React.useRef<HTMLFormElement>(null);
function switchServer(index: number) {
if (index !== serverIndex) {
update({
server: index ? `${index}` : '0',
...(stateContext?.state?.edit ? { edit: 'true' } : undefined),
});
}
}
function updateServerVariables(formData: FormData) {
const variableKeys = Object.keys(server.variables ?? {});
const variables: Record<string, string> = {};
for (const pair of formData) {
if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) {
variables[pair[0]] = `${pair[1]}`;
}
}
update({
server: formData.has('server') ? `${formData.get('server')}` : '0',
...variables,
...(stateContext?.state?.edit ? { edit: 'true' } : undefined),
});
}
function update(variables?: Record<string, string>) {
if (!context.blockKey) {
return;
}
stateContext?.onUpdate({
block: context.blockKey,
...variables,
});
}
// Only make the server url editable if there is some onUpdate callback
// and if there are variations on the server url (e.g. an array of servers or url variables).
const isEditable = stateContext?.onUpdate && (servers.length > 1 || server.variables);
const isEditing = isEditable && stateContext?.state?.edit;
return (
<form
ref={formRef}
onSubmit={(e) => {
e.preventDefault();
updateServerVariables(new FormData(e.currentTarget));
}}
className="contents"
>
<fieldset disabled={stateContext?.isPending} className="contents">
<input type="hidden" name="block" value={context.blockKey} />
{children}
{isEditing && servers.length > 1 ? (
<ServerSelector
lastIndex={servers.length - 1}
currentIndex={serverIndex}
onChange={switchServer}
/>
) : null}
{isEditable ? (
<button
className="openapi-edit-button ml-2"
onClick={() => {
const state = { ...stateContext?.state };
delete state.edit;
update({
server: `${serverIndex}`,
...state,
...(isEditing
? { serverUrl: getServersURL(servers, state) }
: { edit: 'true' }),
});
}}
title={isEditing ? undefined : 'Try different server options'}
aria-label={isEditing ? 'Clear' : 'Edit'}
>
{isEditing ? context.icons.editDone : context.icons.edit}
</button>
) : null}
</fieldset>
</form>
);
}
@@ -1,5 +1,4 @@
'use client';
import * as React from 'react';
import classNames from 'classnames';
import { OpenAPIV3 } from 'openapi-types';
@@ -10,7 +9,64 @@ import { OpenAPIV3 } from 'openapi-types';
export function OpenAPIServerURLVariable(props: {
name: string;
variable: OpenAPIV3.ServerVariableObject;
selectionIndex?: number;
selectable: boolean;
}) {
const { variable } = props;
const { selectable, selectionIndex, name, variable } = props;
if (variable.enum && variable.enum.length > 0) {
if (!selectable) {
return (
<span className={classNames('openapi-url-var')}>
{!isNaN(Number(selectionIndex))
? variable.enum[Number(selectionIndex)]
: variable.default}
</span>
);
}
return (
<VariableSelector
name={name}
variable={variable}
value={
!isNaN(Number(selectionIndex))
? selectionIndex
: variable.enum.findIndex((v) => v === variable.default)
}
/>
);
}
return <span className={classNames('openapi-url-var')}>{variable.default}</span>;
}
/**
* Render a select if there is an enum for a Server URL variable
*/
function VariableSelector(props: {
value?: number;
name: string;
variable: OpenAPIV3.ServerVariableObject;
}) {
const { value, name, variable } = props;
return (
<select
name={name}
onChange={(e) => {
e.preventDefault();
e.currentTarget.form?.requestSubmit();
}}
className={classNames('openapi-select')}
value={value}
>
{variable.enum?.map((value: string, index: number) => {
return (
<option key={value} value={index}>
{value}
</option>
);
}) ?? null}
</select>
);
}
@@ -12,6 +12,7 @@ import {
import React from 'react';
import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation';
import { useOpenAPIClientState } from './OpenAPIClientStateContext';
const ApiClientReact = React.lazy(async () => {
const mod = await import('@scalar/api-client-react');
@@ -58,6 +59,8 @@ export function ScalarApiButton(props: {
export function ScalarApiClient(props: { children: React.ReactNode }) {
const { children } = props;
const stateCtx = useOpenAPIClientState();
const [active, setActive] = React.useState<null | {
operationData: OpenAPIOperationData | null;
}>(null);
@@ -125,12 +128,12 @@ export function ScalarApiClient(props: { children: React.ReactNode }) {
headers: request.headers.map((header: Header) => {
return { ...header, enabled: true };
}),
url: operationData.servers[0]?.url,
url: stateCtx?.serverUrl ?? operationData.servers[0]?.url,
body: request.postData?.text,
};
return data;
}, [active]);
}, [active, stateCtx?.serverUrl]);
return (
<ScalarContext.Provider value={open}>
@@ -0,0 +1,48 @@
'use client';
import * as React from 'react';
export function ServerSelector(props: {
currentIndex: number;
lastIndex: number;
onChange: (value: number) => void;
}) {
const { currentIndex, onChange, lastIndex } = props;
const [index, setIndex] = React.useState(currentIndex);
React.useEffect(() => {
onChange(index);
}, [index]);
return (
<span className="inline-flex pl-4 gap-2">
<input type="hidden" value={`${index}`} name="server" />
<button
className="openapi-select-button"
disabled={index === 0}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIndex((index) => index - 1);
}}
type="button"
aria-label="Previous"
>
</button>
<button
className="openapi-select-button"
disabled={index >= lastIndex}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
setIndex((index) => index + 1);
}}
type="button"
aria-label="Next"
>
</button>
</span>
);
}
+2
View File
@@ -0,0 +1,2 @@
'use client';
export { OpenAPIClientState } from './OpenAPIClientStateContext';
+11 -1
View File
@@ -8,6 +8,8 @@ export interface OpenAPIClientContext {
icons: {
chevronDown: React.ReactNode;
chevronRight: React.ReactNode;
edit: React.ReactNode;
editDone: React.ReactNode;
};
/**
@@ -15,12 +17,20 @@ export interface OpenAPIClientContext {
* @default false
*/
defaultInteractiveOpened?: boolean;
/**
* The key of the block
*/
blockKey?: string;
/** Optional id attached to the OpenAPI Operation heading and used as an anchor */
/**
* Optional id attached to the OpenAPI Operation heading and used as an anchor
*/
id?: string;
/**
* Optional serverUrl to use with OpenAPI operations
*/
serverUrl?: string | null;
}
export interface OpenAPIFetcher {
+38
View File
@@ -11,3 +11,41 @@ export function noReference<T>(input: T | OpenAPIV3.ReferenceObject): T {
export function createStateKey(key: string, scope?: string) {
return scope ? `${scope}_${key}` : key;
}
/**
* Get the default URL for the server.
*/
export function getServersURL(
servers: OpenAPIV3.ServerObject[],
selectors?: Record<string, string>,
): string {
const serverIndex =
selectors && !isNaN(Number(selectors.server)) ? Number(selectors.server) : 0;
const server = servers[serverIndex];
const parts = parseServerURL(server?.url ?? '');
return parts
.map((part) => {
if (part.kind === 'text') {
return part.text;
} else {
return selectors && !isNaN(Number(selectors[part.name]))
? server.variables?.[part.name]?.enum?.[Number(selectors[part.name])]
: (server.variables?.[part.name]?.default ?? `{${part.name}}`);
}
})
.join('');
}
export function parseServerURL(url: string) {
const parts = url.split(/{([^}]+)}/g);
const result: Array<{ kind: 'variable'; name: string } | { kind: 'text'; text: string }> = [];
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 0) {
result.push({ kind: 'text', text: parts[i] });
} else {
result.push({ kind: 'variable', name: parts[i] });
}
}
return result;
}