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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import Description from "./Description.astro";
|
|
2
3
|
import {
|
|
3
4
|
constraints,
|
|
4
5
|
isNullable,
|
|
@@ -54,7 +55,11 @@ const expandable =
|
|
|
54
55
|
!circular && (hasObjectShape(resolved) || Boolean(items && hasObjectShape(items)));
|
|
55
56
|
---
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
{/* The row's vertical scale is 8 / 12 / 16px, and every other reference table copies it. A
|
|
59
|
+
description is a block of Markdown, not a line: at 4px it sat closer to the label above it
|
|
60
|
+
than its own paragraphs sat to each other, and the disclosure below it touched the next row's
|
|
61
|
+
divider. Each step separates a bigger unit than the one before. */}
|
|
62
|
+
<div class="border-border border-t py-4 first:border-t-0 last:pb-0">
|
|
58
63
|
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
59
64
|
<code class="font-mono text-foreground text-sm">{name}</code>
|
|
60
65
|
<span class="text-muted-foreground text-xs"
|
|
@@ -77,17 +82,17 @@ const expandable =
|
|
|
77
82
|
</div>
|
|
78
83
|
{
|
|
79
84
|
description && (
|
|
80
|
-
<
|
|
85
|
+
<Description class="mt-2 text-muted-foreground text-sm" text={description} />
|
|
81
86
|
)
|
|
82
87
|
}
|
|
83
88
|
{
|
|
84
89
|
limits.length > 0 && (
|
|
85
|
-
<div class="mt-
|
|
90
|
+
<div class="mt-2 text-muted-foreground text-xs">{limits.join(" · ")}</div>
|
|
86
91
|
)
|
|
87
92
|
}
|
|
88
93
|
{
|
|
89
94
|
enumValues && (
|
|
90
|
-
<div class="mt-
|
|
95
|
+
<div class="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
|
|
91
96
|
<span class="text-muted-foreground">Allowed:</span>
|
|
92
97
|
{enumValues.map((value) => (
|
|
93
98
|
<code class="rounded bg-muted px-1 py-0.5 text-foreground">
|
|
@@ -99,12 +104,12 @@ const expandable =
|
|
|
99
104
|
}
|
|
100
105
|
{
|
|
101
106
|
expandable && (
|
|
102
|
-
<details class="mt-
|
|
107
|
+
<details class="mt-3" open={expandAll}>
|
|
103
108
|
<summary class="cursor-pointer select-none text-accent text-xs hover:underline">
|
|
104
109
|
<span class="[details[open]>summary_&]:hidden">Show properties</span>
|
|
105
110
|
<span class="hidden [details[open]>summary_&]:inline">Hide properties</span>
|
|
106
111
|
</summary>
|
|
107
|
-
<div class="mt-
|
|
112
|
+
<div class="mt-3 border-border border-l pl-4">
|
|
108
113
|
<SchemaTable
|
|
109
114
|
schema={schema}
|
|
110
115
|
schemas={schemas}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Marked } from "marked";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Render a spec description as Markdown.
|
|
5
|
+
*
|
|
6
|
+
* An OpenAPI `description` is Markdown by specification — "CommonMark syntax MAY be used for rich
|
|
7
|
+
* text representation" — but the reference components printed it with `set:text`, so a schema
|
|
8
|
+
* property, parameter or header showed its source. On a spec generated from code docstrings that
|
|
9
|
+
* is most of them: `**Inline**` printed its asterisks, `` `apiKey` `` printed its backticks, and
|
|
10
|
+
* because HTML collapses newlines every paragraph and list ran together into one wall of text.
|
|
11
|
+
* The operation description does not have this problem — it is emitted into the MDX body and goes
|
|
12
|
+
* through the full pipeline — which is what made the difference visible page by page.
|
|
13
|
+
*
|
|
14
|
+
* `marked` rather than the site's own Markdown pipeline: the pipeline is async, plugin-laden and
|
|
15
|
+
* built for whole documents, while these are thousands of short strings per build — a large
|
|
16
|
+
* reference renders tens of thousands of them. `marked` is synchronous, already a dependency,
|
|
17
|
+
* and already how the Ask AI island renders model Markdown.
|
|
18
|
+
*/
|
|
19
|
+
const TABLE = /<table>[\s\S]*?<\/table>/gu;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* An href the author can have meant: an absolute URL, a site-root path, a fragment or a mailto.
|
|
23
|
+
* A bare relative href in a spec description has never yet been a link, and the lookahead keeps
|
|
24
|
+
* `//host` out: that is a scheme-relative URL to another origin, not a site-root path.
|
|
25
|
+
*/
|
|
26
|
+
const DELIBERATE_HREF = /^(?:https?:|mailto:|#|\/(?!\/))/iu;
|
|
27
|
+
|
|
28
|
+
/** The source text of a demoted construct, made safe for `set:html`. */
|
|
29
|
+
const escapeHtml = (text: string): string =>
|
|
30
|
+
text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
31
|
+
|
|
32
|
+
const markdown = new Marked({
|
|
33
|
+
// `breaks` is deliberately NOT set, unlike the Ask AI island. Docstring prose is hard-wrapped at
|
|
34
|
+
// 72 or 79 columns, so honouring single newlines would break every sentence mid-flow at exactly
|
|
35
|
+
// the width the source file happened to use.
|
|
36
|
+
breaks: false,
|
|
37
|
+
gfm: true,
|
|
38
|
+
hooks: {
|
|
39
|
+
// GFM tables reach the page through `set:html`, so they miss the `blume:table-wrap` plugin
|
|
40
|
+
// that gives every table in the body its scroll frame — and a description sits in a column
|
|
41
|
+
// narrower than the body. Wrapping here reuses that frame rather than reinventing it; the
|
|
42
|
+
// regex is safe because a table cannot nest and this HTML is `marked`'s own output.
|
|
43
|
+
postprocess: (html: string) =>
|
|
44
|
+
html.replaceAll(
|
|
45
|
+
TABLE,
|
|
46
|
+
(table) => `<div class="blume-table-scroll" tabindex="0">${table}</div>`
|
|
47
|
+
),
|
|
48
|
+
},
|
|
49
|
+
renderer: {
|
|
50
|
+
// Raw HTML is escaped rather than passed through. A description is data lifted out of a spec
|
|
51
|
+
// file, frequently generated upstream from source comments, and it is interpolated with
|
|
52
|
+
// `set:html`; the island that renders model output runs DOMPurify over it for the same
|
|
53
|
+
// reason, which needs a DOM and so is unavailable in a component that renders on the server.
|
|
54
|
+
html: ({ text }: { text: string }) => escapeHtml(text),
|
|
55
|
+
// An image is held to the same href policy as a link, for the same reason: `` is the
|
|
56
|
+
// one Markdown construct that fetches a resource, and a `javascript:` or relative source in a
|
|
57
|
+
// description is notation rather than a picture.
|
|
58
|
+
image: ({ href, raw }: { href: string; raw: string }) =>
|
|
59
|
+
DELIBERATE_HREF.test(href) ? false : escapeHtml(raw),
|
|
60
|
+
// A link is emitted only when the author clearly meant one; anything else keeps its source
|
|
61
|
+
// text verbatim. Two failures drove this, and both are prose that was never Markdown.
|
|
62
|
+
//
|
|
63
|
+
// GFM autolinks a BARE url (`raw === href`), and a spec description is full of EXAMPLE hosts
|
|
64
|
+
// — `https://myorg.my.salesforce.com`, `https://yourstore.myshopify.com`. Each became an
|
|
65
|
+
// anchor pointing at a host that does not exist and was never meant to be visited.
|
|
66
|
+
//
|
|
67
|
+
// Worse, regex and format notation reads as link syntax. Debezium's own wording for a column
|
|
68
|
+
// list is `schemaName[.]tableName[.](columnName1|columnName2)`, in which `[.](columnName1|
|
|
69
|
+
// columnName2)` is EXACTLY `[text](href)` — 48 pages of one reference linked to a path made
|
|
70
|
+
// of that notation.
|
|
71
|
+
// Rendering the demoted case as `raw` rather than as `text` is what keeps that intact: the
|
|
72
|
+
// text alone is `.`, so emitting it would silently delete the rest of the notation.
|
|
73
|
+
//
|
|
74
|
+
// The test for "meant one" is the href (see `DELIBERATE_HREF`) plus the shape of the source:
|
|
75
|
+
// an author-written link starts with `[` (inline or reference style) or `<` (an angle
|
|
76
|
+
// autolink). A GFM bare autolink never does — `https://…`, `www.…` or an email — and
|
|
77
|
+
// comparing `raw` to `href` is not enough to catch it, because marked prefixes the missing
|
|
78
|
+
// `http://` or `mailto:` for the last two, so the two strings differ exactly as they would
|
|
79
|
+
// for a deliberate link.
|
|
80
|
+
link({ href, raw }: { href: string; raw: string }) {
|
|
81
|
+
const deliberate =
|
|
82
|
+
DELIBERATE_HREF.test(href) &&
|
|
83
|
+
(raw.startsWith("[") || raw.startsWith("<"));
|
|
84
|
+
return deliberate ? false : escapeHtml(raw);
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/** `description` rendered to HTML, or an empty string when there is nothing to render. */
|
|
90
|
+
export const descriptionHtml = (description: string | undefined): string =>
|
|
91
|
+
description?.trim() ? markdown.parse(description, { async: false }) : "";
|
package/src/core/config-input.ts
CHANGED
|
@@ -895,6 +895,16 @@ export interface AnalyticsScript {
|
|
|
895
895
|
|
|
896
896
|
/** Analytics providers. Configure one, several, or none. */
|
|
897
897
|
export interface AnalyticsConfig {
|
|
898
|
+
/**
|
|
899
|
+
* Cloudflare Web Analytics, for a site Cloudflare doesn't proxy (manual
|
|
900
|
+
* setup). Not needed on a proxied zone with automatic RUM enabled — that
|
|
901
|
+
* injects the beacon at the edge, and configuring it here too would count
|
|
902
|
+
* every pageview twice.
|
|
903
|
+
*/
|
|
904
|
+
cloudflare?: {
|
|
905
|
+
/** Site token from the Web Analytics JS snippet (`data-cf-beacon`). */
|
|
906
|
+
token: string;
|
|
907
|
+
};
|
|
898
908
|
/** PostHog product analytics. */
|
|
899
909
|
posthog?: {
|
|
900
910
|
/** API host (for self-hosted / EU). Defaults to PostHog cloud. */
|
package/src/core/schema.ts
CHANGED
|
@@ -1220,6 +1220,14 @@ const analyticsScriptSchema = z
|
|
|
1220
1220
|
});
|
|
1221
1221
|
|
|
1222
1222
|
const analyticsConfigSchema = z.strictObject({
|
|
1223
|
+
// Cloudflare Web Analytics in manual (JS snippet) mode; the token comes from
|
|
1224
|
+
// the site's snippet in the dashboard. A zone Cloudflare proxies with
|
|
1225
|
+
// automatic RUM injection on needs no config at all.
|
|
1226
|
+
cloudflare: z
|
|
1227
|
+
.strictObject({
|
|
1228
|
+
token: z.string().min(1),
|
|
1229
|
+
})
|
|
1230
|
+
.optional(),
|
|
1223
1231
|
posthog: z
|
|
1224
1232
|
.strictObject({
|
|
1225
1233
|
host: z.string().optional(),
|
package/src/theme/entry.ts
CHANGED
|
@@ -560,9 +560,16 @@ blume-diff {
|
|
|
560
560
|
/* Restore inner padding on every cell. Typography zeroes the first/last cell's
|
|
561
561
|
inline padding so a borderless table aligns to the prose margin; inside the
|
|
562
562
|
framed wrapper that leaves edge text touching the border. The :is() selector
|
|
563
|
-
outweighs Typography's :where()-scoped rules so the outer columns get it too.
|
|
563
|
+
outweighs Typography's :where()-scoped rules so the outer columns get it too.
|
|
564
|
+
|
|
565
|
+
The block padding is 0.75rem against a table line-height near 1.7: at 0.5rem a
|
|
566
|
+
cell whose content wrapped put MORE space between its own two lines than
|
|
567
|
+
between itself and the next row, so a table of wrapping cells read as one
|
|
568
|
+
block rather than as rows. The inline padding stays where it was, deliberately
|
|
569
|
+
— widening it comes out of column width in a capped article, and on one corpus
|
|
570
|
+
that pushed cells fitting on two lines onto three. */
|
|
564
571
|
.blume-table-scroll :is(th, td) {
|
|
565
|
-
padding: 0.
|
|
572
|
+
padding: 0.75rem;
|
|
566
573
|
}
|
|
567
574
|
/* Keep column labels on one line so a two-word header does not wrap into a
|
|
568
575
|
ragged stack; the table just scrolls a little wider instead. Body cells keep
|