@stainless-api/docs 0.1.0-beta.98 → 1.0.0-beta.140

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 (138) hide show
  1. package/CHANGELOG.md +404 -0
  2. package/ambient.d.ts +6 -0
  3. package/eslint-suppressions.json +22 -6
  4. package/{eslint.config.js → eslint.config.ts} +1 -7
  5. package/package.json +57 -40
  6. package/plugin/assets/languages/php.svg +4 -0
  7. package/plugin/buildAlgoliaIndex.ts +6 -12
  8. package/plugin/components/SDKSelect.astro +0 -6
  9. package/plugin/components/SnippetCode.tsx +6 -37
  10. package/plugin/components/search/SearchAlgolia.astro +1 -1
  11. package/plugin/components/search/SearchIsland.tsx +19 -13
  12. package/plugin/generateAPIReferenceLink.ts +0 -40
  13. package/plugin/globalJs/ai-dropdown-options.ts +26 -13
  14. package/plugin/globalJs/code-snippets.ts +5 -5
  15. package/plugin/globalJs/copy.ts +20 -91
  16. package/plugin/globalJs/navigation.ts +13 -13
  17. package/plugin/globalJs/summary-selection-tweak.ts +29 -0
  18. package/plugin/index.ts +107 -163
  19. package/plugin/languages.ts +2 -1
  20. package/plugin/loadPluginConfig.ts +50 -153
  21. package/plugin/markdown/highlighter.ts +100 -0
  22. package/plugin/markdown/index.ts +39 -0
  23. package/plugin/middlewareBuilder/stainlessMiddleware.d.ts +2 -0
  24. package/plugin/react/Routing.tsx +10 -244
  25. package/plugin/referencePlaceholderUtils.ts +1 -1
  26. package/plugin/replaceSidebarPlaceholderMiddleware.ts +1 -1
  27. package/plugin/routes/Docs.astro +3 -1
  28. package/plugin/routes/Overview.astro +14 -7
  29. package/plugin/routes/llms.ts +186 -0
  30. package/plugin/routes/markdown.ts +62 -13
  31. package/plugin/sidebar-utils/sidebar-builder.ts +38 -12
  32. package/plugin/specs/defaultSpecLoader.ts +192 -0
  33. package/plugin/specs/fetchSpecSSR.ts +1 -1
  34. package/plugin/specs/utils.ts +86 -0
  35. package/shared/conditionalIntegration.ts +28 -0
  36. package/shared/getProsePages.ts +6 -7
  37. package/shared/virtualModule.ts +1 -26
  38. package/stl-docs/aiChatExamples.ts +31 -0
  39. package/stl-docs/chat/docs-chat-handler.ts +17 -0
  40. package/stl-docs/chat/hook.ts +225 -0
  41. package/stl-docs/chat/schemas.ts +27 -0
  42. package/stl-docs/chat/ui/AiChat.module.css +591 -0
  43. package/stl-docs/chat/ui/AiChat.tsx +175 -0
  44. package/stl-docs/chat/ui/Trigger.tsx +154 -0
  45. package/stl-docs/chat/ui/components/ChatControls.tsx +51 -0
  46. package/stl-docs/chat/ui/components/ChatEmpty.tsx +42 -0
  47. package/stl-docs/chat/ui/components/ChatLog.tsx +93 -0
  48. package/stl-docs/chat/ui/components/ChatMessage.tsx +47 -0
  49. package/stl-docs/chat/ui/components/CodeBlock.tsx +33 -0
  50. package/stl-docs/chat/ui/components/MessageFeedback.tsx +106 -0
  51. package/stl-docs/chat/ui/components/Table.tsx +15 -0
  52. package/stl-docs/chat/ui/components/ToolCall.tsx +34 -0
  53. package/stl-docs/chat/ui/components/hljs-github.css +81 -0
  54. package/stl-docs/chat/ui/scroll-manager.ts +86 -0
  55. package/stl-docs/chat/ui/types.ts +45 -0
  56. package/stl-docs/components/AiChatIsland.tsx +10 -12
  57. package/stl-docs/components/ContentPanel.astro +9 -0
  58. package/stl-docs/components/Footer.astro +89 -0
  59. package/stl-docs/components/Header.astro +0 -5
  60. package/stl-docs/components/PageFrame.astro +23 -8
  61. package/stl-docs/components/PageSidebar.astro +11 -0
  62. package/stl-docs/components/StainlessLogo.svg +4 -0
  63. package/stl-docs/components/TwoColumnContent.astro +2 -0
  64. package/stl-docs/components/headers/DefaultHeader.astro +6 -8
  65. package/stl-docs/components/headers/StackedHeader.astro +5 -53
  66. package/stl-docs/components/mintlify-compat/Accordion.astro +2 -2
  67. package/stl-docs/components/mintlify-compat/AccordionGroup.astro +0 -4
  68. package/stl-docs/components/mintlify-compat/Columns.astro +2 -2
  69. package/stl-docs/components/mintlify-compat/Frame.astro +2 -2
  70. package/stl-docs/components/mintlify-compat/Tab.astro +2 -2
  71. package/stl-docs/components/mintlify-compat/callouts/Callout.astro +2 -2
  72. package/stl-docs/components/mintlify-compat/callouts/Check.astro +0 -4
  73. package/stl-docs/components/mintlify-compat/callouts/Danger.astro +0 -4
  74. package/stl-docs/components/mintlify-compat/callouts/Info.astro +0 -4
  75. package/stl-docs/components/mintlify-compat/callouts/Note.astro +0 -4
  76. package/stl-docs/components/mintlify-compat/callouts/Tip.astro +0 -4
  77. package/stl-docs/components/mintlify-compat/callouts/Warning.astro +0 -4
  78. package/stl-docs/components/nav-tabs/NavDropdown.astro +12 -7
  79. package/stl-docs/components/nav-tabs/NavTabs.astro +5 -3
  80. package/stl-docs/components/nav-tabs/buildNavLinks.ts +2 -0
  81. package/stl-docs/components/pagination/Pagination.astro +4 -2
  82. package/stl-docs/components/pagination/PaginationLinkEmphasized.astro +2 -2
  83. package/stl-docs/components/pagination/PaginationLinkQuiet.astro +2 -2
  84. package/stl-docs/components/pagination/util.ts +3 -3
  85. package/stl-docs/components/sidebars/BaseSidebar.astro +72 -1
  86. package/stl-docs/disableCalloutSyntax.ts +1 -1
  87. package/stl-docs/fonts.ts +5 -5
  88. package/stl-docs/index.ts +76 -53
  89. package/stl-docs/loadStlDocsConfig.ts +38 -8
  90. package/stl-docs/og-image/components/OpenGraphFunctionSignature.tsx +64 -0
  91. package/stl-docs/og-image/components/OpenGraphImage.tsx +126 -0
  92. package/stl-docs/og-image/config.ts +56 -0
  93. package/stl-docs/og-image/image-gen/generate-api-reference-og-image.tsx +188 -0
  94. package/stl-docs/og-image/image-gen/generate-og-image.tsx +119 -0
  95. package/stl-docs/og-image/image-gen/get-logo-url.ts +47 -0
  96. package/stl-docs/og-image/index.ts +135 -0
  97. package/stl-docs/og-image/routes/add-og-image.ts +45 -0
  98. package/stl-docs/og-image/routes/get-api-reference-og-image.ts +36 -0
  99. package/stl-docs/og-image/routes/get-og-image.ts +28 -0
  100. package/stl-docs/og-image/theme.ts +43 -0
  101. package/stl-docs/og-image/utils.ts +14 -0
  102. package/stl-docs/proseDocSync.test.ts +74 -0
  103. package/stl-docs/proseDocSync.ts +344 -0
  104. package/stl-docs/proseMarkdown/proseMarkdownIntegration.ts +4 -12
  105. package/stl-docs/schema-extension.ts +12 -0
  106. package/stl-docs/tabsMiddleware.ts +1 -1
  107. package/styles/overrides.css +2 -14
  108. package/styles/page.css +210 -71
  109. package/styles/sidebar.css +30 -17
  110. package/styles/sl-variables.css +3 -8
  111. package/styles/stldocs-variables.css +2 -2
  112. package/styles/toc.css +8 -0
  113. package/tsconfig.json +1 -1
  114. package/virtual-module.d.ts +35 -11
  115. package/playground-virtual-modules.d.ts +0 -96
  116. package/plugin/globalJs/create-playground.shim.ts +0 -3
  117. package/plugin/globalJs/playground-data.shim.ts +0 -1
  118. package/plugin/globalJs/playground-data.ts +0 -14
  119. package/plugin/specs/FileCache.ts +0 -99
  120. package/plugin/specs/generateSpec.ts +0 -112
  121. package/plugin/specs/index.ts +0 -132
  122. package/plugin/specs/inputResolver.ts +0 -146
  123. package/plugin/specs/worker.ts +0 -199
  124. package/plugin/vendor/preview.worker.docs.js +0 -26108
  125. package/plugin/vendor/templates/cli.md +0 -1
  126. package/plugin/vendor/templates/go.md +0 -316
  127. package/plugin/vendor/templates/java.md +0 -89
  128. package/plugin/vendor/templates/kotlin.md +0 -89
  129. package/plugin/vendor/templates/node.md +0 -235
  130. package/plugin/vendor/templates/python.md +0 -251
  131. package/plugin/vendor/templates/ruby.md +0 -147
  132. package/plugin/vendor/templates/terraform.md +0 -60
  133. package/plugin/vendor/templates/typescript.md +0 -319
  134. package/scripts/vendor_deps.ts +0 -50
  135. package/stl-docs/components/ClientRouterHead.astro +0 -41
  136. package/stl-docs/components/content-panel/ContentPanel.astro +0 -42
  137. package/stl-docs/components/headers/SplashMobileMenuToggle.astro +0 -65
  138. package/stl-docs/proseSearchIndexing.ts +0 -606
