@stainless-api/docs 0.1.0-beta.7 → 0.1.0-beta.70

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.
Files changed (120) hide show
  1. package/CHANGELOG.md +554 -0
  2. package/README.md +1 -1
  3. package/eslint-suppressions.json +52 -0
  4. package/locals.d.ts +17 -0
  5. package/package.json +51 -40
  6. package/plugin/assets/languages/csharp.svg +1 -0
  7. package/plugin/buildAlgoliaIndex.ts +32 -7
  8. package/plugin/cms/server.ts +130 -58
  9. package/plugin/cms/sidebar-builder.ts +7 -26
  10. package/plugin/cms/worker.ts +83 -5
  11. package/plugin/components/MethodDescription.tsx +54 -0
  12. package/plugin/components/SDKSelect.astro +7 -87
  13. package/plugin/components/SnippetCode.tsx +53 -8
  14. package/plugin/components/search/SearchAlgolia.astro +45 -28
  15. package/plugin/components/search/SearchIsland.tsx +38 -24
  16. package/plugin/create-playground.shim.tsx +3 -0
  17. package/plugin/generateAPIReferenceLink.ts +2 -2
  18. package/plugin/globalJs/ai-dropdown-options.ts +243 -0
  19. package/plugin/globalJs/code-snippets.ts +15 -8
  20. package/plugin/globalJs/copy.ts +81 -16
  21. package/plugin/globalJs/method-descriptions.ts +33 -0
  22. package/plugin/globalJs/navigation.ts +7 -4
  23. package/plugin/helpers/generateDocsRoutes.ts +27 -0
  24. package/plugin/index.ts +178 -35
  25. package/plugin/languages.ts +5 -2
  26. package/plugin/loadPluginConfig.ts +121 -32
  27. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +1 -1
  28. package/plugin/react/Routing.tsx +208 -129
  29. package/plugin/referencePlaceholderUtils.ts +1 -1
  30. package/plugin/replaceSidebarPlaceholderMiddleware.ts +5 -1
  31. package/plugin/routes/Docs.astro +62 -89
  32. package/plugin/routes/DocsStatic.astro +1 -1
  33. package/plugin/routes/Overview.astro +10 -16
  34. package/plugin/routes/markdown.ts +9 -8
  35. package/plugin/vendor/preview.worker.docs.js +19768 -17702
  36. package/plugin/vendor/templates/go.md +1 -1
  37. package/plugin/vendor/templates/python.md +1 -1
  38. package/resolveSrcFile.ts +10 -0
  39. package/scripts/vendor_deps.ts +5 -5
  40. package/shared/getProsePages.ts +42 -0
  41. package/shared/getSharedLogger.ts +15 -0
  42. package/shared/terminalUtils.ts +3 -0
  43. package/src/content.config.ts +9 -0
  44. package/stl-docs/components/AIDropdown.tsx +63 -0
  45. package/stl-docs/components/AiChatIsland.tsx +14 -0
  46. package/stl-docs/components/{content-panel/ContentBreadcrumbs.tsx → ContentBreadcrumbs.tsx} +10 -18
  47. package/stl-docs/components/Head.astro +16 -0
  48. package/stl-docs/components/Header.astro +6 -8
  49. package/stl-docs/components/PageFrame.astro +18 -0
  50. package/stl-docs/components/PageTitle.astro +82 -0
  51. package/stl-docs/components/TableOfContents.astro +34 -0
  52. package/stl-docs/components/ThemeProvider.astro +36 -0
  53. package/stl-docs/components/ThemeSelect.astro +84 -139
  54. package/stl-docs/components/content-panel/ContentPanel.astro +16 -25
  55. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +17 -1
  56. package/stl-docs/components/headers/StackedHeader.astro +29 -24
  57. package/stl-docs/components/icons/chat-gpt.tsx +17 -0
  58. package/stl-docs/components/icons/claude.tsx +10 -0
  59. package/stl-docs/components/icons/cursor.tsx +10 -0
  60. package/stl-docs/components/icons/gemini.tsx +19 -0
  61. package/stl-docs/components/icons/markdown.tsx +10 -0
  62. package/stl-docs/components/index.ts +1 -0
  63. package/stl-docs/components/mintlify-compat/Accordion.astro +7 -5
  64. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +7 -3
  65. package/stl-docs/components/mintlify-compat/Columns.astro +40 -42
  66. package/stl-docs/components/mintlify-compat/Frame.astro +16 -18
  67. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +1 -1
  68. package/stl-docs/components/mintlify-compat/callouts/Check.astro +1 -1
  69. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +1 -1
  70. package/stl-docs/components/mintlify-compat/callouts/Info.astro +1 -1
  71. package/stl-docs/components/mintlify-compat/callouts/Note.astro +1 -1
  72. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +1 -1
  73. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +1 -1
  74. package/stl-docs/components/mintlify-compat/card.css +33 -35
  75. package/stl-docs/components/mintlify-compat/index.ts +2 -4
  76. package/stl-docs/components/nav-tabs/NavDropdown.astro +31 -70
  77. package/stl-docs/components/nav-tabs/NavTabs.astro +78 -80
  78. package/stl-docs/components/nav-tabs/SecondaryNavTabs.astro +15 -8
  79. package/stl-docs/components/nav-tabs/buildNavLinks.ts +3 -2
  80. package/stl-docs/components/pagination/HomeLink.astro +10 -0
  81. package/stl-docs/components/pagination/Pagination.astro +175 -0
  82. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +22 -0
  83. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +13 -0
  84. package/stl-docs/components/pagination/util.ts +71 -0
  85. package/stl-docs/components/scripts.ts +1 -0
  86. package/stl-docs/disableCalloutSyntax.ts +36 -0
  87. package/stl-docs/index.ts +141 -50
  88. package/stl-docs/loadStlDocsConfig.ts +45 -5
  89. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +61 -0
  90. package/stl-docs/proseMarkdown/proseMarkdownMiddleware.ts +39 -0
  91. package/stl-docs/proseMarkdown/toMarkdown.ts +158 -0
  92. package/stl-docs/proseSearchIndexing.ts +450 -0
  93. package/stl-docs/tabsMiddleware.ts +11 -3
  94. package/styles/code.css +108 -140
  95. package/styles/fonts.css +32 -17
  96. package/styles/links.css +11 -48
  97. package/styles/method-descriptions.css +36 -0
  98. package/styles/overrides.css +48 -60
  99. package/styles/page.css +92 -52
  100. package/styles/sdk_select.css +9 -7
  101. package/styles/search.css +56 -69
  102. package/styles/sidebar.css +211 -131
  103. package/styles/{variables.css → sl-variables.css} +3 -2
  104. package/styles/stldocs-variables.css +6 -0
  105. package/styles/toc.css +41 -34
  106. package/theme.css +10 -10
  107. package/tsconfig.json +2 -5
  108. package/virtual-module.d.ts +26 -4
  109. package/components/variables.css +0 -135
  110. package/stl-docs/components/mintlify-compat/Step.astro +0 -58
  111. package/stl-docs/components/mintlify-compat/Steps.astro +0 -17
  112. /package/{plugin/assets → assets}/fonts/geist/OFL.txt +0 -0
  113. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin-ext.woff2 +0 -0
  114. /package/{plugin/assets → assets}/fonts/geist/geist-italic-latin.woff2 +0 -0
  115. /package/{plugin/assets → assets}/fonts/geist/geist-latin-ext.woff2 +0 -0
  116. /package/{plugin/assets → assets}/fonts/geist/geist-latin.woff2 +0 -0
  117. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin-ext.woff2 +0 -0
  118. /package/{plugin/assets → assets}/fonts/geist/geist-mono-italic-latin.woff2 +0 -0
  119. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin-ext.woff2 +0 -0
  120. /package/{plugin/assets → assets}/fonts/geist/geist-mono-latin.woff2 +0 -0
