@stainless-api/docs 0.1.0-beta.82 → 0.1.0-beta.84
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 +31 -0
- package/eslint-suppressions.json +1 -1
- package/package.json +4 -4
- package/plugin/buildAlgoliaIndex.ts +1 -1
- package/plugin/cms/generate-spec.ts +112 -0
- package/plugin/cms/server.ts +84 -59
- package/plugin/cms/sidebar-builder.ts +14 -11
- package/plugin/cms/worker.ts +3 -4
- package/plugin/helpers/generateDocsRoutes.ts +1 -1
- package/plugin/index.ts +56 -17
- package/plugin/routes/Docs.astro +27 -24
- package/plugin/routes/DocsStatic.astro +4 -1
- package/plugin/routes/markdown.ts +2 -0
- package/stl-docs/components/Head.astro +4 -2
- package/stl-docs/components/sidebars/BaseSidebar.astro +7 -2
- package/stl-docs/components/sidebars/SidebarWithComponents.tsx +10 -0
- package/styles/page.css +3 -1
- package/styles/sidebar.css +0 -210
- package/theme.css +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @stainless-api/docs
|
|
2
2
|
|
|
3
|
+
## 0.1.0-beta.84
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6ef241e: Sidebar improvements; rename Sidebar to SDKSidebar
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- d3a85b5: feat: improve not implemented fallback ui
|
|
12
|
+
- 5156697: include markdown alt link for all pages
|
|
13
|
+
- dedd93b: fix incorrect link rel=alternate markdown URLs
|
|
14
|
+
- b532d45: use method summary as page title
|
|
15
|
+
- Updated dependencies [6ef241e]
|
|
16
|
+
- Updated dependencies [d3a85b5]
|
|
17
|
+
- Updated dependencies [d3a85b5]
|
|
18
|
+
- Updated dependencies [2dcb5fb]
|
|
19
|
+
- @stainless-api/docs-ui@0.1.0-beta.62
|
|
20
|
+
- @stainless-api/docs-search@0.1.0-beta.15
|
|
21
|
+
|
|
22
|
+
## 0.1.0-beta.83
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 96e974e: clean up n^2 calls to generateDocsRoutes by passing props
|
|
27
|
+
- a0301a2: Improves build speed by up to 8x for large APIs
|
|
28
|
+
- b6b9d30: fix: should hide skipped resources in sidebar
|
|
29
|
+
- Updated dependencies [3037a19]
|
|
30
|
+
- @stainless-api/ui-primitives@0.1.0-beta.45
|
|
31
|
+
- @stainless-api/docs-search@0.1.0-beta.14
|
|
32
|
+
- @stainless-api/docs-ui@0.1.0-beta.61
|
|
33
|
+
|
|
3
34
|
## 0.1.0-beta.82
|
|
4
35
|
|
|
5
36
|
### Patch Changes
|
package/eslint-suppressions.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stainless-api/docs",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.84",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -58,9 +58,9 @@
|
|
|
58
58
|
"vite-plugin-prebundle-workers": "^0.2.0",
|
|
59
59
|
"web-worker": "^1.5.0",
|
|
60
60
|
"yaml": "^2.8.2",
|
|
61
|
-
"@stainless-api/docs-search": "0.1.0-beta.
|
|
62
|
-
"@stainless-api/docs-ui": "0.1.0-beta.
|
|
63
|
-
"@stainless-api/ui-primitives": "0.1.0-beta.
|
|
61
|
+
"@stainless-api/docs-search": "0.1.0-beta.15",
|
|
62
|
+
"@stainless-api/docs-ui": "0.1.0-beta.62",
|
|
63
|
+
"@stainless-api/ui-primitives": "0.1.0-beta.45"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@astrojs/check": "^0.9.6",
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type * as SDKJSON from '@stainless/sdk-json';
|
|
2
|
+
import { Languages } from '@stainless-api/docs-ui/routing';
|
|
3
|
+
import { createSDKJSON, ParsedConfig, parseInputs, transformOAS } from './worker';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
|
|
8
|
+
function addAuthToSpec(spec: SDKJSON.Spec, config: ParsedConfig) {
|
|
9
|
+
const opts = Object.entries(config.client_settings.opts).map(([k, v]) => ({ name: k, ...v }));
|
|
10
|
+
return {
|
|
11
|
+
data: spec,
|
|
12
|
+
auth: spec.security_schemes.map((scheme) => ({
|
|
13
|
+
...scheme,
|
|
14
|
+
opts: opts.filter((opt) => opt.auth?.security_scheme === scheme.name),
|
|
15
|
+
})),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type SpecWithAuth = ReturnType<typeof addAuthToSpec>;
|
|
20
|
+
|
|
21
|
+
function hashStringBase64(input: string, algo = 'sha256') {
|
|
22
|
+
return createHash(algo).update(input, 'utf8').digest('base64');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class SpecFileSystemCache {
|
|
26
|
+
private specFilePath: string;
|
|
27
|
+
private shaForInputsPath: string;
|
|
28
|
+
|
|
29
|
+
private hashInputs() {
|
|
30
|
+
return hashStringBase64(this.currentInputs.oas + this.currentInputs.config);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async write(spec: SpecWithAuth) {
|
|
34
|
+
await writeFile(this.specFilePath, JSON.stringify(spec), 'utf-8');
|
|
35
|
+
await writeFile(this.shaForInputsPath, this.hashInputs(), 'utf-8');
|
|
36
|
+
console.log('wrote spec to cache', this.specFilePath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async read() {
|
|
40
|
+
try {
|
|
41
|
+
const cachedSha = await readFile(this.shaForInputsPath, 'utf-8');
|
|
42
|
+
if (cachedSha !== this.hashInputs()) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const cachedSpec = JSON.parse(await readFile(this.specFilePath, 'utf-8')) as SpecWithAuth;
|
|
51
|
+
console.log('read spec from cache', this.specFilePath);
|
|
52
|
+
return cachedSpec;
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
cacheDirectory: string,
|
|
60
|
+
private currentInputs: { oas: string; config: string },
|
|
61
|
+
) {
|
|
62
|
+
this.specFilePath = path.join(cacheDirectory, 'spec.json');
|
|
63
|
+
this.shaForInputsPath = path.join(cacheDirectory, 'spec_inputs_sha.txt');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function generateSpecFromStrings({
|
|
68
|
+
oasStr,
|
|
69
|
+
configStr,
|
|
70
|
+
specCacheDirectory,
|
|
71
|
+
projectName,
|
|
72
|
+
}: {
|
|
73
|
+
oasStr: string;
|
|
74
|
+
configStr: string;
|
|
75
|
+
specCacheDirectory: string | null;
|
|
76
|
+
projectName: string;
|
|
77
|
+
}) {
|
|
78
|
+
const fsCache = specCacheDirectory
|
|
79
|
+
? new SpecFileSystemCache(specCacheDirectory, { oas: oasStr, config: configStr })
|
|
80
|
+
: null;
|
|
81
|
+
|
|
82
|
+
const cachedSpec = await fsCache?.read();
|
|
83
|
+
if (cachedSpec) {
|
|
84
|
+
return cachedSpec;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { oas, config } = await parseInputs({
|
|
88
|
+
oas: oasStr,
|
|
89
|
+
config: configStr,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const transformedOAS = await transformOAS({ oas, config });
|
|
93
|
+
|
|
94
|
+
const languages =
|
|
95
|
+
config.docs?.languages ??
|
|
96
|
+
(Object.entries(config.targets)
|
|
97
|
+
.filter(([name, target]) => Languages.includes(name) && !target.skip)
|
|
98
|
+
.map(([name]) => name) as SDKJSON.SpecLanguage[]);
|
|
99
|
+
|
|
100
|
+
const sdkJson = await createSDKJSON({
|
|
101
|
+
oas: transformedOAS,
|
|
102
|
+
config,
|
|
103
|
+
languages,
|
|
104
|
+
projectName,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const specWithAuth = addAuthToSpec(sdkJson, config);
|
|
108
|
+
|
|
109
|
+
await fsCache?.write(specWithAuth);
|
|
110
|
+
|
|
111
|
+
return specWithAuth;
|
|
112
|
+
}
|
package/plugin/cms/server.ts
CHANGED
|
@@ -3,8 +3,7 @@ import { createServer, IncomingMessage } from 'http';
|
|
|
3
3
|
import { readFile } from 'fs/promises';
|
|
4
4
|
|
|
5
5
|
import type * as SDKJSON from '@stainless/sdk-json';
|
|
6
|
-
import {
|
|
7
|
-
import { Languages, parseRoute, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
6
|
+
import { parseRoute, type DocsLanguage } from '@stainless-api/docs-ui/routing';
|
|
8
7
|
import Stainless, { APIError } from '@stainless-api/sdk';
|
|
9
8
|
|
|
10
9
|
import {
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
} from './sidebar-builder';
|
|
15
14
|
import type { VersionUserConfig } from '../loadPluginConfig';
|
|
16
15
|
import { bold } from '../../shared/terminalUtils';
|
|
16
|
+
import { generateSpecFromStrings } from './generate-spec';
|
|
17
17
|
export type InputFilePaths = {
|
|
18
18
|
oasPath?: string;
|
|
19
19
|
configPath?: string;
|
|
@@ -59,18 +59,22 @@ export type Auth = Array<{
|
|
|
59
59
|
}[];
|
|
60
60
|
}>;
|
|
61
61
|
|
|
62
|
+
type LoadedSpec = { data: SDKJSON.Spec; auth: Auth; id: string };
|
|
63
|
+
|
|
62
64
|
async function loadSpec({
|
|
63
65
|
apiKey,
|
|
64
66
|
devPaths,
|
|
65
67
|
version,
|
|
66
68
|
logger,
|
|
69
|
+
specCacheDirectory,
|
|
67
70
|
}: {
|
|
68
71
|
apiKey: string;
|
|
69
72
|
devPaths: InputFilePaths;
|
|
70
73
|
version: VersionUserConfig;
|
|
71
74
|
logger: AstroIntegrationLogger;
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
specCacheDirectory: string | null;
|
|
76
|
+
}): Promise<LoadedSpec> {
|
|
77
|
+
async function unsafeLoad(): Promise<LoadedSpec> {
|
|
74
78
|
let oasStr: string;
|
|
75
79
|
let configStr: string;
|
|
76
80
|
let versions: Record<DocsLanguage, string> | undefined;
|
|
@@ -96,42 +100,25 @@ async function loadSpec({
|
|
|
96
100
|
configStr = configYML['content'];
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const transformedOAS = await transformOAS({ oas, config });
|
|
105
|
-
|
|
106
|
-
const languages =
|
|
107
|
-
config.docs?.languages ??
|
|
108
|
-
(Object.entries(config.targets)
|
|
109
|
-
.filter(([name, target]) => Languages.includes(name) && !target.skip)
|
|
110
|
-
.map(([name]) => name) as SDKJSON.SpecLanguage[]);
|
|
111
|
-
|
|
112
|
-
const sdkJson = await createSDKJSON({
|
|
113
|
-
oas: transformedOAS,
|
|
114
|
-
config,
|
|
115
|
-
languages,
|
|
116
|
-
version,
|
|
103
|
+
const sdkJson = await generateSpecFromStrings({
|
|
104
|
+
oasStr,
|
|
105
|
+
configStr,
|
|
106
|
+
projectName: version.stainlessProject,
|
|
107
|
+
specCacheDirectory,
|
|
117
108
|
});
|
|
118
109
|
|
|
119
110
|
if (versions) {
|
|
120
111
|
for (const [lang, version] of Object.entries(versions)) {
|
|
121
|
-
const meta = sdkJson.metadata[lang as DocsLanguage];
|
|
112
|
+
const meta = sdkJson.data.metadata[lang as DocsLanguage];
|
|
122
113
|
if (meta?.version) meta.version = version;
|
|
123
114
|
}
|
|
124
115
|
}
|
|
125
116
|
|
|
126
117
|
const id = crypto.randomUUID();
|
|
127
|
-
const opts = Object.entries(config.client_settings.opts).map(([k, v]) => ({ name: k, ...v }));
|
|
128
118
|
|
|
129
119
|
return {
|
|
130
|
-
data: sdkJson,
|
|
131
|
-
auth: sdkJson.
|
|
132
|
-
...scheme,
|
|
133
|
-
opts: opts.filter((opt) => opt.auth?.security_scheme === scheme.name),
|
|
134
|
-
})),
|
|
120
|
+
data: sdkJson.data,
|
|
121
|
+
auth: sdkJson.auth,
|
|
135
122
|
id,
|
|
136
123
|
};
|
|
137
124
|
}
|
|
@@ -163,6 +150,7 @@ class Spec {
|
|
|
163
150
|
|
|
164
151
|
reload() {
|
|
165
152
|
this.specPromise = loadSpec({
|
|
153
|
+
specCacheDirectory: this.specCacheDirectory,
|
|
166
154
|
apiKey: this.apiKey,
|
|
167
155
|
devPaths: this.devPaths,
|
|
168
156
|
version: this.version,
|
|
@@ -194,8 +182,10 @@ class Spec {
|
|
|
194
182
|
version: VersionUserConfig,
|
|
195
183
|
devPaths: InputFilePaths,
|
|
196
184
|
private logger: AstroIntegrationLogger,
|
|
185
|
+
private specCacheDirectory: string | null,
|
|
197
186
|
) {
|
|
198
187
|
this.specPromise = loadSpec({
|
|
188
|
+
specCacheDirectory,
|
|
199
189
|
apiKey,
|
|
200
190
|
devPaths,
|
|
201
191
|
version,
|
|
@@ -211,7 +201,7 @@ class Spec {
|
|
|
211
201
|
export type SpecResp = Awaited<ReturnType<Spec['getSpec']>>;
|
|
212
202
|
|
|
213
203
|
// will be necessary once we add POST methods
|
|
214
|
-
function readJsonBody(req: IncomingMessage): Promise<
|
|
204
|
+
function readJsonBody<T>(req: IncomingMessage): Promise<T> {
|
|
215
205
|
return new Promise((resolve, reject) => {
|
|
216
206
|
let body = '';
|
|
217
207
|
req.on('data', (chunk) => {
|
|
@@ -220,7 +210,7 @@ function readJsonBody(req: IncomingMessage): Promise<any> {
|
|
|
220
210
|
req.on('end', () => {
|
|
221
211
|
try {
|
|
222
212
|
const data = JSON.parse(body);
|
|
223
|
-
resolve(data);
|
|
213
|
+
resolve(data as T);
|
|
224
214
|
} catch (error) {
|
|
225
215
|
reject(error);
|
|
226
216
|
}
|
|
@@ -228,6 +218,28 @@ function readJsonBody(req: IncomingMessage): Promise<any> {
|
|
|
228
218
|
});
|
|
229
219
|
}
|
|
230
220
|
|
|
221
|
+
function markCurrentItems(sidebar: SidebarEntry[], currentSlug: string) {
|
|
222
|
+
// IMPORTANT: we need to clone the sidebar to avoid mutating the original sidebar
|
|
223
|
+
const mutableSidebarInstance = structuredClone(sidebar);
|
|
224
|
+
|
|
225
|
+
function recursiveMarkCurrent(entries: SidebarEntry[]) {
|
|
226
|
+
for (const entry of entries) {
|
|
227
|
+
if (entry.type === 'link') {
|
|
228
|
+
entry.isCurrent = entry.href === currentSlug;
|
|
229
|
+
if (entry.isCurrent) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (entry.type === 'group') {
|
|
234
|
+
recursiveMarkCurrent(entry.entries);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
recursiveMarkCurrent(mutableSidebarInstance);
|
|
239
|
+
|
|
240
|
+
return mutableSidebarInstance;
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
export function startDevServer({
|
|
232
244
|
port,
|
|
233
245
|
version,
|
|
@@ -235,15 +247,19 @@ export function startDevServer({
|
|
|
235
247
|
apiKey,
|
|
236
248
|
getGeneratedSidebarConfig,
|
|
237
249
|
logger,
|
|
250
|
+
specCacheDirectory,
|
|
238
251
|
}: {
|
|
239
252
|
port: number;
|
|
240
253
|
version: VersionUserConfig;
|
|
241
254
|
devPaths: InputFilePaths;
|
|
242
255
|
apiKey: string;
|
|
256
|
+
specCacheDirectory: string | null;
|
|
243
257
|
getGeneratedSidebarConfig: (id: number) => GeneratedSidebarConfig | null;
|
|
244
258
|
logger: AstroIntegrationLogger;
|
|
245
259
|
}) {
|
|
246
|
-
const spec = new Spec(apiKey, version, devPaths, logger);
|
|
260
|
+
const spec = new Spec(apiKey, version, devPaths, logger, specCacheDirectory);
|
|
261
|
+
|
|
262
|
+
const sidebarCache = new Map<string, SidebarEntry[]>();
|
|
247
263
|
|
|
248
264
|
const server = createServer(async (req, res) => {
|
|
249
265
|
// Add CORS headers
|
|
@@ -266,7 +282,7 @@ export function startDevServer({
|
|
|
266
282
|
|
|
267
283
|
try {
|
|
268
284
|
if (req.method === 'POST' && req.url === '/retrieve_spec') {
|
|
269
|
-
const body = await readJsonBody(req);
|
|
285
|
+
const body = await readJsonBody<{ currentId: string }>(req);
|
|
270
286
|
|
|
271
287
|
const currentSpec = await spec.getSpec(body.currentId);
|
|
272
288
|
|
|
@@ -275,41 +291,49 @@ export function startDevServer({
|
|
|
275
291
|
|
|
276
292
|
if (req.method === 'POST' && req.url === '/build_sidebar') {
|
|
277
293
|
const currentSpec = await spec.forceGetSpec();
|
|
278
|
-
const body = await readJsonBody(req);
|
|
294
|
+
const body = await readJsonBody<{ sidebarId: number; basePath: string; currentSlug: string }>(req);
|
|
279
295
|
const sidebarId: number = body.sidebarId;
|
|
280
296
|
|
|
281
297
|
const sidebarConfig = getGeneratedSidebarConfig(sidebarId);
|
|
282
298
|
|
|
283
299
|
const sidebarGenerateOptions = sidebarConfig?.options;
|
|
284
300
|
|
|
285
|
-
const {
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
+
const { language } = parseRoute(body.basePath, body.currentSlug);
|
|
302
|
+
|
|
303
|
+
const sidebarCacheKey = `${body.basePath}-${language}-${sidebarId}`;
|
|
304
|
+
|
|
305
|
+
const cachedSidebar = sidebarCache.get(sidebarCacheKey);
|
|
306
|
+
let starlightSidebar: SidebarEntry[] | null = null;
|
|
307
|
+
if (cachedSidebar) {
|
|
308
|
+
starlightSidebar = cachedSidebar;
|
|
309
|
+
} else {
|
|
310
|
+
const configItemsBuilder = new SidebarConfigItemsBuilder(
|
|
311
|
+
currentSpec.data,
|
|
312
|
+
language,
|
|
313
|
+
sidebarGenerateOptions,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
let userSidebarConfig = configItemsBuilder.generateItems();
|
|
317
|
+
|
|
318
|
+
if (sidebarConfig && sidebarConfig.transformFn) {
|
|
319
|
+
const transformedSidebarConfig = sidebarConfig.transformFn(userSidebarConfig, language);
|
|
320
|
+
// the user may not have returned, but they may have mutated the config in place
|
|
321
|
+
// if they did return, we use that
|
|
322
|
+
if (transformedSidebarConfig) {
|
|
323
|
+
userSidebarConfig = transformedSidebarConfig;
|
|
324
|
+
}
|
|
301
325
|
}
|
|
326
|
+
starlightSidebar = toStarlightSidebar({
|
|
327
|
+
basePath: body.basePath,
|
|
328
|
+
spec: currentSpec.data,
|
|
329
|
+
entries: userSidebarConfig,
|
|
330
|
+
currentLanguage: language,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
sidebarCache.set(sidebarCacheKey, starlightSidebar);
|
|
302
334
|
}
|
|
303
|
-
const starlightSidebar = toStarlightSidebar({
|
|
304
|
-
basePath: body.basePath,
|
|
305
|
-
currentSlug: body.currentSlug,
|
|
306
|
-
spec: currentSpec.data,
|
|
307
|
-
entries: userSidebarConfig,
|
|
308
|
-
currentStainlessPath,
|
|
309
|
-
currentLanguage: language,
|
|
310
|
-
});
|
|
311
335
|
|
|
312
|
-
return respond(200, { data: starlightSidebar });
|
|
336
|
+
return respond(200, { data: markCurrentItems(starlightSidebar, body.currentSlug) });
|
|
313
337
|
}
|
|
314
338
|
|
|
315
339
|
return respond(404, { message: 'Not found' });
|
|
@@ -325,6 +349,7 @@ export function startDevServer({
|
|
|
325
349
|
return {
|
|
326
350
|
invalidate: () => {
|
|
327
351
|
spec.reload();
|
|
352
|
+
sidebarCache.clear();
|
|
328
353
|
},
|
|
329
354
|
destroy: function destroy() {
|
|
330
355
|
return new Promise<void>((resolve) => {
|
|
@@ -273,6 +273,7 @@ type SidebarEntry = StarlightRouteData['sidebar'][number];
|
|
|
273
273
|
|
|
274
274
|
// This allows us to be a bit more forgiving to the user.
|
|
275
275
|
// As long as they don't modify the key, we can still find the item.
|
|
276
|
+
|
|
276
277
|
function getResourceOrMethod(spec: SDKJSON.Spec, endpointOrConfigRef: string) {
|
|
277
278
|
for (const entry of walkTree(spec, false)) {
|
|
278
279
|
if (entry.data.kind === 'resource' && entry.data.configRef === endpointOrConfigRef) {
|
|
@@ -306,19 +307,17 @@ export type BuildSidebarParams = {
|
|
|
306
307
|
currentSlug: string;
|
|
307
308
|
};
|
|
308
309
|
|
|
309
|
-
type ToStarlightSidebarParams =
|
|
310
|
+
type ToStarlightSidebarParams = {
|
|
311
|
+
basePath: string;
|
|
310
312
|
spec: SDKJSON.Spec;
|
|
311
313
|
entries: ReferenceSidebarConfigItem[];
|
|
312
|
-
currentStainlessPath: string;
|
|
313
314
|
currentLanguage: DocsLanguage;
|
|
314
315
|
};
|
|
315
316
|
|
|
316
317
|
export function toStarlightSidebar({
|
|
317
318
|
basePath,
|
|
318
|
-
currentSlug,
|
|
319
319
|
spec,
|
|
320
320
|
entries,
|
|
321
|
-
currentStainlessPath,
|
|
322
321
|
currentLanguage,
|
|
323
322
|
}: ToStarlightSidebarParams): SidebarEntry[] {
|
|
324
323
|
const starlightEntries: SidebarEntry[] = [];
|
|
@@ -331,7 +330,7 @@ export function toStarlightSidebar({
|
|
|
331
330
|
type: 'link',
|
|
332
331
|
href: readmeSlug,
|
|
333
332
|
label: entry.label,
|
|
334
|
-
isCurrent:
|
|
333
|
+
isCurrent: false,
|
|
335
334
|
badge: entry.badge,
|
|
336
335
|
attrs: {
|
|
337
336
|
'data-stldocs-overview': 'readme',
|
|
@@ -350,14 +349,12 @@ export function toStarlightSidebar({
|
|
|
350
349
|
language: currentLanguage,
|
|
351
350
|
});
|
|
352
351
|
|
|
353
|
-
const isCurrent = resourceOrMethod.data.stainlessPath === currentStainlessPath;
|
|
354
|
-
|
|
355
352
|
if (resourceOrMethod.data.kind === 'http_method') {
|
|
356
353
|
starlightEntries.push({
|
|
357
354
|
type: 'link',
|
|
358
355
|
href: route,
|
|
359
356
|
label: entry.label,
|
|
360
|
-
isCurrent,
|
|
357
|
+
isCurrent: false,
|
|
361
358
|
badge: entry.badge,
|
|
362
359
|
attrs: {
|
|
363
360
|
'data-stldocs-method': resourceOrMethod.data.httpMethod,
|
|
@@ -368,7 +365,7 @@ export function toStarlightSidebar({
|
|
|
368
365
|
type: 'link',
|
|
369
366
|
href: route,
|
|
370
367
|
label: entry.label,
|
|
371
|
-
isCurrent,
|
|
368
|
+
isCurrent: false,
|
|
372
369
|
badge: entry.badge,
|
|
373
370
|
attrs: {
|
|
374
371
|
// TODO: @Ryan: is data.name unique? This is what we used before so I'm not changing, but I am curious.
|
|
@@ -379,14 +376,20 @@ export function toStarlightSidebar({
|
|
|
379
376
|
throw new Error(`Unknown entry kind ${JSON.stringify(entry)}`);
|
|
380
377
|
}
|
|
381
378
|
} else if (entry.kind === 'group') {
|
|
379
|
+
// Skip pushing the group if if the resource it represents is not available in the current language.
|
|
380
|
+
// This occurs when SDK generation for the current language is skipped in the Stainless config for that resource.
|
|
381
|
+
if (entry.resourceGroupKey) {
|
|
382
|
+
const resourceOrMethod = getResourceOrMethod(spec, entry.resourceGroupKey);
|
|
383
|
+
if (resourceOrMethod?.data?.kind === 'resource' && !resourceOrMethod?.data?.[currentLanguage]) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
382
387
|
starlightEntries.push({
|
|
383
388
|
type: 'group',
|
|
384
389
|
label: entry.label,
|
|
385
390
|
entries: toStarlightSidebar({
|
|
386
391
|
basePath,
|
|
387
|
-
currentSlug,
|
|
388
392
|
spec,
|
|
389
|
-
currentStainlessPath,
|
|
390
393
|
entries: entry.entries,
|
|
391
394
|
currentLanguage,
|
|
392
395
|
}),
|
package/plugin/cms/worker.ts
CHANGED
|
@@ -5,7 +5,6 @@ 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';
|
|
9
8
|
|
|
10
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
10
|
const __dirname = dirname(__filename);
|
|
@@ -161,12 +160,12 @@ export async function createSDKJSON({
|
|
|
161
160
|
oas,
|
|
162
161
|
config,
|
|
163
162
|
languages,
|
|
164
|
-
|
|
163
|
+
projectName,
|
|
165
164
|
}: {
|
|
166
165
|
oas: OpenAPIDocument;
|
|
167
166
|
config: ParsedConfig;
|
|
168
167
|
languages: DocsLanguage[];
|
|
169
|
-
|
|
168
|
+
projectName: string;
|
|
170
169
|
}) {
|
|
171
170
|
const templatePath = resolve(__dirname, '../vendor/templates');
|
|
172
171
|
const readmeLoader = await Promise.all(
|
|
@@ -191,7 +190,7 @@ export async function createSDKJSON({
|
|
|
191
190
|
config,
|
|
192
191
|
languages,
|
|
193
192
|
transform: false,
|
|
194
|
-
projectName
|
|
193
|
+
projectName,
|
|
195
194
|
readmeTemplates,
|
|
196
195
|
},
|
|
197
196
|
});
|
|
@@ -15,7 +15,7 @@ export function generateDocsRoutes(spec: SDKJSON.Spec, excludeLanguages: DocsLan
|
|
|
15
15
|
stainlessPath: null,
|
|
16
16
|
language: language as DocsLanguage,
|
|
17
17
|
title: 'Readme',
|
|
18
|
-
kind: 'readme',
|
|
18
|
+
kind: 'readme' as const,
|
|
19
19
|
}));
|
|
20
20
|
|
|
21
21
|
return [...paths, ...readmes].map(({ slug, stainlessPath, language, title, kind }) => {
|
package/plugin/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AstroIntegration, AstroIntegrationLogger } from 'astro';
|
|
|
4
4
|
import type { BundledTheme } from 'shiki';
|
|
5
5
|
import { config } from 'dotenv';
|
|
6
6
|
import getPort from 'get-port';
|
|
7
|
-
import { startDevServer, type SpecResp } from './cms/server';
|
|
7
|
+
import { DevSpecServer, startDevServer, type SpecResp } from './cms/server';
|
|
8
8
|
import { buildAlgoliaIndex } from './buildAlgoliaIndex';
|
|
9
9
|
import {
|
|
10
10
|
getAPIReferencePlaceholderItemFromSidebarConfig,
|
|
@@ -30,6 +30,7 @@ import { resolveSrcFile } from '../resolveSrcFile';
|
|
|
30
30
|
import { mkdir, writeFile } from 'fs/promises';
|
|
31
31
|
import { fileURLToPath } from 'url';
|
|
32
32
|
import prebundleWorkers from 'vite-plugin-prebundle-workers';
|
|
33
|
+
import { generateMissingRouteList } from '@stainless-api/docs-ui/routing';
|
|
33
34
|
|
|
34
35
|
export { generateAPILink } from './generateAPIReferenceLink';
|
|
35
36
|
export type { ReferenceSidebarConfigItem };
|
|
@@ -130,20 +131,7 @@ async function stlStarlightAstroIntegration(
|
|
|
130
131
|
|
|
131
132
|
const { apiKey, version, devPaths } = tmpGetCMSServerConfig(pluginConfig.specRetrieverConfig);
|
|
132
133
|
|
|
133
|
-
|
|
134
|
-
port: CMS_PORT,
|
|
135
|
-
apiKey: apiKey.value,
|
|
136
|
-
version,
|
|
137
|
-
devPaths,
|
|
138
|
-
logger: stlStarlightPluginLogger,
|
|
139
|
-
getGeneratedSidebarConfig: (id: number) => {
|
|
140
|
-
const config = sidebarConfigs.get(id);
|
|
141
|
-
if (!config) {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
return config;
|
|
145
|
-
},
|
|
146
|
-
});
|
|
134
|
+
let cmsServer: DevSpecServer | undefined;
|
|
147
135
|
|
|
148
136
|
let building: Promise<void> | undefined;
|
|
149
137
|
let reportError: ((message: string) => void) | null = null;
|
|
@@ -186,11 +174,36 @@ async function stlStarlightAstroIntegration(
|
|
|
186
174
|
logger: localLogger,
|
|
187
175
|
command,
|
|
188
176
|
config: astroConfig,
|
|
177
|
+
createCodegenDir,
|
|
189
178
|
}) => {
|
|
190
179
|
const logger = getSharedLogger({ fallback: localLogger });
|
|
191
180
|
const projectDir = astroConfig.root.pathname;
|
|
192
181
|
astroBase = astroConfig.base;
|
|
193
182
|
|
|
183
|
+
const codegenDir = createCodegenDir().pathname;
|
|
184
|
+
|
|
185
|
+
let specCacheDirectory: string | null = null;
|
|
186
|
+
if (command === 'dev') {
|
|
187
|
+
specCacheDirectory = path.join(codegenDir, 'spec_cache');
|
|
188
|
+
fs.mkdirSync(specCacheDirectory, { recursive: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cmsServer = startDevServer({
|
|
192
|
+
port: CMS_PORT,
|
|
193
|
+
apiKey: apiKey.value,
|
|
194
|
+
version,
|
|
195
|
+
devPaths,
|
|
196
|
+
logger: stlStarlightPluginLogger,
|
|
197
|
+
specCacheDirectory,
|
|
198
|
+
getGeneratedSidebarConfig: (id: number) => {
|
|
199
|
+
const config = sidebarConfigs.get(id);
|
|
200
|
+
if (!config) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
return config;
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
|
|
194
207
|
reportError = (message: string) => logger.error(message);
|
|
195
208
|
|
|
196
209
|
if (pluginConfig.experimentalPlaygrounds) {
|
|
@@ -248,7 +261,7 @@ async function stlStarlightAstroIntegration(
|
|
|
248
261
|
server.watcher.on('change', async (changed) => {
|
|
249
262
|
if (Object.values(devPaths).includes(changed)) {
|
|
250
263
|
logger.info(`${changed} changed, reloading...`);
|
|
251
|
-
cmsServer
|
|
264
|
+
cmsServer?.invalidate();
|
|
252
265
|
server.hot.send({
|
|
253
266
|
type: 'full-reload',
|
|
254
267
|
path: '*',
|
|
@@ -341,7 +354,7 @@ async function stlStarlightAstroIntegration(
|
|
|
341
354
|
});
|
|
342
355
|
},
|
|
343
356
|
'astro:server:done': async () => {
|
|
344
|
-
await cmsServer
|
|
357
|
+
await cmsServer?.destroy();
|
|
345
358
|
},
|
|
346
359
|
'astro:build:start'({ logger }) {
|
|
347
360
|
collectedErrors = [];
|
|
@@ -365,6 +378,31 @@ async function stlStarlightAstroIntegration(
|
|
|
365
378
|
astroBase,
|
|
366
379
|
};
|
|
367
380
|
await writeFile(path.join(stainlessDir, 'stl-manifest.json'), JSON.stringify(manifest, null, 2));
|
|
381
|
+
|
|
382
|
+
// Generate a list of missing API routes to enable graceful handling of unimplemented SDK methods.
|
|
383
|
+
// When users switch languages in the docs, some API methods may not be implemented in the target SDK.
|
|
384
|
+
// Instead of showing a generic 404, we statically generate pages for these routes and mark them
|
|
385
|
+
// in this file so Cloudflare can serve them with a 404 status. These pages display helpful information
|
|
386
|
+
// about the missing method and provide links to SDKs where it is available.
|
|
387
|
+
const { data: spec } = (await (
|
|
388
|
+
await fetch(`http://localhost:${CMS_PORT}/retrieve_spec`, {
|
|
389
|
+
method: 'POST',
|
|
390
|
+
body: JSON.stringify({}),
|
|
391
|
+
})
|
|
392
|
+
).json()) as SpecResp;
|
|
393
|
+
if (spec) {
|
|
394
|
+
const missingRoutes = await generateMissingRouteList({
|
|
395
|
+
spec,
|
|
396
|
+
basePath: path.posix.join(astroBase, pluginConfig.basePath),
|
|
397
|
+
});
|
|
398
|
+
await mkdir(stainlessDir, { recursive: true });
|
|
399
|
+
await writeFile(
|
|
400
|
+
path.join(stainlessDir, 'missing-routes.json'),
|
|
401
|
+
JSON.stringify(missingRoutes, null, 2),
|
|
402
|
+
);
|
|
403
|
+
} else {
|
|
404
|
+
console.log('Could not retrieve spec for missing routes check');
|
|
405
|
+
}
|
|
368
406
|
},
|
|
369
407
|
},
|
|
370
408
|
};
|
|
@@ -397,6 +435,7 @@ export function stainlessStarlight(someUserConfig: SomeStainlessStarlightUserCon
|
|
|
397
435
|
}
|
|
398
436
|
process.exit(1);
|
|
399
437
|
}
|
|
438
|
+
|
|
400
439
|
const config = configParseResult.config;
|
|
401
440
|
|
|
402
441
|
const isReactLoaded = astroConfig.integrations.find(({ name }) => name === '@astrojs/react');
|
package/plugin/routes/Docs.astro
CHANGED
|
@@ -3,8 +3,8 @@ import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
|
|
|
3
3
|
import { cmsClient } from '../cms/client';
|
|
4
4
|
import { getReadmeContent, buildPageNavigation, RenderSpec, astroMarkdownRender } from '../react/Routing';
|
|
5
5
|
import { getResourceFromSpec } from '@stainless-api/docs-ui/utils';
|
|
6
|
+
import { parseStainlessPath } from '@stainless-api/docs-ui/routing';
|
|
6
7
|
import {
|
|
7
|
-
BASE_PATH,
|
|
8
8
|
CONTENT_PANEL_LAYOUT,
|
|
9
9
|
MIDDLEWARE,
|
|
10
10
|
EXPERIMENTAL_REQUEST_BUILDER,
|
|
@@ -13,55 +13,58 @@ import { generateDocsRoutes } from '../helpers/generateDocsRoutes';
|
|
|
13
13
|
import { StainlessIslands } from '../components/StainlessIslands';
|
|
14
14
|
|
|
15
15
|
const spec = await cmsClient.getSpec();
|
|
16
|
-
const routes = generateDocsRoutes(spec);
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
export type Props = Awaited<ReturnType<typeof generateDocsRoutes>>[number]['props'] | Record<string, never>;
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const props =
|
|
20
|
+
'language' in Astro.props
|
|
21
|
+
? // In prod, we pass the props down to this page
|
|
22
|
+
Astro.props
|
|
23
|
+
: // In the dev server, we skip the DocsStatic wrapper so we need to find the props ourselves
|
|
24
|
+
generateDocsRoutes(spec).find((r) => r.params.slug === Astro.params.slug)?.props;
|
|
25
|
+
if (!props) throw new Error(`Could not find a route for slug '${Astro.params.slug}'`);
|
|
23
26
|
|
|
24
27
|
// PageTitle override will skip rendering the default Starlight title
|
|
25
28
|
Astro.locals._stlStarlightPage = {
|
|
26
|
-
skipRenderingStarlightTitle:
|
|
29
|
+
skipRenderingStarlightTitle: props.kind === 'readme' ? false : true,
|
|
27
30
|
};
|
|
28
31
|
|
|
29
|
-
const readmeContent = await getReadmeContent(spec,
|
|
30
|
-
const readme =
|
|
32
|
+
const readmeContent = await getReadmeContent(spec, props.language);
|
|
33
|
+
const readme = props.kind === 'readme' ? await astroMarkdownRender(readmeContent ?? '') : null;
|
|
31
34
|
|
|
32
|
-
const resource =
|
|
35
|
+
const resource = props.stainlessPath ? getResourceFromSpec(props.stainlessPath, spec) : null;
|
|
33
36
|
|
|
34
37
|
const pageNav =
|
|
35
|
-
|
|
38
|
+
props.kind === 'resource' && resource
|
|
36
39
|
? buildPageNavigation(resource)
|
|
37
|
-
:
|
|
40
|
+
: props.kind === 'readme' && readme?.metadata
|
|
38
41
|
? readme?.metadata.headings
|
|
39
42
|
: [];
|
|
40
43
|
|
|
41
|
-
const props = route.props;
|
|
42
44
|
Astro.locals.language = props.language;
|
|
43
45
|
|
|
46
|
+
// Use first heading in README as page title
|
|
44
47
|
if (readme) {
|
|
45
48
|
const repo = spec.metadata?.[props.language]?.code_url;
|
|
46
49
|
readme.code = readme.code.replace(/<a href="(?!(?:https?:\/\/|\/\/))([^"]+)"/g, `<a href="${repo}/$1"`);
|
|
47
50
|
props.title = readme.metadata.headings[0]!.text ?? 'Overview';
|
|
48
51
|
}
|
|
52
|
+
|
|
53
|
+
// use summary for the page title of method pages
|
|
54
|
+
if (props.kind === 'http_method' && props.stainlessPath) {
|
|
55
|
+
const parsed = parseStainlessPath(props.stainlessPath);
|
|
56
|
+
const resource = getResourceFromSpec(props.stainlessPath, spec);
|
|
57
|
+
if (parsed?.method && resource?.methods[parsed.method]) {
|
|
58
|
+
const method = resource.methods[parsed.method];
|
|
59
|
+
props.title = method?.summary ?? method?.title ?? props.title;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
49
62
|
---
|
|
50
63
|
|
|
51
64
|
<StarlightPage
|
|
52
65
|
headings={pageNav}
|
|
53
66
|
frontmatter={{
|
|
54
|
-
title: props
|
|
55
|
-
head: [
|
|
56
|
-
{
|
|
57
|
-
tag: 'link',
|
|
58
|
-
attrs: {
|
|
59
|
-
rel: 'alternate',
|
|
60
|
-
type: 'text/markdown',
|
|
61
|
-
href: `${BASE_PATH}/${Astro.params.slug}.md`,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
],
|
|
67
|
+
title: props.title,
|
|
65
68
|
pagefind: false,
|
|
66
69
|
tableOfContents: ['resource', 'readme'].includes(props.kind) ? { maxHeadingLevel: 6 } : false,
|
|
67
70
|
}}
|
|
@@ -9,6 +9,9 @@ export const getStaticPaths = (async () => {
|
|
|
9
9
|
const routes = generateDocsRoutes(spec);
|
|
10
10
|
return routes;
|
|
11
11
|
}) satisfies GetStaticPaths;
|
|
12
|
+
|
|
13
|
+
export type Props = Awaited<ReturnType<typeof getStaticPaths>>[number]['props'];
|
|
14
|
+
const props = Astro.props;
|
|
12
15
|
---
|
|
13
16
|
|
|
14
|
-
<Docs />
|
|
17
|
+
<Docs {...props} />
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Default from '@astrojs/starlight/components/Head.astro';
|
|
3
|
-
|
|
3
|
+
import path from 'path';
|
|
4
4
|
// TODO: for users who are overriding the font stack in their own styles, how can we know that and
|
|
5
5
|
// preload their font instead of ours?
|
|
6
6
|
import geistPath from '../../assets/fonts/geist/geist-latin.woff2';
|
|
7
|
+
|
|
8
|
+
const mdPath = path.posix.join(Astro.url.pathname, 'index.md');
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
<Default />
|
|
10
|
-
|
|
11
12
|
<link rel="preload" as="font" type="font/woff2" crossorigin="anonymous" href={geistPath} />
|
|
13
|
+
<link rel="alternate" type="text/markdown" href={mdPath} />
|
|
12
14
|
|
|
13
15
|
<script>
|
|
14
16
|
import { wireAIDropdown } from '../../plugin/globalJs/ai-dropdown-options.ts';
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
---
|
|
2
|
-
import Default from '@astrojs/starlight/components/Sidebar.astro';
|
|
3
2
|
import HeaderLinks from '../headers/HeaderLinks.astro';
|
|
3
|
+
import { SidebarWithComponents } from './SidebarWithComponents';
|
|
4
|
+
import SidebarPersister from '@astrojs/starlight/components/SidebarPersister.astro';
|
|
5
|
+
|
|
6
|
+
const { sidebar } = Astro.locals.starlightRoute;
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
<div class="stl-sidebar-header-links">
|
|
7
10
|
<HeaderLinks />
|
|
8
11
|
</div>
|
|
9
12
|
<slot name="sdk-select" />
|
|
10
|
-
<
|
|
13
|
+
<SidebarPersister>
|
|
14
|
+
<SidebarWithComponents entries={sidebar} withStarlightRestoration />
|
|
15
|
+
</SidebarPersister>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { StlSidebar, StlSidebarProps } from '@stainless-api/docs-ui/components';
|
|
2
|
+
import { ComponentProvider } from '@stainless-api/docs-ui/contexts/component';
|
|
3
|
+
|
|
4
|
+
export function SidebarWithComponents(props: StlSidebarProps) {
|
|
5
|
+
return (
|
|
6
|
+
<ComponentProvider>
|
|
7
|
+
<StlSidebar {...props} />
|
|
8
|
+
</ComponentProvider>
|
|
9
|
+
);
|
|
10
|
+
}
|
package/styles/page.css
CHANGED
|
@@ -35,7 +35,9 @@
|
|
|
35
35
|
/* on desktop, adjust sidebar so that its _text content_ aligns with the page left edge.
|
|
36
36
|
* padding (visible on hover) bleeds out beyond the page left edge, covered by --stl-ui-page-padding-inline */
|
|
37
37
|
.sidebar-pane {
|
|
38
|
-
|
|
38
|
+
/* 12px comes from --stl-sidebar-item-padding-inline (which is unfortunately defined deeper than
|
|
39
|
+
this component). Please keep in sync. */
|
|
40
|
+
margin-inline-start: calc(-1 * (var(--sl-sidebar-pad-x) + 12px));
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
.header,
|
package/styles/sidebar.css
CHANGED
|
@@ -11,220 +11,10 @@
|
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
.sidebar {
|
|
15
|
-
--stl-sidebar-item-padding-inline: 12px;
|
|
16
|
-
--stl-sidebar-item-padding-block: 6px;
|
|
17
|
-
--stl-sidebar-indent: 12px;
|
|
18
|
-
font-size: var(--stl-typography-scale-sm);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
14
|
.sidebar-pane {
|
|
22
15
|
border-inline-end: 1px solid var(--stl-color-border-faint);
|
|
23
16
|
}
|
|
24
17
|
|
|
25
|
-
.sidebar-content {
|
|
26
|
-
ul,
|
|
27
|
-
summary {
|
|
28
|
-
list-style-type: none;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/* collapsible sections */
|
|
32
|
-
details > summary {
|
|
33
|
-
display: flex;
|
|
34
|
-
align-items: center;
|
|
35
|
-
font-weight: 500;
|
|
36
|
-
|
|
37
|
-
.caret {
|
|
38
|
-
margin-left: auto;
|
|
39
|
-
margin-right: -4px;
|
|
40
|
-
opacity: 0.65;
|
|
41
|
-
transition:
|
|
42
|
-
opacity 0.1s ease-out,
|
|
43
|
-
transform 0.1s ease-out;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
details[open] > summary .caret {
|
|
47
|
-
opacity: 1;
|
|
48
|
-
transform: rotate(90deg);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/* list items */
|
|
52
|
-
summary,
|
|
53
|
-
li {
|
|
54
|
-
margin: 0;
|
|
55
|
-
border-radius: 8px;
|
|
56
|
-
}
|
|
57
|
-
summary,
|
|
58
|
-
li a {
|
|
59
|
-
cursor: pointer;
|
|
60
|
-
padding: var(--stl-sidebar-item-padding-block) var(--stl-sidebar-item-padding-inline);
|
|
61
|
-
&:hover,
|
|
62
|
-
&[aria-current='page'] {
|
|
63
|
-
color: var(--stl-color-foreground);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
li a {
|
|
68
|
-
color: var(--stl-color-foreground-reduced);
|
|
69
|
-
font-weight: 400;
|
|
70
|
-
display: flex;
|
|
71
|
-
text-decoration: none;
|
|
72
|
-
span {
|
|
73
|
-
font-weight: inherit;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
&:hover {
|
|
77
|
-
text-decoration: underline;
|
|
78
|
-
text-decoration-color: var(--stl-color-foreground-reduced);
|
|
79
|
-
}
|
|
80
|
-
&[aria-current='page'] {
|
|
81
|
-
font-weight: 500;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
li:has(> a:is(:hover, [aria-current='page'])),
|
|
85
|
-
summary:hover {
|
|
86
|
-
background-color: var(--stl-color-background-hover);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/* nested list items have flat left edge */
|
|
90
|
-
ul ul :is(summary, li) {
|
|
91
|
-
border-start-start-radius: 0;
|
|
92
|
-
border-end-start-radius: 0;
|
|
93
|
-
}
|
|
94
|
-
ul {
|
|
95
|
-
padding: 0;
|
|
96
|
-
}
|
|
97
|
-
ul ul li {
|
|
98
|
-
border-inline-start: 1px solid var(--stl-color-border-faint);
|
|
99
|
-
margin-inline-start: var(--stl-sidebar-indent);
|
|
100
|
-
&:has(> a[aria-current='page']) {
|
|
101
|
-
border-inline-start: 2px solid var(--stl-color-accent-border-strong);
|
|
102
|
-
& > a {
|
|
103
|
-
margin-left: -1px;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/* Method & resource icons */
|
|
109
|
-
|
|
110
|
-
a[data-stldocs-method],
|
|
111
|
-
a[data-stldocs-resource] {
|
|
112
|
-
--stl-ui-sidebar-icon-size-outer: 18px;
|
|
113
|
-
--stl-ui-sidebar-icon-margin: 8px;
|
|
114
|
-
}
|
|
115
|
-
a[data-stldocs-method] {
|
|
116
|
-
--stl-ui-sidebar-icon-size-inner: 16px;
|
|
117
|
-
--stl-ui-sidebar-icon-color-inverse-background: var(--stl-ui-sidebar-icon-color);
|
|
118
|
-
|
|
119
|
-
&[data-stldocs-method='get'] {
|
|
120
|
-
--stl-ui-sidebar-icon-color: var(--stl-color-green-foreground);
|
|
121
|
-
--stl-ui-sidebar-icon-color-border: var(--stl-color-green-border);
|
|
122
|
-
--stl-ui-sidebar-icon-color-background: var(--stl-color-green-muted-background);
|
|
123
|
-
--stl-ui-sidebar-icon-color-background-hover: var(--stl-color-green-muted-background-hover);
|
|
124
|
-
--stl-ui-sidebar-icon-color-inverse-background: var(--stl-color-green-inverse-background);
|
|
125
|
-
--stl-ui-sidebar-icon-color-inverse-foreground: var(--stl-color-green-inverse-foreground);
|
|
126
|
-
}
|
|
127
|
-
&[data-stldocs-method='post'] {
|
|
128
|
-
--stl-ui-sidebar-icon-color: var(--stl-color-blue-foreground);
|
|
129
|
-
--stl-ui-sidebar-icon-color-border: var(--stl-color-blue-border);
|
|
130
|
-
--stl-ui-sidebar-icon-color-background: var(--stl-color-blue-muted-background);
|
|
131
|
-
--stl-ui-sidebar-icon-color-background-hover: var(--stl-color-blue-muted-background-hover);
|
|
132
|
-
--stl-ui-sidebar-icon-color-inverse-background: var(--stl-color-blue-inverse-background);
|
|
133
|
-
--stl-ui-sidebar-icon-color-inverse-foreground: var(--stl-color-blue-inverse-foreground);
|
|
134
|
-
}
|
|
135
|
-
&[data-stldocs-method='patch'],
|
|
136
|
-
&[data-stldocs-method='put'] {
|
|
137
|
-
--stl-ui-sidebar-icon-color: var(--stl-color-orange-foreground);
|
|
138
|
-
--stl-ui-sidebar-icon-color-border: var(--stl-color-orange-border);
|
|
139
|
-
--stl-ui-sidebar-icon-color-background: var(--stl-color-orange-muted-background);
|
|
140
|
-
--stl-ui-sidebar-icon-color-background-hover: var(--stl-color-orange-muted-background-hover);
|
|
141
|
-
--stl-ui-sidebar-icon-color-inverse-background: var(--stl-color-orange-inverse-background);
|
|
142
|
-
--stl-ui-sidebar-icon-color-inverse-foreground: var(--stl-color-orange-inverse-foreground);
|
|
143
|
-
}
|
|
144
|
-
&[data-stldocs-method='delete'] {
|
|
145
|
-
--stl-ui-sidebar-icon-color: var(--stl-color-red-foreground);
|
|
146
|
-
--stl-ui-sidebar-icon-color-border: var(--stl-color-red-border);
|
|
147
|
-
--stl-ui-sidebar-icon-color-background: var(--stl-color-red-muted-background);
|
|
148
|
-
--stl-ui-sidebar-icon-color-background-hover: var(--stl-color-red-muted-background-hover);
|
|
149
|
-
--stl-ui-sidebar-icon-color-inverse-background: var(--stl-color-red-inverse-background);
|
|
150
|
-
--stl-ui-sidebar-icon-color-inverse-foreground: var(--stl-color-red-inverse-foreground);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
&[data-stldocs-method='get'] {
|
|
154
|
-
--stl-ui-sidebar-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 7 7 17"/><path d="M17 17H7V7"/></svg>');
|
|
155
|
-
}
|
|
156
|
-
&[data-stldocs-method='post'],
|
|
157
|
-
&[data-stldocs-method='put'],
|
|
158
|
-
&[data-stldocs-method='patch'] {
|
|
159
|
-
--stl-ui-sidebar-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 7h10v10"/><path d="M7 17 17 7"/></svg>');
|
|
160
|
-
}
|
|
161
|
-
&[data-stldocs-method='delete'] {
|
|
162
|
-
--stl-ui-sidebar-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>');
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
a[data-stldocs-resource] {
|
|
167
|
-
/* not yet implemented in markup */
|
|
168
|
-
--stl-ui-sidebar-icon-color: var(--stl-color-foreground-muted);
|
|
169
|
-
--stl-ui-sidebar-icon-color-border: var(--stl-color-border);
|
|
170
|
-
--stl-ui-sidebar-icon-color-background: var(--stl-color-faint-background);
|
|
171
|
-
--stl-ui-sidebar-icon-color-background-hover: var(--stl-color-faint-background-hover);
|
|
172
|
-
--stl-ui-sidebar-icon-color-inverse-background: var(--stl-color-inverse-background);
|
|
173
|
-
--stl-ui-sidebar-icon-color-inverse-foreground: var(--stl-color-inverse-foreground);
|
|
174
|
-
|
|
175
|
-
--stl-ui-sidebar-icon-size-inner: 14px;
|
|
176
|
-
--stl-ui-sidebar-icon-url: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 3H7a2 2 0 0 0-2 2v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5c0 1.1.9 2 2 2h1"/><path d="M16 21h1a2 2 0 0 0 2-2v-5c0-1.1.9-2 2-2a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2h-1"/></svg>');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
a[data-stldocs-method],
|
|
180
|
-
a[data-stldocs-resource] {
|
|
181
|
-
display: flex;
|
|
182
|
-
gap: var(--stl-ui-sidebar-icon-margin);
|
|
183
|
-
align-items: center;
|
|
184
|
-
position: relative;
|
|
185
|
-
|
|
186
|
-
&::before,
|
|
187
|
-
&::after {
|
|
188
|
-
content: '';
|
|
189
|
-
width: var(--stl-ui-sidebar-icon-size-outer);
|
|
190
|
-
height: var(--stl-ui-sidebar-icon-size-outer);
|
|
191
|
-
display: block;
|
|
192
|
-
}
|
|
193
|
-
&::before {
|
|
194
|
-
border-radius: 4px;
|
|
195
|
-
background-color: var(--stl-ui-sidebar-icon-color-background);
|
|
196
|
-
flex: 0 0 auto;
|
|
197
|
-
border: 1px solid var(--stl-ui-sidebar-icon-color-border, transparent);
|
|
198
|
-
}
|
|
199
|
-
/* yuck (we can clean this up once we are changing sidebar markup) */
|
|
200
|
-
&::after {
|
|
201
|
-
background-color: var(--stl-ui-sidebar-icon-color);
|
|
202
|
-
mask-image: var(--stl-ui-sidebar-icon-url);
|
|
203
|
-
mask-size: var(--stl-ui-sidebar-icon-size-inner) var(--stl-ui-sidebar-icon-size-inner);
|
|
204
|
-
mask-repeat: no-repeat;
|
|
205
|
-
mask-position: center;
|
|
206
|
-
position: absolute;
|
|
207
|
-
left: var(--stl-sidebar-item-padding-inline);
|
|
208
|
-
top: 50%;
|
|
209
|
-
transform: translateY(-50%);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
&:hover {
|
|
213
|
-
&::before {
|
|
214
|
-
background-color: var(--stl-ui-sidebar-icon-color-background-hover);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
&[aria-current='page'] {
|
|
218
|
-
&::before {
|
|
219
|
-
background-color: var(--stl-ui-sidebar-icon-color-inverse-background);
|
|
220
|
-
}
|
|
221
|
-
&::after {
|
|
222
|
-
background-color: var(--stl-ui-sidebar-icon-color-inverse-foreground);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
18
|
.sidebar-content .stl-mobile-only-sidebar-item-last {
|
|
229
19
|
margin-bottom: 2rem;
|
|
230
20
|
position: relative;
|
package/theme.css
CHANGED