@stainless-api/docs 0.1.0-beta.6 → 0.1.0-beta.61
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 +486 -0
- package/README.md +1 -1
- package/eslint-suppressions.json +52 -0
- package/locals.d.ts +16 -0
- package/package.json +45 -40
- package/plugin/assets/languages/csharp.svg +1 -0
- package/plugin/buildAlgoliaIndex.ts +32 -7
- package/plugin/cms/server.ts +130 -58
- package/plugin/cms/sidebar-builder.ts +7 -26
- package/plugin/cms/worker.ts +83 -5
- package/plugin/components/MethodDescription.tsx +54 -0
- package/plugin/components/SDKSelect.astro +7 -87
- package/plugin/components/SnippetCode.tsx +53 -8
- package/plugin/components/search/SearchAlgolia.astro +14 -26
- package/plugin/components/search/SearchIsland.tsx +38 -24
- package/plugin/create-playground.shim.tsx +3 -0
- package/plugin/generateAPIReferenceLink.ts +2 -2
- package/plugin/globalJs/ai-dropdown-options.ts +235 -0
- package/plugin/globalJs/code-snippets.ts +15 -8
- package/plugin/globalJs/copy.ts +81 -16
- package/plugin/globalJs/method-descriptions.ts +33 -0
- package/plugin/globalJs/navigation.ts +7 -4
- package/plugin/index.ts +179 -35
- package/plugin/languages.ts +5 -2
- package/plugin/loadPluginConfig.ts +121 -32
- package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
- package/plugin/react/Routing.tsx +208 -104
- package/plugin/referencePlaceholderUtils.ts +1 -1
- package/plugin/replaceSidebarPlaceholderMiddleware.ts +5 -1
- package/plugin/routes/Docs.astro +61 -83
- package/plugin/routes/Overview.astro +10 -16
- package/plugin/routes/markdown.ts +7 -7
- package/plugin/vendor/preview.worker.docs.js +19768 -17702
- package/plugin/vendor/templates/go.md +1 -1
- package/plugin/vendor/templates/python.md +1 -1
- package/resolveSrcFile.ts +10 -0
- package/scripts/vendor_deps.ts +5 -5
- package/shared/getSharedLogger.ts +15 -0
- package/shared/terminalUtils.ts +3 -0
- package/src/content.config.ts +9 -0
- package/stl-docs/components/AIDropdown.tsx +63 -0
- package/stl-docs/components/AiChatIsland.tsx +10 -0
- package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +10 -18
- package/stl-docs/components/Head.astro +16 -0
- package/stl-docs/components/Header.astro +6 -8
- package/stl-docs/components/PageFrame.astro +14 -0
- package/stl-docs/components/PageTitle.astro +82 -0
- package/stl-docs/components/TableOfContents.astro +34 -0
- package/stl-docs/components/ThemeSelect.astro +118 -136
- package/stl-docs/components/content-panel/ContentPanel.astro +16 -25
- 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 -5
- package/stl-docs/components/mintlify-compat/AccordionGroup.astro +7 -3
- package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
- package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
- package/stl-docs/components/mintlify-compat/Step.astro +30 -32
- package/stl-docs/components/mintlify-compat/Steps.astro +8 -10
- package/stl-docs/components/mintlify-compat/callouts/Callout.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Check.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Danger.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Info.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Note.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Tip.astro +1 -1
- package/stl-docs/components/mintlify-compat/callouts/Warning.astro +1 -1
- package/stl-docs/components/mintlify-compat/card.css +33 -35
- package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
- package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
- package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
- package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
- package/stl-docs/components/pagination/HomeLink.astro +10 -0
- package/stl-docs/components/pagination/Pagination.astro +175 -0
- package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
- package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
- package/stl-docs/components/pagination/util.ts +71 -0
- package/stl-docs/components/scripts.ts +1 -0
- package/stl-docs/disableCalloutSyntax.ts +36 -0
- package/stl-docs/index.ts +130 -48
- package/stl-docs/loadStlDocsConfig.ts +44 -4
- 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/proseSearchIndexing.ts +113 -0
- package/stl-docs/tabsMiddleware.ts +11 -3
- package/styles/code.css +108 -140
- package/styles/fonts.css +32 -17
- package/styles/links.css +11 -48
- package/styles/method-descriptions.css +36 -0
- package/styles/overrides.css +48 -60
- package/styles/page.css +92 -52
- 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 +10 -10
- package/tsconfig.json +2 -5
- package/virtual-module.d.ts +23 -3
- package/components/variables.css +0 -135
- /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,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
import type { DocsLanguage } from '@stainless-api/docs-ui/
|
|
3
|
-
import { parseRoute } from '@stainless-api/docs-ui/
|
|
2
|
+
import type { DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
3
|
+
import { parseRoute } from '@stainless-api/docs-ui/routing';
|
|
4
4
|
import { cmsClient } from '../cms/client';
|
|
5
5
|
import { BASE_PATH, DEFAULT_LANGUAGE, EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
|
|
6
6
|
import { Languages } from '../languages';
|
|
@@ -53,100 +53,20 @@ const readmeSlug = language === 'http' ? BASE_PATH : `${BASE_PATH}/${language}`;
|
|
|
53
53
|
)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
<style>
|
|
57
|
-
@layer starlight.core {
|
|
58
|
-
label {
|
|
59
|
-
--sl-label-icon-size: 16px;
|
|
60
|
-
--sl-caret-size: 1.25rem;
|
|
61
|
-
--sl-inline-padding: 0.5rem;
|
|
62
|
-
position: relative;
|
|
63
|
-
display: flex;
|
|
64
|
-
align-items: center;
|
|
65
|
-
gap: 0.25rem;
|
|
66
|
-
color: var(--sl-color-gray-1);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
label:hover {
|
|
70
|
-
color: var(--sl-color-gray-2);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
.icon {
|
|
74
|
-
position: absolute;
|
|
75
|
-
top: 50%;
|
|
76
|
-
transform: translateY(-50%);
|
|
77
|
-
pointer-events: none;
|
|
78
|
-
width: 16px;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
select {
|
|
82
|
-
padding-block: 0.3rem;
|
|
83
|
-
padding-inline: calc(var(--sl-label-icon-size) + var(--sl-inline-padding) + 0.5rem)
|
|
84
|
-
calc(var(--sl-caret-size) + var(--sl-inline-padding) + 0.25rem);
|
|
85
|
-
margin-inline: calc(var(--sl-inline-padding) * -1);
|
|
86
|
-
width: calc(var(--sl-select-width) + var(--sl-inline-padding) * 2);
|
|
87
|
-
text-overflow: ellipsis;
|
|
88
|
-
color: inherit;
|
|
89
|
-
cursor: pointer;
|
|
90
|
-
appearance: none;
|
|
91
|
-
font-weight: 600;
|
|
92
|
-
text-transform: capitalize;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
select:active {
|
|
96
|
-
font-weight: inherit;
|
|
97
|
-
/* font-family: sans-serif;
|
|
98
|
-
font-weight: 400; */
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
option {
|
|
102
|
-
background-color: var(--sl-color-bg-nav);
|
|
103
|
-
color: var(--sl-color-gray-1);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
@media (min-width: 50rem) {
|
|
107
|
-
select {
|
|
108
|
-
font-size: var(--sl-text-sm);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@layer starlight.components {
|
|
114
|
-
.label-icon {
|
|
115
|
-
font-size: var(--sl-label-icon-size);
|
|
116
|
-
inset-inline-start: 0;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.caret {
|
|
120
|
-
font-size: var(--sl-caret-size);
|
|
121
|
-
inset-inline-end: 0;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.custom-select-wrapper {
|
|
126
|
-
--sl-inline-padding: 0.5rem;
|
|
127
|
-
position: relative;
|
|
128
|
-
display: inline-block;
|
|
129
|
-
/* These match the padding on the sidebar menu */
|
|
130
|
-
padding-left: var(--sl-inline-padding);
|
|
131
|
-
padding-right: var(--sl-inline-padding);
|
|
132
|
-
|
|
133
|
-
.icon.http path {
|
|
134
|
-
fill: var(--sl-color-text);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
</style>
|
|
138
56
|
<script>
|
|
139
57
|
import { navigate } from 'astro:transitions/client';
|
|
140
58
|
import { updateSelectedLanguage } from '../languages';
|
|
141
|
-
import { initDropdown } from '@stainless-api/docs
|
|
59
|
+
import { initDropdown } from '@stainless-api/docs/components/scripts';
|
|
142
60
|
import { BASE_PATH } from 'virtual:stl-starlight-virtual-module';
|
|
143
61
|
import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
|
|
144
62
|
|
|
145
63
|
document.addEventListener(getPageLoadEvent(), () => {
|
|
64
|
+
const sdkSelect = document.getElementById('sidebar-sdk-select');
|
|
65
|
+
if (!sdkSelect) return;
|
|
146
66
|
initDropdown({
|
|
147
|
-
|
|
67
|
+
root: sdkSelect,
|
|
148
68
|
onSelect: (value) => {
|
|
149
|
-
const originalLanguage =
|
|
69
|
+
const originalLanguage = sdkSelect.dataset.currentValue;
|
|
150
70
|
navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
|
|
151
71
|
},
|
|
152
72
|
});
|
|
@@ -2,12 +2,38 @@ import type {
|
|
|
2
2
|
SnippetCodeProps,
|
|
3
3
|
SnippetContainerProps,
|
|
4
4
|
SnippetRequestContainerProps,
|
|
5
|
-
} from '@stainless-api/docs-ui/
|
|
6
|
-
import { useHighlight, useLanguage } from '@stainless-api/docs-ui/
|
|
7
|
-
import style from '@stainless-api/docs-ui/
|
|
5
|
+
} from '@stainless-api/docs-ui/components';
|
|
6
|
+
import { useHighlight, useLanguage } from '@stainless-api/docs-ui/contexts';
|
|
7
|
+
import style from '@stainless-api/docs-ui/style';
|
|
8
8
|
import * as cheerio from 'cheerio/slim';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
|
|
11
|
+
EXPERIMENTAL_PLAYGROUNDS,
|
|
12
|
+
} from 'virtual:stl-starlight-virtual-module';
|
|
10
13
|
import clsx from 'clsx';
|
|
14
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
15
|
+
import { CopyIcon } from 'lucide-react';
|
|
16
|
+
|
|
17
|
+
function PlaygroundIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg
|
|
20
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
21
|
+
width={16}
|
|
22
|
+
height={16}
|
|
23
|
+
viewBox="0 0 24 24"
|
|
24
|
+
fill="none"
|
|
25
|
+
stroke="currentColor"
|
|
26
|
+
strokeWidth={2}
|
|
27
|
+
strokeLinecap="round"
|
|
28
|
+
strokeLinejoin="round"
|
|
29
|
+
className={'lucide ' + style.Icon}
|
|
30
|
+
aria-hidden="true"
|
|
31
|
+
>
|
|
32
|
+
<path d="m 1,2 h 1 a 4,4 0 0 1 4,4 v 1 m 5,15 H 10 A 4,4 0 0 1 6,18 V 6 a 4,4 0 0 1 4,-4 h 1 M 1,22 H 2 A 4,4 0 0 0 6,18 V 17 M 14.029059,8.147837 A 1.2853426,1.2853426 0 0 1 15.978924,7.0437277 L 22.40178,10.8959 a 1.2853426,1.2853426 0 0 1 0,2.208219 l -6.422856,3.852172 a 1.2853426,1.2853426 0 0 1 -1.949865,-1.105395 z" />
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
11
37
|
/*
|
|
12
38
|
* This may be replaced by additional data from the sdk.
|
|
13
39
|
* Without information from the sdk, we use simple heuristics per language.
|
|
@@ -53,7 +79,7 @@ function wrapFirstNSpaces($line: cheerio.Cheerio<any>, n: number) {
|
|
|
53
79
|
const m = inner.match(new RegExp(`^( {1,${n}})`));
|
|
54
80
|
if (!m) return;
|
|
55
81
|
|
|
56
|
-
const lead = m[1]
|
|
82
|
+
const lead = m[1]!;
|
|
57
83
|
$firstSpan.html(`<span class="leading-ws">${lead}</span>${inner.slice(lead.length)}`);
|
|
58
84
|
}
|
|
59
85
|
|
|
@@ -130,12 +156,14 @@ export function SnippetRequestContainer({ children, signature }: SnippetRequestC
|
|
|
130
156
|
<div className="stl-snippet-request-container">
|
|
131
157
|
{children}
|
|
132
158
|
{signature && isCollapsible && (
|
|
133
|
-
<
|
|
134
|
-
className={
|
|
159
|
+
<Button
|
|
160
|
+
className={'stl-snippet-expand-button'}
|
|
135
161
|
id="stl-snippet-expand-button"
|
|
162
|
+
size="sm"
|
|
163
|
+
variant="outline"
|
|
136
164
|
>
|
|
137
165
|
Show more
|
|
138
|
-
</
|
|
166
|
+
</Button>
|
|
139
167
|
)}
|
|
140
168
|
</div>
|
|
141
169
|
);
|
|
@@ -181,6 +209,23 @@ export function CondensibleSnippetCode({
|
|
|
181
209
|
);
|
|
182
210
|
}
|
|
183
211
|
|
|
212
|
+
export function SnippetButtons({ content }: { content: string }) {
|
|
213
|
+
void content;
|
|
214
|
+
const language = useLanguage();
|
|
215
|
+
return (
|
|
216
|
+
<>
|
|
217
|
+
<Button variant="outline" data-stldocs-snippet-copy>
|
|
218
|
+
<CopyIcon size={16} className={style.Icon} />
|
|
219
|
+
</Button>
|
|
220
|
+
{EXPERIMENTAL_PLAYGROUNDS &&
|
|
221
|
+
(language === 'python' || language === 'typescript' || language === 'http') && (
|
|
222
|
+
<Button data-stldocs-snippet-play variant="muted" border title="Play">
|
|
223
|
+
<PlaygroundIcon />
|
|
224
|
+
</Button>
|
|
225
|
+
)}
|
|
226
|
+
</>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
184
229
|
export function SnippetCode({ content, signature, language: forcedLanguage }: SnippetCodeProps) {
|
|
185
230
|
const lang = useLanguage();
|
|
186
231
|
const language = forcedLanguage || lang;
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { Icon } from '@astrojs/starlight/components';
|
|
3
3
|
import { DocsSearch } from './SearchIsland';
|
|
4
|
-
import {
|
|
4
|
+
import { Button } from '@stainless-api/ui-primitives';
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<site-search>
|
|
8
|
-
<
|
|
9
|
-
|
|
8
|
+
<Button
|
|
9
|
+
popoverTarget="stldocs-search"
|
|
10
10
|
data-open-modal
|
|
11
11
|
aria-label={Astro.locals.t('search.label')}
|
|
12
12
|
aria-keyshortcuts="Control+K"
|
|
13
|
-
|
|
13
|
+
variant="outline"
|
|
14
|
+
className="stl-algolia-search"
|
|
14
15
|
>
|
|
15
16
|
<Icon name="magnifier" />
|
|
16
17
|
<span class="sl-hidden md:sl-block" aria-hidden="true">{Astro.locals.t('search.label')}</span>
|
|
17
18
|
<kbd class="sl-hidden md:sl-flex" style="display: none;">
|
|
18
19
|
<kbd>{Astro.locals.t('search.ctrlKey')}</kbd><kbd>K</kbd>
|
|
19
20
|
</kbd>
|
|
20
|
-
</
|
|
21
|
+
</Button>
|
|
21
22
|
|
|
22
23
|
<DocsSearch
|
|
23
24
|
client:only="react"
|
|
@@ -31,27 +32,6 @@ import { SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
|
31
32
|
/>
|
|
32
33
|
</site-search>
|
|
33
34
|
|
|
34
|
-
{
|
|
35
|
-
SEARCH?.enableAISearch === true && (
|
|
36
|
-
<button id="chat-button" popovertarget="stldocs-chat" data-open-modal>
|
|
37
|
-
<Icon name="comment" />
|
|
38
|
-
</button>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
<style>
|
|
43
|
-
#chat-button {
|
|
44
|
-
background: var(--sl-color-bg-ui);
|
|
45
|
-
border: 1px solid var(--sl-color-hairline);
|
|
46
|
-
height: 2.25rem;
|
|
47
|
-
width: 2.25rem;
|
|
48
|
-
|
|
49
|
-
&:hover {
|
|
50
|
-
border: 1px solid rgb(64, 64, 64);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
</style>
|
|
54
|
-
|
|
55
35
|
<script is:inline>
|
|
56
36
|
function setupShortcut() {
|
|
57
37
|
const openBtn = document.querySelector('button[data-open-modal]');
|
|
@@ -78,3 +58,11 @@ import { SEARCH } from 'virtual:stl-starlight-virtual-module';
|
|
|
78
58
|
}
|
|
79
59
|
});
|
|
80
60
|
</script>
|
|
61
|
+
|
|
62
|
+
<style is:inline>
|
|
63
|
+
.default-tabs-container .stl-algolia-search {
|
|
64
|
+
max-width: unset;
|
|
65
|
+
width: unset;
|
|
66
|
+
flex-grow: 1;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
@@ -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-
|
|
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-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
|
-
|
|
15
|
-
} from '@stainless-api/docs-ui/
|
|
16
|
-
import
|
|
14
|
+
} from '@stainless-api/docs-ui/contexts';
|
|
15
|
+
import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
|
|
16
|
+
import { SearchProvider } from '@stainless-api/docs-search/context';
|
|
17
|
+
import type { SearchSettings } from '@stainless-api/docs-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
|
+
}
|
|
@@ -43,24 +43,31 @@ document.addEventListener(getPageLoadEvent(), () => {
|
|
|
43
43
|
// This is a bit funky, but it animates pretty smooth.
|
|
44
44
|
// 1. Toggle to new state so we can measure the target height
|
|
45
45
|
// 2. Reset to starting height to trigger transition
|
|
46
|
-
|
|
46
|
+
el.style.height = '';
|
|
47
|
+
el.style.overflowY = '';
|
|
48
|
+
const startHeight = el.getBoundingClientRect().height;
|
|
47
49
|
el.classList.toggle('stl-snippet-code-is-expanded', expand);
|
|
48
50
|
el.classList.toggle('stl-snippet-code-is-collapsed', !expand);
|
|
49
51
|
|
|
50
|
-
const endHeight = el.
|
|
51
|
-
|
|
52
|
-
el.style.height = `${startHeight}px`;
|
|
53
|
-
|
|
54
|
-
requestAnimationFrame(() => {
|
|
55
|
-
el.style.height = `${endHeight}px`;
|
|
56
|
-
});
|
|
52
|
+
const endHeight = el.getBoundingClientRect().height;
|
|
57
53
|
|
|
58
54
|
const onEnd = (e: TransitionEvent) => {
|
|
59
55
|
if (e.propertyName !== 'height') return;
|
|
60
56
|
el.style.height = '';
|
|
57
|
+
el.style.overflowY = '';
|
|
61
58
|
el.removeEventListener('transitionend', onEnd);
|
|
62
59
|
};
|
|
63
60
|
el.addEventListener('transitionend', onEnd);
|
|
61
|
+
|
|
62
|
+
// Set initial height
|
|
63
|
+
el.style.height = `${startHeight}px`;
|
|
64
|
+
el.style.overflowY = 'hidden';
|
|
65
|
+
|
|
66
|
+
// Force layout recalculation
|
|
67
|
+
el.getBoundingClientRect();
|
|
68
|
+
|
|
69
|
+
// Start transition to end state
|
|
70
|
+
el.style.height = `${endHeight}px`;
|
|
64
71
|
};
|
|
65
72
|
|
|
66
73
|
if (collapsedDiv) {
|