@@ -0,0 +1,243 @@
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: (el) => {
230
+ triggerOption(primaryAction.id);
231
+ const span = el.querySelector('[data-part="primary-action-text"]');
232
+ if (span) {
233
+ const originalContent = span.textContent;
234
+ span.textContent = 'Copied!';
235
+ setTimeout(() => {
236
+ span.textContent = originalContent;
237
+ }, 2000);
238
+ }
239
+ },
240
+ });
241
+ });
242
+ });
243
+ }
@@ -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
- const startHeight = el.scrollHeight;
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.scrollHeight;
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) {
@@ -1,30 +1,39 @@
1
1
  import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
2
+ import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
2
3
  const copyIcon = `<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>`;
3
4
  const circleAlertIcon = `<circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/>`;
4
5
  const checkIcon = `<path d="M20 6 9 17l-5-5"/>`;
5
6
 
7
+ function getContent(button: HTMLElement, full: boolean) {
8
+ const isContentCollapsed = !!document.querySelector('.stldocs-snippet-code.stl-snippet-code-is-collapsed');
9
+
10
+ const content = button.closest('[data-stldocs-copy-parent]')!.querySelector('[data-stldocs-copy-content]')!;
11
+
12
+ const contentCopy = content.cloneNode(true) as HTMLElement;
13
+
14
+ contentCopy.querySelectorAll('.ellipsis').forEach((el) => el.remove());
15
+ if (EXPERIMENTAL_COLLAPSIBLE_SNIPPETS && isContentCollapsed && !full) {
16
+ contentCopy.querySelectorAll('.hidden').forEach((el) => el.remove());
17
+ contentCopy.querySelectorAll('.leading-ws').forEach((el) => el.remove());
18
+ }
19
+
20
+ return contentCopy.textContent!;
21
+ }
22
+
23
+ const preloadPlayground = async (event: Event) => {
24
+ const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
25
+ if (playButton) {
26
+ loadPlayground(playButton);
27
+ }
28
+ };
29
+ addEventListener('mouseover', preloadPlayground);
6
30
  addEventListener('click', async (event) => {
7
31
  const copyButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-copy]') as HTMLElement;
8
32
  if (copyButton) {
9
33
  const iconElement = copyButton.querySelector('.stldocs-icon') as SVGElement;
10
34
  clearTimeout(copyButton.dataset.__stldocsCopyTimeout);
11
35
  try {
12
- const isContentCollapsed = !!document.querySelector(
13
- '.stldocs-snippet-code.stl-snippet-code-is-collapsed',
14
- );
15
-
16
- const content = copyButton
17
- .closest('[data-stldocs-copy-parent]')!
18
- .querySelector('[data-stldocs-copy-content]')!;
19
-
20
- const contentCopy = content.cloneNode(true) as HTMLElement;
21
-
22
- contentCopy.querySelectorAll('.ellipsis').forEach((el) => el.remove());
23
- if (EXPERIMENTAL_COLLAPSIBLE_SNIPPETS && isContentCollapsed) {
24
- contentCopy.querySelectorAll('.hidden').forEach((el) => el.remove());
25
- contentCopy.querySelectorAll('.leading-ws').forEach((el) => el.remove());
26
- }
27
- await navigator.clipboard.writeText(contentCopy.textContent!);
36
+ await navigator.clipboard.writeText(getContent(copyButton, false));
28
37
  iconElement.innerHTML = checkIcon;
29
38
  } catch {
30
39
  iconElement.innerHTML = circleAlertIcon;
@@ -34,4 +43,60 @@ addEventListener('click', async (event) => {
34
43
  iconElement.innerHTML = copyIcon;
35
44
  }, 1000) + '';
36
45
  }
46
+
47
+ const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
48
+ if (playButton) {
49
+ showPlayground(playButton);
50
+ }
51
+ });
52
+
53
+ async function showPlayground(playButton: HTMLElement) {
54
+ if (playButton.getAttribute('aria-disabled') === 'true') return;
55
+ const iconElement = playButton.querySelector('.stldocs-icon') as SVGElement;
56
+ try {
57
+ // use aria-disabled, not disabled, to avoid losing focus
58
+ playButton.setAttribute('aria-disabled', 'true');
59
+ playButton.setAttribute('aria-label', 'Loading playground...');
60
+ playButton.classList.add('stl-ui-button--loading');
61
+ const showPlayground = loadPlayground(playButton);
62
+ await showPlayground();
63
+ } catch (e) {
64
+ console.error(e);
65
+ iconElement.innerHTML = circleAlertIcon;
66
+ }
67
+ playButton.removeAttribute('aria-disabled');
68
+ playButton.removeAttribute('aria-label');
69
+ playButton.classList.remove('stl-ui-button--loading');
70
+ }
71
+
72
+ function loadPlayground(playButton: HTMLElement) {
73
+ (playButton as any).__playgroundLoadPromise ??= (async () => {
74
+ const container = playButton.closest('.stldocs-snippet') as HTMLElement;
75
+ const language = (container.querySelector('.stl-sdk-select') as HTMLElement).dataset.currentValue;
76
+ const code = getContent(playButton, true);
77
+ // eslint-disable-next-line turbo/no-undeclared-env-vars
78
+ if (import.meta.env.DEV) {
79
+ const id = '/@id/astro:scripts/before-hydration.js';
80
+ await import(/* @vite-ignore */ id).catch(console.warn);
81
+ }
82
+ const { createPlayground } = await import('virtual:stl-playground/create');
83
+ return createPlayground({
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ lang: language as any,
86
+ doc: (language === 'python' ? 'from rich import print\n' : '') + code.trimEnd(),
87
+ container,
88
+ });
89
+ })();
90
+ return async () => {
91
+ const promise = (playButton as any).__playgroundLoadPromise;
92
+ (playButton as any).__playgroundLoadPromise = null;
93
+ await ((await promise) as () => Promise<void>)();
94
+ };
95
+ }
96
+ document.addEventListener(getPageLoadEvent(), () => {
97
+ if (new URL(location.href).searchParams.has('play')) {
98
+ document.querySelectorAll('[data-stldocs-snippet-play]').forEach((e) => {
99
+ showPlayground(e as HTMLElement);
100
+ });
101
+ }
37
102
  });
@@ -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,10 +1,10 @@
1
- import { parseRoute, scrollToPath } from '@stainless-api/docs-ui/src/routing';
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-ui/src/components/scripts/dropdown';
7
+ import { initDropdown } from '@stainless-api/docs/components/scripts';
8
8
 
9
9
  history.scrollRestoration = 'auto';
10
10
 
@@ -22,10 +22,13 @@ window.addEventListener('popstate', (ev: PopStateEvent) => {
22
22
  });
23
23
 
24
24
  document.addEventListener(getPageLoadEvent(), () => {
25
+ const rootElement = document.getElementById('stldocs-snippet-select');
26
+ if (!rootElement) return;
27
+
25
28
  initDropdown({
26
- dropdownId: 'stldocs-snippet-select',
29
+ root: rootElement,
27
30
  onSelect: (value) => {
28
- const originalLanguage = document.getElementById('stldocs-snippet-select')?.dataset.currentValue;
31
+ const originalLanguage = rootElement?.dataset.currentValue;
29
32
  navigate(updateSelectedLanguage(BASE_PATH, originalLanguage, value));
30
33
  },
31
34
  });
@@ -0,0 +1,27 @@
1
+ import type * as SDKJSON from '@stainless/sdk-json';
2
+ import { DocsLanguage, generateRouteList } from '@stainless-api/docs-ui/routing';
3
+ import { EXCLUDE_LANGUAGES } from 'virtual:stl-starlight-virtual-module';
4
+
5
+ export function generateDocsRoutes(spec: SDKJSON.Spec, excludeLanguages: DocsLanguage[] = EXCLUDE_LANGUAGES) {
6
+ const paths = generateRouteList({
7
+ spec,
8
+ excludeLanguages,
9
+ });
10
+ const readmes = Object.entries(spec.readme)
11
+ .filter(([language]) => language !== 'http')
12
+ .filter(([language]) => !excludeLanguages.includes(language as DocsLanguage))
13
+ .map(([language]) => ({
14
+ slug: language,
15
+ stainlessPath: null,
16
+ language: language as DocsLanguage,
17
+ title: 'Readme',
18
+ kind: 'readme',
19
+ }));
20
+
21
+ return [...paths, ...readmes].map(({ slug, stainlessPath, language, title, kind }) => {
22
+ return {
23
+ params: { slug },
24
+ props: { stainlessPath, language, title, kind },
25
+ };
26
+ });
27
+ }