@stainless-api/docs 0.1.0-beta.53 → 0.1.0-beta.56

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 CHANGED
@@ -1,15 +1,51 @@
1
1
  # @stainless-api/docs
2
2
 
3
- ## 0.1.0-beta.53
3
+ ## 0.1.0-beta.56
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 2b0fec9: Added prose indexing for search
7
+ - d75a72f: Adds C# language support
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [d75a72f]
12
+ - Updated dependencies [2464347]
13
+ - @stainless-api/docs-ui@0.1.0-beta.47
14
+
15
+ ## 0.1.0-beta.55
16
+
17
+ ### Patch Changes
18
+
19
+ - ae56b3c: ensure changeset release commit doesn’t include [skip ci]
20
+ - Updated dependencies [ae56b3c]
21
+ - @stainless-api/docs-ui@0.1.0-beta.46
22
+ - @stainless-api/ui-primitives@0.1.0-beta.35
23
+
24
+ ## 0.1.0-beta.54
25
+
26
+ ### Minor Changes
27
+
28
+ - a70f60c: Adds the title property
29
+
30
+ ### Patch Changes
31
+
32
+ - 2ebf696: enable trusted publishing
33
+ - Updated dependencies [2ebf696]
34
+ - Updated dependencies [a70f60c]
35
+ - @stainless-api/docs-ui@0.1.0-beta.45
36
+ - @stainless-api/ui-primitives@0.1.0-beta.34
37
+
38
+ ## 0.1.0-beta.53
8
39
 
9
40
  ### Patch Changes
10
41
 
11
- - Updated dependencies [2b0fec9]
42
+ - 2e1639a: Updated preview worker and added experimental playgrounds feature.
43
+ - Updated dependencies [239e28c]
44
+ - Updated dependencies [ddc4593]
45
+ - Updated dependencies [2e1639a]
46
+ - Updated dependencies [2e1639a]
12
47
  - @stainless-api/docs-ui@0.1.0-beta.44
48
+ - @stainless-api/ui-primitives@0.1.0-beta.33
13
49
 
14
50
  ## 0.1.0-beta.52
15
51
 
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "plugin/cms/worker.ts": {
23
23
  "@typescript-eslint/no-explicit-any": {
24
- "count": 4
24
+ "count": 3
25
25
  }
26
26
  },
27
27
  "plugin/components/SnippetCode.tsx": {
@@ -29,6 +29,11 @@
29
29
  "count": 1
30
30
  }
31
31
  },
