blume 0.4.0 → 0.5.1
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/dist/cli/index.js +1170 -820
- package/dist/cli/index.js.map +32 -27
- package/dist/types/core/data.d.ts +2 -0
- package/dist/types/core/project.d.ts +12 -2
- package/dist/types/core/schema.d.ts +154 -41
- package/dist/types/core/types.d.ts +7 -6
- package/dist/types/migrate/mintlify/config.d.ts +14 -0
- package/docs/01-quickstart.mdx +6 -2
- package/docs/02-deployment.mdx +3 -1
- package/docs/advanced/api-reference.mdx +37 -23
- package/docs/advanced/bridge.mdx +76 -0
- package/docs/advanced/custom-pages.mdx +3 -1
- package/docs/advanced/meta.ts +8 -1
- package/docs/advanced/migrate.mdx +123 -0
- package/docs/configuration/ai.mdx +3 -1
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/export.mdx +6 -2
- package/docs/configuration/index.mdx +1 -1
- package/docs/configuration/seo.mdx +3 -1
- package/docs/content/components.mdx +55 -2
- package/docs/content/i18n.mdx +6 -2
- package/docs/content/islands.mdx +6 -2
- package/docs/content/meta.mdx +3 -1
- package/docs/content/syntax.mdx +40 -14
- package/docs/index.mdx +2 -2
- package/docs/reference/cli.mdx +29 -1
- package/docs/reference/frontmatter.mdx +5 -0
- package/package.json +11 -1
- package/src/astro/generate.ts +18 -9
- package/src/astro/templates.ts +28 -4
- package/src/cli/commands/build.ts +107 -63
- package/src/cli/commands/check.ts +20 -0
- package/src/cli/dev-lock.ts +13 -5
- package/src/cli/prepare.ts +3 -0
- package/src/components/BlumePage.astro +6 -0
- package/src/components/Icon.astro +13 -10
- package/src/components/content/ApiField.astro +75 -0
- package/src/components/content/ParamField.astro +39 -0
- package/src/components/content/RequestField.astro +23 -0
- package/src/components/content/ResponseField.astro +23 -0
- package/src/components/content/Step.astro +1 -1
- package/src/components/layout/Breadcrumbs.astro +7 -2
- package/src/components/layout/NavTree.astro +24 -8
- package/src/components/layout/RootLayout.astro +56 -34
- package/src/components/layout/Search.astro +1 -1
- package/src/components/openapi/ApiOverview.astro +84 -0
- package/src/components/openapi/MethodBadge.astro +28 -0
- package/src/components/openapi/Operation.astro +140 -0
- package/src/components/openapi/ParametersTable.astro +97 -0
- package/src/components/openapi/RequestBody.astro +58 -0
- package/src/components/openapi/RequestPanel.astro +169 -0
- package/src/components/openapi/Responses.astro +91 -0
- package/src/components/openapi/SchemaProperty.astro +118 -0
- package/src/components/openapi/SchemaTable.astro +86 -0
- package/src/components/openapi/helpers.ts +238 -0
- package/src/components/openapi/panel.ts +59 -0
- package/src/components/openapi/snippets.ts +201 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/data.ts +2 -0
- package/src/core/graph.ts +0 -3
- package/src/core/nav-diagnostics.ts +2 -12
- package/src/core/navigation.ts +0 -10
- package/src/core/project-graph.ts +5 -1
- package/src/core/project.ts +25 -3
- package/src/core/schema.ts +47 -14
- package/src/core/sources/mintlify.ts +1 -1
- package/src/core/sources/resolve.ts +28 -6
- package/src/core/types.ts +7 -7
- package/src/migrate/mintlify/config.ts +190 -97
- package/src/migrate/mintlify/content.ts +24 -2
- package/src/migrate/mintlify/index.ts +76 -2
- package/src/migrate/mintlify/transform.ts +2 -0
- package/src/openapi/model.ts +174 -0
- package/src/openapi/parse.ts +48 -0
- package/src/openapi/references.ts +164 -0
- package/src/openapi/render-mdx.ts +76 -0
- package/src/openapi/scalar.ts +15 -103
- package/src/openapi/source.ts +140 -0
- package/src/registry/eject.ts +15 -2
- package/src/theme/chrome-icons.ts +22 -0
- package/src/theme/icons.ts +151 -161
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Shared field row behind the Mintlify-compat <ParamField>/<ResponseField>/
|
|
3
|
+
// <RequestField> components: a labeled name + type + badges, with the field's
|
|
4
|
+
// description rendered from the slot (which may hold rich MDX, including nested
|
|
5
|
+
// <Expandable> or further fields). Styled to match the native OpenAPI reference
|
|
6
|
+
// rows (src/components/openapi/), so a migrated site reads consistently.
|
|
7
|
+
interface Props {
|
|
8
|
+
default?: unknown;
|
|
9
|
+
deprecated?: unknown;
|
|
10
|
+
location?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
required?: unknown;
|
|
13
|
+
type?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
default: defaultValue,
|
|
18
|
+
deprecated,
|
|
19
|
+
location,
|
|
20
|
+
name,
|
|
21
|
+
required,
|
|
22
|
+
type,
|
|
23
|
+
} = Astro.props;
|
|
24
|
+
|
|
25
|
+
// Booleans arrive as MDX shorthand (`required`) or strings (`required="true"`).
|
|
26
|
+
const isTrue = (value: unknown): boolean => value === true || value === "true";
|
|
27
|
+
const hasDefault =
|
|
28
|
+
defaultValue !== undefined && defaultValue !== null && defaultValue !== "";
|
|
29
|
+
const hasDescription = Astro.slots.has("default");
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
<div class="not-prose my-2 rounded-blume border border-border px-4 py-3">
|
|
33
|
+
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
34
|
+
{name && <code class="font-mono text-foreground text-sm">{name}</code>}
|
|
35
|
+
{
|
|
36
|
+
location && (
|
|
37
|
+
<span class="rounded bg-muted px-1.5 py-0.5 font-medium text-[0.625rem] text-muted-foreground uppercase tracking-wide">
|
|
38
|
+
{location}
|
|
39
|
+
</span>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
{type && <span class="text-muted-foreground text-xs">{type}</span>}
|
|
43
|
+
{
|
|
44
|
+
isTrue(required) && (
|
|
45
|
+
<span class="font-medium text-[0.625rem] text-red-600 uppercase tracking-wide dark:text-red-400">
|
|
46
|
+
required
|
|
47
|
+
</span>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
{
|
|
51
|
+
isTrue(deprecated) && (
|
|
52
|
+
<span class="font-medium text-[0.625rem] text-muted-foreground uppercase tracking-wide line-through">
|
|
53
|
+
deprecated
|
|
54
|
+
</span>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
</div>
|
|
58
|
+
{
|
|
59
|
+
hasDefault && (
|
|
60
|
+
<div class="mt-1 text-muted-foreground text-xs">
|
|
61
|
+
Default:{" "}
|
|
62
|
+
<code class="rounded bg-muted px-1 py-0.5 text-foreground">
|
|
63
|
+
{String(defaultValue)}
|
|
64
|
+
</code>
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
{
|
|
69
|
+
hasDescription && (
|
|
70
|
+
<div class="mt-1.5 text-muted-foreground text-sm [&>:first-child]:mt-0 [&>:last-child]:mb-0">
|
|
71
|
+
<slot />
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify-compat <ParamField>. Mintlify encodes the parameter's location in the
|
|
3
|
+
// attribute name (`<ParamField path="id" />`, `query`, `header`, `body`); the
|
|
4
|
+
// attribute's value is the field name. Falls back to a plain `name` prop.
|
|
5
|
+
import ApiField from "./ApiField.astro";
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
body,
|
|
9
|
+
default: defaultValue,
|
|
10
|
+
deprecated,
|
|
11
|
+
header,
|
|
12
|
+
name,
|
|
13
|
+
path,
|
|
14
|
+
query,
|
|
15
|
+
required,
|
|
16
|
+
type,
|
|
17
|
+
} = Astro.props;
|
|
18
|
+
|
|
19
|
+
const locations = [
|
|
20
|
+
{ key: "path", value: path },
|
|
21
|
+
{ key: "query", value: query },
|
|
22
|
+
{ key: "header", value: header },
|
|
23
|
+
{ key: "body", value: body },
|
|
24
|
+
] as const;
|
|
25
|
+
const located = locations.find((entry) => typeof entry.value === "string");
|
|
26
|
+
const fieldName = located ? located.value : name;
|
|
27
|
+
const location = located?.key;
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<ApiField
|
|
31
|
+
default={defaultValue}
|
|
32
|
+
deprecated={deprecated}
|
|
33
|
+
location={location}
|
|
34
|
+
name={fieldName}
|
|
35
|
+
required={required}
|
|
36
|
+
type={type}
|
|
37
|
+
>
|
|
38
|
+
<slot />
|
|
39
|
+
</ApiField>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify-compat <RequestField>: same shape as <ResponseField> — a named field
|
|
3
|
+
// (`name`, `type`, `required`, `default`, `deprecated`) with a slotted body.
|
|
4
|
+
import ApiField from "./ApiField.astro";
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
default: defaultValue,
|
|
8
|
+
deprecated,
|
|
9
|
+
name,
|
|
10
|
+
required,
|
|
11
|
+
type,
|
|
12
|
+
} = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<ApiField
|
|
16
|
+
default={defaultValue}
|
|
17
|
+
deprecated={deprecated}
|
|
18
|
+
name={name}
|
|
19
|
+
required={required}
|
|
20
|
+
type={type}
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</ApiField>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Mintlify-compat <ResponseField>: a named field (`name`, `type`, `required`,
|
|
3
|
+
// `default`, `deprecated`) with its description in the slot.
|
|
4
|
+
import ApiField from "./ApiField.astro";
|
|
5
|
+
|
|
6
|
+
const {
|
|
7
|
+
default: defaultValue,
|
|
8
|
+
deprecated,
|
|
9
|
+
name,
|
|
10
|
+
required,
|
|
11
|
+
type,
|
|
12
|
+
} = Astro.props;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<ApiField
|
|
16
|
+
default={defaultValue}
|
|
17
|
+
deprecated={deprecated}
|
|
18
|
+
name={name}
|
|
19
|
+
required={required}
|
|
20
|
+
type={type}
|
|
21
|
+
>
|
|
22
|
+
<slot />
|
|
23
|
+
</ApiField>
|
|
@@ -4,9 +4,11 @@ import type { Crumb } from "./nav-utils.ts";
|
|
|
4
4
|
interface Props {
|
|
5
5
|
/** Full breadcrumb trail from the site root to the current page. */
|
|
6
6
|
crumbs: Crumb[];
|
|
7
|
+
/** Left-align full width (for the wide API layout) instead of the prose measure. */
|
|
8
|
+
wide?: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
const { crumbs } = Astro.props;
|
|
11
|
+
const { crumbs, wide = false } = Astro.props;
|
|
10
12
|
|
|
11
13
|
// The built-in shows a single "eyebrow" crumb — the parent group — rather than
|
|
12
14
|
// the whole trail. An override receives the complete list and can render more.
|
|
@@ -17,7 +19,10 @@ const eyebrowCrumb = crumbs.length > 1 ? crumbs[crumbs.length - 2] : null;
|
|
|
17
19
|
eyebrowCrumb && (
|
|
18
20
|
<nav
|
|
19
21
|
aria-label="Breadcrumb"
|
|
20
|
-
class=
|
|
22
|
+
class:list={[
|
|
23
|
+
"mb-2 text-muted-foreground text-sm",
|
|
24
|
+
wide ? "max-w-none" : "mx-auto max-w-[42rem]",
|
|
25
|
+
]}
|
|
21
26
|
>
|
|
22
27
|
{eyebrowCrumb.route ? (
|
|
23
28
|
<a class="hover:text-foreground" href={eyebrowCrumb.route}>
|
|
@@ -31,8 +31,24 @@ const {
|
|
|
31
31
|
root = depth === 0,
|
|
32
32
|
} = Astro.props;
|
|
33
33
|
|
|
34
|
-
const
|
|
35
|
-
"shrink-0 rounded-full
|
|
34
|
+
const badgeBase =
|
|
35
|
+
"shrink-0 rounded-full px-1.5 py-0.5 font-medium text-[0.65rem] leading-none";
|
|
36
|
+
const badgeClass = `${badgeBase} bg-muted text-muted-foreground`;
|
|
37
|
+
// HTTP-method badges (from an OpenAPI reference's sidebar) are colour-coded;
|
|
38
|
+
// every other badge keeps the neutral style.
|
|
39
|
+
const METHOD_BADGE: Record<string, string> = {
|
|
40
|
+
DELETE: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
41
|
+
GET: "bg-green-500/15 text-green-700 dark:text-green-300",
|
|
42
|
+
HEAD: "bg-muted text-muted-foreground",
|
|
43
|
+
OPTIONS: "bg-muted text-muted-foreground",
|
|
44
|
+
PATCH: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
45
|
+
POST: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
|
|
46
|
+
PUT: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
|
|
47
|
+
};
|
|
48
|
+
const badgeClassFor = (badge: string): string => {
|
|
49
|
+
const method = METHOD_BADGE[badge.toUpperCase()];
|
|
50
|
+
return method ? `${badgeBase} ${method}` : badgeClass;
|
|
51
|
+
};
|
|
36
52
|
const deprecatedClass =
|
|
37
53
|
"shrink-0 rounded-full bg-amber-500/10 px-1.5 py-0.5 font-medium text-[0.65rem] text-amber-700 leading-none dark:text-amber-300";
|
|
38
54
|
|
|
@@ -154,7 +170,7 @@ const initialId =
|
|
|
154
170
|
/>
|
|
155
171
|
)}
|
|
156
172
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
157
|
-
{item.badge && <span class={
|
|
173
|
+
{item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
|
|
158
174
|
{item.deprecated && (
|
|
159
175
|
<span class={deprecatedClass}>deprecated</span>
|
|
160
176
|
)}
|
|
@@ -188,7 +204,7 @@ const initialId =
|
|
|
188
204
|
/>
|
|
189
205
|
)}
|
|
190
206
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
191
|
-
{item.badge && <span class={
|
|
207
|
+
{item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
|
|
192
208
|
<Icon
|
|
193
209
|
class="shrink-0 text-muted-foreground"
|
|
194
210
|
name="chevron-right"
|
|
@@ -224,7 +240,7 @@ const initialId =
|
|
|
224
240
|
)}
|
|
225
241
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
226
242
|
{item.badge && (
|
|
227
|
-
<span class={
|
|
243
|
+
<span class={badgeClassFor(item.badge)}>{item.badge}</span>
|
|
228
244
|
)}
|
|
229
245
|
</a>
|
|
230
246
|
) : (
|
|
@@ -238,7 +254,7 @@ const initialId =
|
|
|
238
254
|
)}
|
|
239
255
|
<span class="min-w-0 flex-1 truncate">{item.label}</span>
|
|
240
256
|
{item.badge && (
|
|
241
|
-
<span class={
|
|
257
|
+
<span class={badgeClassFor(item.badge)}>{item.badge}</span>
|
|
242
258
|
)}
|
|
243
259
|
</>
|
|
244
260
|
)}
|
|
@@ -279,7 +295,7 @@ const initialId =
|
|
|
279
295
|
)}
|
|
280
296
|
<span class="min-w-0 truncate">{item.label}</span>
|
|
281
297
|
{item.badge && (
|
|
282
|
-
<span class={`ml-auto ${
|
|
298
|
+
<span class={`ml-auto ${badgeClassFor(item.badge)}`}>{item.badge}</span>
|
|
283
299
|
)}
|
|
284
300
|
</a>
|
|
285
301
|
) : (
|
|
@@ -293,7 +309,7 @@ const initialId =
|
|
|
293
309
|
)}
|
|
294
310
|
<span class="min-w-0 truncate">{item.label}</span>
|
|
295
311
|
{item.badge && (
|
|
296
|
-
<span class={`ml-auto ${
|
|
312
|
+
<span class={`ml-auto ${badgeClassFor(item.badge)}`}>{item.badge}</span>
|
|
297
313
|
)}
|
|
298
314
|
</>
|
|
299
315
|
)}
|
|
@@ -200,9 +200,19 @@ const searchLocale =
|
|
|
200
200
|
localeSwitch && localeSwitch.length > 1 ? locale : undefined;
|
|
201
201
|
// TOC entries: the configured heading range, or none when the TOC is disabled
|
|
202
202
|
// (an empty list makes TableOfContents render nothing).
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
203
|
+
// API operation pages own a two-column body (docs + request panel) and their own
|
|
204
|
+
// right rail, so they drop the table of contents and widen the content column.
|
|
205
|
+
const isApiOperation = pageType === "openapi-operation";
|
|
206
|
+
const tocHeadings =
|
|
207
|
+
toc.enabled && !isApiOperation
|
|
208
|
+
? headings.filter((h) => h.depth >= toc.minLevel && h.depth <= toc.maxLevel)
|
|
209
|
+
: [];
|
|
210
|
+
const gridClass = isApiOperation
|
|
211
|
+
? "lg:grid-cols-[17.5rem_minmax(0,1fr)]"
|
|
212
|
+
: "lg:grid-cols-[17.5rem_minmax(0,1fr)] xl:grid-cols-[17.5rem_minmax(0,1fr)_17.5rem]";
|
|
213
|
+
const articleClass = isApiOperation
|
|
214
|
+
? "prose max-w-none"
|
|
215
|
+
: "prose mx-auto max-w-[42rem]";
|
|
206
216
|
const pageTitle = page.title ? `${page.title} - ${site.title}` : site.title;
|
|
207
217
|
const description = page.description ?? site.description;
|
|
208
218
|
|
|
@@ -331,7 +341,7 @@ const bannerScript = banner?.dismissible
|
|
|
331
341
|
>
|
|
332
342
|
<slot name="ask" slot="ask" />
|
|
333
343
|
</HeaderSlot>
|
|
334
|
-
<div class="mx-auto grid grid-cols-1 items-start
|
|
344
|
+
<div class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}>
|
|
335
345
|
<aside
|
|
336
346
|
aria-label="Primary"
|
|
337
347
|
class="fixed top-16 start-0 z-[35] h-[calc(100dvh-4rem)] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:z-auto lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
|
|
@@ -354,47 +364,59 @@ const bannerScript = banner?.dismissible
|
|
|
354
364
|
</nav>
|
|
355
365
|
</aside>
|
|
356
366
|
<main class="min-w-0 px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
|
|
357
|
-
<BreadcrumbsSlot crumbs={crumbs} />
|
|
367
|
+
<BreadcrumbsSlot crumbs={crumbs} wide={isApiOperation} />
|
|
358
368
|
<TableOfContentsSlot
|
|
359
369
|
headings={tocHeadings}
|
|
360
370
|
title={strings.toc.title}
|
|
361
371
|
variant="mobile"
|
|
362
372
|
/>
|
|
363
373
|
<PageHeaderSlot headings={tocHeadings} page={page} route={page.route} />
|
|
364
|
-
<article class=
|
|
374
|
+
<article class:list={[articleClass]} dir={contentDir}>
|
|
365
375
|
<slot />
|
|
366
376
|
</article>
|
|
367
|
-
<PageFooterSlot headings={tocHeadings} page={page} route={page.route} />
|
|
368
377
|
{
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
378
|
+
!isApiOperation && (
|
|
379
|
+
<>
|
|
380
|
+
<PageFooterSlot
|
|
381
|
+
headings={tocHeadings}
|
|
382
|
+
page={page}
|
|
383
|
+
route={page.route}
|
|
384
|
+
/>
|
|
385
|
+
{formattedLastModified && (
|
|
386
|
+
<p class="mx-auto mt-10 max-w-[42rem] text-muted-foreground text-sm">
|
|
387
|
+
{strings.page.lastUpdated} {formattedLastModified}
|
|
388
|
+
</p>
|
|
389
|
+
)}
|
|
390
|
+
{feedback && <FeedbackSlot strings={strings.feedback} />}
|
|
391
|
+
<PaginationSlot next={next} prev={prev} strings={strings.page} />
|
|
392
|
+
</>
|
|
373
393
|
)
|
|
374
394
|
}
|
|
375
|
-
{feedback && <FeedbackSlot strings={strings.feedback} />}
|
|
376
|
-
<PaginationSlot next={next} prev={prev} strings={strings.page} />
|
|
377
395
|
</main>
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
396
|
+
{
|
|
397
|
+
!isApiOperation && (
|
|
398
|
+
<aside
|
|
399
|
+
aria-label={strings.toc.title}
|
|
400
|
+
class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
|
|
401
|
+
>
|
|
402
|
+
<TableOfContentsSlot
|
|
403
|
+
headings={tocHeadings}
|
|
404
|
+
title={strings.toc.title}
|
|
405
|
+
variant="desktop"
|
|
406
|
+
/>
|
|
407
|
+
<PageActions
|
|
408
|
+
askEnabled={askEnabled}
|
|
409
|
+
editUrl={editUrl}
|
|
410
|
+
exportEpub={exportEpub}
|
|
411
|
+
exportPdf={exportPdf}
|
|
412
|
+
mcpName={mcp?.name}
|
|
413
|
+
mcpUrl={mcpUrl}
|
|
414
|
+
route={page.route}
|
|
415
|
+
strings={strings.actions}
|
|
416
|
+
/>
|
|
417
|
+
</aside>
|
|
418
|
+
)
|
|
419
|
+
}
|
|
398
420
|
</div>
|
|
399
421
|
<FooterSlot navigation={navigation} site={site} ui={strings} />
|
|
400
422
|
{
|
|
@@ -414,7 +436,7 @@ const bannerScript = banner?.dismissible
|
|
|
414
436
|
type="button"
|
|
415
437
|
></button>
|
|
416
438
|
<script>
|
|
417
|
-
import { icons } from "../../theme/icons.ts";
|
|
439
|
+
import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
|
|
418
440
|
// Registers the <blume-mermaid> custom element (emitted by ```mermaid
|
|
419
441
|
// fences). Mermaid itself is lazy-loaded only on pages that use a diagram.
|
|
420
442
|
import "../content/mermaid-element.ts";
|
|
@@ -138,7 +138,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
138
138
|
/>
|
|
139
139
|
|
|
140
140
|
<script>
|
|
141
|
-
import { icons } from "../../theme/icons.ts";
|
|
141
|
+
import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
|
|
142
142
|
import {
|
|
143
143
|
escapeHtml,
|
|
144
144
|
highlight,
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
import specs from "blume:openapi";
|
|
3
|
+
import MethodBadge from "./MethodBadge.astro";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
source: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { source } = Astro.props;
|
|
10
|
+
const spec = specs[source];
|
|
11
|
+
const operations = Object.values(spec?.operations ?? {});
|
|
12
|
+
const servers =
|
|
13
|
+
((spec?.document ?? {}) as { servers?: { url?: string }[] }).servers ?? [];
|
|
14
|
+
|
|
15
|
+
// Tags in declared order, then any operation tag not listed under `tags`.
|
|
16
|
+
const declaredSlugs = new Set(spec?.tags.map((tag) => tag.slug));
|
|
17
|
+
const extraSlugs = new Set<string>();
|
|
18
|
+
const extraTags: { slug: string; name: string; description: string }[] = [];
|
|
19
|
+
for (const operation of operations) {
|
|
20
|
+
if (!(declaredSlugs.has(operation.tagSlug) || extraSlugs.has(operation.tagSlug))) {
|
|
21
|
+
extraSlugs.add(operation.tagSlug);
|
|
22
|
+
extraTags.push({ description: "", name: operation.tag, slug: operation.tagSlug });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const sections = [...(spec?.tags ?? []), ...extraTags];
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
spec && (
|
|
30
|
+
<div>
|
|
31
|
+
{spec.version && (
|
|
32
|
+
<div class="not-prose mb-4 text-muted-foreground text-sm">
|
|
33
|
+
Version {spec.version}
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
{servers.length > 0 && (
|
|
37
|
+
<div class="not-prose mb-8 flex flex-wrap items-center gap-2">
|
|
38
|
+
<span class="text-muted-foreground text-xs">Base URL</span>
|
|
39
|
+
{servers.map((server) => (
|
|
40
|
+
<code class="rounded bg-muted px-2 py-0.5 text-foreground text-xs">
|
|
41
|
+
{server.url}
|
|
42
|
+
</code>
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
46
|
+
{sections.map((tag) => {
|
|
47
|
+
const ops = operations.filter(
|
|
48
|
+
(operation) => operation.tagSlug === tag.slug
|
|
49
|
+
);
|
|
50
|
+
if (ops.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return (
|
|
54
|
+
<section class="mb-8">
|
|
55
|
+
<h2 class="mb-1 font-semibold text-foreground text-xl">
|
|
56
|
+
{tag.name}
|
|
57
|
+
</h2>
|
|
58
|
+
{tag.description && (
|
|
59
|
+
<div class="mb-3 text-muted-foreground text-sm" set:text={tag.description} />
|
|
60
|
+
)}
|
|
61
|
+
<ul class="not-prose flex list-none flex-col gap-2 p-0">
|
|
62
|
+
{ops.map((operation) => (
|
|
63
|
+
<li>
|
|
64
|
+
<a
|
|
65
|
+
class="flex items-center gap-3 rounded-blume border border-border p-3 text-inherit no-underline! transition-colors hover:border-accent hover:bg-muted hover:no-underline!"
|
|
66
|
+
href={operation.route}
|
|
67
|
+
>
|
|
68
|
+
<MethodBadge method={operation.method} />
|
|
69
|
+
<span class="font-medium text-foreground text-sm">
|
|
70
|
+
{operation.summary || operation.path}
|
|
71
|
+
</span>
|
|
72
|
+
<code class="ml-auto hidden text-muted-foreground text-xs sm:inline">
|
|
73
|
+
{operation.path}
|
|
74
|
+
</code>
|
|
75
|
+
</a>
|
|
76
|
+
</li>
|
|
77
|
+
))}
|
|
78
|
+
</ul>
|
|
79
|
+
</section>
|
|
80
|
+
);
|
|
81
|
+
})}
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
method: string;
|
|
4
|
+
class?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const { method, class: className } = Astro.props;
|
|
8
|
+
const upper = method.toUpperCase();
|
|
9
|
+
|
|
10
|
+
const COLORS: Record<string, string> = {
|
|
11
|
+
DELETE: "bg-red-500/15 text-red-700 dark:text-red-300",
|
|
12
|
+
GET: "bg-green-500/15 text-green-700 dark:text-green-300",
|
|
13
|
+
HEAD: "bg-muted text-muted-foreground",
|
|
14
|
+
OPTIONS: "bg-muted text-muted-foreground",
|
|
15
|
+
PATCH: "bg-yellow-500/20 text-yellow-800 dark:text-yellow-300",
|
|
16
|
+
POST: "bg-blue-500/15 text-blue-700 dark:text-blue-300",
|
|
17
|
+
PUT: "bg-orange-500/15 text-orange-700 dark:text-orange-300",
|
|
18
|
+
};
|
|
19
|
+
const color = COLORS[upper] ?? "bg-muted text-muted-foreground";
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
<span
|
|
23
|
+
class:list={[
|
|
24
|
+
"not-prose inline-flex items-center rounded-md px-2 py-0.5 font-mono font-semibold text-xs uppercase tracking-wide",
|
|
25
|
+
color,
|
|
26
|
+
className,
|
|
27
|
+
]}>{upper}</span
|
|
28
|
+
>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
---
|
|
2
|
+
import specs from "blume:openapi";
|
|
3
|
+
import type { SchemaLike } from "./helpers.ts";
|
|
4
|
+
import { buildRequestSample, sampleLanguages } from "./snippets.ts";
|
|
5
|
+
import MethodBadge from "./MethodBadge.astro";
|
|
6
|
+
import ParametersTable from "./ParametersTable.astro";
|
|
7
|
+
import RequestBody from "./RequestBody.astro";
|
|
8
|
+
import RequestPanel from "./RequestPanel.astro";
|
|
9
|
+
import Responses from "./Responses.astro";
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
source: string;
|
|
13
|
+
id: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ParamLike {
|
|
17
|
+
$ref?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
in?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
deprecated?: boolean;
|
|
23
|
+
schema?: SchemaLike;
|
|
24
|
+
example?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface MediaTypeLike {
|
|
28
|
+
schema?: SchemaLike;
|
|
29
|
+
example?: unknown;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FullOperation {
|
|
33
|
+
summary?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
deprecated?: boolean;
|
|
36
|
+
parameters?: ParamLike[];
|
|
37
|
+
requestBody?: {
|
|
38
|
+
description?: string;
|
|
39
|
+
required?: boolean;
|
|
40
|
+
content?: Record<string, MediaTypeLike>;
|
|
41
|
+
};
|
|
42
|
+
responses?: Record<
|
|
43
|
+
string,
|
|
44
|
+
{ description?: string; content?: Record<string, MediaTypeLike> }
|
|
45
|
+
>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { source, id } = Astro.props;
|
|
49
|
+
const spec = specs[source];
|
|
50
|
+
const ref = spec?.operations[id];
|
|
51
|
+
|
|
52
|
+
const doc = (spec?.document ?? {}) as {
|
|
53
|
+
paths?: Record<string, Record<string, unknown> & { parameters?: ParamLike[] }>;
|
|
54
|
+
components?: {
|
|
55
|
+
schemas?: Record<string, SchemaLike>;
|
|
56
|
+
parameters?: Record<string, ParamLike>;
|
|
57
|
+
};
|
|
58
|
+
servers?: { url?: string }[];
|
|
59
|
+
};
|
|
60
|
+
const pathItem = ref ? doc.paths?.[ref.path] : undefined;
|
|
61
|
+
const operation = (
|
|
62
|
+
pathItem && ref ? pathItem[ref.method] : undefined
|
|
63
|
+
) as FullOperation | undefined;
|
|
64
|
+
|
|
65
|
+
const schemas = doc.components?.schemas ?? {};
|
|
66
|
+
const componentParams = doc.components?.parameters ?? {};
|
|
67
|
+
const PARAM_REF = /#\/components\/parameters\/(?<name>[^/]+)$/u;
|
|
68
|
+
const resolveParam = (param: ParamLike): ParamLike => {
|
|
69
|
+
if (typeof param.$ref === "string") {
|
|
70
|
+
const name = PARAM_REF.exec(param.$ref)?.groups?.name;
|
|
71
|
+
return (name && componentParams[name]) || param;
|
|
72
|
+
}
|
|
73
|
+
return param;
|
|
74
|
+
};
|
|
75
|
+
const params = [
|
|
76
|
+
...(pathItem?.parameters ?? []),
|
|
77
|
+
...(operation?.parameters ?? []),
|
|
78
|
+
].map(resolveParam);
|
|
79
|
+
|
|
80
|
+
const sample =
|
|
81
|
+
ref && operation
|
|
82
|
+
? buildRequestSample(
|
|
83
|
+
{ parameters: params, requestBody: operation.requestBody },
|
|
84
|
+
ref.method,
|
|
85
|
+
ref.path,
|
|
86
|
+
doc.servers ?? [],
|
|
87
|
+
schemas
|
|
88
|
+
)
|
|
89
|
+
: null;
|
|
90
|
+
const languages = sampleLanguages(spec?.codeSamples ?? []);
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
{
|
|
94
|
+
!(spec && ref && operation) ? (
|
|
95
|
+
<div class="text-muted-foreground">This API operation could not be found.</div>
|
|
96
|
+
) : (
|
|
97
|
+
<div class="not-prose">
|
|
98
|
+
<div class="mb-6 flex flex-wrap items-center gap-3">
|
|
99
|
+
<MethodBadge method={ref.method} />
|
|
100
|
+
<code class="break-all font-mono text-foreground text-sm">
|
|
101
|
+
{ref.path}
|
|
102
|
+
</code>
|
|
103
|
+
{operation.deprecated && (
|
|
104
|
+
<span class="font-medium text-[0.625rem] text-orange-600 uppercase tracking-wide dark:text-orange-400">
|
|
105
|
+
deprecated
|
|
106
|
+
</span>
|
|
107
|
+
)}
|
|
108
|
+
</div>
|
|
109
|
+
<div class="grid grid-cols-1 items-start gap-x-10 gap-y-8 xl:grid-cols-[minmax(0,1fr)_minmax(0,28rem)]">
|
|
110
|
+
<div>
|
|
111
|
+
<ParametersTable parameters={params} schemas={schemas} />
|
|
112
|
+
{operation.requestBody && (
|
|
113
|
+
<RequestBody
|
|
114
|
+
expandAll={spec.expandSchemas}
|
|
115
|
+
requestBody={operation.requestBody}
|
|
116
|
+
schemas={schemas}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
{operation.responses && (
|
|
120
|
+
<Responses
|
|
121
|
+
expandAll={spec.expandSchemas}
|
|
122
|
+
responses={operation.responses}
|
|
123
|
+
schemas={schemas}
|
|
124
|
+
/>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
{sample && (
|
|
128
|
+
<div class="xl:sticky xl:top-24 xl:self-start">
|
|
129
|
+
<RequestPanel
|
|
130
|
+
languages={languages}
|
|
131
|
+
responses={operation.responses ?? {}}
|
|
132
|
+
sample={sample}
|
|
133
|
+
schemas={schemas}
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
)
|
|
140
|
+
}
|