@stainless-api/docs 0.1.0-beta.5 → 0.1.0-beta.50
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 +381 -0
- package/eslint-suppressions.json +47 -0
- package/locals.d.ts +14 -0
- package/package.json +42 -38
- package/plugin/buildAlgoliaIndex.ts +31 -6
- package/plugin/cms/server.ts +98 -56
- package/plugin/cms/sidebar-builder.ts +7 -26
- package/plugin/cms/worker.ts +3 -3
- package/plugin/components/MethodDescription.tsx +54 -0
- package/plugin/components/SDKSelect.astro +7 -87
- package/plugin/components/SnippetCode.tsx +11 -7
- package/plugin/components/search/SearchAlgolia.astro +5 -33
- package/plugin/components/search/SearchIsland.tsx +37 -23
- package/plugin/generateAPIReferenceLink.ts +2 -2
- package/plugin/globalJs/ai-dropdown-options.ts +235 -0
- package/plugin/globalJs/method-descriptions.ts +33 -0
- package/plugin/globalJs/navigation.ts +7 -27
- package/plugin/helpers/getPageLoadEvent.ts +1 -1
- package/plugin/index.ts +54 -34
- package/plugin/languages.ts +2 -2
- package/plugin/loadPluginConfig.ts +112 -32
- package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
- package/plugin/react/Routing.tsx +176 -80
- package/plugin/referencePlaceholderUtils.ts +1 -1
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +5 -1
- package/plugin/routes/Docs.astro +60 -85
- package/plugin/routes/Overview.astro +10 -16
- package/plugin/routes/markdown.ts +7 -7
- package/plugin/vendor/preview.worker.docs.js +17973 -16561
- 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 +1 -1
- 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/{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/PageTitle.astro +82 -0
- package/stl-docs/components/TableOfContents.astro +34 -0
- package/stl-docs/components/ThemeSelect.astro +118 -141
- package/stl-docs/components/content-panel/ContentPanel.astro +16 -46
- package/stl-docs/components/headers/DefaultHeader.astro +1 -1
- package/stl-docs/components/headers/HeaderLinks.astro +1 -1
- 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 +17 -0
- package/stl-docs/components/icons/claude.tsx +10 -0
- 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 +10 -0
- package/stl-docs/components/index.ts +1 -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 +31 -75
- 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 +4 -3
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +174 -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/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 +98 -26
- package/stl-docs/loadStlDocsConfig.ts +37 -5
- 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 +104 -141
- 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 +58 -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 +12 -2
- package/tsconfig.json +2 -5
- package/virtual-module.d.ts +8 -4
- package/components/variables.css +0 -139
- 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
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { BASE_PATH,
|
|
3
|
-
import { parseRoute, generateRoute } from '@stainless-api/docs-ui/
|
|
4
|
-
import { SearchModal } from '@stainless-api/docs-ui/
|
|
5
|
-
import { ChatModal } from '@stainless-api/docs-ui/src/components/chat';
|
|
2
|
+
import { BASE_PATH, HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
|
|
3
|
+
import { parseRoute, generateRoute } from '@stainless-api/docs-ui/routing';
|
|
4
|
+
import { SearchModal } from '@stainless-api/docs-ui/search';
|
|
6
5
|
import * as Markdoc from '@markdoc/markdoc';
|
|
7
6
|
import { createHighlighter } from 'shiki';
|
|
8
7
|
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
|
|
9
8
|
|
|
10
9
|
import {
|
|
11
10
|
DocsProvider,
|
|
11
|
+
type MarkdownContext,
|
|
12
12
|
MarkdownProvider,
|
|
13
13
|
NavigationProvider,
|
|
14
14
|
SearchProvider,
|
|
15
|
-
} from '@stainless-api/docs-ui/
|
|
16
|
-
import
|
|
15
|
+
} from '@stainless-api/docs-ui/contexts';
|
|
16
|
+
import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
|
|
17
|
+
import type { SearchSettings } from '@stainless-api/docs-ui/search/types';
|
|
17
18
|
|
|
18
19
|
let $$highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
|
|
19
20
|
async function getHighlighter() {
|
|
@@ -27,7 +28,7 @@ async function getHighlighter() {
|
|
|
27
28
|
return $$highlighter;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
async function createMarkdownRenderer() {
|
|
31
|
+
async function createMarkdownRenderer(): Promise<MarkdownContext> {
|
|
31
32
|
const highlighter = await getHighlighter();
|
|
32
33
|
const markdocConfig: Markdoc.Config = {
|
|
33
34
|
nodes: {
|
|
@@ -37,7 +38,10 @@ async function createMarkdownRenderer() {
|
|
|
37
38
|
transform(node, config) {
|
|
38
39
|
const attributes = node.transformAttributes(config);
|
|
39
40
|
const lang = node.attributes.language === 'node' ? 'typescript' : node.attributes.language;
|
|
40
|
-
const code = highlighter.codeToTokens(node.attributes.content, {
|
|
41
|
+
const code = highlighter.codeToTokens(node.attributes.content, {
|
|
42
|
+
lang,
|
|
43
|
+
theme: 'github-dark',
|
|
44
|
+
});
|
|
41
45
|
|
|
42
46
|
return new Markdoc.Tag(
|
|
43
47
|
'pre',
|
|
@@ -66,16 +70,25 @@ async function createMarkdownRenderer() {
|
|
|
66
70
|
},
|
|
67
71
|
};
|
|
68
72
|
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
return {
|
|
74
|
+
render: (content: string) => {
|
|
75
|
+
const ast = Markdoc.parse(content);
|
|
76
|
+
const transformed = Markdoc.transform(ast, markdocConfig);
|
|
77
|
+
return Markdoc.renderers.html(transformed);
|
|
78
|
+
},
|
|
79
|
+
highlight: (content: string, language: string) => {
|
|
80
|
+
return highlighter.codeToHtml(content, {
|
|
81
|
+
lang: language ?? 'javascript',
|
|
82
|
+
themes: HIGHLIGHT_THEMES || {},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
73
85
|
};
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
|
|
77
|
-
const
|
|
89
|
+
const markdownRenderer = React.use(createMarkdownRenderer());
|
|
78
90
|
const { stainlessPath, language } = parseRoute(BASE_PATH, currentPath);
|
|
91
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
79
92
|
const pageFind = import.meta.env.DEV ? undefined : '/pagefind/pagefind.js';
|
|
80
93
|
|
|
81
94
|
function handleSelect(path: string) {
|
|
@@ -85,16 +98,17 @@ export function DocsSearch({ settings, currentPath }: { settings: SearchSettings
|
|
|
85
98
|
|
|
86
99
|
return (
|
|
87
100
|
<DocsProvider spec={null} language={language}>
|
|
88
|
-
<
|
|
89
|
-
<
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
<ComponentProvider>
|
|
102
|
+
<NavigationProvider basePath="/" selectedPath={stainlessPath}>
|
|
103
|
+
<MarkdownProvider {...markdownRenderer}>
|
|
104
|
+
<SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
|
|
105
|
+
<div className="stldocs-root">
|
|
106
|
+
<SearchModal id="stldocs-search" />
|
|
107
|
+
</div>
|
|
108
|
+
</SearchProvider>
|
|
109
|
+
</MarkdownProvider>
|
|
110
|
+
</NavigationProvider>
|
|
111
|
+
</ComponentProvider>
|
|
98
112
|
</DocsProvider>
|
|
99
113
|
);
|
|
100
114
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
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 '
|
|
5
|
-
import { walkTree } from '@stainless-api/docs-ui/
|
|
4
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
5
|
+
import { walkTree } from '@stainless-api/docs-ui/routing';
|
|
6
6
|
|
|
7
7
|
const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
|
|
8
8
|
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
|
|
2
|
+
import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
|
|
3
|
+
|
|
4
|
+
export type DropdownIcon = 'markdown' | 'copy' | 'claude' | 'chatgpt' | 'gemini' | 'cursor';
|
|
5
|
+
|
|
6
|
+
interface DropdownOptionInputProps {
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
icon: DropdownIcon;
|
|
9
|
+
primaryAction?: boolean;
|
|
10
|
+
clientHidden?: boolean;
|
|
11
|
+
external: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function option(label: string[] | string, props: DropdownOptionInputProps) {
|
|
15
|
+
const labelArr = typeof label === 'string' ? [label] : label;
|
|
16
|
+
return {
|
|
17
|
+
...props,
|
|
18
|
+
label: labelArr,
|
|
19
|
+
primaryAction: props.primaryAction ?? false,
|
|
20
|
+
clientHidden: props.clientHidden ?? false,
|
|
21
|
+
id: labelArr.join('').toLowerCase().replace(/ /g, '-'),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type DropdownOption = ReturnType<typeof option>;
|
|
26
|
+
|
|
27
|
+
function getMarkdownUrl(type: 'relative' | 'absolute') {
|
|
28
|
+
const currentUrl = new URL(window.location.href);
|
|
29
|
+
const hasTrailingSlash = currentUrl.pathname.endsWith('/');
|
|
30
|
+
|
|
31
|
+
const markdownUrl = [
|
|
32
|
+
type === 'absolute' ? currentUrl.origin : '',
|
|
33
|
+
currentUrl.pathname,
|
|
34
|
+
hasTrailingSlash ? '' : '/',
|
|
35
|
+
'index.md',
|
|
36
|
+
].join('');
|
|
37
|
+
|
|
38
|
+
return markdownUrl;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getURLEncodedPrompt() {
|
|
42
|
+
const mdUrl = getMarkdownUrl('absolute');
|
|
43
|
+
const aiPrompt = encodeURIComponent(
|
|
44
|
+
`Load the contents of ${mdUrl} into this chat's context so we can discuss it.`,
|
|
45
|
+
);
|
|
46
|
+
return aiPrompt;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function openDeepLink({ deepLinkUrl, fallbackUrl }: { deepLinkUrl: string; fallbackUrl: string }) {
|
|
50
|
+
if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrom')) {
|
|
51
|
+
// safari doesn't let us detect if the deep link worked
|
|
52
|
+
window.open(fallbackUrl, '_blank');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
let frame: HTMLIFrameElement | null;
|
|
58
|
+
|
|
59
|
+
// We are using a native deep link with a fallback web url.
|
|
60
|
+
if (navigator.userAgent.includes('Chrom')) {
|
|
61
|
+
// In Chrome, load it in a hidden frame, this shows the "Do you want to open ...?" prompt, but unlike
|
|
62
|
+
// top level navigation this preserves our userActivation, so we can open the fallback if it fails.
|
|
63
|
+
frame = Object.assign(document.createElement('iframe'), { src: deepLinkUrl });
|
|
64
|
+
document.head.append(frame);
|
|
65
|
+
} else {
|
|
66
|
+
// In Firefox do the opposite.
|
|
67
|
+
location.href = deepLinkUrl;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The popup (in non-Safari browsers) fires a `blur` event.
|
|
71
|
+
window.addEventListener(
|
|
72
|
+
'blur',
|
|
73
|
+
() => {
|
|
74
|
+
controller.abort();
|
|
75
|
+
},
|
|
76
|
+
controller,
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// If it's been 300ms with no popup, open the fallback web url.
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
window.open(fallbackUrl, '_blank');
|
|
82
|
+
}, 300);
|
|
83
|
+
|
|
84
|
+
controller.signal.addEventListener('abort', () => {
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
frame?.remove();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Browser_detection_using_the_user_agent#mobile_tablet_or_desktop
|
|
91
|
+
const hasMobileUserAgent = navigator.userAgent.includes('Mobi');
|
|
92
|
+
|
|
93
|
+
// 2d array of dropdown options
|
|
94
|
+
// each sub-array is a group, separated by a horizontal rule in the UI
|
|
95
|
+
const aiDropdownOptions: DropdownOption[][] = [
|
|
96
|
+
[
|
|
97
|
+
option(['Open in ', 'Claude'], {
|
|
98
|
+
onClick: () => {
|
|
99
|
+
window.open(`https://claude.ai/new?q=${getURLEncodedPrompt()}`, '_blank');
|
|
100
|
+
},
|
|
101
|
+
icon: 'claude',
|
|
102
|
+
primaryAction: false,
|
|
103
|
+
external: true,
|
|
104
|
+
}),
|
|
105
|
+
option(['Open in ', 'ChatGPT'], {
|
|
106
|
+
onClick: () => {
|
|
107
|
+
window.open(`https://chatgpt.com/?hints=search&prompt=${getURLEncodedPrompt()}`, '_blank');
|
|
108
|
+
},
|
|
109
|
+
icon: 'chatgpt',
|
|
110
|
+
primaryAction: false,
|
|
111
|
+
external: true,
|
|
112
|
+
}),
|
|
113
|
+
option(['Open in ', 'Cursor'], {
|
|
114
|
+
onClick: () => {
|
|
115
|
+
const aiPrompt = getURLEncodedPrompt();
|
|
116
|
+
openDeepLink({
|
|
117
|
+
deepLinkUrl: `cursor://anysphere.cursor-deeplink/prompt?text=${aiPrompt}`,
|
|
118
|
+
fallbackUrl: `https://cursor.com/link/prompt?text=${aiPrompt}`,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
clientHidden: hasMobileUserAgent,
|
|
122
|
+
icon: 'cursor',
|
|
123
|
+
primaryAction: false,
|
|
124
|
+
external: true,
|
|
125
|
+
}),
|
|
126
|
+
],
|
|
127
|
+
[
|
|
128
|
+
option('Copy Markdown', {
|
|
129
|
+
onClick: () => {
|
|
130
|
+
// Source: https://wolfgangrittner.dev/how-to-use-clipboard-api-in-firefox/
|
|
131
|
+
const markdownUrl = getMarkdownUrl('relative');
|
|
132
|
+
// ClipboardItem doesn't exist in every browser
|
|
133
|
+
// eslint-disable-next-line no-constant-binary-expression
|
|
134
|
+
if (typeof ClipboardItem && navigator.clipboard.write) {
|
|
135
|
+
// NOTE: Safari locks down the clipboard API to only work when triggered
|
|
136
|
+
// by a direct user interaction. You can't use it async in a promise.
|
|
137
|
+
// But! You can wrap the promise in a ClipboardItem, and give that to
|
|
138
|
+
// the clipboard API.
|
|
139
|
+
// Found this on https://developer.apple.com/forums/thread/691873
|
|
140
|
+
const text = new ClipboardItem({
|
|
141
|
+
'text/plain': fetch(markdownUrl)
|
|
142
|
+
.then((response) => response.text())
|
|
143
|
+
.then((text) => new Blob([text], { type: 'text/plain' })),
|
|
144
|
+
});
|
|
145
|
+
navigator.clipboard.write([text]);
|
|
146
|
+
} else {
|
|
147
|
+
// NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
|
|
148
|
+
// but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
|
|
149
|
+
// Good news is that other than Safari, Firefox does not care about
|
|
150
|
+
// Clipboard API being used async in a Promise.
|
|
151
|
+
fetch(markdownUrl)
|
|
152
|
+
.then((response) => response.text())
|
|
153
|
+
.then((text) => navigator.clipboard.writeText(text));
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
icon: 'copy',
|
|
157
|
+
primaryAction: true,
|
|
158
|
+
external: false,
|
|
159
|
+
}),
|
|
160
|
+
option('View as Markdown', {
|
|
161
|
+
onClick: () => {
|
|
162
|
+
window.open(getMarkdownUrl('absolute'), '_blank');
|
|
163
|
+
},
|
|
164
|
+
icon: 'markdown',
|
|
165
|
+
primaryAction: false,
|
|
166
|
+
external: true,
|
|
167
|
+
}),
|
|
168
|
+
],
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
// TODO: Add support for more LLMs
|
|
172
|
+
// {
|
|
173
|
+
// label: ['Open in ', 'Gemini'],
|
|
174
|
+
// onClick: () => {
|
|
175
|
+
// openInLLM('https://gemini.google.com?prompt_action=prefill&prompt_text=');
|
|
176
|
+
// },
|
|
177
|
+
// icon: 'gemini',
|
|
178
|
+
// primaryAction: false,
|
|
179
|
+
// },
|
|
180
|
+
|
|
181
|
+
export function getAIDropdownOptions() {
|
|
182
|
+
const renderedOptions = aiDropdownOptions.map((group, index) => {
|
|
183
|
+
return {
|
|
184
|
+
options: group,
|
|
185
|
+
isLast: index === aiDropdownOptions.length - 1,
|
|
186
|
+
reactKey: index,
|
|
187
|
+
};
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const allOptions = renderedOptions.flatMap((group) => group.options);
|
|
191
|
+
const primaryAction = allOptions.find((o) => o.primaryAction) ?? allOptions[0]!;
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
primaryAction,
|
|
195
|
+
groups: renderedOptions,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function wireAIDropdown() {
|
|
200
|
+
const { primaryAction, groups } = getAIDropdownOptions();
|
|
201
|
+
const flatOptions = groups.flatMap((group) => group.options);
|
|
202
|
+
function triggerOption(id: string) {
|
|
203
|
+
const option = flatOptions.find((option) => option.id === id);
|
|
204
|
+
if (!option) return;
|
|
205
|
+
option.onClick();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
document.addEventListener(getPageLoadEvent(), () => {
|
|
209
|
+
// we hide the Cursor option on non-desktop devices
|
|
210
|
+
for (const option of flatOptions) {
|
|
211
|
+
if (option.clientHidden === true) {
|
|
212
|
+
const el = document.querySelector(
|
|
213
|
+
`[data-dropdown-id="ai-dropdown-button"] [data-value="${option.id}"]`,
|
|
214
|
+
);
|
|
215
|
+
if (el) {
|
|
216
|
+
el.remove();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const dropdowns = document.querySelectorAll('[data-dropdown-id="ai-dropdown-button"]');
|
|
222
|
+
|
|
223
|
+
dropdowns.forEach((dropdown) => {
|
|
224
|
+
initDropdownButton({
|
|
225
|
+
dropdown: dropdown,
|
|
226
|
+
onSelect: (value) => {
|
|
227
|
+
triggerOption(value);
|
|
228
|
+
},
|
|
229
|
+
onPrimaryAction: () => {
|
|
230
|
+
triggerOption(primaryAction.id);
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
2
|
+
const COLLAPSED_HEIGHT = 170;
|
|
3
|
+
|
|
4
|
+
const container = document.querySelector<HTMLElement>('[data-stldocs-property-group="method-description"]');
|
|
5
|
+
|
|
6
|
+
if (!container) return;
|
|
7
|
+
|
|
8
|
+
const toggle = document?.querySelector<HTMLButtonElement>('[data-method-description-toggle]');
|
|
9
|
+
if (!toggle) return;
|
|
10
|
+
|
|
11
|
+
// If content isn't tall enough, don't show the button
|
|
12
|
+
if (container.scrollHeight <= COLLAPSED_HEIGHT + 1) {
|
|
13
|
+
toggle.hidden = true;
|
|
14
|
+
container.dataset.collapsed = 'false';
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Only show button if content is taller than collapsed max height
|
|
19
|
+
if (container.scrollHeight > COLLAPSED_HEIGHT + 1) {
|
|
20
|
+
toggle.hidden = false;
|
|
21
|
+
} else {
|
|
22
|
+
// Not tall enough to need collapsing — show full content and hide button
|
|
23
|
+
container.dataset.collapsed = 'false';
|
|
24
|
+
toggle.hidden = true;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toggle.addEventListener('click', function () {
|
|
29
|
+
const isCollapsed = container.dataset.collapsed !== 'false';
|
|
30
|
+
container.dataset.collapsed = isCollapsed ? 'false' : 'true';
|
|
31
|
+
toggle.textContent = isCollapsed ? 'Show less' : 'Show more';
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/
|
|
1
|
+
import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/routing';
|
|
2
2
|
import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
|
|
3
3
|
import { updateSelectedLanguage } from '../languages';
|
|
4
4
|
import { navigate } from 'astro:transitions/client';
|
|
5
5
|
import { getPageLoadEvent } from '../helpers/getPageLoadEvent.ts';
|
|
6
6
|
|
|
7
|
-
import { initDropdown } from '@stainless-api/docs
|
|
8
|
-
import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
|
|
9
|
-
import { copyCurrentPageAsMarkdown, onSelectAIOption } from './ai-dropdown.ts';
|
|
7
|
+
import { initDropdown } from '@stainless-api/docs/components/scripts';
|
|
10
8
|
|
|
11
9
|
history.scrollRestoration = 'auto';
|
|
12
10
|
|
|
@@ -24,10 +22,13 @@ window.addEventListener('popstate', (ev: PopStateEvent) => {
|
|
|
24
22
|
});
|
|
25
23
|
|
|
26
24
|
document.addEventListener(getPageLoadEvent(), () => {
|
|
25
|
+
const rootElement = document.getElementById('stldocs-snippet-select');
|
|
26
|
+
if (!rootElement) return;
|
|
27
|
+
|
|
27
28
|
initDropdown({
|
|
28
|
-
|
|
29
|
+
root: rootElement,
|
|
29
30
|
onSelect: (value) => {
|
|
30
|
-
const originalLanguage =
|
|
31
|
+
const originalLanguage = rootElement?.dataset.currentValue;
|
|
31
32
|
navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
|
|
32
33
|
},
|
|
33
34
|
});
|
|
@@ -36,27 +37,6 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
36
37
|
if (path) setTimeout(() => scrollToPath(path.slice(1)), 10);
|
|
37
38
|
});
|
|
38
39
|
|
|
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
40
|
document.addEventListener('click', (event) => {
|
|
61
41
|
const toggle = (event.target as HTMLElement).closest(
|
|
62
42
|
'[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';
|
|
@@ -21,13 +21,18 @@ import {
|
|
|
21
21
|
type SpecRetrieverConfig,
|
|
22
22
|
} from './loadPluginConfig';
|
|
23
23
|
import { buildVirtualModuleString } from '../shared/virtualModule';
|
|
24
|
+
import type * as StlStarlightVirtualModule from 'virtual:stl-starlight-virtual-module';
|
|
24
25
|
import path from 'path';
|
|
25
26
|
import fs from 'fs';
|
|
27
|
+
import { getSharedLogger } from '../shared/getSharedLogger';
|
|
28
|
+
import { resolveSrcFile } from '../resolveSrcFile';
|
|
26
29
|
|
|
27
30
|
export { generateAPILink } from './generateAPIReferenceLink';
|
|
28
31
|
export type { ReferenceSidebarConfigItem };
|
|
29
32
|
|
|
30
|
-
config(
|
|
33
|
+
config({
|
|
34
|
+
quiet: true,
|
|
35
|
+
});
|
|
31
36
|
|
|
32
37
|
let sidebarIdCounter = 0;
|
|
33
38
|
|
|
@@ -108,6 +113,7 @@ function tmpGetCMSServerConfig(specRetrieverConfig: SpecRetrieverConfig) {
|
|
|
108
113
|
|
|
109
114
|
async function stlStarlightAstroIntegration(
|
|
110
115
|
pluginConfig: NormalizedStainlessStarlightConfig,
|
|
116
|
+
stlStarlightPluginLogger: AstroIntegrationLogger,
|
|
111
117
|
): Promise<AstroIntegration> {
|
|
112
118
|
const virtualId = `virtual:stl-starlight-virtual-module`;
|
|
113
119
|
// The '\0' prefix tells Vite “this is a virtual module” and prevents it from being resolved again.
|
|
@@ -117,11 +123,12 @@ async function stlStarlightAstroIntegration(
|
|
|
117
123
|
|
|
118
124
|
const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
|
|
119
125
|
|
|
120
|
-
const cmsServer =
|
|
126
|
+
const cmsServer = startDevServer({
|
|
121
127
|
port: CMS_PORT,
|
|
122
|
-
apiKey,
|
|
128
|
+
apiKey: apiKey.value,
|
|
123
129
|
version,
|
|
124
130
|
devPaths,
|
|
131
|
+
logger: stlStarlightPluginLogger,
|
|
125
132
|
getGeneratedSidebarConfig: (id: number) => {
|
|
126
133
|
const config = sidebarConfigs.get(id);
|
|
127
134
|
if (!config) {
|
|
@@ -134,43 +141,45 @@ async function stlStarlightAstroIntegration(
|
|
|
134
141
|
return {
|
|
135
142
|
name: 'stl-starlight-astro',
|
|
136
143
|
hooks: {
|
|
137
|
-
'astro:config:setup': async ({
|
|
144
|
+
'astro:config:setup': async ({
|
|
145
|
+
injectRoute,
|
|
146
|
+
updateConfig,
|
|
147
|
+
logger: localLogger,
|
|
148
|
+
command,
|
|
149
|
+
config: astroConfig,
|
|
150
|
+
}) => {
|
|
151
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
138
152
|
const projectDir = astroConfig.root.pathname;
|
|
139
153
|
|
|
140
154
|
const middlewareFile = path.join(projectDir, 'middleware.stainless.ts');
|
|
141
155
|
|
|
142
156
|
let vmMiddlewareExport = 'export const MIDDLEWARE = {};';
|
|
143
157
|
if (fs.existsSync(middlewareFile)) {
|
|
144
|
-
logger.
|
|
158
|
+
logger.debug(`Loading middleware from ${middlewareFile}`);
|
|
145
159
|
vmMiddlewareExport = `export { default as MIDDLEWARE } from '${middlewareFile}';`;
|
|
146
160
|
}
|
|
147
161
|
|
|
148
162
|
injectRoute({
|
|
149
|
-
pattern: `${pluginConfig.basePath}/[...slug].md`,
|
|
150
|
-
entrypoint: '
|
|
163
|
+
pattern: `${pluginConfig.basePath}/[...slug]/index.md`,
|
|
164
|
+
entrypoint: resolveSrcFile('/plugin/routes/markdown.ts'),
|
|
151
165
|
prerender: command === 'build',
|
|
152
166
|
});
|
|
153
167
|
|
|
154
|
-
const astroFile = command === 'build' ? '
|
|
168
|
+
const astroFile = command === 'build' ? 'DocsStatic' : 'Docs';
|
|
155
169
|
injectRoute({
|
|
156
170
|
pattern: `${pluginConfig.basePath}/[...slug]`,
|
|
157
|
-
|
|
158
|
-
entrypoint: `@stainless-api/docs/${astroFile}`,
|
|
171
|
+
entrypoint: resolveSrcFile(`/plugin/routes/${astroFile}.astro`),
|
|
159
172
|
prerender: command === 'build',
|
|
160
173
|
});
|
|
161
174
|
|
|
162
175
|
injectRoute({
|
|
163
176
|
pattern: pluginConfig.basePath,
|
|
164
|
-
entrypoint: '
|
|
177
|
+
entrypoint: resolveSrcFile('/plugin/routes/Overview.astro'),
|
|
165
178
|
prerender: command === 'build',
|
|
166
179
|
});
|
|
167
180
|
|
|
168
181
|
updateConfig({
|
|
169
182
|
vite: {
|
|
170
|
-
ssr: {
|
|
171
|
-
noExternal: ['@stainless-api/ui-primitives'],
|
|
172
|
-
},
|
|
173
|
-
optimizeDeps: { include: ['@stainless-api/ui-primitives'] },
|
|
174
183
|
plugins: [
|
|
175
184
|
{
|
|
176
185
|
name: 'stl-starlight-vite',
|
|
@@ -211,9 +220,11 @@ async function stlStarlightAstroIntegration(
|
|
|
211
220
|
HIGHLIGHT_THEMES: pluginConfig.highlighting.themes,
|
|
212
221
|
CONTENT_PANEL_LAYOUT: pluginConfig.contentPanel.layout,
|
|
213
222
|
EXPERIMENTAL_COLLAPSIBLE_SNIPPETS: pluginConfig.experimentalCollapsibleSnippets,
|
|
223
|
+
EXPERIMENTAL_COLLAPSIBLE_METHOD_DESCRIPTIONS:
|
|
224
|
+
pluginConfig.experimentalCollapsibleMethodDescriptions,
|
|
214
225
|
PROPERTY_SETTINGS: pluginConfig.propertySettings,
|
|
215
|
-
|
|
216
|
-
}),
|
|
226
|
+
ENABLE_CONTEXT_MENU: pluginConfig.contextMenu,
|
|
227
|
+
} satisfies Omit<typeof StlStarlightVirtualModule, 'MIDDLEWARE'>),
|
|
217
228
|
vmMiddlewareExport,
|
|
218
229
|
].join('\n');
|
|
219
230
|
}
|
|
@@ -241,15 +252,20 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
241
252
|
command,
|
|
242
253
|
config: starlightConfig,
|
|
243
254
|
astroConfig,
|
|
244
|
-
logger,
|
|
255
|
+
logger: localLogger,
|
|
245
256
|
}) => {
|
|
246
257
|
if (command !== 'build' && command !== 'dev') {
|
|
247
258
|
return;
|
|
248
259
|
}
|
|
249
260
|
|
|
261
|
+
const logger = getSharedLogger({ fallback: localLogger });
|
|
262
|
+
|
|
250
263
|
const configParseResult = parseStarlightPluginConfig(someUserConfig, command);
|
|
251
264
|
if (configParseResult.result === 'error') {
|
|
252
|
-
|
|
265
|
+
const errorLines = configParseResult.message.split('\n');
|
|
266
|
+
for (const line of errorLines) {
|
|
267
|
+
logger.error(line);
|
|
268
|
+
}
|
|
253
269
|
process.exit(1);
|
|
254
270
|
}
|
|
255
271
|
const config = configParseResult.config;
|
|
@@ -260,17 +276,30 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
260
276
|
addIntegration(react());
|
|
261
277
|
}
|
|
262
278
|
|
|
279
|
+
if ('apiKey' in config.specRetrieverConfig) {
|
|
280
|
+
if (!config.specRetrieverConfig.apiKey) {
|
|
281
|
+
logger.info(`Stainless credentials not loaded`);
|
|
282
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'explicit-config') {
|
|
283
|
+
logger.info(`Stainless credentials loaded from user config`);
|
|
284
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'environment-variable') {
|
|
285
|
+
logger.info('Stainless credentials loaded from `STAINLESS_API_KEY` environment variable');
|
|
286
|
+
} else if (config.specRetrieverConfig.apiKey.source === 'cli') {
|
|
287
|
+
logger.info('Stainless credentials loaded from `stl` CLI');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
263
291
|
if (
|
|
264
292
|
command === 'build' &&
|
|
265
293
|
config.specRetrieverConfig.kind === 'local_spec_server_with_remote_files'
|
|
266
294
|
) {
|
|
267
295
|
await buildAlgoliaIndex({
|
|
268
296
|
version: config.specRetrieverConfig.version,
|
|
269
|
-
apiKey: config.specRetrieverConfig.apiKey,
|
|
297
|
+
apiKey: config.specRetrieverConfig.apiKey.value,
|
|
298
|
+
logger,
|
|
270
299
|
});
|
|
271
300
|
}
|
|
272
301
|
|
|
273
|
-
addIntegration(await stlStarlightAstroIntegration(config));
|
|
302
|
+
addIntegration(await stlStarlightAstroIntegration(config, logger));
|
|
274
303
|
|
|
275
304
|
if (starlightConfig.sidebar) {
|
|
276
305
|
// for pagination (https://starlight.astro.build/reference/configuration/#pagination) to work correctly
|
|
@@ -281,21 +310,12 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
281
310
|
}
|
|
282
311
|
}
|
|
283
312
|
|
|
284
|
-
const currentExpressiveCode =
|
|
285
|
-
typeof starlightConfig.expressiveCode === 'object' ? starlightConfig.expressiveCode : {};
|
|
286
|
-
updateConfig({
|
|
287
|
-
expressiveCode: {
|
|
288
|
-
...currentExpressiveCode,
|
|
289
|
-
themes: [...(currentExpressiveCode.themes || []), 'github-light', 'github-dark'],
|
|
290
|
-
},
|
|
291
|
-
});
|
|
292
|
-
|
|
293
313
|
updateConfig({
|
|
294
314
|
sidebar: starlightConfig.sidebar,
|
|
295
315
|
});
|
|
296
316
|
|
|
297
317
|
addRouteMiddleware({
|
|
298
|
-
entrypoint: '
|
|
318
|
+
entrypoint: resolveSrcFile('/plugin/replaceSidebarPlaceholderMiddleware.ts'),
|
|
299
319
|
order: 'post',
|
|
300
320
|
});
|
|
301
321
|
},
|
|
@@ -304,5 +324,5 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
304
324
|
}
|
|
305
325
|
|
|
306
326
|
// Additional exports we want for Stainless <-> docs integration.
|
|
307
|
-
export { parseStainlessPath } from '@stainless-api/docs-ui/
|
|
308
|
-
export { renderMarkdown } from '@stainless-api/docs-ui/
|
|
327
|
+
export { parseStainlessPath } from '@stainless-api/docs-ui/routing';
|
|
328
|
+
export { renderMarkdown } from '@stainless-api/docs-ui/markdown';
|
package/plugin/languages.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DocsLanguage } from '@stainless-api/docs-ui/
|
|
1
|
+
import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
2
2
|
import KotlinIcon from './assets/languages/kotlin.svg';
|
|
3
3
|
import RubyIcon from './assets/languages/ruby.svg';
|
|
4
4
|
import TerraformIcon from './assets/languages/terraform.svg';
|
|
@@ -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)) {
|