@stainless-api/docs 0.1.0-beta.2 → 0.1.0-beta.20
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 +147 -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 +49 -17
- package/plugin/languages.ts +1 -1
- package/plugin/loadPluginConfig.ts +92 -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 +6357 -6132
- 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 +9 -0
- package/stl-docs/components/Header.astro +3 -2
- package/stl-docs/components/PageTitle.astro +65 -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 +30 -25
- 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 -13
- 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 +24 -9
- package/styles/links.css +10 -49
- package/styles/overrides.css +55 -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 +4 -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/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +0 -0
|
@@ -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
|
+
}
|
|
@@ -5,8 +5,6 @@ import { navigate } from 'astro:transitions/client';
|
|
|
5
5
|
import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
|
|
6
6
|
|
|
7
7
|
import { initDropdown } from '@stainless-api/docs-ui/src/components/scripts/dropdown';
|
|
8
|
-
import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
|
|
9
|
-
import { copyCurrentPageAsMarkdown, onSelectAIOption } from './ai-dropdown.ts';
|
|
10
8
|
|
|
11
9
|
history.scrollRestoration = 'auto';
|
|
12
10
|
|
|
@@ -36,27 +34,6 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
36
34
|
if (path) setTimeout(() => scrollToPath(path.slice(1)), 10);
|
|
37
35
|
});
|
|
38
36
|
|
|
39
|
-
document.addEventListener(getPageLoadEvent(), () => {
|
|
40
|
-
console.log('Initializing AI Dropdown');
|
|
41
|
-
initDropdownButton({
|
|
42
|
-
dropdownId: 'ai-dropdown-button',
|
|
43
|
-
onSelect: onSelectAIOption,
|
|
44
|
-
onPrimaryAction: (el) => {
|
|
45
|
-
copyCurrentPageAsMarkdown();
|
|
46
|
-
const innerText = el.querySelector('[data-part="primary-action-text"]');
|
|
47
|
-
if (!innerText) return;
|
|
48
|
-
|
|
49
|
-
const originalInnerHtml = innerText.innerHTML;
|
|
50
|
-
innerText.innerHTML = 'Copied!';
|
|
51
|
-
el.classList.add('disabled');
|
|
52
|
-
setTimeout(() => {
|
|
53
|
-
innerText.innerHTML = originalInnerHtml;
|
|
54
|
-
el.classList.remove('disabled');
|
|
55
|
-
}, 1000);
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
37
|
document.addEventListener('click', (event) => {
|
|
61
38
|
const toggle = (event.target as HTMLElement).closest(
|
|
62
39
|
'[data-stldocs-property-toggle-expanded] > .stldocs-expand-toggle-content',
|
package/plugin/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import react from '@astrojs/react';
|
|
2
2
|
import type { StarlightPlugin } from '@astrojs/starlight/types';
|
|
3
|
-
import type { AstroIntegration } from 'astro';
|
|
3
|
+
import type { AstroIntegration, AstroIntegrationLogger } from 'astro';
|
|
4
4
|
import { config } from 'dotenv';
|
|
5
5
|
import getPort from 'get-port';
|
|
6
6
|
import { startDevServer } from './cms/server';
|
|
@@ -23,11 +23,15 @@ import {
|
|
|
23
23
|
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
24
24
|
import path from 'path';
|
|
25
25
|
import fs from 'fs';
|
|
26
|
+
import { getSharedLogger } from '../shared/getSharedLogger';
|
|
27
|
+
import { resolveSrcFile } from '../resolveSrcFile';
|
|
26
28
|
|
|
27
29
|
export { generateAPILink } from './generateAPIReferenceLink';
|
|
28
30
|
export type { ReferenceSidebarConfigItem };
|
|
29
31
|
|
|
30
|
-
config(
|
|
32
|
+
config({
|
|
33
|
+
quiet: true,
|
|
34
|
+
});
|
|
31
35
|
|
|
32
36
|
let sidebarIdCounter = 0;
|
|
33
37
|
|
|
@@ -108,6 +112,7 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
|
|
|
108
112
|
|
|
109
113
|
async function stlStarlightAstroIntegration(
|
|
110
114
|
pluginConfig: NormalizedStainlessStarlightConfig,
|
|
115
|
+
stlStarlightPluginLogger: AstroIntegrationLogger,
|
|
111
116
|
): Promise<AstroIntegration> {
|
|
112
117
|
const virtualId = `virtual:stl-starlight-virtual-module`;
|
|
113
118
|
// The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
|
|
@@ -117,11 +122,12 @@ async function stlStarlightAstroIntegration(
|
|
|
117
122
|
|
|
118
123
|
const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
|
|
119
124
|
|
|
120
|
-
const cmsServer =
|
|
125
|
+
const cmsServer = startDevServer({
|
|
121
126
|
port: CMS_PORT,
|
|
122
|
-
apiKey,
|
|
127
|
+
apiKey: apiKey.value,
|
|
123
128
|
version,
|
|
124
129
|
devPaths,
|
|
130
|
+
logger: stlStarlightPluginLogger,
|
|
125
131
|
getGeneratedSidebarConfig: (id: number) => {
|
|
126
132
|
const config = sidebarConfigs.get(id);
|
|
127
133
|
if (!config) {
|
|
@@ -134,34 +140,40 @@ async function stlStarlightAstroIntegration(
|
|
|
134
140
|
return {
|
|
135
141
|
name: 'stl-starlight-astro',
|
|
136
142
|
hooks: {
|
|
137
|
-
'astro:config:setup': async ({
|
|
143
|
+
'astro:config:setup': async ({
|
|
144
|
+
injectRoute,
|
|
145
|
+
updateConfig,
|
|
146
|
+
logger: localLogger,
|
|
147
|
+
command,
|
|
148
|
+
config: astroConfig,
|
|
149
|
+
}) => {
|
|
150
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
138
151
|
const projectDir = astroConfig.root.pathname;
|
|
139
152
|
|
|
140
153
|
const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
|
|
141
154
|
|
|
142
155
|
let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
|
|
143
156
|
if (fs.existsSync(middlewareFile)) {
|
|
144
|
-
logger.
|
|
157
|
+
logger.debug(`Loading middleware from ${middlewareFile}`);
|
|
145
158
|
vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
injectRoute({
|
|
149
|
-
pattern: `${pluginConfig.basePath}/[...slug].md`,
|
|
150
|
-
entrypoint: '
|
|
162
|
+
pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
|
|
163
|
+
entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
|
|
151
164
|
prerender: command === 'build',
|
|
152
165
|
});
|
|
153
166
|
|
|
154
|
-
const astroFile = command === 'build' ? '
|
|
167
|
+
const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
|
|
155
168
|
injectRoute({
|
|
156
169
|
pattern: `${pluginConfig.basePath}/[...slug]`,
|
|
157
|
-
|
|
158
|
-
entrypoint: `@stainless-api/docs/${astroFile}`,
|
|
170
|
+
entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
|
|
159
171
|
prerender: command === 'build',
|
|
160
172
|
});
|
|
161
173
|
|
|
162
174
|
injectRoute({
|
|
163
175
|
pattern: pluginConfig.basePath,
|
|
164
|
-
entrypoint: '
|
|
176
|
+
entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
|
|
165
177
|
prerender: command === 'build',
|
|
166
178
|
});
|
|
167
179
|
|
|
@@ -213,6 +225,8 @@ async function stlStarlightAstroIntegration(
|
|
|
213
225
|
EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
|
|
214
226
|
PROPERTY_SETTINGS: pluginConfig.propertySettings,
|
|
215
227
|
SEARCH: pluginConfig.search,
|
|
228
|
+
// @ts-expect-error internal prop
|
|
229
|
+
ENABLE_CONTEXT_MENU: pluginConfig._contextMenu,
|
|
216
230
|
}),
|
|
217
231
|
vmMiddlewareExport,
|
|
218
232
|
].join('\n');
|
|
@@ -241,15 +255,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
241
255
|
command,
|
|
242
256
|
config: starlightConfig,
|
|
243
257
|
astroConfig,
|
|
244
|
-
logger,
|
|
258
|
+
logger: localLogger,
|
|
245
259
|
}) => {
|
|
246
260
|
if (command !== 'build' && command !== 'dev') {
|
|
247
261
|
return;
|
|
248
262
|
}
|
|
249
263
|
|
|
264
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
265
|
+
|
|
250
266
|
const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
|
|
251
267
|
if (configParseResult.result === 'error') {
|
|
252
|
-
|
|
268
|
+
const errorLines = configParseResult.message.split('\n');
|
|
269
|
+
for (const line of errorLines) {
|
|
270
|
+
logger.error(line);
|
|
271
|
+
}
|
|
253
272
|
process.exit(1);
|
|
254
273
|
}
|
|
255
274
|
const config = configParseResult.config;
|
|
@@ -260,17 +279,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
260
279
|
addIntegration(react());
|
|
261
280
|
}
|
|
262
281
|
|
|
282
|
+
if ('apiKey' in config.specRetrieverConfig) {
|
|
283
|
+
if (!config.specRetrieverConfig.apiKey) {
|
|
284
|
+
logger.info(`Stainless credentials not loaded`);
|
|
285
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
|
|
286
|
+
logger.info(`Stainless credentials loaded from user config`);
|
|
287
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
|
|
288
|
+
logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
|
|
289
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'cli') {
|
|
290
|
+
logger.info('Stainless credentials loaded from `stl` CLI');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
263
294
|
if (
|
|
264
295
|
command === 'build' &&
|
|
265
296
|
config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
|
|
266
297
|
) {
|
|
267
298
|
await buildAlgoliaIndex({
|
|
268
299
|
version: config.specRetrieverConfig.version,
|
|
269
|
-
apiKey: config.specRetrieverConfig.apiKey,
|
|
300
|
+
apiKey: config.specRetrieverConfig.apiKey.value,
|
|
301
|
+
logger,
|
|
270
302
|
});
|
|
271
303
|
}
|
|
272
304
|
|
|
273
|
-
addIntegration(await stlStarlightAstroIntegration(config));
|
|
305
|
+
addIntegration(await stlStarlightAstroIntegration(config, logger));
|
|
274
306
|
|
|
275
307
|
if (starlightConfig.sidebar) {
|
|
276
308
|
// for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
|
|
@@ -295,7 +327,7 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
295
327
|
});
|
|
296
328
|
|
|
297
329
|
addRouteMiddleware({
|
|
298
|
-
entrypoint: '
|
|
330
|
+
entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
|
|
299
331
|
order: 'post',
|
|
300
332
|
});
|
|
301
333
|
},
|
package/plugin/languages.ts
CHANGED
|
@@ -44,7 +44,7 @@ export function applyLanguageToLinks(basePath?: string, defaultLanguage?: string
|
|
|
44
44
|
`[data-stldocs-overview],[data-stldocs-method],a.nav-link[href^='${basePath}']`,
|
|
45
45
|
);
|
|
46
46
|
|
|
47
|
-
for (
|
|
47
|
+
for (const link of links) {
|
|
48
48
|
const href = link.getAttribute('href');
|
|
49
49
|
const prefix = generatePrefix(basePath, language);
|
|
50
50
|
if (href?.startsWith(basePath) && !href?.startsWith(prefix)) {
|