blume 1.6.4 → 1.6.5
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 +19 -0
- package/dist/cli/index.js +15 -5
- package/dist/cli/index.js.map +5 -5
- package/dist/types/core/config-input.d.ts +10 -0
- package/dist/types/core/schema.d.ts +4 -1
- package/docs/configuration/analytics.mdx +21 -2
- package/docs/content/syntax.mdx +1 -1
- package/package.json +13 -13
- package/src/astro/generate.ts +5 -6
- package/src/components/content/mermaid-element.ts +8 -0
- package/src/components/layout/Analytics.astro +20 -1
- package/src/components/layout/ReferenceLayout.astro +1 -0
- package/src/components/layout/RootLayout.astro +1 -0
- package/src/components/layout/analytics-client.ts +2 -1
- package/src/components/openapi/AsyncApiOperation.astro +5 -3
- package/src/components/openapi/Authorization.astro +4 -6
- package/src/components/openapi/Bindings.astro +2 -2
- package/src/components/openapi/Description.astro +109 -0
- package/src/components/openapi/GraphqlFieldsTable.astro +5 -7
- package/src/components/openapi/GraphqlOperation.astro +4 -3
- package/src/components/openapi/GraphqlType.astro +4 -6
- package/src/components/openapi/ParametersTable.astro +5 -7
- package/src/components/openapi/RequestBody.astro +2 -4
- package/src/components/openapi/Responses.astro +4 -3
- package/src/components/openapi/SchemaProperty.astro +11 -6
- package/src/components/openapi/description.ts +91 -0
- package/src/core/config-input.ts +10 -0
- package/src/core/schema.ts +8 -0
- package/src/theme/entry.ts +9 -2
|
@@ -789,6 +789,16 @@ export interface AnalyticsScript {
|
|
|
789
789
|
}
|
|
790
790
|
/** Analytics providers. Configure one, several, or none. */
|
|
791
791
|
export interface AnalyticsConfig {
|
|
792
|
+
/**
|
|
793
|
+
* Cloudflare Web Analytics, for a site Cloudflare doesn't proxy (manual
|
|
794
|
+
* setup). Not needed on a proxied zone with automatic RUM enabled — that
|
|
795
|
+
* injects the beacon at the edge, and configuring it here too would count
|
|
796
|
+
* every pageview twice.
|
|
797
|
+
*/
|
|
798
|
+
cloudflare?: {
|
|
799
|
+
/** Site token from the Web Analytics JS snippet (`data-cf-beacon`). */
|
|
800
|
+
token: string;
|
|
801
|
+
};
|
|
792
802
|
/** PostHog product analytics. */
|
|
793
803
|
posthog?: {
|
|
794
804
|
/** API host (for self-hosted / EU). Defaults to PostHog cloud. */
|
|
@@ -561,6 +561,9 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
561
561
|
webmcp: z.ZodDefault<z.ZodBoolean>;
|
|
562
562
|
}, z.core.$strict>>;
|
|
563
563
|
analytics: z.ZodOptional<z.ZodObject<{
|
|
564
|
+
cloudflare: z.ZodOptional<z.ZodObject<{
|
|
565
|
+
token: z.ZodString;
|
|
566
|
+
}, z.core.$strict>>;
|
|
564
567
|
posthog: z.ZodOptional<z.ZodObject<{
|
|
565
568
|
host: z.ZodOptional<z.ZodString>;
|
|
566
569
|
key: z.ZodString;
|
|
@@ -733,10 +736,10 @@ export declare const blumeConfigSchema: z.ZodObject<{
|
|
|
733
736
|
}, z.core.$strict>>;
|
|
734
737
|
deployment: z.ZodPrefault<z.ZodObject<{
|
|
735
738
|
adapter: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
|
|
739
|
+
cloudflare: "cloudflare";
|
|
736
740
|
vercel: "vercel";
|
|
737
741
|
node: "node";
|
|
738
742
|
netlify: "netlify";
|
|
739
|
-
cloudflare: "cloudflare";
|
|
740
743
|
}>>>;
|
|
741
744
|
base: z.ZodOptional<z.ZodString>;
|
|
742
745
|
output: z.ZodDefault<z.ZodEnum<{
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Analytics
|
|
3
|
-
description: First-party web analytics — Vercel Web Analytics, PostHog, or any custom script — wired up from blume.config.ts.
|
|
3
|
+
description: First-party web analytics — Vercel Web Analytics, Cloudflare Web Analytics, PostHog, or any custom script — wired up from blume.config.ts.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Blume injects analytics for you from a single `analytics` block in `blume.config.ts`. Vercel Web Analytics and PostHog are first-class, and a `scripts` escape hatch covers every other provider — Plausible, Fathom, Google Analytics, Umami, and the rest.
|
|
6
|
+
Blume injects analytics for you from a single `analytics` block in `blume.config.ts`. Vercel Web Analytics, Cloudflare Web Analytics, and PostHog are first-class, and a `scripts` escape hatch covers every other provider — Plausible, Fathom, Google Analytics, Umami, and the rest.
|
|
7
7
|
|
|
8
8
|
Analytics loads in **production builds only**. The scripts are emitted by `blume build`, never by `blume dev`, so local traffic never reaches your dashboards and you don't need a separate "development" project.
|
|
9
9
|
|
|
@@ -23,6 +23,24 @@ analytics: {
|
|
|
23
23
|
|
|
24
24
|
No keys are needed — the script reports to the project it's deployed under. This only collects data on Vercel deployments, where the `/_vercel/insights` endpoint exists.
|
|
25
25
|
|
|
26
|
+
## Cloudflare Web Analytics
|
|
27
|
+
|
|
28
|
+
[Cloudflare Web Analytics](https://developers.cloudflare.com/web-analytics/) has two setups, and only one of them needs config.
|
|
29
|
+
|
|
30
|
+
**Proxied zone (automatic setup).** If Cloudflare serves your site — a Worker with a custom domain, Pages, or any zone with the orange cloud on — enable Web Analytics for the zone in the Cloudflare dashboard and stop there. Cloudflare injects the beacon at the edge, so leave `analytics.cloudflare` unset; configuring it as well would count every pageview twice.
|
|
31
|
+
|
|
32
|
+
**Any other host (manual setup).** For a site Cloudflare doesn't proxy, add the site under Web Analytics in the dashboard, copy the token out of the JS snippet it gives you (the `token` inside `data-cf-beacon`), and pass it here. Blume renders the same beacon tag the snippet does.
|
|
33
|
+
|
|
34
|
+
```ts blume.config.ts lineNumbers
|
|
35
|
+
analytics: {
|
|
36
|
+
cloudflare: {
|
|
37
|
+
token: "0123456789abcdef0123456789abcdef",
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The token is safe to ship to the browser — it only identifies the site. The beacon tracks history changes on its own, so client-router navigations count without any extra wiring.
|
|
43
|
+
|
|
26
44
|
## PostHog
|
|
27
45
|
|
|
28
46
|
Provide your **project API key** to add [PostHog](https://posthog.com). The host defaults to PostHog Cloud US; set `host` for EU Cloud (`https://eu.i.posthog.com`) or a self-hosted instance.
|
|
@@ -72,6 +90,7 @@ analytics: {
|
|
|
72
90
|
| Option | Default | Description |
|
|
73
91
|
| --- | --- | --- |
|
|
74
92
|
| `vercel` | `false` | Add Vercel Web Analytics (Vercel deployments only). |
|
|
93
|
+
| `cloudflare.token` | — | Cloudflare Web Analytics site token (manual setup). Enables the beacon when set. |
|
|
75
94
|
| `posthog.key` | — | PostHog project API key. Enables PostHog when set. |
|
|
76
95
|
| `posthog.host` | `https://us.i.posthog.com` | PostHog ingestion host (EU Cloud or self-hosted). |
|
|
77
96
|
| `scripts[].src` | — | External script URL. Mutually exclusive with `content`. |
|
package/docs/content/syntax.mdx
CHANGED
|
@@ -384,7 +384,7 @@ flowchart LR
|
|
|
384
384
|
```
|
|
385
385
|
````
|
|
386
386
|
|
|
387
|
-
Diagrams render on the client, so this is an MDX-only feature, and the Mermaid library loads only on pages that include one. The rest of this section is a gallery of common types — see the [Mermaid docs](https://mermaid.js.org/intro/) for the full list.
|
|
387
|
+
Diagrams render on the client, so this is an MDX-only feature, and the Mermaid library loads only on pages that include one. Diagrams use Mermaid's dagre layout and classic look by default; opt a single diagram into another layout or look through Mermaid front matter (a `config:` block with `layout: elk` or `look: neo`), and the ELK engine loads only for diagrams that ask for it. The rest of this section is a gallery of common types — see the [Mermaid docs](https://mermaid.js.org/intro/) for the full list.
|
|
388
388
|
|
|
389
389
|
### Flowchart
|
|
390
390
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blume",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.5",
|
|
4
4
|
"description": "Documentation that's fast, AI-ready, and zero-config.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -74,12 +74,12 @@
|
|
|
74
74
|
"@astrojs/vercel": "^11.0.10",
|
|
75
75
|
"@asyncapi/converter": "^2.0.2",
|
|
76
76
|
"@clack/prompts": "^1.8.0",
|
|
77
|
-
"@iconify-json/lucide": "^1.2.
|
|
77
|
+
"@iconify-json/lucide": "^1.2.131",
|
|
78
78
|
"@iconify/types": "^2.0.0",
|
|
79
79
|
"@iconify/utils": "^3.1.7",
|
|
80
80
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
81
81
|
"@orama/orama": "^3.1.18",
|
|
82
|
-
"@pierre/diffs": "^1.4.
|
|
82
|
+
"@pierre/diffs": "^1.4.2",
|
|
83
83
|
"@scalar/astro": "^0.4.18",
|
|
84
84
|
"@scalar/openapi-parser": "^0.29.1",
|
|
85
85
|
"@scalar/openapi-types": "^0.9.5",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@tailwindcss/vite": "^4.3.3",
|
|
90
90
|
"@types/mdast": "^4.0.4",
|
|
91
91
|
"@vercel/analytics": "^2.0.1",
|
|
92
|
-
"ai": "^7.0.
|
|
92
|
+
"ai": "^7.0.99",
|
|
93
93
|
"astro": "^7.3.2",
|
|
94
94
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
95
95
|
"chokidar": "^5.0.0",
|
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
"mdast-util-gfm": "^3.1.0",
|
|
115
115
|
"mdast-util-to-string": "^4.0.0",
|
|
116
116
|
"medium-zoom": "^1.1.0",
|
|
117
|
-
"mermaid": "^
|
|
117
|
+
"mermaid": "^12.0.0",
|
|
118
118
|
"micromark-extension-gfm": "^3.0.0",
|
|
119
119
|
"nanotar": "^0.3.0",
|
|
120
120
|
"node-html-parser": "^9.0.4",
|
|
@@ -127,8 +127,8 @@
|
|
|
127
127
|
"pathe": "^2.0.3",
|
|
128
128
|
"perfect-debounce": "^2.1.0",
|
|
129
129
|
"picomatch": "^4.0.7",
|
|
130
|
-
"react": "^19.
|
|
131
|
-
"react-dom": "^19.
|
|
130
|
+
"react": "^19.3.0",
|
|
131
|
+
"react-dom": "^19.3.0",
|
|
132
132
|
"robots-parser": "^3.0.1",
|
|
133
133
|
"satteri": "^0.10.5",
|
|
134
134
|
"semver": "^7.8.5",
|
|
@@ -145,10 +145,10 @@
|
|
|
145
145
|
"ufo": "^1.6.4",
|
|
146
146
|
"undici": "^8.10.2",
|
|
147
147
|
"write-file-atomic": "^8.0.0",
|
|
148
|
-
"zod": "^4.
|
|
148
|
+
"zod": "^4.6.2"
|
|
149
149
|
},
|
|
150
150
|
"devDependencies": {
|
|
151
|
-
"@ai-sdk/openai-compatible": "^3.0.
|
|
151
|
+
"@ai-sdk/openai-compatible": "^3.0.48",
|
|
152
152
|
"@mixedbread/sdk": "^0.77.0",
|
|
153
153
|
"@notionhq/client": "^5.26.0",
|
|
154
154
|
"@openrouter/ai-sdk-provider": "^3.0.0",
|
|
@@ -156,14 +156,14 @@
|
|
|
156
156
|
"@sanity/client": "^8.6.1",
|
|
157
157
|
"@types/cross-spawn": "^6.0.6",
|
|
158
158
|
"@types/html-escaper": "^3.0.4",
|
|
159
|
-
"@types/node": "^22.20.
|
|
159
|
+
"@types/node": "^22.20.2",
|
|
160
160
|
"@types/picomatch": "^4.0.3",
|
|
161
|
-
"@types/react": "^19.
|
|
162
|
-
"@types/react-dom": "^19.
|
|
161
|
+
"@types/react": "^19.3.0",
|
|
162
|
+
"@types/react-dom": "^19.3.0",
|
|
163
163
|
"@types/semver": "^7.8.0",
|
|
164
164
|
"@types/write-file-atomic": "^4.0.3",
|
|
165
165
|
"@typescript/native-preview": "^7.0.0-dev.20260707.2",
|
|
166
|
-
"algoliasearch": "^5.
|
|
166
|
+
"algoliasearch": "^5.59.0",
|
|
167
167
|
"bun-types": "^1.4.2",
|
|
168
168
|
"flexsearch": "^0.8.212",
|
|
169
169
|
"typesense": "^3.0.6"
|
package/src/astro/generate.ts
CHANGED
|
@@ -601,19 +601,18 @@ const islandFrameworkWarnings = (
|
|
|
601
601
|
* rather than let the build die with an opaque ERR_MODULE_NOT_FOUND from the
|
|
602
602
|
* hidden generated config. Availability mirrors the search-provider check: a
|
|
603
603
|
* dep resolves from the project root or from the Blume package itself.
|
|
604
|
+
* `pkgDir` is injectable for testing.
|
|
604
605
|
*/
|
|
605
|
-
const deploymentAdapterWarnings = (
|
|
606
|
+
export const deploymentAdapterWarnings = (
|
|
606
607
|
deployment: ResolvedConfig["deployment"],
|
|
607
|
-
root: string
|
|
608
|
+
root: string,
|
|
609
|
+
pkgDir: string = packageRoot()
|
|
608
610
|
): string[] => {
|
|
609
611
|
const dep =
|
|
610
612
|
deployment.output === "server" && deployment.adapter
|
|
611
613
|
? DEPLOYMENT_ADAPTER_DEPS.get(deployment.adapter)
|
|
612
614
|
: undefined;
|
|
613
|
-
if (
|
|
614
|
-
dep &&
|
|
615
|
-
!(canResolveFrom(root, dep) || canResolveFrom(packageRoot(), dep))
|
|
616
|
-
) {
|
|
615
|
+
if (dep && !(canResolveFrom(root, dep) || canResolveFrom(pkgDir, dep))) {
|
|
617
616
|
return [
|
|
618
617
|
`Deployment adapter "${deployment.adapter}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`,
|
|
619
618
|
];
|
|
@@ -42,6 +42,14 @@ class BlumeMermaid extends HTMLElement {
|
|
|
42
42
|
const token = this.#renderToken;
|
|
43
43
|
const mermaid = await loadMermaid();
|
|
44
44
|
mermaid.initialize({
|
|
45
|
+
// Mermaid 12 defaults to the bundled ELK layout and the "neo" look,
|
|
46
|
+
// which re-lays out and restyles every existing diagram and pulls a
|
|
47
|
+
// ~1.4 MB ELK chunk onto any page with a flowchart. Pin the previous
|
|
48
|
+
// defaults so diagrams keep rendering as authored; a diagram opts
|
|
49
|
+
// into ELK or neo through its own front matter (`config: { layout:
|
|
50
|
+
// elk }`), which outranks these initialize() values.
|
|
51
|
+
layout: "dagre",
|
|
52
|
+
look: "classic",
|
|
45
53
|
securityLevel: "strict",
|
|
46
54
|
startOnLoad: false,
|
|
47
55
|
theme: prefersDark() ? "dark" : "default",
|
|
@@ -3,7 +3,7 @@ import VercelAnalytics from "@vercel/analytics/astro";
|
|
|
3
3
|
|
|
4
4
|
// Analytics scripts injected into <head>. PostHog and custom providers use the
|
|
5
5
|
// inline-snippet house style (banner/theme/JSON-LD); Vercel uses its official
|
|
6
|
-
// Astro component. Loads ONLY in production builds so `blume dev` stays clean
|
|
6
|
+
// Astro component; Cloudflare is its beacon script tag. Loads ONLY in production builds so `blume dev` stays clean
|
|
7
7
|
// and local traffic never reaches your analytics.
|
|
8
8
|
interface AnalyticsScript {
|
|
9
9
|
attributes?: Record<string, string>;
|
|
@@ -14,6 +14,7 @@ interface AnalyticsScript {
|
|
|
14
14
|
|
|
15
15
|
interface Props {
|
|
16
16
|
analytics?: {
|
|
17
|
+
cloudflare?: { token: string };
|
|
17
18
|
posthog?: { host?: string; key: string };
|
|
18
19
|
scripts?: AnalyticsScript[];
|
|
19
20
|
vercel?: boolean;
|
|
@@ -45,6 +46,14 @@ const posthogSnippet = posthog
|
|
|
45
46
|
? `${POSTHOG_LOADER}posthog.init(${JSON.stringify(posthog.key)},{api_host:${JSON.stringify(posthog.host ?? "https://us.i.posthog.com")}});${POSTHOG_SPA_PAGEVIEWS}`
|
|
46
47
|
: null;
|
|
47
48
|
|
|
49
|
+
// Cloudflare Web Analytics (manual setup): the beacon script keyed by the
|
|
50
|
+
// site token, exactly as the dashboard's snippet renders it. It tracks
|
|
51
|
+
// history changes itself, so client-router navigations need no extra hook.
|
|
52
|
+
const cloudflareBeacon =
|
|
53
|
+
enabled && analytics?.cloudflare
|
|
54
|
+
? JSON.stringify({ token: analytics.cloudflare.token })
|
|
55
|
+
: null;
|
|
56
|
+
|
|
48
57
|
// Custom scripts: any other provider. Each is either external (`src`) or inline
|
|
49
58
|
// (`content`). Explicit fields win over spread `attributes`.
|
|
50
59
|
const customScripts = enabled ? (analytics?.scripts ?? []) : [];
|
|
@@ -62,6 +71,16 @@ const inlineScripts = customScripts
|
|
|
62
71
|
---
|
|
63
72
|
|
|
64
73
|
{vercelEnabled && <VercelAnalytics />}
|
|
74
|
+
{
|
|
75
|
+
cloudflareBeacon && (
|
|
76
|
+
<script
|
|
77
|
+
is:inline
|
|
78
|
+
defer
|
|
79
|
+
src="https://static.cloudflareinsights.com/beacon.min.js"
|
|
80
|
+
data-cf-beacon={cloudflareBeacon}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
65
84
|
{posthogSnippet && <script is:inline set:html={posthogSnippet} />}
|
|
66
85
|
{externalScripts.map((attrs) => <script is:inline {...attrs} />)}
|
|
67
86
|
{
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Send a custom analytics event to every analytics platform configured in
|
|
3
3
|
* `blume.config.ts`. Mirrors the providers wired by `Analytics.astro`: Vercel
|
|
4
|
-
* Web Analytics and PostHog are first-class
|
|
4
|
+
* Web Analytics and PostHog are first-class (Cloudflare Web Analytics is too,
|
|
5
|
+
* but has no custom-event API to forward to); any other provider added through
|
|
5
6
|
* `analytics.scripts` is reached via best-effort global detection or the
|
|
6
7
|
* `blume:track` CustomEvent, which fires unconditionally so a project can bridge
|
|
7
8
|
* the event to anything. Every call no-ops cleanly when a provider isn't present
|
|
@@ -23,6 +23,7 @@ import { asyncSampleLanguages } from "./async-snippets.ts";
|
|
|
23
23
|
import { languageSamplePanels } from "./sample-panels.ts";
|
|
24
24
|
import { buildMessage, defaultMessageValues } from "./message.ts";
|
|
25
25
|
import { messageModel } from "./message-model.ts";
|
|
26
|
+
import Description from "./Description.astro";
|
|
26
27
|
import MessageComposer from "./MessageComposer.astro";
|
|
27
28
|
import Authorization from "./Authorization.astro";
|
|
28
29
|
import Bindings from "./Bindings.astro";
|
|
@@ -156,9 +157,10 @@ const channelBindings = bindingGroups(channel?.bindings);
|
|
|
156
157
|
: "Message"}
|
|
157
158
|
</div>
|
|
158
159
|
{named.message.description && (
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
<Description
|
|
161
|
+
class="mb-2 text-muted-foreground text-sm"
|
|
162
|
+
text={named.message.description}
|
|
163
|
+
/>
|
|
162
164
|
)}
|
|
163
165
|
{named.message.contentType && (
|
|
164
166
|
<div class="mb-2 text-muted-foreground text-xs">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import Description from "./Description.astro";
|
|
2
3
|
import {
|
|
3
4
|
type OperationSecurity,
|
|
4
5
|
schemeCarrier,
|
|
@@ -38,7 +39,7 @@ const { security } = Astro.props;
|
|
|
38
39
|
{alternative.map((resolved) => {
|
|
39
40
|
const carrier = schemeCarrier(resolved);
|
|
40
41
|
return (
|
|
41
|
-
<div class="border-border border-t py-
|
|
42
|
+
<div class="border-border border-t py-4 first:border-t-0">
|
|
42
43
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
43
44
|
<code class="font-mono text-foreground text-sm">
|
|
44
45
|
{carrier?.name ?? resolved.key}
|
|
@@ -54,13 +55,10 @@ const { security } = Astro.props;
|
|
|
54
55
|
)}
|
|
55
56
|
</div>
|
|
56
57
|
{resolved.scheme?.description && (
|
|
57
|
-
<
|
|
58
|
-
class="mt-1 text-muted-foreground text-sm"
|
|
59
|
-
set:text={resolved.scheme.description}
|
|
60
|
-
/>
|
|
58
|
+
<Description class="mt-2 text-muted-foreground text-sm" text={resolved.scheme.description} />
|
|
61
59
|
)}
|
|
62
60
|
{resolved.scopes.length > 0 && (
|
|
63
|
-
<div class="mt-
|
|
61
|
+
<div class="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
|
|
64
62
|
<span class="text-muted-foreground">Scopes:</span>
|
|
65
63
|
{resolved.scopes.map((scope) => (
|
|
66
64
|
<code class="rounded bg-muted px-1 py-0.5 text-foreground">
|
|
@@ -54,13 +54,13 @@ const isSchemaish = (value: unknown): value is SchemaLike => {
|
|
|
54
54
|
</div>
|
|
55
55
|
{groups.map((group) => (
|
|
56
56
|
<div class="not-prose mb-3 rounded-blume border border-border px-4 last:mb-0">
|
|
57
|
-
<div class="flex items-baseline gap-2 border-border py-
|
|
57
|
+
<div class="flex items-baseline gap-2 border-border py-4">
|
|
58
58
|
<code class="font-mono font-semibold text-foreground text-sm">
|
|
59
59
|
{group.protocol}
|
|
60
60
|
</code>
|
|
61
61
|
</div>
|
|
62
62
|
{group.rows.map((row) => (
|
|
63
|
-
<div class="border-border border-t py-
|
|
63
|
+
<div class="border-border border-t py-4">
|
|
64
64
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
65
65
|
<code class="font-mono text-foreground text-sm">{row.name}</code>
|
|
66
66
|
{!isSchemaish(row.value) && (
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { descriptionHtml } from "./description.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A spec description, rendered as Markdown.
|
|
6
|
+
*
|
|
7
|
+
* One component rather than the same three lines in six places, and that is not only tidiness:
|
|
8
|
+
* the styling below is emitted ONCE per page here, where writing it as utility classes on each
|
|
9
|
+
* element repeated it per description instead. On the largest reference page that is 2,400
|
|
10
|
+
* descriptions — the first attempt at this put 1.5 MB of identical class attributes into a single
|
|
11
|
+
* HTML document and pushed eight pages past Googlebot's 2 MB crawl limit, which `blume audit`
|
|
12
|
+
* caught. A class name and one stylesheet cost the same at one description as at two thousand.
|
|
13
|
+
*/
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Extra classes for the wrapper — position and base type come from the caller. */
|
|
16
|
+
class?: string;
|
|
17
|
+
text?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { class: className = "", text } = Astro.props;
|
|
21
|
+
const html = descriptionHtml(text);
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
{html && <div class:list={["blume-api-description", className]} set:html={html} />}
|
|
25
|
+
|
|
26
|
+
<style is:global>
|
|
27
|
+
/* Plain CSS on a single class, not the typography plugin's `prose`. `prose` sets its own font
|
|
28
|
+
size, colour and rhythm, all of which fight the muted small type these sit in — a property
|
|
29
|
+
description would come out larger and darker than the property name above it. Only what
|
|
30
|
+
Markdown needs is styled; everything else inherits, so a description still reads as
|
|
31
|
+
annotation rather than as body copy. */
|
|
32
|
+
.blume-api-description > :first-child {
|
|
33
|
+
margin-top: 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.blume-api-description > :last-child {
|
|
37
|
+
margin-bottom: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.blume-api-description p,
|
|
41
|
+
.blume-api-description ol,
|
|
42
|
+
.blume-api-description ul,
|
|
43
|
+
.blume-api-description pre {
|
|
44
|
+
margin-block: 0.5rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.blume-api-description ol {
|
|
48
|
+
list-style: decimal;
|
|
49
|
+
padding-inline-start: 1.25rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.blume-api-description ul {
|
|
53
|
+
list-style: disc;
|
|
54
|
+
padding-inline-start: 1.25rem;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.blume-api-description li {
|
|
58
|
+
margin-block: 0.25rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* A heading in a description is a label for the paragraph under it, not a section of the
|
|
62
|
+
page: it keeps the description's own size and only gains the weight and colour of a label.
|
|
63
|
+
Without this it inherits the reset's unstyled heading and is indistinguishable from text. */
|
|
64
|
+
.blume-api-description :is(h1, h2, h3, h4, h5, h6) {
|
|
65
|
+
color: var(--color-foreground);
|
|
66
|
+
font-weight: 600;
|
|
67
|
+
margin-block: 0.5rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.blume-api-description strong {
|
|
71
|
+
color: var(--color-foreground);
|
|
72
|
+
font-weight: 600;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.blume-api-description a {
|
|
76
|
+
text-decoration: underline;
|
|
77
|
+
text-underline-offset: 2px;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Matched to the chip the reference already draws for an enum value, so an `apiKey` in prose
|
|
81
|
+
looks like the `apiKey` in the chips beside it. */
|
|
82
|
+
.blume-api-description code {
|
|
83
|
+
background: var(--color-muted);
|
|
84
|
+
border-radius: 0.25rem;
|
|
85
|
+
color: var(--color-foreground);
|
|
86
|
+
font-family: var(--font-mono);
|
|
87
|
+
font-size: 0.75rem;
|
|
88
|
+
padding: 0.125rem 0.25rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.blume-api-description pre {
|
|
92
|
+
background: var(--color-muted);
|
|
93
|
+
border-radius: 0.25rem;
|
|
94
|
+
overflow-x: auto;
|
|
95
|
+
padding: 0.5rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.blume-api-description pre code {
|
|
99
|
+
background: none;
|
|
100
|
+
padding: 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* The scroll frame a table gets from `descriptionHtml` carries the body's own 1.5rem rhythm,
|
|
104
|
+
which is three times what everything else in a description sits at. Only the margin is
|
|
105
|
+
restated; the frame, the cell padding and the scrolling are the renderer's. */
|
|
106
|
+
.blume-api-description .blume-table-scroll {
|
|
107
|
+
margin-block: 0.5rem;
|
|
108
|
+
}
|
|
109
|
+
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { GraphqlFieldRow } from "./graphql-helpers.ts";
|
|
3
3
|
import { isOutputField } from "./graphql-helpers.ts";
|
|
4
|
+
import Description from "./Description.astro";
|
|
4
5
|
import GraphqlChip from "./GraphqlChip.astro";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -46,7 +47,7 @@ const isRequired = (row: GraphqlFieldRow): boolean =>
|
|
|
46
47
|
const route = routes.get(row.type.name);
|
|
47
48
|
const args = isOutputField(row) ? row.args : [];
|
|
48
49
|
return (
|
|
49
|
-
<div class="border-border border-t py-
|
|
50
|
+
<div class="border-border border-t py-4 first:border-t-0">
|
|
50
51
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
51
52
|
<code class="font-mono text-foreground text-sm">
|
|
52
53
|
{row.name}
|
|
@@ -68,13 +69,10 @@ const isRequired = (row: GraphqlFieldRow): boolean =>
|
|
|
68
69
|
)}
|
|
69
70
|
</div>
|
|
70
71
|
{row.description && (
|
|
71
|
-
<
|
|
72
|
-
class="mt-1 text-muted-foreground text-sm"
|
|
73
|
-
set:text={row.description}
|
|
74
|
-
/>
|
|
72
|
+
<Description class="mt-2 text-muted-foreground text-sm" text={row.description} />
|
|
75
73
|
)}
|
|
76
74
|
{"default" in row && row.default !== undefined && (
|
|
77
|
-
<div class="mt-
|
|
75
|
+
<div class="mt-2 text-muted-foreground text-xs">
|
|
78
76
|
Default:{" "}
|
|
79
77
|
<code class="rounded bg-muted px-1 py-0.5 text-foreground">
|
|
80
78
|
{row.default}
|
|
@@ -82,7 +80,7 @@ const isRequired = (row: GraphqlFieldRow): boolean =>
|
|
|
82
80
|
</div>
|
|
83
81
|
)}
|
|
84
82
|
{row.deprecationReason && (
|
|
85
|
-
<div class="mt-
|
|
83
|
+
<div class="mt-2 text-muted-foreground text-xs">
|
|
86
84
|
Deprecated: <span set:text={row.deprecationReason} />
|
|
87
85
|
</div>
|
|
88
86
|
)}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import { buildRequest, defaultValues } from "./request.ts";
|
|
24
24
|
import { languageSamplePanels } from "./sample-panels.ts";
|
|
25
25
|
import { sampleLanguages } from "./snippets.ts";
|
|
26
|
+
import Description from "./Description.astro";
|
|
26
27
|
import GraphqlChip from "./GraphqlChip.astro";
|
|
27
28
|
import GraphqlFieldsTable from "./GraphqlFieldsTable.astro";
|
|
28
29
|
import GraphqlType from "./GraphqlType.astro";
|
|
@@ -154,9 +155,9 @@ const returnRoute = field ? routes.get(field.type.name) : undefined;
|
|
|
154
155
|
/>
|
|
155
156
|
</div>
|
|
156
157
|
{document.types[field.type.name]?.description && (
|
|
157
|
-
<
|
|
158
|
-
class="mt-
|
|
159
|
-
|
|
158
|
+
<Description
|
|
159
|
+
class="mt-2 text-muted-foreground text-sm"
|
|
160
|
+
text={document.types[field.type.name]?.description}
|
|
160
161
|
/>
|
|
161
162
|
)}
|
|
162
163
|
</section>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
graphqlRoutes,
|
|
8
8
|
graphqlUsage,
|
|
9
9
|
} from "./graphql-helpers.ts";
|
|
10
|
+
import Description from "./Description.astro";
|
|
10
11
|
import GraphqlChip from "./GraphqlChip.astro";
|
|
11
12
|
import GraphqlFieldsTable from "./GraphqlFieldsTable.astro";
|
|
12
13
|
import MethodBadge from "./MethodBadge.astro";
|
|
@@ -86,7 +87,7 @@ const SECTION_HEADING = "mb-2 font-semibold text-foreground text-sm";
|
|
|
86
87
|
</div>
|
|
87
88
|
<div class="rounded-blume border border-border px-4">
|
|
88
89
|
{(type.enumValues ?? []).map((value) => (
|
|
89
|
-
<div class="border-border border-t py-
|
|
90
|
+
<div class="border-border border-t py-4 first:border-t-0">
|
|
90
91
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
91
92
|
<code class="font-mono text-foreground text-sm">
|
|
92
93
|
{value.name}
|
|
@@ -98,13 +99,10 @@ const SECTION_HEADING = "mb-2 font-semibold text-foreground text-sm";
|
|
|
98
99
|
)}
|
|
99
100
|
</div>
|
|
100
101
|
{value.description && (
|
|
101
|
-
<
|
|
102
|
-
class="mt-1 text-muted-foreground text-sm"
|
|
103
|
-
set:text={value.description}
|
|
104
|
-
/>
|
|
102
|
+
<Description class="mt-2 text-muted-foreground text-sm" text={value.description} />
|
|
105
103
|
)}
|
|
106
104
|
{value.deprecationReason && (
|
|
107
|
-
<div class="mt-
|
|
105
|
+
<div class="mt-2 text-muted-foreground text-xs">
|
|
108
106
|
Deprecated: <span set:text={value.deprecationReason} />
|
|
109
107
|
</div>
|
|
110
108
|
)}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import Description from "./Description.astro";
|
|
2
3
|
import {
|
|
3
4
|
constraints,
|
|
4
5
|
resolveSchema,
|
|
@@ -54,7 +55,7 @@ const groups = SECTIONS.map((section) => ({
|
|
|
54
55
|
const limits = constraints(resolved);
|
|
55
56
|
const enumValues = Array.isArray(resolved.enum) ? resolved.enum : null;
|
|
56
57
|
return (
|
|
57
|
-
<div class="border-border border-t py-
|
|
58
|
+
<div class="border-border border-t py-4 first:border-t-0">
|
|
58
59
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
59
60
|
<code class="font-mono text-foreground text-sm">{param.name}</code>
|
|
60
61
|
<span class="text-muted-foreground text-xs">{type}</span>
|
|
@@ -70,18 +71,15 @@ const groups = SECTIONS.map((section) => ({
|
|
|
70
71
|
)}
|
|
71
72
|
</div>
|
|
72
73
|
{param.description && (
|
|
73
|
-
<
|
|
74
|
-
class="mt-1 text-muted-foreground text-sm"
|
|
75
|
-
set:text={param.description}
|
|
76
|
-
/>
|
|
74
|
+
<Description class="mt-2 text-muted-foreground text-sm" text={param.description} />
|
|
77
75
|
)}
|
|
78
76
|
{limits.length > 0 && (
|
|
79
|
-
<div class="mt-
|
|
77
|
+
<div class="mt-2 text-muted-foreground text-xs">
|
|
80
78
|
{limits.join(" · ")}
|
|
81
79
|
</div>
|
|
82
80
|
)}
|
|
83
81
|
{enumValues && (
|
|
84
|
-
<div class="mt-
|
|
82
|
+
<div class="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
|
|
85
83
|
<span class="text-muted-foreground">Allowed:</span>
|
|
86
84
|
{enumValues.map((value) => (
|
|
87
85
|
<code class="rounded bg-muted px-1 py-0.5 text-foreground">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import Description from "./Description.astro";
|
|
2
3
|
import type { SchemaLike } from "./helpers.ts";
|
|
3
4
|
import SchemaTable from "./SchemaTable.astro";
|
|
4
5
|
|
|
@@ -46,10 +47,7 @@ const schema = chosen?.[1]?.schema ?? {};
|
|
|
46
47
|
</div>
|
|
47
48
|
{
|
|
48
49
|
requestBody.description && (
|
|
49
|
-
<
|
|
50
|
-
class="text-muted-foreground text-sm"
|
|
51
|
-
set:text={requestBody.description}
|
|
52
|
-
/>
|
|
50
|
+
<Description class="text-muted-foreground text-sm" text={requestBody.description} />
|
|
53
51
|
)
|
|
54
52
|
}
|
|
55
53
|
<div class="mt-3">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { statusColor } from "../colors.ts";
|
|
3
|
+
import Description from "./Description.astro";
|
|
3
4
|
import type { SchemaLike } from "./helpers.ts";
|
|
4
5
|
import SchemaTable from "./SchemaTable.astro";
|
|
5
6
|
|
|
@@ -52,9 +53,9 @@ const items = Object.entries(responses);
|
|
|
52
53
|
{status}
|
|
53
54
|
</span>
|
|
54
55
|
{response.description && (
|
|
55
|
-
<
|
|
56
|
-
class="text-muted-foreground text-sm"
|
|
57
|
-
|
|
56
|
+
<Description
|
|
57
|
+
class="min-w-0 text-muted-foreground text-sm"
|
|
58
|
+
text={response.description}
|
|
58
59
|
/>
|
|
59
60
|
)}
|
|
60
61
|
</div>
|