@@ -8,7 +8,6 @@ import style from '@stainless-api/docs-ui/style';
8
8
  import * as cheerio from 'cheerio/slim';
9
9
  import {
10
10
  EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
11
- EXPERIMENTAL_PLAYGROUNDS,
12
11
  EXPERIMENTAL_REQUEST_BUILDER,
13
12
  } from 'virtual:stl-starlight-virtual-module';
14
13
  import clsx from 'clsx';
@@ -18,26 +17,6 @@ import React from 'react';
18
17
  import { RequestBuilder } from './RequestBuilder';
19
18
  import { Method } from '@stainless/sdk-json';
20
19
 
21
- function PlaygroundIcon() {
22
- return (
23
- <svg
24
- xmlns="http://www.w3.org/2000/svg"
25
- width={16}
26
- height={16}
27
- viewBox="0 0 24 24"
28
- fill="none"
29
- stroke="currentColor"
30
- strokeWidth={2}
31
- strokeLinecap="round"
32
- strokeLinejoin="round"
33
- className={'lucide ' + style.Icon}
34
- aria-hidden="true"
35
- >
36
- <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" />
37
- </svg>
38
- );
39
- }
40
-
41
20
  /*
42
21
  * This may be replaced by additional data from the sdk.
43
22
  * Without information from the sdk, we use simple heuristics per language.
@@ -49,7 +28,7 @@ function getCollapsedRanges(content: string, language: string, signature?: strin
49
28
  const sigIdx = raw.findIndex((l) => l.includes(signature));
50
29
  if (sigIdx < 0) return [];
51
30
 
52
- let finalIndex = undefined;
31
+ let finalIndex;
53
32
  if (language === 'kotlin' || language === 'java') {
54
33
  finalIndex = raw.findIndex((l, i) => i > sigIdx && l.trim() === '}');
55
34
  } else if (language === 'python') {
@@ -140,7 +119,7 @@ function condensedShikiHtmlFull(docHtml: string, language: string, ranges: [numb
140
119
  out.push('<span class="line ellipsis">…\n</span>');
141
120
  didPushEllipsis = true;
142
121
  }
143
- out.push($.html(lineEl)!);
122
+ out.push($.html(lineEl));
144
123
  });
145
124
 
146
125
  $code.html(out.join(''));
@@ -172,21 +151,11 @@ export function SnippetContainer({ children, signature, method }: SnippetContain
172
151
  );
173
152
  }
174
153
 
175
- export function SnippetButtons({ content }: { content: string }) {
176
- void content;
177
- const language = useLanguage();
154
+ export function SnippetButtons() {
178
155
  return (
179
- <>
180
- <Button variant="outline" data-stldocs-snippet-copy>
181
- <CopyIcon size={16} className={style.Icon} />
182
- </Button>
183
- {EXPERIMENTAL_PLAYGROUNDS &&
184
- (language === 'python' || language === 'typescript' || language === 'http') && (
185
- <Button data-stldocs-snippet-play variant="muted" border title="Play">
186
- <PlaygroundIcon />
187
- </Button>
188
- )}
189
- </>
156
+ <Button variant="outline" data-stldocs-snippet-copy>
157
+ <CopyIcon size={16} className={style.Icon} />
158
+ </Button>
190
159
  );
191
160
  }
192
161
 
@@ -4,7 +4,7 @@ import { Button } from '@stainless-api/ui-primitives';
4
4
  import { SearchIcon, XIcon } from 'lucide-react';
5
5
  ---
6
6
 
7
- <site-search>
7
+ <site-search class={Astro.props.class}>
8
8
  <Button
9
9
  popoverTarget="stldocs-search"
10
10
  data-open-modal
@@ -1,4 +1,4 @@
1
- import * as React from 'react';
1
+ import { useMemo } from 'react';
2
2
  import { RESOLVED_API_REFERENCE_PATH, HIGHLIGHT_THEMES } from 'virtual:stl-starlight-virtual-module';
3
3
  import { parseRoute, generateRoute } from '@stainless-api/docs-ui/routing';
4
4
  import { SearchModal } from '@stainless-api/docs-search';
@@ -8,27 +8,28 @@ import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
8
8
 
9
9
  import {
10
10
  DocsProvider,
11
- type MarkdownContext,
12
- MarkdownProvider,
11
+ type MarkdownContextValue,
13
12
  NavigationProvider,
13
+ SuspensefulMarkdownProvider,
14
14
  } from '@stainless-api/docs-ui/contexts';
15
15
  import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
16
16
  import { SearchProvider } from '@stainless-api/docs-search/context';
17
17
  import type { SearchSettings } from '@stainless-api/docs-search/types';
18
18
 
19
- let $$highlighter: HighlighterGeneric<BundledLanguage, BundledTheme> | null = null;
20
- async function getHighlighter() {
21
- if ($$highlighter === null) {
22
- $$highlighter = await createHighlighter({
19
+ declare global {
20
+ var __docsSearchShikiSingleton: Promise<HighlighterGeneric<BundledLanguage, BundledTheme>> | undefined;
21
+ }
22
+ function getHighlighter() {
23
+ if (!globalThis.__docsSearchShikiSingleton) {
24
+ globalThis.__docsSearchShikiSingleton = createHighlighter({
23
25
  themes: ['github-dark'],
24
26
  langs: ['typescript', 'python', 'go', 'java', 'kotlin', 'ruby'],
25
27
  });
26
28
  }
27
-
28
- return $$highlighter;
29
+ return globalThis.__docsSearchShikiSingleton;
29
30
  }
30
31
 
31
- async function createMarkdownRenderer(): Promise<MarkdownContext> {
32
+ async function createMarkdownRenderer(): Promise<MarkdownContextValue> {
32
33
  const highlighter = await getHighlighter();
33
34
  const markdocConfig: Markdoc.Config = {
34
35
  nodes: {
@@ -37,8 +38,12 @@ async function createMarkdownRenderer(): Promise<MarkdownContext> {
37
38
  ...Markdoc.nodes.fence,
38
39
  transform(node, config) {
39
40
  const attributes = node.transformAttributes(config);
41
+ if (typeof node.attributes.language !== 'string' || typeof node.attributes.content !== 'string') {
42
+ throw new Error('Expected language and content to be strings');
43
+ }
40
44
  const lang = node.attributes.language === 'node' ? 'typescript' : node.attributes.language;
41
45
  const code = highlighter.codeToTokens(node.attributes.content, {
46
+ // @ts-expect-error - TODO: narrowing
42
47
  lang,
43
48
  theme: 'github-dark',
44
49
  });
@@ -63,6 +68,7 @@ async function createMarkdownRenderer(): Promise<MarkdownContext> {
63
68
  transform(node, config) {
64
69
  const children = node.transformChildren(config);
65
70
  const attrs = node.transformAttributes(config);
71
+ if (typeof attrs['href'] !== 'string') throw new Error('Expected href to be a string');
66
72
  const href = attrs['href'].replace('docs://BASE_PATH', RESOLVED_API_REFERENCE_PATH);
67
73
  return new Markdoc.Tag(this.render, { ...attrs, href }, children);
68
74
  },
@@ -86,7 +92,7 @@ async function createMarkdownRenderer(): Promise<MarkdownContext> {
86
92
  }
87
93
 
88
94
  export function DocsSearch({ settings, currentPath }: { settings: SearchSettings; currentPath: string }) {
89
- const markdownRenderer = React.use(createMarkdownRenderer());
95
+ const rendererPromise = useMemo(() => createMarkdownRenderer(), []);
90
96
  const { stainlessPath, language } = parseRoute(RESOLVED_API_REFERENCE_PATH, currentPath);
91
97
  // eslint-disable-next-line turbo/no-undeclared-env-vars
92
98
  const pageFind = import.meta.env.DEV
@@ -104,13 +110,13 @@ export function DocsSearch({ settings, currentPath }: { settings: SearchSettings
104
110
  <DocsProvider spec={null} language={language}>
105
111
  <ComponentProvider>
106
112
  <NavigationProvider basePath="/" selectedPath={stainlessPath}>
107
- <MarkdownProvider {...markdownRenderer}>
113
+ <SuspensefulMarkdownProvider value={rendererPromise}>
108
114
  <SearchProvider onSelect={handleSelect} pageFind={pageFind} settings={settings}>
109
115
  <div className="stldocs-root">
110
116
  <SearchModal id="stldocs-search" />
111
117
  </div>
112
118
  </SearchProvider>
113
- </MarkdownProvider>
119
+ </SuspensefulMarkdownProvider>
114
120
  </NavigationProvider>
115
121
  </ComponentProvider>
116
122
  </DocsProvider>
@@ -1,47 +1,7 @@
1
1
  // This is probably temporary, but it fills in functionality needed for Mintlify imports
2
2
 
3
- import type { StarlightRouteData } from '@astrojs/starlight/route-data';
4
- import type * as SDKJSON from '@stainless/sdk-json';
5
- import { walkTree } from '@stainless-api/docs-ui/routing';
6
-
7
3
  const INTERNAL_REFERENCE_ENTRY_MARKER = 'STL_STARLIGHT_API_REFERENCE_METHOD_LINK_PLACEHOLDER';
8
4
 
9
- type SidebarEntry = StarlightRouteData['sidebar'][number];
10
-
11
- type SidebarLink = Extract<SidebarEntry, { href: string }>;
12
-
13
- export function getMethodFromSDKJSON(spec: SDKJSON.Spec, endpoint: string) {
14
- for (const entry of walkTree(spec)) {
15
- if (entry.data.kind === 'http_method' && entry.data.endpoint === endpoint) {
16
- return entry.data;
17
- }
18
- }
19
- throw new Error(`Endpoint ${endpoint} not found in API`);
20
- }
21
-
22
- export function recursiveReplacePlaceholderItems(
23
- sidebar: SidebarEntry[],
24
- modifyFn: (entry: SidebarLink, props: { endpoint: string; label?: string }) => void,
25
- ) {
26
- for (const entry of sidebar) {
27
- const endpoint = 'attrs' in entry && entry.attrs?.['data-stldocs-endpoint'];
28
- if (
29
- 'attrs' in entry &&
30
- entry.attrs?.about === INTERNAL_REFERENCE_ENTRY_MARKER &&
31
- endpoint &&
32
- typeof endpoint === 'string'
33
- ) {
34
- modifyFn(entry, {
35
- endpoint,
36
- label: entry.attrs?.['data-stldocs-label'],
37
- });
38
- }
39
- if ('entries' in entry) {
40
- recursiveReplacePlaceholderItems(entry.entries, modifyFn);
41
- }
42
- }
43
- }
44
-
45
5
  type GenerateProps = string | { label?: string; endpoint: string };
46
6
 
47
7
  function normalizeGenerateProps(generateProps: GenerateProps) {
@@ -1,5 +1,6 @@
1
1
  import { initDropdownButton } from '@stainless-api/ui-primitives/scripts';
2
2
  import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
3
+ import { CONTEXT_MENU_ENABLE_THIRD_PARTY } from 'virtual:stl-docs-virtual-module';
3
4
 
4
5
  export type DropdownIcon = 'markdown' | 'copy' | 'claude' | 'chatgpt' | 'gemini' | 'cursor';
5
6
 
@@ -7,8 +8,9 @@ interface DropdownOptionInputProps {
7
8
  onClick: () => void;
8
9
  icon: DropdownIcon;
9
10
  primaryAction?: boolean;
10
- clientHidden?: boolean;
11
+ clientHidden?: () => boolean;
11
12
  external: boolean;
13
+ thirdParty?: boolean;
12
14
  }
13
15
 
14
16
  function option(label: string[] | string, props: DropdownOptionInputProps) {
@@ -17,7 +19,7 @@ function option(label: string[] | string, props: DropdownOptionInputProps) {
17
19
  ...props,
18
20
  label: labelArr,
19
21
  primaryAction: props.primaryAction ?? false,
20
- clientHidden: props.clientHidden ?? false,
22
+ clientHidden: props.clientHidden ?? (() => false),
21
23
  id: labelArr.join('').toLowerCase().replace(/ /g, '-'),
22
24
  };
23
25
  }
@@ -88,7 +90,7 @@ function openDeepLink({ deepLinkUrl, fallbackUrl }: { deepLinkUrl: string; fallb
88
90
  }
89
91
 
90
92
  // 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');
93
+ const hasMobileUserAgent = () => navigator.userAgent.includes('Mobi');
92
94
 
93
95
  // 2d array of dropdown options
94
96
  // each sub-array is a group, separated by a horizontal rule in the UI
@@ -101,6 +103,7 @@ const aiDropdownOptions: DropdownOption[][] = [
101
103
  icon: 'claude',
102
104
  primaryAction: false,
103
105
  external: true,
106
+ thirdParty: true,
104
107
  }),
105
108
  option(['Open in ', 'ChatGPT'], {
106
109
  onClick: () => {
@@ -109,6 +112,7 @@ const aiDropdownOptions: DropdownOption[][] = [
109
112
  icon: 'chatgpt',
110
113
  primaryAction: false,
111
114
  external: true,
115
+ thirdParty: true,
112
116
  }),
113
117
  option(['Open in ', 'Cursor'], {
114
118
  onClick: () => {
@@ -122,6 +126,7 @@ const aiDropdownOptions: DropdownOption[][] = [
122
126
  icon: 'cursor',
123
127
  primaryAction: false,
124
128
  external: true,
129
+ thirdParty: true,
125
130
  }),
126
131
  ],
127
132
  [
@@ -142,7 +147,9 @@ const aiDropdownOptions: DropdownOption[][] = [
142
147
  .then((response) => response.text())
143
148
  .then((text) => new Blob([text], { type: 'text/plain' })),
144
149
  });
145
- navigator.clipboard.write([text]);
150
+ navigator.clipboard.write([text]).catch(() => {
151
+ console.error('Failed to copy to clipboard using ClipboardItem');
152
+ });
146
153
  } else {
147
154
  // NOTE: Firefox has support for ClipboardItem and navigator.clipboard.write,
148
155
  // but those are behind `dom.events.asyncClipboard.clipboardItem` preference.
@@ -150,7 +157,10 @@ const aiDropdownOptions: DropdownOption[][] = [
150
157
  // Clipboard API being used async in a Promise.
151
158
  fetch(markdownUrl)
152
159
  .then((response) => response.text())
153
- .then((text) => navigator.clipboard.writeText(text));
160
+ .then((text) => navigator.clipboard.writeText(text))
161
+ .catch(() => {
162
+ console.error('Failed to copy to clipboard');
163
+ });
154
164
  }
155
165
  },
156
166
  icon: 'copy',
@@ -179,13 +189,16 @@ const aiDropdownOptions: DropdownOption[][] = [
179
189
  // },
180
190
 
181
191
  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
- });
192
+ const renderedOptions = aiDropdownOptions
193
+ .map((e) => e.filter((e) => CONTEXT_MENU_ENABLE_THIRD_PARTY || !e.thirdParty))
194
+ .filter((e) => e.length)
195
+ .map((group, index, array) => {
196
+ return {
197
+ options: group,
198
+ isLast: index === array.length - 1,
199
+ reactKey: index,
200
+ };
201
+ });
189
202
 
190
203
  const allOptions = renderedOptions.flatMap((group) => group.options);
191
204
  const primaryAction = allOptions.find((o) => o.primaryAction) ?? allOptions[0]!;
@@ -208,7 +221,7 @@ export function wireAIDropdown() {
208
221
  document.addEventListener(getPageLoadEvent(), () => {
209
222
  // we hide the Cursor option on non-desktop devices
210
223
  for (const option of flatOptions) {
211
- if (option.clientHidden === true) {
224
+ if (option.clientHidden() === true) {
212
225
  const el = document.querySelector(
213
226
  `[data-dropdown-id="ai-dropdown-button"] [data-value="${option.id}"]`,
214
227
  );
@@ -13,7 +13,7 @@ document.addEventListener(getPageLoadEvent(), () => {
13
13
  const panelId = (tab as HTMLButtonElement).dataset.snippetResponseTabId;
14
14
 
15
15
  if (!panelId) {
16
- console.error(`No panel found for tab: ${tab}`);
16
+ console.error(`No panel found for tab: ${tab.outerHTML}`);
17
17
  return;
18
18
  }
19
19
  document
@@ -29,8 +29,8 @@ document.addEventListener(getPageLoadEvent(), () => {
29
29
  document.getElementById('stl-snippet-expand-button')?.addEventListener('click', (e) => {
30
30
  const btn = e.currentTarget as HTMLButtonElement;
31
31
 
32
- const collapsedDiv = document.querySelector('.stl-snippet-code-is-collapsed') as HTMLElement | null;
33
- const expandedDiv = document.querySelector('.stl-snippet-code-is-expanded') as HTMLElement | null;
32
+ const collapsedDiv = document.querySelector('.stl-snippet-code-is-collapsed');
33
+ const expandedDiv = document.querySelector('.stl-snippet-code-is-expanded');
34
34
 
35
35
  // We need to use javascript for height animations due to having dynamic heights based on snippet size.
36
36
  const animateHeight = (el: HTMLElement, expand: boolean) => {
@@ -71,7 +71,7 @@ document.addEventListener(getPageLoadEvent(), () => {
71
71
  el.style.height = `${endHeight}px`;
72
72
  };
73
73
 
74
- if (collapsedDiv) {
74
+ if (collapsedDiv instanceof HTMLElement) {
75
75
  if (collapsedDiv) {
76
76
  collapsedDiv.querySelector('.shiki')?.setAttribute('style', `counter-reset: codeblock-line 0`);
77
77
  }
@@ -80,7 +80,7 @@ document.addEventListener(getPageLoadEvent(), () => {
80
80
  return;
81
81
  }
82
82
 
83
- if (expandedDiv) {
83
+ if (expandedDiv instanceof HTMLElement) {
84
84
  const dataSnippetExpandedOffset = expandedDiv?.dataset.snippetExpandedOffset;
85
85
  if (dataSnippetExpandedOffset) {
86
86
  const offset = parseInt(dataSnippetExpandedOffset, 10);
@@ -1,10 +1,4 @@
1
- import {
2
- RESOLVED_API_REFERENCE_PATH,
3
- EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
4
- } from 'virtual:stl-starlight-virtual-module';
5
- import { getPageLoadEvent } from '../helpers/getPageLoadEvent';
6
- import { updateSelectedLanguage } from '../languages';
7
- import { navigate } from 'astro/virtual-modules/transitions-router.js';
1
+ import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
8
2
  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"/>`;
9
3
  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"/>`;
10
4
  const checkIcon = `<path d="M20 6 9 17l-5-5"/>`;
@@ -22,94 +16,29 @@ function getContent(button: HTMLElement, full: boolean) {
22
16
  contentCopy.querySelectorAll('.leading-ws').forEach((el) => el.remove());
23
17
  }
24
18
 
25
- return contentCopy.textContent!;
19
+ return contentCopy.textContent;
26
20
  }
27
21
 
28
- const preloadPlayground = async (event: Event) => {
29
- const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
30
- if (playButton) {
31
- loadPlayground(playButton);
32
- }
33
- };
34
- addEventListener('mouseover', preloadPlayground);
35
- addEventListener('click', async (event) => {
36
- const copyButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-copy]') as HTMLElement;
22
+ addEventListener('click', (event) => {
23
+ if (!(event.target instanceof Element)) return;
24
+ const copyButton = event.target.closest('[data-stldocs-snippet-copy]');
25
+ if (!(copyButton instanceof HTMLElement)) return;
26
+
37
27
  if (copyButton) {
38
28
  const iconElement = copyButton.querySelector('.stldocs-icon') as SVGElement;
39
29
  clearTimeout(copyButton.dataset.__stldocsCopyTimeout);
40
- try {
41
- await navigator.clipboard.writeText(getContent(copyButton, false));
42
- iconElement.innerHTML = checkIcon;
43
- } catch {
44
- iconElement.innerHTML = circleAlertIcon;
45
- }
46
- copyButton.dataset.__stldocsCopyTimeout =
47
- setTimeout(() => {
48
- iconElement.innerHTML = copyIcon;
49
- }, 1000) + '';
50
- }
51
-
52
- const playButton = (event.target as HTMLElement).closest('[data-stldocs-snippet-play]') as HTMLElement;
53
- if (playButton) {
54
- showPlayground(playButton);
55
- }
56
- });
57
-
58
- async function showPlayground(playButton: HTMLElement) {
59
- if (playButton.getAttribute('aria-disabled') === 'true') return;
60
- const iconElement = playButton.querySelector('.stldocs-icon') as SVGElement;
61
- try {
62
- // use aria-disabled, not disabled, to avoid losing focus
63
- playButton.setAttribute('aria-disabled', 'true');
64
- playButton.setAttribute('aria-label', 'Loading playground...');
65
- playButton.classList.add('stl-ui-button--loading');
66
- const showPlayground = loadPlayground(playButton);
67
- await showPlayground();
68
- } catch (e) {
69
- console.error(e);
70
- iconElement.innerHTML = circleAlertIcon;
71
- }
72
- playButton.removeAttribute('aria-disabled');
73
- playButton.removeAttribute('aria-label');
74
- playButton.classList.remove('stl-ui-button--loading');
75
- }
76
-
77
- function loadPlayground(playButton: HTMLElement) {
78
- (playButton as any).__playgroundLoadPromise ??= (async () => {
79
- const container = playButton.closest('.stldocs-snippet') as HTMLElement;
80
- const language = (container.querySelector('.stl-sdk-select') as HTMLElement).dataset.currentValue;
81
- const code = getContent(playButton, true);
82
- // eslint-disable-next-line turbo/no-undeclared-env-vars
83
- if (import.meta.env.DEV) {
84
- const id = '/@id/astro:scripts/before-hydration.js';
85
- await import(/* @vite-ignore */ id).catch(console.warn);
86
- }
87
- const { createPlayground } = await import('virtual:stl-playground/create');
88
- const { default: playgroundData } = await import('virtual:stl-playground/data');
89
- return createPlayground({
90
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
- lang: language as any,
92
- doc: (language === 'python' ? 'from rich import print\n' : '') + code.trimEnd(),
93
- container,
94
- onLanguageSelect: (value) => {
95
- const originalLanguage = container.querySelector<HTMLElement>('[data-stldocs-snippet-select]')
96
- ?.dataset.currentValue;
97
- const path: string = updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value);
98
- navigate(path.replace(/(\?.+)?($|#)/, (_, str, end) => (str ? str + '&play' : '?play') + end));
99
- },
100
- ...playgroundData,
101
- });
102
- })();
103
- return async () => {
104
- const promise = (playButton as any).__playgroundLoadPromise;
105
- (playButton as any).__playgroundLoadPromise = null;
106
- await ((await promise) as () => Promise<void>)();
107
- };
108
- }
109
- document.addEventListener(getPageLoadEvent(), () => {
110
- if (new URL(location.href).searchParams.has('play')) {
111
- document.querySelectorAll('[data-stldocs-snippet-play]').forEach((e) => {
112
- showPlayground(e as HTMLElement);
113
- });
30
+ navigator.clipboard
31
+ .writeText(getContent(copyButton, false))
32
+ .then(() => {
33
+ iconElement.innerHTML = checkIcon;
34
+ })
35
+ .catch(() => {
36
+ iconElement.innerHTML = circleAlertIcon;
37
+ })
38
+ .finally(() => {
39
+ copyButton.dataset.__stldocsCopyTimeout = setTimeout(() => {
40
+ iconElement.innerHTML = copyIcon;
41
+ }).toString();
42
+ });
114
43
  }
115
44
  });
