Skip to content

Commit 802ed7d

Browse files
committed
Revised client state with context
1 parent f783b40 commit 802ed7d

File tree

15 files changed

+202
-137
lines changed

15 files changed

+202
-137
lines changed

packages/gitbook/src/app/(space)/(content)/[[...pathname]]/page.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ export default async function Page(props: {
9090
// Display the page feedback in the page footer if the aside is not visible
9191
withPageFeedback && !page.layout.outline
9292
}
93-
searchParams={searchParams}
9493
/>
9594
{page.layout.outline ? (
9695
<PageAside

packages/gitbook/src/components/DocumentView/DocumentView.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ export interface DocumentContext {
4646
* https://linear.app/gitbook-x/issue/RND-3588/gitbook-open-code-syntax-highlighting-runs-out-of-memory-after-a
4747
*/
4848
shouldHighlightCode: (spaceId: string | undefined) => boolean;
49-
50-
searchParams?: Record<string, string>;
5149
}
5250

5351
export interface DocumentContextProps {

packages/gitbook/src/components/DocumentView/OpenAPI/OpenAPI.tsx

+18-45
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { DocumentBlockSwagger } from '@gitbook/api';
22
import { Icon } from '@gitbook/icons';
3-
import { OpenAPIOperation, OpenAPIOperationData } from '@gitbook/react-openapi';
3+
import { OpenAPIOperation } from '@gitbook/react-openapi';
44
import React from 'react';
55

66
import { LoadingPane } from '@/components/primitives';
77
import { fetchOpenAPIBlock } from '@/lib/openapi';
88
import { tcls } from '@/lib/tailwind';
99

10+
import OpenAPIContext from './OpenAPIContext';
1011
import { BlockProps } from '../Block';
1112
import { PlainCodeBlock } from '../CodeBlock';
1213

@@ -45,27 +46,23 @@ async function OpenAPIBody(props: BlockProps<DocumentBlockSwagger>) {
4546
return null;
4647
}
4748

48-
const enumSelectors =
49-
context.searchParams && context.searchParams.block === block.key
50-
? parseModifiers(data, context.searchParams)
51-
: undefined;
52-
5349
return (
54-
<OpenAPIOperation
55-
data={data}
56-
context={{
57-
icons: {
58-
chevronDown: <Icon icon="chevron-down" />,
59-
chevronRight: <Icon icon="chevron-right" />,
60-
},
61-
CodeBlock: PlainCodeBlock,
62-
defaultInteractiveOpened: context.mode === 'print',
63-
id: block.meta?.id,
64-
enumSelectors,
65-
blockKey: block.key,
66-
}}
67-
className="openapi-block"
68-
/>
50+
<OpenAPIContext block={block} data={data}>
51+
<OpenAPIOperation
52+
data={data}
53+
context={{
54+
icons: {
55+
chevronDown: <Icon icon="chevron-down" />,
56+
chevronRight: <Icon icon="chevron-right" />,
57+
},
58+
CodeBlock: PlainCodeBlock,
59+
defaultInteractiveOpened: context.mode === 'print',
60+
id: block.meta?.id,
61+
blockKey: block.key
62+
}}
63+
className="openapi-block"
64+
/>
65+
</OpenAPIContext>
6966
);
7067
}
7168

@@ -97,27 +94,3 @@ function OpenAPIFallback() {
9794
</div>
9895
);
9996
}
100-
101-
function parseModifiers(data: OpenAPIOperationData, params: Record<string, string>) {
102-
if (!data) {
103-
return;
104-
}
105-
const { server: serverQueryParam } = params;
106-
const serverIndex =
107-
serverQueryParam && !isNaN(Number(serverQueryParam))
108-
? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1))
109-
: 0;
110-
const server = data.servers[serverIndex];
111-
if (server) {
112-
return Object.keys(server.variables ?? {}).reduce<Record<string, number>>(
113-
(result, key) => {
114-
const selection = Number(params[key]);
115-
if (!isNaN(selection)) {
116-
result[key] = selection;
117-
}
118-
return result;
119-
},
120-
{ server: serverIndex },
121-
);
122-
}
123-
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use client'
2+
3+
import { DocumentBlock } from "@gitbook/api";
4+
import { OpenAPIOperationData } from "@gitbook/react-openapi";
5+
import {OpenAPIContextProvider} from "@gitbook/react-openapi/client";
6+
import { useRouter, useSearchParams } from 'next/navigation';
7+
import * as React from 'react';
8+
9+
export default function OpenAPIContext(props: { children: React.ReactNode; block: DocumentBlock; data: OpenAPIOperationData }) {
10+
const { block, children, data } = props;
11+
const [isPending, startTransition] = React.useTransition();
12+
const router = useRouter();
13+
const searchParams = useSearchParams();
14+
15+
return <OpenAPIContextProvider isPending={isPending} data={data} params={searchParams.get("block") === block.key ? Object.fromEntries(searchParams.entries()) : undefined} onUpdate={async (params) => {
16+
startTransition(() => {
17+
const queryParams = new URLSearchParams(params ?? '');
18+
router.replace(`?${queryParams}`, { scroll: false });
19+
})
20+
}}>{children}</OpenAPIContextProvider>
21+
}

packages/gitbook/src/components/PageBody/PageBody.tsx

-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export function PageBody(props: {
3333
document: JSONDocument | null;
3434
context: ContentRefContext;
3535
withPageFeedback: boolean;
36-
searchParams: Record<string, string>;
3736
}) {
3837
const {
3938
space,
@@ -44,7 +43,6 @@ export function PageBody(props: {
4443
page,
4544
document,
4645
withPageFeedback,
47-
searchParams,
4846
} = props;
4947

5048
const asFullWidth = document ? hasFullWidthBlock(document) : false;
@@ -96,7 +94,6 @@ export function PageBody(props: {
9694
resolveContentRef: (ref, options) =>
9795
resolveContentRef(ref, context, options),
9896
shouldHighlightCode,
99-
searchParams,
10097
}}
10198
/>
10299
) : (

packages/react-openapi/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"types": "./dist/index.d.ts",
77
"development": "./src/index.ts",
88
"default": "./dist/index.js"
9-
}
9+
},
10+
"./client": "./src/client.ts"
1011
},
1112
"version": "0.6.0",
1213
"dependencies": {

packages/react-openapi/src/OpenAPICodeSample.tsx

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ import { CodeSampleInput, codeSampleGenerators } from './code-samples';
44
import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
55
import { generateMediaTypeExample, generateSchemaExample } from './generateSchemaExample';
66
import { InteractiveSection } from './InteractiveSection';
7-
import { getServersURL } from './OpenAPIServerURL';
87
import { ScalarApiButton } from './ScalarApiButton';
98
import { OpenAPIContextProps } from './types';
10-
import { noReference } from './utils';
9+
import { getServersURL, noReference } from './utils';
1110

1211
/**
1312
* Display code samples to execute the operation.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use client'
2+
3+
import * as React from 'react';
4+
import { OpenAPIOperationData } from './fetchOpenAPIOperation';
5+
import { getServersURL } from './utils';
6+
7+
type OpenAPIContextProps = { isPending?: boolean; serverUrl?: string; state?: Record<string, string> | null, onUpdate: (params: Record<string, string> | null) => void; }
8+
const OpenAPIContext = React.createContext<OpenAPIContextProps | null>(null);
9+
10+
export function useOpenAPIContext() {
11+
return React.useContext(OpenAPIContext);
12+
}
13+
14+
export function OpenAPIContextProvider(props: { children: React.ReactNode; data: OpenAPIOperationData; isPending?: boolean; params?: Record<string, string>; onUpdate: OpenAPIContextProps['onUpdate']; }) {
15+
const { children, data, isPending, params, onUpdate } = props;
16+
17+
const clientState = React.useMemo(() => {
18+
if (!params) { return null; }
19+
return parseClientStateModifiers(data, params);
20+
}, [data, params]);
21+
const serverUrl = getServersURL(data.servers, clientState ?? undefined)
22+
23+
return <OpenAPIContext.Provider value={{
24+
isPending,
25+
state: clientState,
26+
serverUrl,
27+
onUpdate
28+
}}>{children}</OpenAPIContext.Provider>
29+
}
30+
31+
32+
function parseClientStateModifiers(data: OpenAPIOperationData, params: Record<string, string>) {
33+
if (!data) {
34+
return null;
35+
}
36+
const serverQueryParam = params['server'];
37+
const serverIndex =
38+
serverQueryParam && !isNaN(Number(serverQueryParam))
39+
? Math.max(0, Math.min(Number(serverQueryParam), data.servers.length - 1))
40+
: 0;
41+
const server = data.servers[serverIndex];
42+
return server ? Object.keys(server.variables ?? {}).reduce<Record<string, string>>(
43+
(result, key) => {
44+
const selection = params[key];
45+
if (!isNaN(Number(selection))) {
46+
result[key] = selection;
47+
}
48+
return result;
49+
},
50+
{ server: `${serverIndex}`, edit: params['edit'] },
51+
) : null;
52+
}

packages/react-openapi/src/OpenAPIOperation.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { OpenAPIOperationData, toJSON } from './fetchOpenAPIOperation';
55
import { Markdown } from './Markdown';
66
import { OpenAPICodeSample } from './OpenAPICodeSample';
77
import { OpenAPIResponseExample } from './OpenAPIResponseExample';
8-
import { getServersURL, OpenAPIServerURL } from './OpenAPIServerURL';
8+
import { OpenAPIServerURL } from './OpenAPIServerURL';
99
import { OpenAPISpec } from './OpenAPISpec';
1010
import { OpenAPIClientContext, OpenAPIContextProps } from './types';
1111
import { ScalarApiClient } from './ScalarApiButton';
@@ -25,11 +25,10 @@ export async function OpenAPIOperation(props: {
2525
defaultInteractiveOpened: context.defaultInteractiveOpened,
2626
icons: context.icons,
2727
blockKey: context.blockKey,
28-
enumSelectors: context.enumSelectors,
2928
};
3029

3130
return (
32-
<ScalarApiClient serverUrl={getServersURL(data.servers, context.enumSelectors)}>
31+
<ScalarApiClient>
3332
<div className={classNames('openapi-operation', className)}>
3433
<div className="openapi-intro">
3534
<h2 className="openapi-summary" id={context.id}>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
'use client';
2+
13
import * as React from 'react';
24
import { OpenAPIV3 } from 'openapi-types';
35
import { OpenAPIServerURLVariable } from './OpenAPIServerURLVariable';
46
import { OpenAPIClientContext } from './types';
57
import { ServerURLForm } from './OpenAPIServerURLForm';
6-
import { ServerSelector } from './ServerSelector';
8+
import { useOpenAPIContext } from './OpenAPIContextProvider';
9+
import { parseServerURL } from './utils';
710

811
/**
912
* Show the url of the server with variables replaced by their default values.
@@ -14,9 +17,12 @@ export function OpenAPIServerURL(props: {
1417
path?: string;
1518
}) {
1619
const { path, servers, context } = props;
17-
const serverIndex = context.enumSelectors?.server ?? 0;
20+
const ctx = useOpenAPIContext();
21+
22+
const serverIndex = !isNaN(Number(ctx?.state?.server)) ? Number(ctx?.state?.server) : 0;
1823
const server = servers[serverIndex];
1924
const parts = parseServerURL(server?.url ?? '');
25+
console.log({ ctxState: ctx?.state })
2026

2127
return (
2228
<ServerURLForm context={context} servers={servers} serverIndex={serverIndex}>
@@ -33,7 +39,8 @@ export function OpenAPIServerURL(props: {
3339
key={i}
3440
name={part.name}
3541
variable={server.variables[part.name]}
36-
enumIndex={context.enumSelectors?.[part.name]}
42+
selectionIndex={Number(ctx?.state?.[part.name])}
43+
selectable={Boolean(ctx?.state?.edit)}
3744
/>
3845
);
3946
}
@@ -42,40 +49,3 @@ export function OpenAPIServerURL(props: {
4249
</ServerURLForm>
4350
);
4451
}
45-
46-
/**
47-
* Get the default URL for the server.
48-
*/
49-
export function getServersURL(
50-
servers: OpenAPIV3.ServerObject[],
51-
selectors?: Record<string, number>,
52-
): string {
53-
const serverIndex = selectors && !isNaN(selectors.server) ? Number(selectors.server) : 0;
54-
const server = servers[serverIndex];
55-
const parts = parseServerURL(server?.url ?? '');
56-
57-
return parts
58-
.map((part) => {
59-
if (part.kind === 'text') {
60-
return part.text;
61-
} else {
62-
return selectors && !isNaN(selectors[part.name])
63-
? server.variables?.[part.name]?.enum?.[selectors[part.name]]
64-
: (server.variables?.[part.name]?.default ?? `{${part.name}}`);
65-
}
66-
})
67-
.join('');
68-
}
69-
70-
function parseServerURL(url: string) {
71-
const parts = url.split(/{([^}]+)}/g);
72-
const result: Array<{ kind: 'variable'; name: string } | { kind: 'text'; text: string }> = [];
73-
for (let i = 0; i < parts.length; i++) {
74-
if (i % 2 === 0) {
75-
result.push({ kind: 'text', text: parts[i] });
76-
} else {
77-
result.push({ kind: 'variable', name: parts[i] });
78-
}
79-
}
80-
return result;
81-
}

0 commit comments

Comments
 (0)