@stainless-api/docs 0.1.0-beta.3 → 0.1.0-beta.30
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 +214 -0
- package/components/variables.css +1 -27
- package/eslint-suppressions.json +47 -0
- package/locals.d.ts +14 -0
- package/package.json +30 -27
- package/plugin/buildAlgoliaIndex.ts +29 -4
- package/plugin/cms/server.ts +97 -54
- package/plugin/cms/sidebar-builder.ts +6 -25
- package/plugin/cms/worker.ts +2 -2
- package/plugin/components/SnippetCode.tsx +7 -4
- package/plugin/components/search/SearchAlgolia.astro +0 -7
- package/plugin/components/search/SearchIsland.tsx +30 -17
- package/plugin/generateAPIReferenceLink.ts +1 -1
- package/plugin/globalJs/ai-dropdown-options.ts +161 -0
- package/plugin/globalJs/navigation.ts +0 -23
- package/plugin/helpers/getPageLoadEvent.ts +1 -1
- package/plugin/index.ts +48 -18
- package/plugin/languages.ts +1 -1
- package/plugin/loadPluginConfig.ts +100 -13
- package/plugin/react/Routing.tsx +30 -33
- package/plugin/referencePlaceholderUtils.ts +1 -1
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +4 -0
- package/plugin/routes/Docs.astro +59 -85
- package/plugin/routes/Overview.astro +9 -15
- package/plugin/routes/markdown.ts +1 -1
- package/plugin/vendor/preview.worker.docs.js +7566 -6784
- package/resolveSrcFile.ts +10 -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 +52 -0
- package/stl-docs/components/Head.astro +16 -0
- package/stl-docs/components/Header.astro +3 -2
- package/stl-docs/components/PageTitle.astro +64 -0
- package/stl-docs/components/TableOfContents.astro +34 -0
- package/stl-docs/components/ThemeSelect.astro +4 -2
- package/stl-docs/components/content-panel/ContentPanel.astro +9 -39
- package/stl-docs/components/headers/DefaultHeader.astro +1 -1
- package/stl-docs/components/headers/HeaderLinks.astro +1 -1
- package/stl-docs/components/headers/StackedHeader.astro +29 -24
- package/stl-docs/components/icons/chat-gpt.tsx +17 -0
- package/stl-docs/components/icons/claude.tsx +10 -0
- package/stl-docs/components/icons/markdown.tsx +10 -0
- package/stl-docs/components/index.ts +2 -0
- package/stl-docs/components/mintlify-compat/Accordion.astro +7 -38
- package/stl-docs/components/mintlify-compat/AccordionGroup.astro +9 -23
- 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/Step.astro +30 -32
- package/stl-docs/components/mintlify-compat/Steps.astro +8 -10
- package/stl-docs/components/mintlify-compat/callouts/Callout.astro +10 -3
- package/stl-docs/components/mintlify-compat/callouts/Check.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Danger.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Info.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Note.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Tip.astro +7 -3
- package/stl-docs/components/mintlify-compat/callouts/Warning.astro +7 -3
- package/stl-docs/components/mintlify-compat/card.css +33 -35
- package/stl-docs/components/nav-tabs/NavDropdown.astro +1 -1
- package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -7
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +4 -3
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +173 -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/{Sidebar.astro → sidebars/BaseSidebar.astro} +2 -3
- package/stl-docs/components/sidebars/SDKSelectSidebar.astro +8 -0
- package/stl-docs/disableCalloutSyntax.ts +36 -0
- package/stl-docs/index.ts +76 -26
- package/stl-docs/loadStlDocsConfig.ts +25 -3
- package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +64 -0
- package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +34 -0
- package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
- package/stl-docs/tabsMiddleware.ts +12 -4
- package/styles/code.css +115 -127
- package/styles/fonts.css +32 -17
- package/styles/links.css +10 -49
- package/styles/overrides.css +54 -57
- package/styles/page.css +89 -59
- package/styles/sdk_select.css +6 -7
- package/styles/search.css +65 -67
- package/styles/sidebar.css +199 -128
- package/styles/toc.css +37 -33
- package/theme.css +9 -1
- package/tsconfig.json +2 -5
- package/virtual-module.d.ts +5 -1
- package/plugin/globalJs/ai-dropdown.ts +0 -57
- package/stl-docs/components/APIReferenceAIDropdown.tsx +0 -86
- package/stl-docs/components/content-panel/ProseAIDropdown.tsx +0 -64
- /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/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +0 -0
package/plugin/cms/server.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import type { AstroIntegrationLogger } from 'astro';
|
|
1
2
|
import { createServer, IncomingMessage } from 'http';
|
|
2
3
|
import { readFile } from 'fs/promises';
|
|
3
4
|
|
|
4
|
-
import type * as SDKJSON from '
|
|
5
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
5
6
|
import { createSDKJSON, parseInputs, transformOAS } from './worker';
|
|
6
7
|
import { Languages, parseRoute, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
|
|
7
|
-
import Stainless from '@stainless-api/sdk';
|
|
8
|
+
import Stainless, { APIError } from '@stainless-api/sdk';
|
|
8
9
|
|
|
9
10
|
import {
|
|
10
11
|
toStarlightSidebar,
|
|
@@ -12,88 +13,118 @@ import {
|
|
|
12
13
|
SidebarConfigItemsBuilder,
|
|
13
14
|
} from './sidebar-builder';
|
|
14
15
|
import type { VersionUserConfig } from '../loadPluginConfig';
|
|
16
|
+
import { bold } from '../../shared/terminalUtils';
|
|
15
17
|
|
|
16
18
|
export type InputFilePaths = {
|
|
17
19
|
oasPath?: string;
|
|
18
20
|
configPath?: string;
|
|
19
21
|
};
|
|
20
22
|
|
|
21
|
-
async function versionInfo(project: string, apiKey: string) {
|
|
23
|
+
async function versionInfo(project: string, apiKey: string): Promise<Record<DocsLanguage, string>> {
|
|
22
24
|
const data = await fetch(`https://api.stainless.com/api/projects/${project}/package-versions`, {
|
|
23
25
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
24
26
|
});
|
|
25
27
|
|
|
26
28
|
const content = await data.text();
|
|
27
|
-
return JSON.parse(content)
|
|
29
|
+
return JSON.parse(content) as Record<DocsLanguage, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function redactApiKey(apiKey: string) {
|
|
33
|
+
return apiKey
|
|
34
|
+
.split('')
|
|
35
|
+
.map((char, index) => (index < 10 ? char : '*'))
|
|
36
|
+
.join('');
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
async function loadSpec({
|
|
31
40
|
apiKey,
|
|
32
41
|
devPaths,
|
|
33
42
|
version,
|
|
43
|
+
logger,
|
|
34
44
|
}: {
|
|
35
45
|
apiKey: string;
|
|
36
46
|
devPaths: InputFilePaths;
|
|
37
47
|
version: VersionUserConfig;
|
|
48
|
+
logger: AstroIntegrationLogger;
|
|
38
49
|
}) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
async function unsafeLoad() {
|
|
51
|
+
let oasStr: string;
|
|
52
|
+
let configStr: string;
|
|
53
|
+
let versions: Record<DocsLanguage, string> | undefined;
|
|
54
|
+
|
|
55
|
+
if (devPaths.oasPath && devPaths.configPath) {
|
|
56
|
+
[oasStr, configStr] = await Promise.all([
|
|
57
|
+
readFile(devPaths.oasPath, 'utf-8'),
|
|
58
|
+
readFile(devPaths.configPath, 'utf-8'),
|
|
59
|
+
]);
|
|
60
|
+
} else {
|
|
61
|
+
const client = new Stainless({ apiKey });
|
|
62
|
+
const configs = await client.projects.configs.retrieve({
|
|
63
|
+
project: version.stainlessProject,
|
|
64
|
+
branch: version.branch,
|
|
65
|
+
include: 'openapi',
|
|
66
|
+
});
|
|
55
67
|
|
|
56
|
-
|
|
68
|
+
versions = await versionInfo(version.stainlessProject, apiKey);
|
|
57
69
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
70
|
+
const configYML = Object.values(configs)[0] as { content: any };
|
|
71
|
+
const oasJson = Object.values(configs)[1] as { content: any };
|
|
72
|
+
oasStr = oasJson['content'];
|
|
73
|
+
configStr = configYML['content'];
|
|
74
|
+
}
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
const { oas, config } = await parseInputs({
|
|
77
|
+
oas: oasStr,
|
|
78
|
+
config: configStr,
|
|
79
|
+
});
|
|
68
80
|
|
|
69
|
-
|
|
81
|
+
const transformedOAS = await transformOAS({ oas, config });
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
const languages =
|
|
84
|
+
config.docs?.languages ??
|
|
85
|
+
(Object.entries(config.targets)
|
|
86
|
+
// @ts-expect-error we don't have the actual Stainless config type here
|
|
87
|
+
.filter(([name, target]) => Languages.includes(name) && !target.skip)
|
|
88
|
+
.map(([name]) => name) as SDKJSON.SpecLanguage[]);
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
90
|
+
const sdkJson = await createSDKJSON({
|
|
91
|
+
oas: transformedOAS,
|
|
92
|
+
config,
|
|
93
|
+
languages,
|
|
94
|
+
});
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
96
|
+
if (versions) {
|
|
97
|
+
for (const [lang, version] of Object.entries(versions)) {
|
|
98
|
+
const meta = sdkJson.metadata[lang as DocsLanguage];
|
|
99
|
+
if (meta?.version) meta.version = version;
|
|
100
|
+
}
|
|
88
101
|
}
|
|
102
|
+
|
|
103
|
+
const id = crypto.randomUUID();
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
data: sdkJson,
|
|
107
|
+
id,
|
|
108
|
+
};
|
|
89
109
|
}
|
|
90
110
|
|
|
91
|
-
|
|
111
|
+
try {
|
|
112
|
+
const result = await unsafeLoad();
|
|
113
|
+
return result;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.error(bold('Failed to fetch API reference information from Stainless:'));
|
|
116
|
+
if (error instanceof APIError && error.status >= 400 && error.status < 500) {
|
|
117
|
+
logger.error(`Requested project slug: "${version.stainlessProject}"`);
|
|
118
|
+
logger.error(`API key: "${redactApiKey(apiKey)}"`);
|
|
119
|
+
logger.error(
|
|
120
|
+
`This error can usually be corrected by re-authenticating with the Stainless. Use the CLI (stl auth login) or verify that the Stainless API key you're using can access the project mentioned above.`,
|
|
121
|
+
);
|
|
122
|
+
} else {
|
|
123
|
+
logger.error(error instanceof Error ? error.message : 'Unknown error');
|
|
124
|
+
}
|
|
92
125
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
id,
|
|
96
|
-
};
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
97
128
|
}
|
|
98
129
|
|
|
99
130
|
class Spec {
|
|
@@ -107,6 +138,7 @@ class Spec {
|
|
|
107
138
|
apiKey: this.apiKey,
|
|
108
139
|
devPaths: this.devPaths,
|
|
109
140
|
version: this.version,
|
|
141
|
+
logger: this.logger,
|
|
110
142
|
});
|
|
111
143
|
}
|
|
112
144
|
|
|
@@ -128,15 +160,22 @@ class Spec {
|
|
|
128
160
|
return spec;
|
|
129
161
|
}
|
|
130
162
|
|
|
131
|
-
constructor(
|
|
163
|
+
constructor(
|
|
164
|
+
apiKey: string,
|
|
165
|
+
version: VersionUserConfig,
|
|
166
|
+
devPaths: InputFilePaths,
|
|
167
|
+
private logger: AstroIntegrationLogger,
|
|
168
|
+
) {
|
|
132
169
|
this.specPromise = loadSpec({
|
|
133
170
|
apiKey,
|
|
134
171
|
devPaths,
|
|
135
172
|
version,
|
|
173
|
+
logger,
|
|
136
174
|
});
|
|
137
175
|
this.devPaths = devPaths;
|
|
138
176
|
this.apiKey = apiKey;
|
|
139
177
|
this.version = version;
|
|
178
|
+
this.logger = logger;
|
|
140
179
|
}
|
|
141
180
|
}
|
|
142
181
|
|
|
@@ -166,14 +205,16 @@ export function startDevServer({
|
|
|
166
205
|
devPaths,
|
|
167
206
|
apiKey,
|
|
168
207
|
getGeneratedSidebarConfig,
|
|
208
|
+
logger,
|
|
169
209
|
}: {
|
|
170
210
|
port: number;
|
|
171
211
|
version: VersionUserConfig;
|
|
172
212
|
devPaths: InputFilePaths;
|
|
173
213
|
apiKey: string;
|
|
174
214
|
getGeneratedSidebarConfig: (id: number) => GeneratedSidebarConfig | null;
|
|
215
|
+
logger: AstroIntegrationLogger;
|
|
175
216
|
}) {
|
|
176
|
-
const spec = new Spec(apiKey, version, devPaths);
|
|
217
|
+
const spec = new Spec(apiKey, version, devPaths, logger);
|
|
177
218
|
|
|
178
219
|
const server = createServer(async (req, res) => {
|
|
179
220
|
// Add CORS headers
|
|
@@ -250,7 +291,7 @@ export function startDevServer({
|
|
|
250
291
|
});
|
|
251
292
|
|
|
252
293
|
server.listen(port, () => {
|
|
253
|
-
|
|
294
|
+
logger.debug(`Stainless spec server is running on port: ${port}`);
|
|
254
295
|
});
|
|
255
296
|
|
|
256
297
|
return {
|
|
@@ -266,3 +307,5 @@ export function startDevServer({
|
|
|
266
307
|
},
|
|
267
308
|
};
|
|
268
309
|
}
|
|
310
|
+
|
|
311
|
+
export type DevSpecServer = Awaited<ReturnType<typeof startDevServer>>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type * as SDKJSON from '
|
|
1
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
2
2
|
import { generateRoute, walkTree, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
|
|
3
3
|
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
4
4
|
|
|
@@ -120,22 +120,12 @@ export type GeneratedSidebarConfig = {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
function countKeys(obj?: Record<string, any>) {
|
|
123
|
-
|
|
123
|
+
const o = obj ?? {};
|
|
124
124
|
return Object.keys(o).length;
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const decl = decls[entry.stainlessPath];
|
|
130
|
-
if (decl !== undefined) {
|
|
131
|
-
if ('ident' in decl) {
|
|
132
|
-
return decl;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
type MethodDecl = Exclude<ReturnType<typeof getMethodDeclForLanguage>, null>;
|
|
127
|
+
type HasIdent<T> = T extends { ident: unknown } ? T : never;
|
|
128
|
+
type MethodDecl = HasIdent<SDKJSON.LanguageDeclNodes[SDKJSON.SpecLanguage]>;
|
|
139
129
|
|
|
140
130
|
function makeAPIOverviewPage(): UserSidebarAPIOverviewPage {
|
|
141
131
|
return {
|
|
@@ -166,7 +156,7 @@ function pullOutSharedModelsResource(resources: SDKJSON.Resource[]): {
|
|
|
166
156
|
}
|
|
167
157
|
|
|
168
158
|
export class SidebarConfigItemsBuilder {
|
|
169
|
-
private getMethodDeclForLanguage(entry: SDKJSON.Method) {
|
|
159
|
+
private getMethodDeclForLanguage(entry: SDKJSON.Method): MethodDecl | null {
|
|
170
160
|
const decls = this.spec.decls[this.language] ?? {};
|
|
171
161
|
const decl = decls[entry.stainlessPath];
|
|
172
162
|
if (decl !== undefined) {
|
|
@@ -208,13 +198,6 @@ export class SidebarConfigItemsBuilder {
|
|
|
208
198
|
};
|
|
209
199
|
}
|
|
210
200
|
|
|
211
|
-
private sortByLabel<T extends UserSidebarConfigItem>(items: T[]) {
|
|
212
|
-
// sorts in place
|
|
213
|
-
items.sort((a, b) => {
|
|
214
|
-
return a.label.localeCompare(b.label);
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
201
|
private generateResourceGroup(resource: SDKJSON.Resource, collapsed: boolean): ReferenceSidebarGroup {
|
|
219
202
|
const entries: ReferenceSidebarConfigItem[] = [];
|
|
220
203
|
if (!this.options?.excludeResourceOverviewPages) {
|
|
@@ -228,7 +211,6 @@ export class SidebarConfigItemsBuilder {
|
|
|
228
211
|
methodPages.push(this.toMethodPage(m, langDecl));
|
|
229
212
|
}
|
|
230
213
|
}
|
|
231
|
-
this.sortByLabel(methodPages);
|
|
232
214
|
entries.push(...methodPages);
|
|
233
215
|
|
|
234
216
|
const subresources = Object.values(resource.subresources ?? {});
|
|
@@ -238,7 +220,6 @@ export class SidebarConfigItemsBuilder {
|
|
|
238
220
|
subresourceGroups.push(this.generateResourceGroup(sub, true));
|
|
239
221
|
}
|
|
240
222
|
}
|
|
241
|
-
this.sortByLabel(subresourceGroups);
|
|
242
223
|
entries.push(...subresourceGroups);
|
|
243
224
|
|
|
244
225
|
return {
|
|
@@ -253,7 +234,7 @@ export class SidebarConfigItemsBuilder {
|
|
|
253
234
|
|
|
254
235
|
public generateItems(): ReferenceSidebarConfigItem[] {
|
|
255
236
|
const resourceMap = this.spec.resources;
|
|
256
|
-
|
|
237
|
+
const { resources, sharedModelsResource } = pullOutSharedModelsResource(Object.values(resourceMap ?? {}));
|
|
257
238
|
|
|
258
239
|
const entries: ReferenceSidebarConfigItem[] = resources.filter(isResourceNonEmpty).map((r) => {
|
|
259
240
|
return this.generateResourceGroup(r, false);
|
package/plugin/cms/worker.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Worker from 'web-worker';
|
|
2
2
|
import { Languages, type DocsLanguage } from '@stainless-api/docs-ui/src/routing';
|
|
3
|
-
import type * as SDKJSON from '
|
|
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';
|
|
@@ -98,7 +98,7 @@ export async function createSDKJSON({
|
|
|
98
98
|
try {
|
|
99
99
|
const content = await fs.readFile(mdfile);
|
|
100
100
|
return [language, content.toString()];
|
|
101
|
-
} catch
|
|
101
|
+
} catch {
|
|
102
102
|
return [language, null];
|
|
103
103
|
}
|
|
104
104
|
}),
|
|
@@ -8,6 +8,7 @@ import style from '@stainless-api/docs-ui/src/style';
|
|
|
8
8
|
import * as cheerio from 'cheerio/slim';
|
|
9
9
|
import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
|
|
10
10
|
import clsx from 'clsx';
|
|
11
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
11
12
|
/*
|
|
12
13
|
* This may be replaced by additional data from the sdk.
|
|
13
14
|
* Without information from the sdk, we use simple heuristics per language.
|
|
@@ -53,7 +54,7 @@ function wrapFirstNSpaces($line: cheerio.Cheerio<any>, n: number) {
|
|
|
53
54
|
const m = inner.match(new RegExp(`^( {1,${n}})`));
|
|
54
55
|
if (!m) return;
|
|
55
56
|
|
|
56
|
-
const lead = m[1]
|
|
57
|
+
const lead = m[1]!;
|
|
57
58
|
$firstSpan.html(`<span class="leading-ws">${lead}</span>${inner.slice(lead.length)}`);
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -130,12 +131,14 @@ export function SnippetRequestContainer({ children, signature }: SnippetRequestC
|
|
|
130
131
|
<div className="stl-snippet-request-container">
|
|
131
132
|
{children}
|
|
132
133
|
{signature && isCollapsible && (
|
|
133
|
-
<
|
|
134
|
-
className={
|
|
134
|
+
<Button
|
|
135
|
+
className={'stl-snippet-expand-button'}
|
|
135
136
|
id="stl-snippet-expand-button"
|
|
137
|
+
size="sm"
|
|
138
|
+
variant="outline"
|
|
136
139
|
>
|
|
137
140
|
Show more
|
|
138
|
-
</
|
|
141
|
+
</Button>
|
|
139
142
|
)}
|
|
140
143
|
</div>
|
|
141
144
|
);
|
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
import { Icon } from '@astrojs/starlight/components';
|
|
3
3
|
import { DocsSearch } from './SearchIsland';
|
|
4
4
|
import { SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
5
|
-
|
|
6
|
-
import '@stainless-api/docs-ui/src/styles/resets.css';
|
|
7
|
-
import '@stainless-api/docs-ui/src/styles/primitives.css';
|
|
8
|
-
import '@stainless-api/docs-ui/src/styles/main.css';
|
|
9
|
-
import '@stainless-api/docs-ui/src/styles/snippets.css';
|
|
10
|
-
import '@stainless-api/docs-ui/src/styles/search.css';
|
|
11
|
-
import '../../../components/variables.css';
|
|
12
5
|
---
|
|
13
6
|
|
|
14
7
|
<site-search>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { BASE_PATH, SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
2
|
+
import { BASE_PATH, HIGHLIGHT_THEMES, SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
3
3
|
import { parseRoute, generateRoute } from '@stainless-api/docs-ui/src/routing';
|
|
4
4
|
import { SearchModal } from '@stainless-api/docs-ui/src/search/index';
|
|
5
5
|
import { ChatModal } from '@stainless-api/docs-ui/src/components/chat';
|
|
@@ -9,10 +9,12 @@ import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
DocsProvider,
|
|
12
|
+
type MarkdownContext,
|
|
12
13
|
MarkdownProvider,
|
|
13
14
|
NavigationProvider,
|
|
14
15
|
SearchProvider,
|
|
15
16
|
} from '@stainless-api/docs-ui/src/contexts';
|
|
17
|
+
import { ComponentProvider } from '@stainless-api/docs-ui/src/contexts/component';
|
|
16
18
|
import type { SearchSettings } from '@stainless-api/docs-ui/src/search/types';
|
|
17
19
|
|
|
18
20
|
let $$highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
|
|
@@ -27,7 +29,7 @@ async function getHighlighter() {
|
|
|
27
29
|
return $$highlighter;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
async function createMarkdownRenderer() {
|
|
32
|
+
async function createMarkdownRenderer(): Promise<MarkdownContext> {
|
|
31
33
|
const highlighter = await getHighlighter();
|
|
32
34
|
const markdocConfig: Markdoc.Config = {
|
|
33
35
|
nodes: {
|
|
@@ -66,16 +68,25 @@ async function createMarkdownRenderer() {
|
|
|
66
68
|
},
|
|
67
69
|
};
|
|
68
70
|
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
return {
|
|
72
|
+
render: (content: string) => {
|
|
73
|
+
const ast = Markdoc.parse(content);
|
|
74
|
+
const transformed = Markdoc.transform(ast, markdocConfig);
|
|
75
|
+
return Markdoc.renderers.html(transformed);
|
|
76
|
+
},
|
|
77
|
+
highlight: (content: string, language: string) => {
|
|
78
|
+
return highlighter.codeToHtml(content, {
|
|
79
|
+
lang: language ?? 'javascript',
|
|
80
|
+
themes: HIGHLIGHT_THEMES || {},
|
|
81
|
+
});
|
|
82
|
+
},
|
|
73
83
|
};
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
|
|
77
|
-
const
|
|
87
|
+
const markdownRenderer = React.use(createMarkdownRenderer());
|
|
78
88
|
const { stainlessPath, language } = parseRoute(BASE_PATH, currentPath);
|
|
89
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
79
90
|
const pageFind = import.meta.env.DEV ? undefined : '/pagefind/pagefind.js';
|
|
80
91
|
|
|
81
92
|
function handleSelect(path: string) {
|
|
@@ -85,16 +96,18 @@ export function DocsSearch({ settings, currentPath }: { settings: SearchSettings
|
|
|
85
96
|
|
|
86
97
|
return (
|
|
87
98
|
<DocsProvider spec={null} language={language}>
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
<ComponentProvider>
|
|
100
|
+
<NavigationProvider basePath="/" selectedPath={stainlessPath}>
|
|
101
|
+
<MarkdownProvider {...markdownRenderer}>
|
|
102
|
+
<SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
|
|
103
|
+
<div className="stldocs-root">
|
|
104
|
+
<SearchModal id="stldocs-search" />
|
|
105
|
+
{SEARCH?.enableAISearch === true && <ChatModal id="stldocs-chat" />}
|
|
106
|
+
</div>
|
|
107
|
+
</SearchProvider>
|
|
108
|
+
</MarkdownProvider>
|
|
109
|
+
</NavigationProvider>
|
|
110
|
+
</ComponentProvider>
|
|
98
111
|
</DocsProvider>
|
|
99
112
|
);
|
|
100
113
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// This is probably temporary, but it fills in functionality needed for Mintlify imports
|
|
2
2
|
|
|
3
3
|
import type { StarlightRouteData } from '@astrojs/starlight/route-data';
|
|
4
|
-
import type * as SDKJSON from '
|
|
4
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
5
5
|
import { walkTree } from '@stainless-api/docs-ui/src/routing';
|
|
6
6
|
|
|
7
7
|
const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
|
|
2
|
+
import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
|
|
3
|
+
|
|
4
|
+
export type DropdownIcon = 'markdown' | 'copy' | 'claude' | 'chatgpt';
|
|
5
|
+
|
|
6
|
+
interface DropdownOptionInputProps {
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
icon: DropdownIcon;
|
|
9
|
+
primaryAction?: boolean;
|
|
10
|
+
label: string[] | string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getMarkdownUrl(type: 'relative' | 'absolute') {
|
|
14
|
+
const currentUrl = new URL(window.location.href);
|
|
15
|
+
const hasTrailingSlash = currentUrl.pathname.endsWith('/');
|
|
16
|
+
|
|
17
|
+
const markdownUrl = [
|
|
18
|
+
type === 'absolute' ? currentUrl.origin : '',
|
|
19
|
+
currentUrl.pathname,
|
|
20
|
+
hasTrailingSlash ? '' : '/',
|
|
21
|
+
'index.md',
|
|
22
|
+
].join('');
|
|
23
|
+
|
|
24
|
+
return markdownUrl;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function openInLLM(serviceUrl: string) {
|
|
28
|
+
const mdUrl = getMarkdownUrl('absolute');
|
|
29
|
+
const prompt = encodeURIComponent(
|
|
30
|
+
`Load the contents of ${mdUrl} into this chat's context so we can discuss it.`,
|
|
31
|
+
);
|
|
32
|
+
window.open(`${serviceUrl}${prompt}`, '_blank');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2d array of dropdown options
|
|
36
|
+
// each sub-array is a group, separated by a horizontal rule in the UI
|
|
37
|
+
const aiDropdownOptions: DropdownOptionInputProps[][] = [
|
|
38
|
+
[
|
|
39
|
+
{
|
|
40
|
+
label: 'View as Markdown',
|
|
41
|
+
onClick: () => {
|
|
42
|
+
window.open(getMarkdownUrl('absolute'), '_blank');
|
|
43
|
+
},
|
|
44
|
+
icon: 'markdown',
|
|
45
|
+
primaryAction: true,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: 'Copy Markdown',
|
|
49
|
+
onClick: () => {
|
|
50
|
+
// Source: https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
|
|
51
|
+
const markdownUrl = getMarkdownUrl('relative');
|
|
52
|
+
// ClipboardItem doesn't exist in every browser
|
|
53
|
+
// eslint-disable-next-line no-constant-binary-expression
|
|
54
|
+
if (typeof ClipboardItem && navigator.clipboard.write) {
|
|
55
|
+
// NOTE: Safari locks down the clipboard API to only work when triggered
|
|
56
|
+
// by a direct user interaction. You can't use it async in a promise.
|
|
57
|
+
// But! You can wrap the promise in a ClipboardItem, and give that to
|
|
58
|
+
// the clipboard API.
|
|
59
|
+
// Found this on https://developer.apple.com/forums/thread/691873
|
|
60
|
+
const text = new ClipboardItem({
|
|
61
|
+
'text/plain': fetch(markdownUrl)
|
|
62
|
+
.then((response) => response.text())
|
|
63
|
+
.then((text) => new Blob([text], { type: 'text/plain' })),
|
|
64
|
+
});
|
|
65
|
+
navigator.clipboard.write([text]);
|
|
66
|
+
} else {
|
|
67
|
+
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
|
|
68
|
+
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
|
|
69
|
+
// Good news is that other than Safari, Firefox does not care about
|
|
70
|
+
// Clipboard API being used async in a Promise.
|
|
71
|
+
fetch(markdownUrl)
|
|
72
|
+
.then((response) => response.text())
|
|
73
|
+
.then((text) => navigator.clipboard.writeText(text));
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
icon: 'copy',
|
|
77
|
+
primaryAction: false,
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
{
|
|
82
|
+
label: ['Open in ', 'Claude'],
|
|
83
|
+
onClick: () => {
|
|
84
|
+
openInLLM('https://claude.ai/new?q=');
|
|
85
|
+
},
|
|
86
|
+
icon: 'claude',
|
|
87
|
+
primaryAction: false,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
label: ['Open in ', 'ChatGPT'],
|
|
91
|
+
onClick: () => {
|
|
92
|
+
openInLLM('https://chatgpt.com/?hints=search&prompt=');
|
|
93
|
+
},
|
|
94
|
+
icon: 'chatgpt',
|
|
95
|
+
primaryAction: false,
|
|
96
|
+
},
|
|
97
|
+
// TODO: Add Cursor support
|
|
98
|
+
// {
|
|
99
|
+
// label: ['Open in ', 'Cursor'],
|
|
100
|
+
// onClick: () => {
|
|
101
|
+
// openInLLM('https://www.cursor.so/?prompt=');
|
|
102
|
+
// },
|
|
103
|
+
// icon: 'cursor',
|
|
104
|
+
// primaryAction: false,
|
|
105
|
+
// }
|
|
106
|
+
],
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
function renderGroup(group: DropdownOptionInputProps[]) {
|
|
110
|
+
return group.map((option) => {
|
|
111
|
+
const label = typeof option.label === 'string' ? [option.label] : option.label;
|
|
112
|
+
return {
|
|
113
|
+
...option,
|
|
114
|
+
label: label,
|
|
115
|
+
primaryAction: option.primaryAction ?? false,
|
|
116
|
+
id: label.join('').toLowerCase().replace(/ /g, '-'),
|
|
117
|
+
};
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function getAIDropdownOptions() {
|
|
122
|
+
const renderedOptions = aiDropdownOptions.map((group, index) => {
|
|
123
|
+
return {
|
|
124
|
+
options: renderGroup(group),
|
|
125
|
+
isLast: index === aiDropdownOptions.length - 1,
|
|
126
|
+
reactKey: index,
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const allOptions = renderedOptions.flatMap((group) => group.options);
|
|
131
|
+
const primaryAction = allOptions.find((o) => o.primaryAction) ?? allOptions[0]!;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
primaryAction,
|
|
135
|
+
groups: renderedOptions,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function wireAIDropdown() {
|
|
140
|
+
const { primaryAction, groups } = getAIDropdownOptions();
|
|
141
|
+
function triggerOption(id: string) {
|
|
142
|
+
const option = groups.flatMap((group) => group.options).find((option) => option.id === id);
|
|
143
|
+
if (!option) return;
|
|
144
|
+
option.onClick();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
document.addEventListener(getPageLoadEvent(), () => {
|
|
148
|
+
const dropdowns = document.querySelectorAll('[data-dropdown-id]');
|
|
149
|
+
dropdowns.forEach((dropdown) => {
|
|
150
|
+
initDropdownButton({
|
|
151
|
+
dropdown: dropdown,
|
|
152
|
+
onSelect: (value) => {
|
|
153
|
+
triggerOption(value);
|
|
154
|
+
},
|
|
155
|
+
onPrimaryAction: () => {
|
|
156
|
+
triggerOption(primaryAction.id);
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
}
|