@@ -27,7 +27,9 @@ document.addEventListener(getPageLoadEvent(), () => {
27
27
  root: rootElement,
28
28
  onSelect: (value) => {
29
29
  const originalLanguage = rootElement.dataset.currentValue;
30
- navigate(updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value));
30
+ navigate(updateSelectedLanguage(RESOLVED_API_REFERENCE_PATH, originalLanguage, value)).catch((e) => {
31
+ console.error('Failed to navigate to selected language', e);
32
+ });
31
33
  },
32
34
  });
33
35
  });
@@ -37,24 +39,22 @@ document.addEventListener(getPageLoadEvent(), () => {
37
39
  });
38
40
 
39
41
  document.addEventListener('click', (event) => {
40
- const toggle = (event.target as HTMLElement).closest(
41
- '[data-stldocs-property-toggle-expanded] > .stldocs-expand-toggle-content',
42
- )?.parentElement;
42
+ const toggle =
43
+ event.target instanceof HTMLElement && event.target.closest('[data-stldocs-property-toggle-expanded]');
44
+ if (!toggle || !(toggle instanceof HTMLElement)) return;
43
45
 
44
- if (!toggle) return;
45
-
46
- const state = toggle.dataset.stldocsPropertyToggleExpanded === 'true';
47
- toggle.dataset.stldocsPropertyToggleExpanded = state ? 'false' : 'true';
46
+ // Toggle the “expanded” state of the toggle
47
+ const toggleIsExpanded = toggle.dataset.stldocsPropertyToggleExpanded === 'true';
48
+ toggle.dataset.stldocsPropertyToggleExpanded = (!toggleIsExpanded).toString();
48
49
 
50
+ // Find the described “property group”
49
51
  const targetGroup = toggle.dataset.stldocsPropertyToggleTarget;
50
52
  if (!targetGroup) return;
51
-
52
53
  const target = document.querySelector(`[data-stldocs-property-group=${targetGroup}]`);
53
54
  if (!target) return;
54
55
 
55
- target.querySelectorAll('details').forEach((details) => {
56
- const el = details as HTMLDetailsElement;
57
- const initial = el.dataset.stldocsExpanderInitialState;
58
- el.open = initial === 'true' ? true : !state;
56
+ // Expand or collapse all <details> elements within the target group
57
+ [...target.getElementsByTagName('details')].forEach((el) => {
58
+ el.open = el.dataset.stldocsExpanderInitialState === 'true' ? true : !toggleIsExpanded;
59
59
  });
60
60
  });
@@ -0,0 +1,29 @@
1
+ // Let users select text in <summary> elements without toggling them
2
+
3
+ document.addEventListener(
4
+ 'mousedown',
5
+ (e) => {
6
+ const summary = (e.target as HTMLElement).closest('summary.stldocs-expander-summary');
7
+ if (!summary) return;
8
+ const selectionOnDown = window.getSelection()?.toString() ?? '';
9
+
10
+ document.addEventListener(
11
+ 'mouseup',
12
+ () => {
13
+ const selectionOnUp = window.getSelection()?.toString() ?? '';
14
+
15
+ if (selectionOnUp && selectionOnUp !== selectionOnDown) {
16
+ summary.addEventListener(
17
+ 'click',
18
+ (e) => {
19
+ e.preventDefault();
20
+ },
21
+ { once: true },
22
+ );
23
+ }
24
+ },
25
+ { once: true },
26
+ );
27
+ },
28
+ { capture: true },
29
+ );