32
+ "plugin/globalJs/copy.ts": {
33
+ "@typescript-eslint/no-explicit-any": {
34
+ "count": 3
35
+ }
36
+ },
32
37
  "plugin/index.ts": {
33
38
  "@typescript-eslint/no-explicit-any": {
34
39
  "count": 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stainless-api/docs",
3
- "version": "0.1.0-beta.53",
3
+ "version": "0.1.0-beta.56",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -11,6 +11,7 @@
11
11
  "./plugin": "./plugin/index.ts",
12
12
  "./plugin/middleware": "./plugin/middlewareBuilder/stlStarlightMiddleware.ts",
13
13
  "./plugin/MiddlewareTypes": "./plugin/middlewareBuilder/stainlessMiddleware.d.ts",
14
+ "./plugin/languages": "./plugin/languages.ts",
14
15
  "./stainless-docs/mintlify-compat": "./stl-docs/components/mintlify-compat/index.ts",
15
16
  "./mintlify-compat.css": "./styles/mintlify-compat.css",
16
17
  "./font-imports": "./styles/fonts.css",
@@ -34,7 +35,6 @@
34
35
  "@astrojs/markdown-remark": "^6.3.9",
35
36
  "@astrojs/react": "^4.4.2",
36
37
  "@stainless-api/sdk": "0.1.0-alpha.16",
37
- "astro-expressive-code": "^0.41.3",
38
38
  "cheerio": "^1.1.2",
39
39
  "clsx": "^2.1.1",
40
40
  "dotenv": "17.2.3",
@@ -49,16 +49,17 @@
49
49
  "remark-stringify": "^11.0.0",
50
50
  "shiki": "^3.19.0",
51
51
  "unified": "^11.0.5",
52
+ "vite-plugin-prebundle-workers": "^0.2.0",
52
53
  "web-worker": "^1.5.0",
53
54
  "yaml": "^2.8.2",
54
- "@stainless-api/ui-primitives": "0.1.0-beta.32",
55
- "@stainless-api/docs-ui": "0.1.0-beta.44"
55
+ "@stainless-api/ui-primitives": "0.1.0-beta.35",
56
+ "@stainless-api/docs-ui": "0.1.0-beta.47"
56
57
  },
57
58
  "devDependencies": {
58
59
  "@astrojs/check": "^0.9.6",
59
60
  "@markdoc/markdoc": "^0.5.4",
60
61
  "@types/node": "24.10.1",
61
- "@types/react": "^19.2.7",
62
+ "@types/react": "19.2.7",
62
63
  "@types/react-dom": "^19.2.3",
63
64
  "react": "^19.2.1",
64
65
  "react-dom": "^19.2.1",
@@ -66,8 +67,8 @@
66
67
  "typescript": "5.9.3",
67
68
  "vite": "^6.4.1",
68
69
  "zod": "^4.1.13",
69
- "@stainless/sdk-json": "^0.1.0-beta.0",
70
- "@stainless/eslint-config": "0.1.0-beta.0"
70
+ "@stainless/eslint-config": "0.1.0-beta.0",
71
+ "@stainless/sdk-json": "^0.1.0-beta.2"
71
72
  },
72
73
  "scripts": {
73
74
  "vendor-deps": "tsx scripts/vendor_deps.ts",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" fill="none"><defs><linearGradient id="a-_r_1_" x1="46.77" x2="69.91" y1="86.46" y2="126.73" gradientTransform="matrix(8.78996,0,0,8.78996,-233.98,-518.97)" gradientUnits="userSpaceOnUse"><stop stop-color="#927be5"></stop><stop offset="1" stop-color="#512bd4"></stop></linearGradient><filter id="b-_r_1_" width="42.84" height="39.14" x="44.63" y="91.89" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"></feColorMatrix><feOffset></feOffset><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"></feColorMatrix><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_2037_2800"></feBlend><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"></feColorMatrix><feOffset dy="1"></feOffset><feGaussianBlur stdDeviation="2.5"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"></feColorMatrix><feBlend in2="effect1_dropShadow_2037_2800" result="effect2_dropShadow_2037_2800"></feBlend><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"></feColorMatrix><feOffset dy="4"></feOffset><feGaussianBlur stdDeviation="2"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.09 0"></feColorMatrix><feBlend in2="effect2_dropShadow_2037_2800" result="effect3_dropShadow_2037_2800"></feBlend><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"></feColorMatrix><feOffset dy="9"></feOffset><feGaussianBlur stdDeviation="2.5"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"></feColorMatrix><feBlend in2="effect3_dropShadow_2037_2800" result="effect4_dropShadow_2037_2800"></feBlend><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"></feColorMatrix><feOffset dy="15"></feOffset><feGaussianBlur stdDeviation="3"></feGaussianBlur><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.01 0"></feColorMatrix><feBlend in2="effect4_dropShadow_2037_2800" result="effect5_dropShadow_2037_2800"></feBlend><feBlend in="SourceGraphic" in2="effect5_dropShadow_2037_2800" result="shape"></feBlend></filter></defs><path fill="url(#a-_r_1_)" d="M135.73 285.85v173.93a60.2 60.2 0 0 0 30.13 52.17l150.62 86.97a60.2 60.2 0 0 0 60.25 0l150.62-86.97a60.2 60.2 0 0 0 30.13-52.17V285.85a60.2 60.2 0 0 0-30.13-52.18l-150.62-86.95a60.2 60.2 0 0 0-60.25 0l-150.62 86.95a60.3 60.3 0 0 0-30.13 52.18" transform="scale(0.44288615) matrix(0.1,0,0,0.1,-7.57,-10.19)"></path><path fill="#fff" d="M54.06 98.03v6.86a1.7 1.7 0 0 0 1.71 1.7 1.7 1.7 0 0 0 1.71-1.7 1.71 1.71 0 1 1 3.43 0 5.14 5.14 0 1 1-10.28 0v-6.86a5.14 5.14 0 1 1 10.28 0 1.71 1.71 0 1 1-3.43 0 1.71 1.71 0 1 0-3.42 0zm27.41 6.86a1.7 1.7 0 0 1-1.71 1.7h-1.71v1.72c0 .46-.18.9-.5 1.21a1.7 1.7 0 0 1-2.43 0 1.7 1.7 0 0 1-.5-1.2v-1.72h-3.43v1.71a1.7 1.7 0 0 1-1.71 1.72 1.7 1.7 0 0 1-1.72-1.72v-1.71h-1.71a1.71 1.71 0 1 1 0-3.43h1.71v-3.43h-1.71a1.71 1.71 0 1 1 0-3.42h1.71V94.6a1.71 1.71 0 1 1 3.43 0v1.72h3.43V94.6a1.71 1.71 0 1 1 3.43 0v1.72h1.7c.46 0 .9.18 1.22.5a1.7 1.7 0 0 1 0 2.42 1.7 1.7 0 0 1-1.21.5h-1.71v3.43h1.7a1.7 1.7 0 0 1 1.72 1.71m-6.85-5.14h-3.43v3.42h3.43z" filter="url(#b-_r_1_)" style="mix-blend-mode: screen;" transform="scale(0.44288615) matrix(0.879,0,0,0.879,-30.96,-62.09)"></path></svg>
@@ -67,7 +67,6 @@ export async function buildAlgoliaIndex({
67
67
  const languages =
68
68
  config.docs?.languages ??
69
69
  (Object.entries(config.targets)
70
- // @ts-expect-error we don't have the actual Stainless config type here
71
70
  .filter(([name, target]) => Languages.includes(name) && !target.skip)
72
71
  .map(([name]) => name) as SDKJSON.SpecLanguage[]);
73
72
 
@@ -75,6 +74,7 @@ export async function buildAlgoliaIndex({
75
74
  oas: transformedOAS,
76
75
  config,
77
76
  languages,
77
+ version,
78
78
  });
79
79
 
80
80
  const {
@@ -35,6 +35,30 @@ function redactApiKey(apiKey: string) {
35
35
  .join('');
36
36
  }
37
37
 
38
+ export type Auth = Array<{
39
+ type: 'http_bearer' | 'query' | 'header' | 'oauth2' | 'http_basic' | 'http_digest';
40
+ description?: string;
41
+ name: string;
42
+ title: string;
43
+ header: string | undefined;
44
+ example: string | undefined;
45
+ opts: {
46
+ type: 'string' | 'number' | 'boolean' | 'null' | 'integer';
47
+ nullable: boolean;
48
+ description?: string | undefined;
49
+ example?: unknown;
50
+ default?: unknown;
51
+ read_env?: string | undefined;
52
+ auth?:
53
+ | {
54
+ security_scheme: string;
55
+ role?: 'value' | 'password' | 'username' | 'client_id' | 'client_secret' | undefined;
56
+ }
57
+ | undefined;
58
+ name: string;
59
+ }[];
60
+ }>;
61
+
38
62
  async function loadSpec({
39
63
  apiKey,
40
64
  devPaths,
@@ -45,8 +69,8 @@ async function loadSpec({
45
69
  devPaths: InputFilePaths;
46
70
  version: VersionUserConfig;
47
71
  logger: AstroIntegrationLogger;
48
- }) {
49
- async function unsafeLoad() {
72
+ }): Promise<{ data: SDKJSON.Spec; auth: Auth; id: string }> {
73
+ async function unsafeLoad(): Promise<{ data: SDKJSON.Spec; auth: Auth; id: string }> {
50
74
  let oasStr: string;
51
75
  let configStr: string;
52
76
  let versions: Record<DocsLanguage, string> | undefined;
@@ -82,7 +106,6 @@ async function loadSpec({
82
106
  const languages =
83
107
  config.docs?.languages ??
84
108
  (Object.entries(config.targets)
85
- // @ts-expect-error we don't have the actual Stainless config type here
86
109
  .filter(([name, target]) => Languages.includes(name) && !target.skip)
87
110
  .map(([name]) => name) as SDKJSON.SpecLanguage[]);
88
111
 
@@ -90,6 +113,7 @@ async function loadSpec({
90
113
  oas: transformedOAS,
91
114
  config,
92
115
  languages,
116
+ version,
93
117
  });
94
118
 
95
119
  if (versions) {
@@ -100,9 +124,14 @@ async function loadSpec({
100
124
  }
101
125
 
102
126
  const id = crypto.randomUUID();
127
+ const opts = Object.entries(config.client_settings.opts).map(([k, v]) => ({ name: k, ...v }));
103
128
 
104
129
  return {
105
130
  data: sdkJson,
131
+ auth: sdkJson.security_schemes.map((scheme) => ({
132
+ ...scheme,
133
+ opts: opts.filter((opt) => opt.auth?.security_scheme === scheme.name),
134
+ })),
106
135
  id,
107
136
  };
108
137
  }
@@ -127,7 +156,7 @@ async function loadSpec({
127
156
  }
128
157
 
129
158
  class Spec {
130
- private specPromise: Promise<{ id: string; data: SDKJSON.Spec }>;
159
+ private specPromise: Promise<{ id: string; data: SDKJSON.Spec; auth: Auth }>;
131
160
  private devPaths: InputFilePaths;
132
161
  private apiKey: string;
133
162
  private version: VersionUserConfig;
@@ -153,6 +182,7 @@ class Spec {
153
182
  return {
154
183
  id: spec.id,
155
184
  data: null,
185
+ auth: null,
156
186
  };
157
187
  }
158
188
 
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
5
5
  import { dirname, resolve } from 'node:path';
6
6
  import fs from 'fs/promises';
7
7
  import pathutils from 'path';
8
+ import type { VersionUserConfig } from '../loadPluginConfig';
8
9
 
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = dirname(__filename);
@@ -12,7 +13,81 @@ const __dirname = dirname(__filename);
12
13
  const workerPath = resolve(__dirname, '..', 'vendor', 'preview.worker.docs.js');
13
14
 
14
15
  type OpenAPIDocument = Record<string, any>;
15
- type ParsedConfig = Record<string, any>;
16
+ export type ParsedConfig = {
17
+ docs:
18
+ | {
19
+ title?: string | undefined;
20
+ favicon?: string | undefined;
21
+ logo_icon?: string | undefined;
22
+ search?:
23
+ | {
24
+ algolia?:
25
+ | {
26
+ app_id: string;
27
+ index_name: string;
28
+ search_key: string;
29
+ }
30
+ | undefined;
31
+ }
32
+ | undefined;
33
+ description?: string | undefined;
34
+ languages?:
35
+ | ('node' | 'typescript' | 'python' | 'java' | 'kotlin' | 'go' | 'ruby' | 'terraform' | 'http')[]
36
+ | undefined;
37
+ snippets?:
38
+ | {
39
+ exclude_languages?: string[] | undefined;
40
+ }
41
+ | undefined;
42
+ show_security?: boolean | undefined;
43
+ show_readme?: boolean | undefined;
44
+ base_path?: string | undefined;
45
+ navigation?:
46
+ | {
47
+ menubar?:
48
+ | {
49
+ title: string;
50
+ icon?: string;
51
+ variant?: string;
52
+ href?: string | undefined;
53
+ page?: string | undefined;
54
+ }[]
55
+ | undefined;
56
+ sidebar?:
57
+ | {
58
+ title: string;
59
+ icon?: string;
60
+ variant?: string;
61
+ href?: string | undefined;
62
+ page?: string | undefined;
63
+ }[]
64
+ | undefined;
65
+ }
66
+ | undefined;
67
+ pages?: unknown;
68
+ resources?: unknown[] | undefined;
69
+ }
70
+ | undefined;
71
+ targets: Record<string, { skip?: boolean }>;
72
+ client_settings: {
73
+ opts: {
74
+ [x: string]: {
75
+ type: 'string' | 'number' | 'boolean' | 'null' | 'integer';
76
+ nullable: boolean;
77
+ description?: string | undefined;
78
+ example?: unknown;
79
+ default?: unknown;
80
+ read_env?: string | undefined;
81
+ auth?:
82
+ | {
83
+ security_scheme: string;
84
+ role?: 'value' | 'password' | 'username' | 'client_id' | 'client_secret' | undefined;
85
+ }
86
+ | undefined;
87
+ };
88
+ };
89
+ };
90
+ };
16
91
 
17
92
  function runJob({ type, signal, data }: { type: string; signal?: AbortSignal; data: any }) {
18
93
  const stainlessWorker = new Worker(workerPath, {
@@ -22,6 +97,7 @@ function runJob({ type, signal, data }: { type: string; signal?: AbortSignal; da
22
97
 
23
98
  return new Promise<any>((resolve, reject) => {
24
99
  stainlessWorker.addEventListener('error', (e) => {
100
+ e.preventDefault();
25
101
  reject(e);
26
102
  });
27
103
 
@@ -85,10 +161,12 @@ export async function createSDKJSON({
85
161
  oas,
86
162
  config,
87
163
  languages,
164
+ version,
88
165
  }: {
89
166
  oas: OpenAPIDocument;
90
167
  config: ParsedConfig;
91
168
  languages: DocsLanguage[];
169
+ version: VersionUserConfig;
92
170
  }) {
93
171
  const templatePath = resolve(__dirname, '../vendor/templates');
94
172
  const readmeLoader = await Promise.all(
@@ -113,7 +191,7 @@ export async function createSDKJSON({
113
191
  config,
114
192
  languages,
115
193
  transform: false,
116
- projectName: '',
194
+ projectName: version.stainlessProject,
117
195
  readmeTemplates,
118
196
  },
119
197
  });
@@ -6,9 +6,33 @@ import type {
6
6
  import { useHighlight, useLanguage } from '@stainless-api/docs-ui/contexts';
7
7
  import style from '@stainless-api/docs-ui/style';
8
8
  import * as cheerio from 'cheerio/slim';
9
- import { EXPERIMENTAL_COLLAPSIBLE_SNIPPETS } from 'virtual:stl-starlight-virtual-module';
9
+ import {
10
+ EXPERIMENTAL_COLLAPSIBLE_SNIPPETS,
11
+ EXPERIMENTAL_PLAYGROUNDS,
12
+ } from 'virtual:stl-starlight-virtual-module';
10
13
  import clsx from 'clsx';
11
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
+ }
12
36
 
13
37
  /*
14
38
  * This may be replaced by additional data from the sdk.
@@ -185,6 +209,23 @@ export function CondensibleSnippetCode({
185
209
  );
186
210
  }
187
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
+ }
188
229
  export function SnippetCode({ content, signature, language: forcedLanguage }: SnippetCodeProps) {
189
230
  const lang = useLanguage();
190
231
  const language = forcedLanguage || lang;
@@ -0,0 +1,3 @@
1
+ export function createPlayground(): () => Promise<void> {
2
+ return () => Promise.resolve();
3
+ }
@@ -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
  });