@stainless-api/docs 0.1.0-beta.8 → 0.1.0-beta.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +636 -0
- package/eslint-suppressions.json +52 -0
- package/locals.d.ts +17 -0
- package/package.json +50 -39
- package/plugin/assets/languages/cli.svg +14 -0
- package/plugin/assets/languages/csharp.svg +1 -0
- package/plugin/buildAlgoliaIndex.ts +32 -7
- package/plugin/cms/server.ts +130 -59
- package/plugin/cms/sidebar-builder.ts +7 -26
- package/plugin/cms/worker.ts +83 -5
- package/plugin/components/MethodDescription.tsx +54 -0
- package/plugin/components/RequestBuilder/ParamEditor.tsx +55 -0
- package/plugin/components/RequestBuilder/SnippetStainlessIsland.tsx +107 -0
- package/plugin/components/RequestBuilder/index.tsx +31 -0
- package/plugin/components/RequestBuilder/props.ts +9 -0
- package/plugin/components/RequestBuilder/spec-helpers.ts +50 -0
- package/plugin/components/RequestBuilder/styles.css +67 -0
- package/plugin/components/SDKSelect.astro +7 -87
- package/plugin/components/SnippetCode.tsx +107 -67
- package/plugin/components/StainlessIslands.tsx +126 -0
- package/plugin/components/search/SearchAlgolia.astro +45 -28
- package/plugin/components/search/SearchIsland.tsx +45 -27
- package/plugin/generateAPIReferenceLink.ts +2 -2
- package/plugin/globalJs/ai-dropdown-options.ts +243 -0
- package/plugin/globalJs/code-snippets.ts +15 -8
- package/plugin/globalJs/copy.ts +91 -17
- package/plugin/globalJs/create-playground.shim.ts +3 -0
- package/plugin/globalJs/method-descriptions.ts +33 -0
- package/plugin/globalJs/navigation.ts +7 -26
- package/plugin/globalJs/playground-data.shim.ts +1 -0
- package/plugin/globalJs/playground-data.ts +14 -0
- package/plugin/helpers/generateDocsRoutes.ts +27 -0
- package/plugin/index.ts +198 -36
- package/plugin/languages.ts +7 -2
- package/plugin/loadPluginConfig.ts +122 -36
- package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
- package/plugin/react/Routing.tsx +199 -126
- package/plugin/referencePlaceholderUtils.ts +1 -1
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +3 -3
- package/plugin/routes/Docs.astro +55 -102
- package/plugin/routes/DocsStatic.astro +1 -1
- package/plugin/routes/Overview.astro +10 -16
- package/plugin/routes/markdown.ts +9 -8
- package/plugin/vendor/preview.worker.docs.js +20928 -17830
- package/plugin/vendor/templates/go.md +1 -1
- package/plugin/vendor/templates/python.md +1 -1
- package/resolveSrcFile.ts +10 -0
- package/scripts/vendor_deps.ts +5 -5
- package/shared/getProsePages.ts +42 -0
- package/shared/getSharedLogger.ts +15 -0
- package/shared/terminalUtils.ts +3 -0
- package/src/content.config.ts +9 -0
- package/stl-docs/components/AIDropdown.tsx +63 -0
- package/stl-docs/components/AiChatIsland.tsx +14 -0
- package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +2 -2
- package/stl-docs/components/Head.astro +16 -0
- package/stl-docs/components/Header.astro +6 -8
- package/stl-docs/components/PageFrame.astro +18 -0
- package/stl-docs/components/PageTitle.astro +82 -0
- package/stl-docs/components/TableOfContents.astro +34 -0
- package/stl-docs/components/ThemeProvider.astro +36 -0
- package/stl-docs/components/ThemeSelect.astro +84 -139
- package/stl-docs/components/content-panel/ContentPanel.astro +15 -46
- package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
- package/stl-docs/components/headers/StackedHeader.astro +29 -24
- package/stl-docs/components/icons/chat-gpt.tsx +2 -2
- package/stl-docs/components/icons/cursor.tsx +10 -0
- package/stl-docs/components/icons/gemini.tsx +19 -0
- package/stl-docs/components/icons/markdown.tsx +1 -1
- package/stl-docs/components/index.ts +1 -0
- package/stl-docs/components/mintlify-compat/Accordion.astro +7 -5
- package/stl-docs/components/mintlify-compat/AccordionGroup.astro +7 -3
- package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
- package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
- package/stl-docs/components/mintlify-compat/callouts/Callout.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Check.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Danger.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Info.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Note.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Tip.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Warning.astro +1 -1
- package/stl-docs/components/mintlify-compat/card.css +33 -35
- package/stl-docs/components/mintlify-compat/index.ts +2 -4
- package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
- package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
- package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +175 -0
- package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
- package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
- package/stl-docs/components/pagination/util.ts +71 -0
- package/stl-docs/components/scripts.ts +1 -0
- package/stl-docs/disableCalloutSyntax.ts +36 -0
- package/stl-docs/index.ts +147 -52
- package/stl-docs/loadStlDocsConfig.ts +45 -7
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +61 -0
- package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +39 -0
- package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
- package/stl-docs/proseSearchIndexing.ts +450 -0
- package/stl-docs/tabsMiddleware.ts +13 -4
- package/styles/code.css +128 -136
- package/styles/fonts.css +32 -17
- package/styles/links.css +11 -48
- package/styles/method-descriptions.css +36 -0
- package/styles/overrides.css +49 -57
- package/styles/page.css +90 -59
- package/styles/sdk_select.css +9 -7
- package/styles/search.css +57 -69
- package/styles/sidebar.css +211 -131
- package/styles/{variables.css → sl-variables.css} +3 -2
- package/styles/stldocs-variables.css +6 -0
- package/styles/toc.css +41 -34
- package/theme.css +10 -10
- package/tsconfig.json +2 -5
- package/virtual-module.d.ts +121 -5
- package/components/variables.css +0 -135
- package/plugin/globalJs/ai-dropdown.ts +0 -57
- package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -58
- package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -55
- package/stl-docs/components/mintlify-compat/Step.astro +0 -58
- package/stl-docs/components/mintlify-compat/Steps.astro +0 -17
- /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
- /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
package/plugin/cms/worker.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import Worker from 'web-worker';
|
|
2
|
-
import { Languages, type DocsLanguage } from '@stainless-api/docs-ui/
|
|
3
|
-
import type * as SDKJSON from '
|
|
2
|
+
import { Languages, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
3
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { dirname, resolve } from 'node:path';
|
|
6
6
|
import fs from 'fs/promises';
|
|
7
7
|
import pathutils from 'path';
|
|
8
|
+
import type { VersionUserConfig } from '../loadPluginConfig';
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = dirname(__filename);
|
|
@@ -12,7 +13,81 @@ const __dirname = dirname(__filename);
|
|
|
12
13
|
const workerPath = resolve(__dirname, '..', 'vendor', 'preview.worker.docs.js');
|
|
13
14
|
|
|
14
15
|
type OpenAPIDocument = Record<string, any>;
|
|
15
|
-
type ParsedConfig =
|
|
16
|
+
export type ParsedConfig = {
|
|
17
|
+
docs:
|
|
18
|
+
| {
|
|
19
|
+
title?: string | undefined;
|
|
20
|
+
favicon?: string | undefined;
|
|
21
|
+
logo_icon?: string | undefined;
|
|
22
|
+
search?:
|
|
23
|
+
| {
|
|
24
|
+
algolia?:
|
|
25
|
+
| {
|
|
26
|
+
app_id: string;
|
|
27
|
+
index_name: string;
|
|
28
|
+
search_key: string;
|
|
29
|
+
}
|
|
30
|
+
| undefined;
|
|
31
|
+
}
|
|
32
|
+
| undefined;
|
|
33
|
+
description?: string | undefined;
|
|
34
|
+
languages?:
|
|
35
|
+
| ('node' | 'typescript' | 'python' | 'java' | 'kotlin' | 'go' | 'ruby' | 'terraform' | 'http')[]
|
|
36
|
+
| undefined;
|
|
37
|
+
snippets?:
|
|
38
|
+
| {
|
|
39
|
+
exclude_languages?: string[] | undefined;
|
|
40
|
+
}
|
|
41
|
+
| undefined;
|
|
42
|
+
show_security?: boolean | undefined;
|
|
43
|
+
show_readme?: boolean | undefined;
|
|
44
|
+
base_path?: string | undefined;
|
|
45
|
+
navigation?:
|
|
46
|
+
| {
|
|
47
|
+
menubar?:
|
|
48
|
+
| {
|
|
49
|
+
title: string;
|
|
50
|
+
icon?: string;
|
|
51
|
+
variant?: string;
|
|
52
|
+
href?: string | undefined;
|
|
53
|
+
page?: string | undefined;
|
|
54
|
+
}[]
|
|
55
|
+
| undefined;
|
|
56
|
+
sidebar?:
|
|
57
|
+
| {
|
|
58
|
+
title: string;
|
|
59
|
+
icon?: string;
|
|
60
|
+
variant?: string;
|
|
61
|
+
href?: string | undefined;
|
|
62
|
+
page?: string | undefined;
|
|
63
|
+
}[]
|
|
64
|
+
| undefined;
|
|
65
|
+
}
|
|
66
|
+
| undefined;
|
|
67
|
+
pages?: unknown;
|
|
68
|
+
resources?: unknown[] | undefined;
|
|
69
|
+
}
|
|
70
|
+
| undefined;
|
|
71
|
+
targets: Record<string, { skip?: boolean }>;
|
|
72
|
+
client_settings: {
|
|
73
|
+
opts: {
|
|
74
|
+
[x: string]: {
|
|
75
|
+
type: 'string' | 'number' | 'boolean' | 'null' | 'integer';
|
|
76
|
+
nullable: boolean;
|
|
77
|
+
description?: string | undefined;
|
|
78
|
+
example?: unknown;
|
|
79
|
+
default?: unknown;
|
|
80
|
+
read_env?: string | undefined;
|
|
81
|
+
auth?:
|
|
82
|
+
| {
|
|
83
|
+
security_scheme: string;
|
|
84
|
+
role?: 'value' | 'password' | 'username' | 'client_id' | 'client_secret' | undefined;
|
|
85
|
+
}
|
|
86
|
+
| undefined;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
};
|
|
16
91
|
|
|
17
92
|
function runJob({ type, signal, data }: { type: string; signal?: AbortSignal; data: any }) {
|
|
18
93
|
const stainlessWorker = new Worker(workerPath, {
|
|
@@ -22,6 +97,7 @@ function runJob({ type, signal, data }: { type: string; signal?: AbortSignal; da
|
|
|
22
97
|
|
|
23
98
|
return new Promise<any>((resolve, reject) => {
|
|
24
99
|
stainlessWorker.addEventListener('error', (e) => {
|
|
100
|
+
e.preventDefault();
|
|
25
101
|
reject(e);
|
|
26
102
|
});
|
|
27
103
|
|
|
@@ -85,10 +161,12 @@ export async function createSDKJSON({
|
|
|
85
161
|
oas,
|
|
86
162
|
config,
|
|
87
163
|
languages,
|
|
164
|
+
version,
|
|
88
165
|
}: {
|
|
89
166
|
oas: OpenAPIDocument;
|
|
90
167
|
config: ParsedConfig;
|
|
91
168
|
languages: DocsLanguage[];
|
|
169
|
+
version: VersionUserConfig;
|
|
92
170
|
}) {
|
|
93
171
|
const templatePath = resolve(__dirname, '../vendor/templates');
|
|
94
172
|
const readmeLoader = await Promise.all(
|
|
@@ -98,7 +176,7 @@ export async function createSDKJSON({
|
|
|
98
176
|
try {
|
|
99
177
|
const content = await fs.readFile(mdfile);
|
|
100
178
|
return [language, content.toString()];
|
|
101
|
-
} catch
|
|
179
|
+
} catch {
|
|
102
180
|
return [language, null];
|
|
103
181
|
}
|
|
104
182
|
}),
|
|
@@ -113,7 +191,7 @@ export async function createSDKJSON({
|
|
|
113
191
|
config,
|
|
114
192
|
languages,
|
|
115
193
|
transform: false,
|
|
116
|
-
projectName:
|
|
194
|
+
projectName: version.stainlessProject,
|
|
117
195
|
readmeTemplates,
|
|
118
196
|
},
|
|
119
197
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { MethodDescriptionProps } from '@stainless-api/docs-ui/components';
|
|
2
|
+
import { useComponents } from '@stainless-api/docs-ui/contexts/use-components';
|
|
3
|
+
import style from '@stainless-api/docs-ui/style';
|
|
4
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
5
|
+
|
|
6
|
+
function shouldCollapseDescription(description: string) {
|
|
7
|
+
const MIN_CHARS = 400;
|
|
8
|
+
const MIN_LINES = 6;
|
|
9
|
+
|
|
10
|
+
const lineCount = description.split('\n').length;
|
|
11
|
+
|
|
12
|
+
if (description.length >= MIN_CHARS) return true;
|
|
13
|
+
if (lineCount >= MIN_LINES) return true;
|
|
14
|
+
|
|
15
|
+
// Markdown structure often means longer content
|
|
16
|
+
if (/#\s/.test(description)) return true; // has headings
|
|
17
|
+
if (/```/.test(description)) return true; // has code blocks
|
|
18
|
+
if (/^\s*[-*]\s+/m.test(description)) return true; // has lists
|
|
19
|
+
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function MethodDescription({ description }: MethodDescriptionProps) {
|
|
24
|
+
const { Markdown } = useComponents();
|
|
25
|
+
|
|
26
|
+
if (description) {
|
|
27
|
+
// Attempt to determine if we should make the description collapsible initially
|
|
28
|
+
// or not. If we get this right, there will be no FOUC.
|
|
29
|
+
const collapsible = shouldCollapseDescription(description);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="stl-method-description">
|
|
33
|
+
<div
|
|
34
|
+
className={style.MethodDescription}
|
|
35
|
+
data-stldocs-property-group="method-description"
|
|
36
|
+
data-collapsed={collapsible ? 'true' : 'false'}
|
|
37
|
+
>
|
|
38
|
+
<Markdown content={description} />
|
|
39
|
+
</div>
|
|
40
|
+
<div className="stl-method-description-overflow-wrapper">
|
|
41
|
+
<Button
|
|
42
|
+
type="button"
|
|
43
|
+
data-method-description-toggle
|
|
44
|
+
size="sm"
|
|
45
|
+
variant="ghost"
|
|
46
|
+
hidden={!collapsible}
|
|
47
|
+
>
|
|
48
|
+
Show more
|
|
49
|
+
</Button>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
3
|
+
import type { Param } from './spec-helpers';
|
|
4
|
+
import { InfoIcon } from 'lucide-react';
|
|
5
|
+
import { Input } from '@stainless-api/docs-ui/components';
|
|
6
|
+
|
|
7
|
+
function setHighlight(stainlessPath: string, highlighted: boolean) {
|
|
8
|
+
const ele = document.getElementById(stainlessPath);
|
|
9
|
+
if (!ele) return;
|
|
10
|
+
ele.classList.toggle('stldocs-property-highlighted', highlighted);
|
|
11
|
+
if (highlighted) {
|
|
12
|
+
if (location.hash) {
|
|
13
|
+
const prevScroll = document.documentElement.scrollTop;
|
|
14
|
+
location.hash = '';
|
|
15
|
+
document.documentElement.scrollTop = prevScroll;
|
|
16
|
+
}
|
|
17
|
+
if (document.body.clientWidth >= 1280) {
|
|
18
|
+
ele.scrollIntoView({
|
|
19
|
+
behavior: 'smooth',
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function htmlToText(html: string) {
|
|
26
|
+
const template = document.createElement('template');
|
|
27
|
+
template.innerHTML = html;
|
|
28
|
+
return template.content.textContent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ParamEditor({ param }: { param: Param }) {
|
|
32
|
+
const type = useMemo(() => htmlToText(param.type), [param.type]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<label className="request-builder-param">
|
|
36
|
+
<Button
|
|
37
|
+
className="request-builder-param-info-button"
|
|
38
|
+
variant="ghost"
|
|
39
|
+
href={`#${encodeURIComponent(param.stainlessPath)}`}
|
|
40
|
+
>
|
|
41
|
+
<Button.Icon icon={InfoIcon} size={16} />
|
|
42
|
+
</Button>
|
|
43
|
+
|
|
44
|
+
<span className="request-builder-param-label">{param.key}</span>
|
|
45
|
+
<span className="request-builder-param-colon">:</span>
|
|
46
|
+
|
|
47
|
+
<Input
|
|
48
|
+
className="request-builder-param-value"
|
|
49
|
+
onFocus={() => setHighlight(param.stainlessPath, true)}
|
|
50
|
+
onBlur={() => setHighlight(param.stainlessPath, false)}
|
|
51
|
+
placeholder={type}
|
|
52
|
+
/>
|
|
53
|
+
</label>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useState, useMemo, useEffect, useId, useSyncExternalStore, Activity, Fragment } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
4
|
+
import { PlayIcon, RotateCcw } from 'lucide-react';
|
|
5
|
+
import { SnippetStainlessIslandPropsSchema } from './props';
|
|
6
|
+
import { ParamEditor } from './ParamEditor';
|
|
7
|
+
import './styles.css';
|
|
8
|
+
|
|
9
|
+
function useRequiredChild<T extends Element = Element>(
|
|
10
|
+
parent: Element | null,
|
|
11
|
+
selector: string,
|
|
12
|
+
): React.RefObject<T> {
|
|
13
|
+
const elementRef = useMemo(() => {
|
|
14
|
+
const el = parent?.querySelector<T>(selector);
|
|
15
|
+
return el ? { current: el } : null;
|
|
16
|
+
}, [parent, selector]);
|
|
17
|
+
if (!elementRef) throw new Error(`Required child not found: ${selector}`);
|
|
18
|
+
return elementRef;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function useSetVisibility(elementRef: React.RefObject<HTMLElement>, visible: boolean) {
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
elementRef.current.style.display = visible ? '' : 'none';
|
|
24
|
+
}, [elementRef, visible]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function SnippetStainlessIsland({ parent }: { parent: HTMLElement }) {
|
|
28
|
+
const [expanded, setExpanded] = useState(false);
|
|
29
|
+
|
|
30
|
+
const trigger = useRequiredChild<HTMLButtonElement>(parent, '.try-it-footer .try-it-button');
|
|
31
|
+
const codeContainer = useRequiredChild<HTMLElement>(parent, '.stldocs-snippet-code');
|
|
32
|
+
const exampleContainer = useRequiredChild<HTMLElement>(parent, '.stldocs-snippet-multi-response');
|
|
33
|
+
useSetVisibility(codeContainer, !expanded);
|
|
34
|
+
useSetVisibility(exampleContainer, !expanded);
|
|
35
|
+
useSetVisibility(trigger, !expanded);
|
|
36
|
+
// Attach click handler to trigger
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!trigger.current) return;
|
|
39
|
+
const ac = new AbortController();
|
|
40
|
+
trigger.current.addEventListener('click', () => setExpanded(true), { signal: ac.signal });
|
|
41
|
+
return () => ac.abort();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const requestBuilderContainer = useRequiredChild<HTMLElement>(parent, '.request-builder-container');
|
|
45
|
+
const requestBuilderFooter = useRequiredChild<HTMLElement>(parent, '.request-builder-footer');
|
|
46
|
+
const requestBuilderResponse = useRequiredChild<HTMLElement>(parent, '.request-builder-response');
|
|
47
|
+
const requestBuilderProps = useRequiredChild<HTMLTemplateElement>(parent, '.request-builder-props').current;
|
|
48
|
+
const serializedProps = useSyncExternalStore(
|
|
49
|
+
(cb) => {
|
|
50
|
+
const mutationObserver = new MutationObserver(() => cb());
|
|
51
|
+
mutationObserver.observe(requestBuilderProps, { childList: true });
|
|
52
|
+
return () => mutationObserver.disconnect();
|
|
53
|
+
},
|
|
54
|
+
() => requestBuilderProps.content.textContent,
|
|
55
|
+
);
|
|
56
|
+
const deserializedProps = SnippetStainlessIslandPropsSchema.parse(JSON.parse(serializedProps));
|
|
57
|
+
const groupedParams = Map.groupBy(deserializedProps.params, (p) => p.location);
|
|
58
|
+
|
|
59
|
+
const formId = `request-builder-form-${useId()}`;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
{createPortal(
|
|
64
|
+
<Activity mode={expanded ? 'visible' : 'hidden'}>
|
|
65
|
+
<form
|
|
66
|
+
onSubmit={(e) => {
|
|
67
|
+
alert('TODO: Submit button clicked');
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
}}
|
|
70
|
+
id={formId}
|
|
71
|
+
>
|
|
72
|
+
{[...groupedParams.entries()].map(([location, params]) => (
|
|
73
|
+
<Fragment key={location}>
|
|
74
|
+
<h4>{location} parameters</h4>
|
|
75
|
+
{params.map((e) => (
|
|
76
|
+
<ParamEditor param={e} key={e.location + e.key} />
|
|
77
|
+
))}
|
|
78
|
+
</Fragment>
|
|
79
|
+
))}
|
|
80
|
+
</form>
|
|
81
|
+
</Activity>,
|
|
82
|
+
requestBuilderContainer.current,
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
{createPortal(
|
|
86
|
+
<Activity mode={expanded ? 'visible' : 'hidden'}>
|
|
87
|
+
<Button variant="ghost" border={true} onClick={() => setExpanded(false)}>
|
|
88
|
+
<Button.Icon icon={RotateCcw} />
|
|
89
|
+
</Button>
|
|
90
|
+
|
|
91
|
+
<Button variant="success" className="send-button" type="submit" form={formId}>
|
|
92
|
+
<Button.Label>Send</Button.Label>
|
|
93
|
+
<Button.Icon icon={PlayIcon} />
|
|
94
|
+
</Button>
|
|
95
|
+
</Activity>,
|
|
96
|
+
requestBuilderFooter.current,
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{createPortal(
|
|
100
|
+
<Activity mode={expanded ? 'visible' : 'hidden'}>
|
|
101
|
+
<div>{/* TODO */}</div>
|
|
102
|
+
</Activity>,
|
|
103
|
+
requestBuilderResponse.current,
|
|
104
|
+
)}
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import * as SDKJSON from '@stainless/sdk-json';
|
|
3
|
+
import { useSpec } from '@stainless-api/docs-ui/contexts';
|
|
4
|
+
import { extractParams } from './spec-helpers';
|
|
5
|
+
import type { SnippetStainlessIslandProps } from './props';
|
|
6
|
+
|
|
7
|
+
/** Load and process the spec on the server side to avoid inflating client bundle */
|
|
8
|
+
export function RequestBuilder({
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
method,
|
|
12
|
+
}: {
|
|
13
|
+
className: string;
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
method: SDKJSON.Method;
|
|
16
|
+
}) {
|
|
17
|
+
const spec = useSpec();
|
|
18
|
+
if (!spec) throw new Error('Spec is required for RequestBuilder');
|
|
19
|
+
const params = spec && extractParams(spec, method);
|
|
20
|
+
const [httpMethod, path] = method.endpoint.split(' ') as [string, string];
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<stl-island component="SnippetStainlessIsland" className={className}>
|
|
24
|
+
{/* Pass state down to the client component. TODO: we need a better solution for this */}
|
|
25
|
+
<template className="request-builder-props">
|
|
26
|
+
{JSON.stringify({ method: httpMethod, path, params } satisfies SnippetStainlessIslandProps)}
|
|
27
|
+
</template>
|
|
28
|
+
{children}
|
|
29
|
+
</stl-island>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { ParamSchema } from './spec-helpers';
|
|
3
|
+
|
|
4
|
+
export const SnippetStainlessIslandPropsSchema = z.object({
|
|
5
|
+
method: z.string(),
|
|
6
|
+
path: z.string(),
|
|
7
|
+
params: z.array(ParamSchema),
|
|
8
|
+
});
|
|
9
|
+
export type SnippetStainlessIslandProps = z.infer<typeof SnippetStainlessIslandPropsSchema>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
2
|
+
import { printer } from '@stainless-api/docs-ui/markdown';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
|
|
5
|
+
export const ParamSchema = z.object({
|
|
6
|
+
stainlessPath: z.string(),
|
|
7
|
+
location: z.string(),
|
|
8
|
+
key: z.string(),
|
|
9
|
+
type: z.string(),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type Param = z.infer<typeof ParamSchema>;
|
|
13
|
+
|
|
14
|
+
export function extractParams(spec: SDKJSON.Spec | undefined, method: SDKJSON.Method): Param[] {
|
|
15
|
+
const httpDecls = spec?.decls?.http;
|
|
16
|
+
if (!httpDecls) throw new Error('expected http language to be present in SDKJSON');
|
|
17
|
+
const decl = httpDecls?.[method.stainlessPath];
|
|
18
|
+
if (decl?.kind !== 'HttpDeclFunction') {
|
|
19
|
+
throw new Error(
|
|
20
|
+
'expected HttpDeclFunction at stainlessPath "' + method.stainlessPath + '", got ' + decl?.kind,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
const bodyTypes = Object.keys(decl.bodyParamsChildren ?? {});
|
|
24
|
+
if (bodyTypes.length > 0 && !bodyTypes.includes('application/json')) {
|
|
25
|
+
throw new Error('TODO: support non-json body params');
|
|
26
|
+
}
|
|
27
|
+
const bodyParams = decl.bodyParamsChildren?.['application/json'];
|
|
28
|
+
const params = [
|
|
29
|
+
...Object.entries(decl.paramsChildren ?? {}).map(([location, children]) => ({ location, children })),
|
|
30
|
+
...(bodyParams ? [{ location: 'body', children: bodyParams }] : []),
|
|
31
|
+
]
|
|
32
|
+
.filter((e) => e.children.length)
|
|
33
|
+
.flatMap(({ location, children }) =>
|
|
34
|
+
children.map((child) => {
|
|
35
|
+
const resolved = httpDecls[child];
|
|
36
|
+
if (resolved?.kind !== 'HttpDeclProperty') {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'expected HttpDeclProperty at stainlessPath "' + child + '", got ' + resolved?.kind,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
stainlessPath: resolved.stainlessPath,
|
|
43
|
+
location,
|
|
44
|
+
key: resolved.key,
|
|
45
|
+
type: (resolved.optional ? 'optional ' : '') + printer.type('http', resolved.type),
|
|
46
|
+
};
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
return params;
|
|
50
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
.request-builder-container form {
|
|
2
|
+
display: grid;
|
|
3
|
+
|
|
4
|
+
/* prettier-ignore */
|
|
5
|
+
grid-template-columns: /*info button*/max-content /*label*/max-content /*colon*/max-content /*input*/1fr;
|
|
6
|
+
font-family: var(--stl-typography-font);
|
|
7
|
+
font-size: var(--stl-typography-text-body-sm);
|
|
8
|
+
color: var(--stl-color-foreground);
|
|
9
|
+
|
|
10
|
+
& > * {
|
|
11
|
+
padding-inline-start: 12px;
|
|
12
|
+
padding-inline-end: 8px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
& > h4 {
|
|
16
|
+
grid-column: 1 / -1;
|
|
17
|
+
text-transform: capitalize;
|
|
18
|
+
font-size: var(--stl-typography-scale-sm);
|
|
19
|
+
margin-top: 0;
|
|
20
|
+
margin-bottom: 0.25em;
|
|
21
|
+
color: var(--stl-color-foreground-muted);
|
|
22
|
+
padding-top: 0.75rem;
|
|
23
|
+
|
|
24
|
+
&:not(:first-child) {
|
|
25
|
+
border-top: 1px solid var(--stl-color-border);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.request-builder-param {
|
|
30
|
+
grid-column: 1 / -1;
|
|
31
|
+
display: grid;
|
|
32
|
+
grid-template-columns: subgrid;
|
|
33
|
+
align-items: center;
|
|
34
|
+
column-gap: 0.5rem;
|
|
35
|
+
padding-block: 0.35rem;
|
|
36
|
+
|
|
37
|
+
.request-builder-param-info-button {
|
|
38
|
+
color: var(--stl-color-foreground-muted);
|
|
39
|
+
margin-inline: -4px;
|
|
40
|
+
padding: 0;
|
|
41
|
+
}
|
|
42
|
+
.request-builder-param-label {
|
|
43
|
+
font-family: var(--stl-typography-font-mono);
|
|
44
|
+
}
|
|
45
|
+
.request-builder-param-colon {
|
|
46
|
+
font-family: var(--stl-typography-font-mono);
|
|
47
|
+
color: var(--stl-color-foreground-muted);
|
|
48
|
+
}
|
|
49
|
+
/* TODO: new input component that is better stylable */
|
|
50
|
+
.request-builder-param-value {
|
|
51
|
+
input {
|
|
52
|
+
margin: 6px 8px;
|
|
53
|
+
font-size: inherit;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
&:has(+ h4),
|
|
58
|
+
&:last-child {
|
|
59
|
+
padding-bottom: 0.75rem;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.request-builder-footer .stl-ui-button--ghost .stl-ui-button__icon {
|
|
65
|
+
color: var(--stl-color-foreground);
|
|
66
|
+
opacity: var(--stl-opacity-level-040);
|
|
67
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import type { DocsLanguage } from '@stainless-api/docs-ui/
|
|
3
|
-
import { parseRoute } from '@stainless-api/docs-ui/
|
|
2
|
+
import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
3
|
+
import { parseRoute } from '@stainless-api/docs-ui/routing';
|
|
4
4
|
import { cmsClient } from '../cms/client';
|
|
5
5
|
import { BASE_PATH, DEFAULT_LANGUAGE, EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
|
|
6
6
|
import { Languages } from '../languages';
|
|
@@ -53,100 +53,20 @@ const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
|
|
|
53
53
|
)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
<style>
|
|
57
|
-
@layer starlight.core {
|
|
58
|
-
label {
|
|
59
|
-
--sl-label-icon-size: 16px;
|
|
60
|
-
--sl-caret-size: 1.25rem;
|
|
61
|
-
--sl-inline-padding: 0.5rem;
|
|
62
|
-
position: relative;
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 0.25rem;
|
|
66
|
-
color: var(--sl-color-gray-1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
label:hover {
|
|
70
|
-
color: var(--sl-color-gray-2);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.icon {
|
|
74
|
-
position: absolute;
|
|
75
|
-
top: 50%;
|
|
76
|
-
transform: translateY(-50%);
|
|
77
|
-
pointer-events: none;
|
|
78
|
-
width: 16px;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
select {
|
|
82
|
-
padding-block: 0.3rem;
|
|
83
|
-
padding-inline: calc(var(--sl-label-icon-size) + var(--sl-inline-padding) + 0.5rem)
|
|
84
|
-
calc(var(--sl-caret-size) + var(--sl-inline-padding) + 0.25rem);
|
|
85
|
-
margin-inline: calc(var(--sl-inline-padding) * -1);
|
|
86
|
-
width: calc(var(--sl-select-width) + var(--sl-inline-padding) * 2);
|
|
87
|
-
text-overflow: ellipsis;
|
|
88
|
-
color: inherit;
|
|
89
|
-
cursor: pointer;
|
|
90
|
-
appearance: none;
|
|
91
|
-
font-weight: 600;
|
|
92
|
-
text-transform: capitalize;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
select:active {
|
|
96
|
-
font-weight: inherit;
|
|
97
|
-
/* font-family: sans-serif;
|
|
98
|
-
font-weight: 400; */
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
option {
|
|
102
|
-
background-color: var(--sl-color-bg-nav);
|
|
103
|
-
color: var(--sl-color-gray-1);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@media (min-width: 50rem) {
|
|
107
|
-
select {
|
|
108
|
-
font-size: var(--sl-text-sm);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@layer starlight.components {
|
|
114
|
-
.label-icon {
|
|
115
|
-
font-size: var(--sl-label-icon-size);
|
|
116
|
-
inset-inline-start: 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.caret {
|
|
120
|
-
font-size: var(--sl-caret-size);
|
|
121
|
-
inset-inline-end: 0;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.custom-select-wrapper {
|
|
126
|
-
--sl-inline-padding: 0.5rem;
|
|
127
|
-
position: relative;
|
|
128
|
-
display: inline-block;
|
|
129
|
-
/* These match the padding on the sidebar menu */
|
|
130
|
-
padding-left: var(--sl-inline-padding);
|
|
131
|
-
padding-right: var(--sl-inline-padding);
|
|
132
|
-
|
|
133
|
-
.icon.http path {
|
|
134
|
-
fill: var(--sl-color-text);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
</style>
|
|
138
56
|
<script>
|
|
139
57
|
import { navigate } from 'astro:transitions/client';
|
|
140
58
|
import { updateSelectedLanguage } from '../languages';
|
|
141
|
-
import { initDropdown } from '@stainless-api/docs
|
|
59
|
+
import { initDropdown } from '@stainless-api/docs/components/scripts';
|
|
142
60
|
import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
|
|
143
61
|
import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
|
|
144
62
|
|
|
145
63
|
document.addEventListener(getPageLoadEvent(), () => {
|
|
64
|
+
const sdkSelect = document.getElementById('sidebar-sdk-select');
|
|
65
|
+
if (!sdkSelect) return;
|
|
146
66
|
initDropdown({
|
|
147
|
-
|
|
67
|
+
root: sdkSelect,
|
|
148
68
|
onSelect: (value) => {
|
|
149
|
-
const originalLanguage =
|
|
69
|
+
const originalLanguage = sdkSelect.dataset.currentValue;
|
|
150
70
|
navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
|
|
151
71
|
},
|
|
152
72
|
});
|