From 4d476a8cbf5b5ec6b9a61b92be59335c7b6ae618 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Tue, 27 Aug 2024 19:03:20 +0100 Subject: [PATCH 01/13] Select variables in server url --- .../src/OpenAPIServerURLVariable.tsx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx index 408c84117..7bf93c997 100644 --- a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx @@ -12,5 +12,25 @@ export function OpenAPIServerURLVariable(props: { variable: OpenAPIV3.ServerVariableObject; }) { const { variable } = props; + + if (variable.enum && variable.enum.length > 0) { + return (); + + } return {variable.default}; } From 7e79e14a6214683724590f276ce4417a12da0687 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Tue, 3 Sep 2024 10:11:58 +0100 Subject: [PATCH 02/13] Update server url based on variable selection --- .../(content)/[[...pathname]]/page.tsx | 1 + .../components/DocumentView/DocumentView.tsx | 2 + .../DocumentView/OpenAPI/OpenAPI.tsx | 31 ++++++++- .../src/components/PageBody/PageBody.tsx | 4 +- .../react-openapi/src/OpenAPICodeSample.tsx | 2 +- .../react-openapi/src/OpenAPIOperation.tsx | 22 ++++++- .../react-openapi/src/OpenAPIServerURL.tsx | 27 +++++--- .../src/OpenAPIServerURLForm.tsx | 43 ++++++++++++ .../src/OpenAPIServerURLVariable.tsx | 65 +++++++++++++------ packages/react-openapi/src/types.ts | 4 ++ 10 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 packages/react-openapi/src/OpenAPIServerURLForm.tsx diff --git a/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx b/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx index b5ab1424e..84a925860 100644 --- a/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx +++ b/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx @@ -90,6 +90,7 @@ export default async function Page(props: { // Display the page feedback in the page footer if the aside is not visible withPageFeedback && !page.layout.outline } + searchParams={searchParams} /> {page.layout.outline ? ( boolean; + + searchParams?: Record; } export interface DocumentContextProps { diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 014a53193..ee96a211e 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -1,6 +1,6 @@ import { DocumentBlockSwagger } from '@gitbook/api'; import { Icon } from '@gitbook/icons'; -import { OpenAPIOperation } from '@gitbook/react-openapi'; +import { OpenAPIOperation, OpenAPIOperationData } from '@gitbook/react-openapi'; import React from 'react'; import { LoadingPane } from '@/components/primitives'; @@ -45,6 +45,10 @@ async function OpenAPIBody(props: BlockProps) { return null; } + const enumSelectors = + context.searchParams && context.searchParams.block === block.key + ? parseModifiers(data, context.searchParams) + : undefined; return ( ) { CodeBlock: PlainCodeBlock, defaultInteractiveOpened: context.mode === 'print', id: block.meta?.id, + enumSelectors, blockKey: block.key, }} className="openapi-block" @@ -91,3 +96,27 @@ function OpenAPIFallback() { ); } + +function parseModifiers(data: OpenAPIOperationData, params: Record) { + if (!data) { + return; + } + const { servers } = params; + const serverIndex = + servers && !isNaN(Number(servers)) + ? Math.min(0, Math.max(Number(servers), servers.length - 1)) + : 0; + const server = data.servers[serverIndex]; + if (server && server.variables) { + return Object.keys(server.variables).reduce>( + (result, key) => { + const selection = Number(params[key]); + if (!isNaN(selection)) { + result[key] = selection; + } + return result; + }, + { servers: serverIndex }, + ); + } +} diff --git a/packages/gitbook/src/components/PageBody/PageBody.tsx b/packages/gitbook/src/components/PageBody/PageBody.tsx index 05440f9d9..98dd20f97 100644 --- a/packages/gitbook/src/components/PageBody/PageBody.tsx +++ b/packages/gitbook/src/components/PageBody/PageBody.tsx @@ -34,6 +34,7 @@ export function PageBody(props: { document: JSONDocument | null; context: ContentRefContext; withPageFeedback: boolean; + searchParams: Record; }) { const { space, @@ -44,6 +45,7 @@ export function PageBody(props: { page, document, withPageFeedback, + searchParams, } = props; const asFullWidth = document ? hasFullWidthBlock(document) : false; @@ -54,7 +56,6 @@ export function PageBody(props: { 'siteId' in contentPointer ? { organizationId: contentPointer.organizationId, siteId: contentPointer.siteId } : undefined; - return ( <>
resolveContentRef(ref, context, options), shouldHighlightCode, + searchParams, }} /> ) : ( diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index 265d57207..670d2712d 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -54,7 +54,7 @@ export function OpenAPICodeSample(props: { const input: CodeSampleInput = { url: - getServersURL(data.servers) + + getServersURL(data.servers, context.enumSelectors) + data.path + (searchParams.size ? `?${searchParams.toString()}` : ''), method: data.method, diff --git a/packages/react-openapi/src/OpenAPIOperation.tsx b/packages/react-openapi/src/OpenAPIOperation.tsx index 365cede51..a6f495097 100644 --- a/packages/react-openapi/src/OpenAPIOperation.tsx +++ b/packages/react-openapi/src/OpenAPIOperation.tsx @@ -5,7 +5,7 @@ import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation'; import { Markdown } from './Markdown'; import { OpenAPICodeSample } from './OpenAPICodeSample'; import { OpenAPIResponseExample } from './OpenAPIResponseExample'; -import { OpenAPIServerURL } from './OpenAPIServerURL'; +import { getServersURL, OpenAPIServerURL } from './OpenAPIServerURL'; import { OpenAPISpec } from './OpenAPISpec'; import { OpenAPIClientContext, OpenAPIContextProps } from './types'; import { ScalarApiClient } from './ScalarApiButton'; @@ -13,7 +13,7 @@ import { ScalarApiClient } from './ScalarApiButton'; /** * Display an interactive OpenAPI operation. */ -export function OpenAPIOperation(props: { +export async function OpenAPIOperation(props: { className?: string; data: OpenAPIOperationData; context: OpenAPIContextProps; @@ -25,8 +25,10 @@ export function OpenAPIOperation(props: { defaultInteractiveOpened: context.defaultInteractiveOpened, icons: context.icons, blockKey: context.blockKey, + enumSelectors: context.enumSelectors, }; + const config = await getConfiguration(context); return (
@@ -47,7 +49,7 @@ export function OpenAPIOperation(props: { {method.toUpperCase()} - + {path}
@@ -67,3 +69,17 @@ export function OpenAPIOperation(props: {
); } + +async function getConfiguration(context: OpenAPIContextProps) { + const response = await fetch(context.specUrl); + const doc = await response.json(); + + return { + spec: { + content: { + ...doc, + servers: [{ url: getServersURL(doc.servers, context.enumSelectors) }], + }, + }, + }; +} diff --git a/packages/react-openapi/src/OpenAPIServerURL.tsx b/packages/react-openapi/src/OpenAPIServerURL.tsx index e81fb47ba..79973d621 100644 --- a/packages/react-openapi/src/OpenAPIServerURL.tsx +++ b/packages/react-openapi/src/OpenAPIServerURL.tsx @@ -1,18 +1,23 @@ import * as React from 'react'; import { OpenAPIV3 } from 'openapi-types'; import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable'; +import { OpenAPIClientContext } from './types'; +import { ServerURLForm } from './OpenAPIServerURLForm'; /** * Show the url of the server with variables replaced by their default values. */ -export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) { - const { servers } = props; - const server = servers[0]; - +export function OpenAPIServerURL(props: { + servers: OpenAPIV3.ServerObject[]; + context: OpenAPIClientContext; +}) { + const { servers, context } = props; + const serverIndex = context.enumSelectors?.servers ?? 0; + const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); return ( - + {parts.map((part, i) => { if (part.kind === 'text') { return {part.text}; @@ -26,18 +31,22 @@ export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[] }) { key={i} name={part.name} variable={server.variables[part.name]} + enumIndex={context.enumSelectors?.[part.name]} /> ); } })} - + ); } /** * Get the default URL for the server. */ -export function getServersURL(servers: OpenAPIV3.ServerObject[]): string { +export function getServersURL( + servers: OpenAPIV3.ServerObject[], + selectors?: Record, +): string { const server = servers[0]; const parts = parseServerURL(server?.url ?? ''); @@ -46,7 +55,9 @@ export function getServersURL(servers: OpenAPIV3.ServerObject[]): string { if (part.kind === 'text') { return part.text; } else { - return server.variables?.[part.name]?.default ?? `{${part.name}}`; + return selectors && !isNaN(selectors[part.name]) + ? server.variables?.[part.name]?.enum?.[selectors[part.name]] + : (server.variables?.[part.name]?.default ?? `{${part.name}}`); } }) .join(''); diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx new file mode 100644 index 000000000..565108417 --- /dev/null +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -0,0 +1,43 @@ +'use client'; + +import * as React from 'react'; +import { useRouter } from 'next/navigation'; +import { OpenAPIClientContext } from './types'; +import { OpenAPIV3 } from 'openapi-types'; +import { useApiClientModal } from '@scalar/api-client-react'; + +export function ServerURLForm(props: { + children: React.ReactNode; + context: OpenAPIClientContext; + server: OpenAPIV3.ServerObject; +}) { + const { children, context, server } = props; + const router = useRouter(); + const client = useApiClientModal(); + const [isPending, startTransition] = React.useTransition(); + + function updateServerUrl(formData: FormData) { + startTransition(() => { + if (!server.variables) { + return; + } + let params = new URLSearchParams(`block=${context.blockKey}`); + const variableKeys = Object.keys(server.variables); + for (const pair of formData.entries()) { + if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) { + params.set(pair[0], `${pair[1]}`); + } + } + router.push(`?${params}`, { scroll: false }); + }); + } + + return ( +
+
+ + {children} +
+
+ ); +} diff --git a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx index 7bf93c997..5bc9d62bc 100644 --- a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx @@ -1,8 +1,9 @@ 'use client'; - import * as React from 'react'; +import { useRouter } from 'next/navigation'; import classNames from 'classnames'; import { OpenAPIV3 } from 'openapi-types'; +import { OpenAPIClientContext } from './types'; /** * Interactive component to show the value of a server variable and let the user change it. @@ -10,27 +11,53 @@ import { OpenAPIV3 } from 'openapi-types'; export function OpenAPIServerURLVariable(props: { name: string; variable: OpenAPIV3.ServerVariableObject; + enumIndex?: number; }) { - const { variable } = props; + const { enumIndex, name, variable } = props; if (variable.enum && variable.enum.length > 0) { - return (); - + return ( + v === variable.default) + } + /> + ); } + return {variable.default}; } + +/** + * Render a select if there is an enum for a Server URL variable + */ +function EnumSelect(props: { + value?: number; + name: string; + variable: OpenAPIV3.ServerVariableObject; +}) { + const { value, name, variable } = props; + return ( + + ); +} diff --git a/packages/react-openapi/src/types.ts b/packages/react-openapi/src/types.ts index 156a28ac0..b278f2efd 100644 --- a/packages/react-openapi/src/types.ts +++ b/packages/react-openapi/src/types.ts @@ -21,6 +21,10 @@ export interface OpenAPIClientContext { blockKey?: string; /** Optional id attached to the OpenAPI Operation heading and used as an anchor */ id?: string; + + blockKey?: string; + + enumSelectors?: Record; } export interface OpenAPIFetcher { From e85220657321d5e8c9db15a2b315f6c51e2e47e9 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Thu, 12 Sep 2024 16:46:18 +0100 Subject: [PATCH 03/13] Update server choice based on selection --- .../DocumentView/OpenAPI/OpenAPI.tsx | 13 ++++---- .../components/DocumentView/OpenAPI/style.css | 4 +++ .../react-openapi/src/OpenAPICodeSample.tsx | 1 - .../react-openapi/src/OpenAPIOperation.tsx | 20 ++---------- .../react-openapi/src/OpenAPIServerURL.tsx | 15 +++++---- .../src/OpenAPIServerURLForm.tsx | 32 ++++++++++++------- .../react-openapi/src/ScalarApiButton.tsx | 8 ++--- packages/react-openapi/src/ServerSelector.tsx | 26 +++++++++++++++ packages/react-openapi/src/types.ts | 11 +++++-- 9 files changed, 81 insertions(+), 49 deletions(-) create mode 100644 packages/react-openapi/src/ServerSelector.tsx diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index ee96a211e..438c3ca27 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -49,6 +49,7 @@ async function OpenAPIBody(props: BlockProps) { context.searchParams && context.searchParams.block === block.key ? parseModifiers(data, context.searchParams) : undefined; + return ( >( + if (server) { + return Object.keys(server.variables ?? {}).reduce>( (result, key) => { const selection = Number(params[key]); if (!isNaN(selection)) { @@ -116,7 +117,7 @@ function parseModifiers(data: OpenAPIOperationData, params: Record *:last-child { @apply mb-0; } + +.openapi-server-button { + @apply disabled:opacity-5; +} \ No newline at end of file diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index 670d2712d..eff0c9154 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -51,7 +51,6 @@ export function OpenAPICodeSample(props: { const requestBody = noReference(data.operation.requestBody); const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined; - const input: CodeSampleInput = { url: getServersURL(data.servers, context.enumSelectors) + diff --git a/packages/react-openapi/src/OpenAPIOperation.tsx b/packages/react-openapi/src/OpenAPIOperation.tsx index a6f495097..fb1daa951 100644 --- a/packages/react-openapi/src/OpenAPIOperation.tsx +++ b/packages/react-openapi/src/OpenAPIOperation.tsx @@ -28,9 +28,8 @@ export async function OpenAPIOperation(props: { enumSelectors: context.enumSelectors, }; - const config = await getConfiguration(context); return ( - +

@@ -49,8 +48,7 @@ export async function OpenAPIOperation(props: { {method.toUpperCase()} - - {path} +

@@ -69,17 +67,3 @@ export async function OpenAPIOperation(props: {
); } - -async function getConfiguration(context: OpenAPIContextProps) { - const response = await fetch(context.specUrl); - const doc = await response.json(); - - return { - spec: { - content: { - ...doc, - servers: [{ url: getServersURL(doc.servers, context.enumSelectors) }], - }, - }, - }; -} diff --git a/packages/react-openapi/src/OpenAPIServerURL.tsx b/packages/react-openapi/src/OpenAPIServerURL.tsx index 79973d621..c30517a9d 100644 --- a/packages/react-openapi/src/OpenAPIServerURL.tsx +++ b/packages/react-openapi/src/OpenAPIServerURL.tsx @@ -3,6 +3,7 @@ import { OpenAPIV3 } from 'openapi-types'; import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable'; import { OpenAPIClientContext } from './types'; import { ServerURLForm } from './OpenAPIServerURLForm'; +import { ServerSelector } from './ServerSelector'; /** * Show the url of the server with variables replaced by their default values. @@ -10,16 +11,17 @@ import { ServerURLForm } from './OpenAPIServerURLForm'; export function OpenAPIServerURL(props: { servers: OpenAPIV3.ServerObject[]; context: OpenAPIClientContext; + path?: string; }) { - const { servers, context } = props; - const serverIndex = context.enumSelectors?.servers ?? 0; + const { path, servers, context } = props; + const serverIndex = context.enumSelectors?.server ?? 0; const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); return ( - + {parts.map((part, i) => { - if (part.kind === 'text') { + if (part.kind === 'text') { return {part.text}; } else { if (!server.variables?.[part.name]) { @@ -35,7 +37,7 @@ export function OpenAPIServerURL(props: { /> ); } - })} + })}{path} ); } @@ -47,7 +49,8 @@ export function getServersURL( servers: OpenAPIV3.ServerObject[], selectors?: Record, ): string { - const server = servers[0]; + const serverIndex = selectors && !isNaN(selectors.server) ? Number(selectors.server) : 0; + const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); return parts diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 565108417..2818bbbf6 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -4,25 +4,34 @@ import * as React from 'react'; import { useRouter } from 'next/navigation'; import { OpenAPIClientContext } from './types'; import { OpenAPIV3 } from 'openapi-types'; -import { useApiClientModal } from '@scalar/api-client-react'; +import { ServerSelector } from './ServerSelector'; export function ServerURLForm(props: { children: React.ReactNode; context: OpenAPIClientContext; - server: OpenAPIV3.ServerObject; + servers: OpenAPIV3.ServerObject[]; + serverIndex: number; }) { - const { children, context, server } = props; + const { children, context, servers, serverIndex } = props; const router = useRouter(); - const client = useApiClientModal(); const [isPending, startTransition] = React.useTransition(); + + const server = servers[serverIndex]; + const formRef = React.useRef(null); - function updateServerUrl(formData: FormData) { + function switchServer(index: number) { startTransition(() => { - if (!server.variables) { - return; + if (index !== serverIndex) { + let params = new URLSearchParams(`block=${context.blockKey}&server=${index ?? '0'}`); + router.push(`?${params}`, { scroll: false }); } - let params = new URLSearchParams(`block=${context.blockKey}`); - const variableKeys = Object.keys(server.variables); + }); + } + + function updateServerVariables(formData: FormData) { + startTransition(() => { + let params = new URLSearchParams(`block=${context.blockKey}&server=${formData.get('server') ?? '0'}`); + const variableKeys = Object.keys(server.variables ?? {}); for (const pair of formData.entries()) { if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) { params.set(pair[0], `${pair[1]}`); @@ -33,10 +42,11 @@ export function ServerURLForm(props: { } return ( -
+
- {children} + {children} + { servers.length > 1 ? : null }
); diff --git a/packages/react-openapi/src/ScalarApiButton.tsx b/packages/react-openapi/src/ScalarApiButton.tsx index 02d9ffaa1..60b7f1586 100644 --- a/packages/react-openapi/src/ScalarApiButton.tsx +++ b/packages/react-openapi/src/ScalarApiButton.tsx @@ -55,8 +55,8 @@ export function ScalarApiButton(props: { /** * Wrap the rendering with a context to open the scalar modal. */ -export function ScalarApiClient(props: { children: React.ReactNode }) { - const { children } = props; +export function ScalarApiClient(props: { children: React.ReactNode; serverUrl?: string }) { + const { children, serverUrl } = props; const [active, setActive] = React.useState { return { ...header, enabled: true }; }), - url: operationData.servers[0]?.url, + url: serverUrl ?? operationData.servers[0]?.url, body: request.postData?.text, }; return data; - }, [active]); + }, [active, serverUrl]); return ( diff --git a/packages/react-openapi/src/ServerSelector.tsx b/packages/react-openapi/src/ServerSelector.tsx new file mode 100644 index 000000000..a235b7b67 --- /dev/null +++ b/packages/react-openapi/src/ServerSelector.tsx @@ -0,0 +1,26 @@ +'use client'; + +import * as React from 'react'; + +export function ServerSelector(props: { currentIndex: number; onChange: (value:number) => void; servers: any[] }) { + const { currentIndex, onChange, servers } = props; + const [index, setIndex] = React.useState(currentIndex); + + React.useEffect(() => { + onChange(index); + }, [index]); + + return + + + + ; +} \ No newline at end of file diff --git a/packages/react-openapi/src/types.ts b/packages/react-openapi/src/types.ts index b278f2efd..215d8ef56 100644 --- a/packages/react-openapi/src/types.ts +++ b/packages/react-openapi/src/types.ts @@ -15,15 +15,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; - blockKey?: string; - + /** + * Selectors to update openapi enums, e.g. for server url variables + */ enumSelectors?: Record; } From cb454a3c4ea97eeb4e0d7100922aa82a5e24728e Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Thu, 12 Sep 2024 16:48:47 +0100 Subject: [PATCH 04/13] Format --- .../components/DocumentView/OpenAPI/style.css | 2 +- .../react-openapi/src/OpenAPIOperation.tsx | 6 ++- .../react-openapi/src/OpenAPIServerURL.tsx | 5 +- .../src/OpenAPIServerURLForm.tsx | 18 +++++-- packages/react-openapi/src/ServerSelector.tsx | 52 +++++++++++++------ packages/react-openapi/src/types.ts | 4 +- 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index 1882a423c..6bd0206ad 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -367,4 +367,4 @@ .openapi-server-button { @apply disabled:opacity-5; -} \ No newline at end of file +} diff --git a/packages/react-openapi/src/OpenAPIOperation.tsx b/packages/react-openapi/src/OpenAPIOperation.tsx index fb1daa951..f722ce213 100644 --- a/packages/react-openapi/src/OpenAPIOperation.tsx +++ b/packages/react-openapi/src/OpenAPIOperation.tsx @@ -48,7 +48,11 @@ export async function OpenAPIOperation(props: { {method.toUpperCase()} - + diff --git a/packages/react-openapi/src/OpenAPIServerURL.tsx b/packages/react-openapi/src/OpenAPIServerURL.tsx index c30517a9d..14d245aa4 100644 --- a/packages/react-openapi/src/OpenAPIServerURL.tsx +++ b/packages/react-openapi/src/OpenAPIServerURL.tsx @@ -21,7 +21,7 @@ export function OpenAPIServerURL(props: { return ( {parts.map((part, i) => { - if (part.kind === 'text') { + if (part.kind === 'text') { return {part.text}; } else { if (!server.variables?.[part.name]) { @@ -37,7 +37,8 @@ export function OpenAPIServerURL(props: { /> ); } - })}{path} + })} + {path} ); } diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 2818bbbf6..5b16213a7 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -15,14 +15,16 @@ export function ServerURLForm(props: { const { children, context, servers, serverIndex } = props; const router = useRouter(); const [isPending, startTransition] = React.useTransition(); - + const server = servers[serverIndex]; const formRef = React.useRef(null); function switchServer(index: number) { startTransition(() => { if (index !== serverIndex) { - let params = new URLSearchParams(`block=${context.blockKey}&server=${index ?? '0'}`); + let params = new URLSearchParams( + `block=${context.blockKey}&server=${index ?? '0'}`, + ); router.push(`?${params}`, { scroll: false }); } }); @@ -30,7 +32,9 @@ export function ServerURLForm(props: { function updateServerVariables(formData: FormData) { startTransition(() => { - let params = new URLSearchParams(`block=${context.blockKey}&server=${formData.get('server') ?? '0'}`); + let params = new URLSearchParams( + `block=${context.blockKey}&server=${formData.get('server') ?? '0'}`, + ); const variableKeys = Object.keys(server.variables ?? {}); for (const pair of formData.entries()) { if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) { @@ -46,7 +50,13 @@ export function ServerURLForm(props: {
{children} - { servers.length > 1 ? : null } + {servers.length > 1 ? ( + + ) : null}
); diff --git a/packages/react-openapi/src/ServerSelector.tsx b/packages/react-openapi/src/ServerSelector.tsx index a235b7b67..53051e3b8 100644 --- a/packages/react-openapi/src/ServerSelector.tsx +++ b/packages/react-openapi/src/ServerSelector.tsx @@ -2,7 +2,11 @@ import * as React from 'react'; -export function ServerSelector(props: { currentIndex: number; onChange: (value:number) => void; servers: any[] }) { +export function ServerSelector(props: { + currentIndex: number; + onChange: (value: number) => void; + servers: any[]; +}) { const { currentIndex, onChange, servers } = props; const [index, setIndex] = React.useState(currentIndex); @@ -10,17 +14,35 @@ export function ServerSelector(props: { currentIndex: number; onChange: (value:n onChange(index); }, [index]); - return - - - - ; -} \ No newline at end of file + return ( + + + + + + ); +} diff --git a/packages/react-openapi/src/types.ts b/packages/react-openapi/src/types.ts index 215d8ef56..90d9889bd 100644 --- a/packages/react-openapi/src/types.ts +++ b/packages/react-openapi/src/types.ts @@ -15,13 +15,13 @@ 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 */ id?: string; From ca4518d068823356ab24a45e1f880260945d4b1a Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Thu, 12 Sep 2024 16:58:10 +0100 Subject: [PATCH 05/13] Changeset --- .changeset/long-shrimps-judge.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/long-shrimps-judge.md diff --git a/.changeset/long-shrimps-judge.md b/.changeset/long-shrimps-judge.md new file mode 100644 index 000000000..edae3be68 --- /dev/null +++ b/.changeset/long-shrimps-judge.md @@ -0,0 +1,6 @@ +--- +'@gitbook/react-openapi': minor +'gitbook': minor +--- + +Allow selection of server url From 2d15453e51a8aa77d2da2d664ba7b170853f2192 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Fri, 20 Sep 2024 09:35:43 +0100 Subject: [PATCH 06/13] Revised client state with context --- .../(content)/[[...pathname]]/page.tsx | 1 - .../components/DocumentView/DocumentView.tsx | 2 - .../DocumentView/OpenAPI/OpenAPI.tsx | 63 +++++------------- .../DocumentView/OpenAPI/OpenAPIContext.tsx | 21 ++++++ .../src/components/PageBody/PageBody.tsx | 3 - packages/react-openapi/package.json | 3 +- .../react-openapi/src/OpenAPICodeSample.tsx | 3 +- .../src/OpenAPIContextProvider.tsx | 52 +++++++++++++++ .../react-openapi/src/OpenAPIOperation.tsx | 5 +- .../react-openapi/src/OpenAPIServerURL.tsx | 50 +++----------- .../src/OpenAPIServerURLForm.tsx | 65 +++++++++++-------- .../src/OpenAPIServerURLVariable.tsx | 19 +++--- .../react-openapi/src/ScalarApiButton.tsx | 12 ++-- packages/react-openapi/src/client.ts | 2 + packages/react-openapi/src/utils.ts | 38 +++++++++++ 15 files changed, 202 insertions(+), 137 deletions(-) create mode 100644 packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx create mode 100644 packages/react-openapi/src/OpenAPIContextProvider.tsx create mode 100644 packages/react-openapi/src/client.ts diff --git a/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx b/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx index 84a925860..b5ab1424e 100644 --- a/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx +++ b/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx @@ -90,7 +90,6 @@ export default async function Page(props: { // Display the page feedback in the page footer if the aside is not visible withPageFeedback && !page.layout.outline } - searchParams={searchParams} /> {page.layout.outline ? ( boolean; - - searchParams?: Record; } export interface DocumentContextProps { diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 438c3ca27..05c094467 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -1,12 +1,13 @@ import { DocumentBlockSwagger } from '@gitbook/api'; import { Icon } from '@gitbook/icons'; -import { OpenAPIOperation, OpenAPIOperationData } from '@gitbook/react-openapi'; +import { OpenAPIOperation } from '@gitbook/react-openapi'; import React from 'react'; import { LoadingPane } from '@/components/primitives'; import { fetchOpenAPIBlock } from '@/lib/openapi'; import { tcls } from '@/lib/tailwind'; +import OpenAPIContext from './OpenAPIContext'; import { BlockProps } from '../Block'; import { PlainCodeBlock } from '../CodeBlock'; @@ -45,27 +46,23 @@ async function OpenAPIBody(props: BlockProps) { return null; } - const enumSelectors = - context.searchParams && context.searchParams.block === block.key - ? parseModifiers(data, context.searchParams) - : undefined; - return ( - , - chevronRight: , - }, - CodeBlock: PlainCodeBlock, - defaultInteractiveOpened: context.mode === 'print', - id: block.meta?.id, - enumSelectors, - blockKey: block.key, - }} - className="openapi-block" - /> + + , + chevronRight: , + }, + CodeBlock: PlainCodeBlock, + defaultInteractiveOpened: context.mode === 'print', + id: block.meta?.id, + blockKey: block.key + }} + className="openapi-block" + /> + ); } @@ -97,27 +94,3 @@ function OpenAPIFallback() { ); } - -function parseModifiers(data: OpenAPIOperationData, params: Record) { - if (!data) { - return; - } - const { server: serverQueryParam } = params; - const serverIndex = - serverQueryParam && !isNaN(Number(serverQueryParam)) - ? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1)) - : 0; - const server = data.servers[serverIndex]; - if (server) { - return Object.keys(server.variables ?? {}).reduce>( - (result, key) => { - const selection = Number(params[key]); - if (!isNaN(selection)) { - result[key] = selection; - } - return result; - }, - { server: serverIndex }, - ); - } -} diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx new file mode 100644 index 000000000..16a48beca --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPIContext.tsx @@ -0,0 +1,21 @@ +'use client' + +import { DocumentBlock } from "@gitbook/api"; +import { OpenAPIOperationData } from "@gitbook/react-openapi"; +import {OpenAPIContextProvider} from "@gitbook/react-openapi/client"; +import { useRouter, useSearchParams } from 'next/navigation'; +import * as React from 'react'; + +export default function OpenAPIContext(props: { children: React.ReactNode; block: DocumentBlock; data: OpenAPIOperationData }) { + const { block, children, data } = props; + const [isPending, startTransition] = React.useTransition(); + const router = useRouter(); + const searchParams = useSearchParams(); + + return { + startTransition(() => { + const queryParams = new URLSearchParams(params ?? ''); + router.replace(`?${queryParams}`, { scroll: false }); + }) + }}>{children} +} \ No newline at end of file diff --git a/packages/gitbook/src/components/PageBody/PageBody.tsx b/packages/gitbook/src/components/PageBody/PageBody.tsx index 98dd20f97..052d9abb2 100644 --- a/packages/gitbook/src/components/PageBody/PageBody.tsx +++ b/packages/gitbook/src/components/PageBody/PageBody.tsx @@ -34,7 +34,6 @@ export function PageBody(props: { document: JSONDocument | null; context: ContentRefContext; withPageFeedback: boolean; - searchParams: Record; }) { const { space, @@ -45,7 +44,6 @@ export function PageBody(props: { page, document, withPageFeedback, - searchParams, } = props; const asFullWidth = document ? hasFullWidthBlock(document) : false; @@ -98,7 +96,6 @@ export function PageBody(props: { resolveContentRef: (ref, options) => resolveContentRef(ref, context, options), shouldHighlightCode, - searchParams, }} /> ) : ( diff --git a/packages/react-openapi/package.json b/packages/react-openapi/package.json index fa08009bc..ba2de7a13 100644 --- a/packages/react-openapi/package.json +++ b/packages/react-openapi/package.json @@ -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": { diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index eff0c9154..b3021263a 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -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. diff --git a/packages/react-openapi/src/OpenAPIContextProvider.tsx b/packages/react-openapi/src/OpenAPIContextProvider.tsx new file mode 100644 index 000000000..3ee32c4f4 --- /dev/null +++ b/packages/react-openapi/src/OpenAPIContextProvider.tsx @@ -0,0 +1,52 @@ +'use client' + +import * as React from 'react'; +import { OpenAPIOperationData } from './fetchOpenAPIOperation'; +import { getServersURL } from './utils'; + +type OpenAPIContextProps = { isPending?: boolean; serverUrl?: string; state?: Record | null, onUpdate: (params: Record | null) => void; } +const OpenAPIContext = React.createContext(null); + +export function useOpenAPIContext() { + return React.useContext(OpenAPIContext); +} + +export function OpenAPIContextProvider(props: { children: React.ReactNode; data: OpenAPIOperationData; isPending?: boolean; params?: Record; onUpdate: OpenAPIContextProps['onUpdate']; }) { + const { children, data, isPending, params, onUpdate } = props; + + const clientState = React.useMemo(() => { + if (!params) { return null; } + return parseClientStateModifiers(data, params); + }, [data, params]); + const serverUrl = getServersURL(data.servers, clientState ?? undefined) + + return {children} +} + + +function parseClientStateModifiers(data: OpenAPIOperationData, params: Record) { + if (!data) { + return null; + } + const serverQueryParam = params['server']; + const serverIndex = + serverQueryParam && !isNaN(Number(serverQueryParam)) + ? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1)) + : 0; + const server = data.servers[serverIndex]; + return server ? Object.keys(server.variables ?? {}).reduce>( + (result, key) => { + const selection = params[key]; + if (!isNaN(Number(selection))) { + result[key] = selection; + } + return result; + }, + { server: `${serverIndex}`, edit: params['edit'] }, + ) : null; +} \ No newline at end of file diff --git a/packages/react-openapi/src/OpenAPIOperation.tsx b/packages/react-openapi/src/OpenAPIOperation.tsx index f722ce213..b0a2b0325 100644 --- a/packages/react-openapi/src/OpenAPIOperation.tsx +++ b/packages/react-openapi/src/OpenAPIOperation.tsx @@ -5,7 +5,7 @@ import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation'; import { Markdown } from './Markdown'; import { OpenAPICodeSample } from './OpenAPICodeSample'; import { OpenAPIResponseExample } from './OpenAPIResponseExample'; -import { getServersURL, OpenAPIServerURL } from './OpenAPIServerURL'; +import { OpenAPIServerURL } from './OpenAPIServerURL'; import { OpenAPISpec } from './OpenAPISpec'; import { OpenAPIClientContext, OpenAPIContextProps } from './types'; import { ScalarApiClient } from './ScalarApiButton'; @@ -25,11 +25,10 @@ export async function OpenAPIOperation(props: { defaultInteractiveOpened: context.defaultInteractiveOpened, icons: context.icons, blockKey: context.blockKey, - enumSelectors: context.enumSelectors, }; return ( - +

diff --git a/packages/react-openapi/src/OpenAPIServerURL.tsx b/packages/react-openapi/src/OpenAPIServerURL.tsx index 14d245aa4..6b90a5c86 100644 --- a/packages/react-openapi/src/OpenAPIServerURL.tsx +++ b/packages/react-openapi/src/OpenAPIServerURL.tsx @@ -1,9 +1,12 @@ +'use client'; + import * as React from 'react'; import { OpenAPIV3 } from 'openapi-types'; import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable'; import { OpenAPIClientContext } from './types'; import { ServerURLForm } from './OpenAPIServerURLForm'; -import { ServerSelector } from './ServerSelector'; +import { useOpenAPIContext } from './OpenAPIContextProvider'; +import { parseServerURL } from './utils'; /** * Show the url of the server with variables replaced by their default values. @@ -14,9 +17,12 @@ export function OpenAPIServerURL(props: { path?: string; }) { const { path, servers, context } = props; - const serverIndex = context.enumSelectors?.server ?? 0; + const ctx = useOpenAPIContext(); + + const serverIndex = !isNaN(Number(ctx?.state?.server)) ? Number(ctx?.state?.server) : 0; const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); + console.log({ ctxState: ctx?.state }) return ( @@ -33,7 +39,8 @@ export function OpenAPIServerURL(props: { key={i} name={part.name} variable={server.variables[part.name]} - enumIndex={context.enumSelectors?.[part.name]} + selectionIndex={Number(ctx?.state?.[part.name])} + selectable={Boolean(ctx?.state?.edit)} /> ); } @@ -42,40 +49,3 @@ export function OpenAPIServerURL(props: { ); } - -/** - * Get the default URL for the server. - */ -export function getServersURL( - servers: OpenAPIV3.ServerObject[], - selectors?: Record, -): string { - const serverIndex = selectors && !isNaN(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(selectors[part.name]) - ? server.variables?.[part.name]?.enum?.[selectors[part.name]] - : (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; -} diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 5b16213a7..36ee6df57 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -1,10 +1,10 @@ 'use client'; import * as React from 'react'; -import { useRouter } from 'next/navigation'; import { OpenAPIClientContext } from './types'; import { OpenAPIV3 } from 'openapi-types'; import { ServerSelector } from './ServerSelector'; +import { useOpenAPIContext } from './OpenAPIContextProvider'; export function ServerURLForm(props: { children: React.ReactNode; @@ -13,50 +13,59 @@ export function ServerURLForm(props: { serverIndex: number; }) { const { children, context, servers, serverIndex } = props; - const router = useRouter(); - const [isPending, startTransition] = React.useTransition(); - + const ctx = useOpenAPIContext(); const server = servers[serverIndex]; const formRef = React.useRef(null); function switchServer(index: number) { - startTransition(() => { - if (index !== serverIndex) { - let params = new URLSearchParams( - `block=${context.blockKey}&server=${index ?? '0'}`, - ); - router.push(`?${params}`, { scroll: false }); - } - }); + if (index !== serverIndex) { + update({ + server: `${index}` ?? '0', + ...(ctx?.state?.edit ? { edit: 'true' } : undefined) + }); + } } function updateServerVariables(formData: FormData) { - startTransition(() => { - let params = new URLSearchParams( - `block=${context.blockKey}&server=${formData.get('server') ?? '0'}`, - ); - const variableKeys = Object.keys(server.variables ?? {}); - for (const pair of formData.entries()) { - if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) { - params.set(pair[0], `${pair[1]}`); - } + const variableKeys = Object.keys(server.variables ?? {}); + const variables: Record = {}; + for (const pair of formData) { + if (variableKeys.includes(pair[0]) && !isNaN(Number(pair[1]))) { + variables[pair[0]] = `${pair[1]}`; } - router.push(`?${params}`, { scroll: false }); + } + update({ + server: `${formData.get('server')}` ?? '0', + ...variables, + ...(ctx?.state?.edit ? { edit: 'true' } : undefined) + }); + } + + function update(variables?: Record) { + if (!context.blockKey) { return; } + ctx?.onUpdate({ + block: context.blockKey, + ...variables, }); } return ( -
-
+ { e.preventDefault(); updateServerVariables(new FormData(e.currentTarget)); }} className="contents"> +
{children} - {servers.length > 1 ? ( + {ctx?.state?.edit && servers.length > 1 ? ( ) : null} +
); diff --git a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx index 5bc9d62bc..83a316269 100644 --- a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx @@ -1,9 +1,7 @@ 'use client'; import * as React from 'react'; -import { useRouter } from 'next/navigation'; import classNames from 'classnames'; import { OpenAPIV3 } from 'openapi-types'; -import { OpenAPIClientContext } from './types'; /** * Interactive component to show the value of a server variable and let the user change it. @@ -11,18 +9,23 @@ import { OpenAPIClientContext } from './types'; export function OpenAPIServerURLVariable(props: { name: string; variable: OpenAPIV3.ServerVariableObject; - enumIndex?: number; + selectionIndex?: number; + selectable: boolean; }) { - const { enumIndex, name, variable } = props; + const { selectable, selectionIndex, name, variable } = props; if (variable.enum && variable.enum.length > 0) { + if (!selectable) { + return {!isNaN(Number(selectionIndex)) ? variable.enum[Number(selectionIndex)] : variable.default}; + } + return ( - v === variable.default) } /> @@ -35,7 +38,7 @@ export function OpenAPIServerURLVariable(props: { /** * Render a select if there is an enum for a Server URL variable */ -function EnumSelect(props: { +function VariableSelector(props: { value?: number; name: string; variable: OpenAPIV3.ServerVariableObject; diff --git a/packages/react-openapi/src/ScalarApiButton.tsx b/packages/react-openapi/src/ScalarApiButton.tsx index 60b7f1586..dcf1d7a70 100644 --- a/packages/react-openapi/src/ScalarApiButton.tsx +++ b/packages/react-openapi/src/ScalarApiButton.tsx @@ -12,6 +12,8 @@ import { import React from 'react'; import { OpenAPIOperationData, fromJSON } from './fetchOpenAPIOperation'; +import { useOpenAPIContext } from './OpenAPIContextProvider'; +import { getServersURL } from './utils'; const ApiClientReact = React.lazy(async () => { const mod = await import('@scalar/api-client-react'); @@ -55,8 +57,10 @@ export function ScalarApiButton(props: { /** * Wrap the rendering with a context to open the scalar modal. */ -export function ScalarApiClient(props: { children: React.ReactNode; serverUrl?: string }) { - const { children, serverUrl } = props; +export function ScalarApiClient(props: { children: React.ReactNode; }) { + const { children } = props; + + const ctx = useOpenAPIContext(); const [active, setActive] = React.useState { return { ...header, enabled: true }; }), - url: serverUrl ?? operationData.servers[0]?.url, + url: ctx?.serverUrl ?? operationData.servers[0]?.url, body: request.postData?.text, }; return data; - }, [active, serverUrl]); + }, [active, ctx?.state?.serverUrl]); return ( diff --git a/packages/react-openapi/src/client.ts b/packages/react-openapi/src/client.ts new file mode 100644 index 000000000..923168320 --- /dev/null +++ b/packages/react-openapi/src/client.ts @@ -0,0 +1,2 @@ +'use client'; +export {OpenAPIContextProvider} from './OpenAPIContextProvider'; \ No newline at end of file diff --git a/packages/react-openapi/src/utils.ts b/packages/react-openapi/src/utils.ts index 5bc3ee092..c94d11df2 100644 --- a/packages/react-openapi/src/utils.ts +++ b/packages/react-openapi/src/utils.ts @@ -11,3 +11,41 @@ export function noReference(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 { + 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; +} \ No newline at end of file From d919d2ccfa56720af02a51332e312daac79f3805 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Fri, 20 Sep 2024 11:27:27 +0100 Subject: [PATCH 07/13] Add icons for editing url --- .../components/DocumentView/OpenAPI/OpenAPI.tsx | 2 ++ .../components/DocumentView/OpenAPI/style.css | 17 ++++++++++++++--- packages/react-openapi/src/OpenAPIOperation.tsx | 2 +- .../react-openapi/src/OpenAPIServerURLForm.tsx | 5 +++-- packages/react-openapi/src/ServerSelector.tsx | 4 ++-- packages/react-openapi/src/types.ts | 2 ++ 6 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 05c094467..1c907c928 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -54,6 +54,8 @@ async function OpenAPIBody(props: BlockProps) { icons: { chevronDown: , chevronRight: , + edit: , + clear: }, CodeBlock: PlainCodeBlock, defaultInteractiveOpened: context.mode === 'print', diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css index 6bd0206ad..cd2c8b5ca 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/style.css +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/style.css @@ -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 { @@ -365,6 +365,17 @@ @apply mb-0; } -.openapi-server-button { - @apply disabled:opacity-5; +.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; } diff --git a/packages/react-openapi/src/OpenAPIOperation.tsx b/packages/react-openapi/src/OpenAPIOperation.tsx index b0a2b0325..e4a643303 100644 --- a/packages/react-openapi/src/OpenAPIOperation.tsx +++ b/packages/react-openapi/src/OpenAPIOperation.tsx @@ -37,7 +37,7 @@ export async function OpenAPIOperation(props: { {operation.description ? ( ) : null} -
+
1 || server.variables; return (
{ e.preventDefault(); updateServerVariables(new FormData(e.currentTarget)); }} className="contents">
@@ -61,11 +62,11 @@ export function ServerURLForm(props: { onChange={switchServer} /> ) : null} - + }} title={ctx?.state?.edit ? undefined : "Try different server options"} aria-label={ctx?.state?.edit ? "Clear" : "Edit"}>{ctx?.state?.edit ? context.icons.clear : context.icons.edit} : null}
); diff --git a/packages/react-openapi/src/ServerSelector.tsx b/packages/react-openapi/src/ServerSelector.tsx index 53051e3b8..bd2997619 100644 --- a/packages/react-openapi/src/ServerSelector.tsx +++ b/packages/react-openapi/src/ServerSelector.tsx @@ -18,7 +18,7 @@ export function ServerSelector(props: { : null} + {isEditable ? ( + + ) : null}
); diff --git a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx index 83a316269..27e82a00c 100644 --- a/packages/react-openapi/src/OpenAPIServerURLVariable.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLVariable.tsx @@ -16,7 +16,13 @@ export function OpenAPIServerURLVariable(props: { if (variable.enum && variable.enum.length > 0) { if (!selectable) { - return {!isNaN(Number(selectionIndex)) ? variable.enum[Number(selectionIndex)] : variable.default}; + return ( + + {!isNaN(Number(selectionIndex)) + ? variable.enum[Number(selectionIndex)] + : variable.default} + + ); } return ( diff --git a/packages/react-openapi/src/ScalarApiButton.tsx b/packages/react-openapi/src/ScalarApiButton.tsx index dcf1d7a70..af6257fca 100644 --- a/packages/react-openapi/src/ScalarApiButton.tsx +++ b/packages/react-openapi/src/ScalarApiButton.tsx @@ -57,7 +57,7 @@ export function ScalarApiButton(props: { /** * Wrap the rendering with a context to open the scalar modal. */ -export function ScalarApiClient(props: { children: React.ReactNode; }) { +export function ScalarApiClient(props: { children: React.ReactNode }) { const { children } = props; const ctx = useOpenAPIContext(); diff --git a/packages/react-openapi/src/client.ts b/packages/react-openapi/src/client.ts index 923168320..17e54c128 100644 --- a/packages/react-openapi/src/client.ts +++ b/packages/react-openapi/src/client.ts @@ -1,2 +1,2 @@ 'use client'; -export {OpenAPIContextProvider} from './OpenAPIContextProvider'; \ No newline at end of file +export { OpenAPIContextProvider } from './OpenAPIContextProvider'; diff --git a/packages/react-openapi/src/utils.ts b/packages/react-openapi/src/utils.ts index c94d11df2..d2d66e2be 100644 --- a/packages/react-openapi/src/utils.ts +++ b/packages/react-openapi/src/utils.ts @@ -12,7 +12,6 @@ export function createStateKey(key: string, scope?: string) { return scope ? `${scope}_${key}` : key; } - /** * Get the default URL for the server. */ @@ -20,7 +19,8 @@ export function getServersURL( servers: OpenAPIV3.ServerObject[], selectors?: Record, ): string { - const serverIndex = selectors && !isNaN(Number(selectors.server)) ? Number(selectors.server) : 0; + const serverIndex = + selectors && !isNaN(Number(selectors.server)) ? Number(selectors.server) : 0; const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); @@ -48,4 +48,4 @@ export function parseServerURL(url: string) { } } return result; -} \ No newline at end of file +} From 3b0f5d82d58207a0459f8c66ea216859eaefc8c3 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Tue, 24 Sep 2024 11:37:40 +0100 Subject: [PATCH 10/13] Add server url cache to update request code sample --- .../src/app/(space)/(content)/[[...pathname]]/page.tsx | 3 +++ .../gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx | 4 ++++ .../src/components/DocumentView/OpenAPI/ServerUrlCache.tsx | 5 +++++ packages/react-openapi/src/OpenAPICodeSample.tsx | 3 ++- packages/react-openapi/src/OpenAPIServerURL.tsx | 1 - packages/react-openapi/src/OpenAPIServerURLForm.tsx | 3 ++- packages/react-openapi/src/types.ts | 5 ++--- 7 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx diff --git a/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx b/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx index b5ab1424e..4b126ee90 100644 --- a/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx +++ b/packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx @@ -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, diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index bf18e3be5..7fd367184 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -8,6 +8,7 @@ import { fetchOpenAPIBlock } from '@/lib/openapi'; import { tcls } from '@/lib/tailwind'; import OpenAPIContext from './OpenAPIContext'; +import { serverUrlCache } from './ServerUrlCache'; import { BlockProps } from '../Block'; import { PlainCodeBlock } from '../CodeBlock'; @@ -46,6 +47,8 @@ async function OpenAPIBody(props: BlockProps) { return null; } + const serverUrl = serverUrlCache.get('serverUrl'); + return ( ) { defaultInteractiveOpened: context.mode === 'print', id: block.meta?.id, blockKey: block.key, + serverUrl }} className="openapi-block" /> diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx new file mode 100644 index 000000000..b715ce3db --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx @@ -0,0 +1,5 @@ +import { createSearchParamsCache, parseAsString } from 'nuqs/server'; + +export const serverUrlCache = createSearchParamsCache({ + serverUrl: parseAsString +}) \ No newline at end of file diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index b3021263a..ac63e4779 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -48,11 +48,12 @@ 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, context.enumSelectors) + + serverUrl + data.path + (searchParams.size ? `?${searchParams.toString()}` : ''), method: data.method, diff --git a/packages/react-openapi/src/OpenAPIServerURL.tsx b/packages/react-openapi/src/OpenAPIServerURL.tsx index ec225c430..5bce96035 100644 --- a/packages/react-openapi/src/OpenAPIServerURL.tsx +++ b/packages/react-openapi/src/OpenAPIServerURL.tsx @@ -22,7 +22,6 @@ export function OpenAPIServerURL(props: { const serverIndex = !isNaN(Number(ctx?.state?.server)) ? Number(ctx?.state?.server) : 0; const server = servers[serverIndex]; const parts = parseServerURL(server?.url ?? ''); - console.log({ ctxState: ctx?.state }); return ( diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 98aa20534..8864f9b2d 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -5,6 +5,7 @@ import { OpenAPIClientContext } from './types'; import { OpenAPIV3 } from 'openapi-types'; import { ServerSelector } from './ServerSelector'; import { useOpenAPIContext } from './OpenAPIContextProvider'; +import { getServersURL } from './utils'; export function ServerURLForm(props: { children: React.ReactNode; @@ -80,7 +81,7 @@ export function ServerURLForm(props: { update({ server: `${serverIndex}`, ...state, - ...(ctx?.state?.edit ? undefined : { edit: 'true' }), + ...(ctx?.state?.edit ? { serverUrl: getServersURL(servers, state) } : { edit: 'true' }), }); }} title={ctx?.state?.edit ? undefined : 'Try different server options'} diff --git a/packages/react-openapi/src/types.ts b/packages/react-openapi/src/types.ts index e8297efa4..415bf8e49 100644 --- a/packages/react-openapi/src/types.ts +++ b/packages/react-openapi/src/types.ts @@ -27,11 +27,10 @@ export interface OpenAPIClientContext { * Optional id attached to the OpenAPI Operation heading and used as an anchor */ id?: string; - /** - * Selectors to update openapi enums, e.g. for server url variables + * Optional serverUrl to use with OpenAPI operations */ - enumSelectors?: Record; + serverUrl?: string | null; } export interface OpenAPIFetcher { From 087a783574aff351e1e7899b3f6217fd7b63e491 Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Tue, 24 Sep 2024 11:39:45 +0100 Subject: [PATCH 11/13] Formatting --- .../DocumentView/OpenAPI/OpenAPI.tsx | 2 +- .../DocumentView/OpenAPI/ServerUrlCache.tsx | 4 +-- .../react-openapi/src/OpenAPICodeSample.tsx | 5 +--- .../src/OpenAPIServerURLForm.tsx | 26 +++++++++++-------- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 7fd367184..16dccc025 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -64,7 +64,7 @@ async function OpenAPIBody(props: BlockProps) { defaultInteractiveOpened: context.mode === 'print', id: block.meta?.id, blockKey: block.key, - serverUrl + serverUrl, }} className="openapi-block" /> diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx index b715ce3db..47ae56d22 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/ServerUrlCache.tsx @@ -1,5 +1,5 @@ import { createSearchParamsCache, parseAsString } from 'nuqs/server'; export const serverUrlCache = createSearchParamsCache({ - serverUrl: parseAsString -}) \ No newline at end of file + serverUrl: parseAsString, +}); diff --git a/packages/react-openapi/src/OpenAPICodeSample.tsx b/packages/react-openapi/src/OpenAPICodeSample.tsx index ac63e4779..6345dfe29 100644 --- a/packages/react-openapi/src/OpenAPICodeSample.tsx +++ b/packages/react-openapi/src/OpenAPICodeSample.tsx @@ -52,10 +52,7 @@ export function OpenAPICodeSample(props: { const requestBody = noReference(data.operation.requestBody); const requestBodyContent = requestBody ? Object.entries(requestBody.content)[0] : undefined; const input: CodeSampleInput = { - url: - serverUrl + - data.path + - (searchParams.size ? `?${searchParams.toString()}` : ''), + url: serverUrl + data.path + (searchParams.size ? `?${searchParams.toString()}` : ''), method: data.method, body: requestBodyContent ? generateMediaTypeExample(requestBodyContent[1], { onlyRequired: true }) diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 8864f9b2d..45b277a69 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -14,7 +14,7 @@ export function ServerURLForm(props: { serverIndex: number; }) { const { children, context, servers, serverIndex } = props; - const ctx = useOpenAPIContext(); + const stateContext = useOpenAPIContext(); const server = servers[serverIndex]; const formRef = React.useRef(null); @@ -22,7 +22,7 @@ export function ServerURLForm(props: { if (index !== serverIndex) { update({ server: `${index}` ?? '0', - ...(ctx?.state?.edit ? { edit: 'true' } : undefined), + ...(stateContext?.state?.edit ? { edit: 'true' } : undefined), }); } } @@ -38,7 +38,7 @@ export function ServerURLForm(props: { update({ server: `${formData.get('server')}` ?? '0', ...variables, - ...(ctx?.state?.edit ? { edit: 'true' } : undefined), + ...(stateContext?.state?.edit ? { edit: 'true' } : undefined), }); } @@ -46,7 +46,7 @@ export function ServerURLForm(props: { if (!context.blockKey) { return; } - ctx?.onUpdate({ + stateContext?.onUpdate({ block: context.blockKey, ...variables, }); @@ -62,10 +62,10 @@ export function ServerURLForm(props: { }} className="contents" > -
+
{children} - {ctx?.state?.edit && servers.length > 1 ? ( + {stateContext?.state?.edit && servers.length > 1 ? ( { - const state = { ...ctx?.state }; + const state = { ...stateContext?.state }; delete state.edit; update({ server: `${serverIndex}`, ...state, - ...(ctx?.state?.edit ? { serverUrl: getServersURL(servers, state) } : { edit: 'true' }), + ...(stateContext?.state?.edit + ? { serverUrl: getServersURL(servers, state) } + : { edit: 'true' }), }); }} - title={ctx?.state?.edit ? undefined : 'Try different server options'} - aria-label={ctx?.state?.edit ? 'Clear' : 'Edit'} + title={ + stateContext?.state?.edit ? undefined : 'Try different server options' + } + aria-label={stateContext?.state?.edit ? 'Clear' : 'Edit'} > - {ctx?.state?.edit ? context.icons.clear : context.icons.edit} + {stateContext?.state?.edit ? context.icons.clear : context.icons.edit} ) : null}
From 9573535eba2e94115fec15d4d13017b52d9661fb Mon Sep 17 00:00:00 2001 From: Brett Jephson Date: Tue, 24 Sep 2024 12:05:59 +0100 Subject: [PATCH 12/13] Update icon --- .../gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx | 2 +- packages/react-openapi/src/OpenAPIServerURLForm.tsx | 2 +- packages/react-openapi/src/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx index 16dccc025..08fe0be58 100644 --- a/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx +++ b/packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx @@ -58,7 +58,7 @@ async function OpenAPIBody(props: BlockProps) { chevronDown: , chevronRight: , edit: , - clear: , + editDone: , }, CodeBlock: PlainCodeBlock, defaultInteractiveOpened: context.mode === 'print', diff --git a/packages/react-openapi/src/OpenAPIServerURLForm.tsx b/packages/react-openapi/src/OpenAPIServerURLForm.tsx index 45b277a69..6dc16c601 100644 --- a/packages/react-openapi/src/OpenAPIServerURLForm.tsx +++ b/packages/react-openapi/src/OpenAPIServerURLForm.tsx @@ -91,7 +91,7 @@ export function ServerURLForm(props: { } aria-label={stateContext?.state?.edit ? 'Clear' : 'Edit'} > - {stateContext?.state?.edit ? context.icons.clear : context.icons.edit} + {stateContext?.state?.edit ? context.icons.editDone : context.icons.edit} ) : null}
diff --git a/packages/react-openapi/src/types.ts b/packages/react-openapi/src/types.ts index 415bf8e49..7be2deb50 100644 --- a/packages/react-openapi/src/types.ts +++ b/packages/react-openapi/src/types.ts @@ -9,7 +9,7 @@ export interface OpenAPIClientContext { chevronDown: React.ReactNode; chevronRight: React.ReactNode; edit: React.ReactNode; - clear: React.ReactNode; + editDone: React.ReactNode; }; /** From 34d36c6de1dede84017e231ac37ce6d4333945d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Tue, 24 Sep 2024 17:25:58 +0200 Subject: [PATCH 13/13] Fix next-on-pages configuration for ~gitbook/static (#2482) --- .changeset/khaki-phones-jog.md | 5 +++++ bun.lockb | Bin 680712 -> 675880 bytes packages/gitbook/_routes.json | 4 ++++ packages/gitbook/package.json | 2 +- packages/gitbook/public/_headers | 3 +++ 5 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .changeset/khaki-phones-jog.md create mode 100644 packages/gitbook/_routes.json create mode 100644 packages/gitbook/public/_headers diff --git a/.changeset/khaki-phones-jog.md b/.changeset/khaki-phones-jog.md new file mode 100644 index 000000000..fc3905c60 --- /dev/null +++ b/.changeset/khaki-phones-jog.md @@ -0,0 +1,5 @@ +--- +'gitbook': patch +--- + +Fix GitBook specific static assets not being served correctly when deployed on Cloudflare diff --git a/bun.lockb b/bun.lockb index 7da1719ec0874bd9a8a025c55624c70efe4589ce..2cb95c8350a2297ff308bfc8bef6633410f083b1 100755 GIT binary patch delta 10009 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 delta 12360 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 diff --git a/packages/gitbook/_routes.json b/packages/gitbook/_routes.json new file mode 100644 index 000000000..8f1cb3e1a --- /dev/null +++ b/packages/gitbook/_routes.json @@ -0,0 +1,4 @@ +{ + "version": 1, + "exclude": ["/~gitbook/static/*"] +} diff --git a/packages/gitbook/package.json b/packages/gitbook/package.json index 0a9dbdd8e..d9f867570 100644 --- a/packages/gitbook/package.json +++ b/packages/gitbook/package.json @@ -68,7 +68,7 @@ "devDependencies": { "@argos-ci/playwright": "^2.0.0", "@playwright/test": "^1.42.1", - "@cloudflare/next-on-pages": "^1.12.1", + "@cloudflare/next-on-pages": "^1.13.3", "@cloudflare/workers-types": "^4.20240725.0", "@types/js-cookie": "^3.0.6", "@types/jsontoxml": "^1.0.5", diff --git a/packages/gitbook/public/_headers b/packages/gitbook/public/_headers new file mode 100644 index 000000000..193088b36 --- /dev/null +++ b/packages/gitbook/public/_headers @@ -0,0 +1,3 @@ +# GitBook immutable static assets +/~gitbook/static/* + cache-control: public,max-age=31536000,immutable