blume 2.0.2 → 2.0.3
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 +65 -0
- package/dist/cli/{chunk-6k4ftwze.js → chunk-1d7ve1dm.js} +1 -1
- package/dist/cli/{chunk-zp79m0ts.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-2hsdwb9n.js} +19 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-2hsdwb9n.js.map} +1 -1
- package/dist/cli/{chunk-g698a744.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-35d4wj9f.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-35d4wj9f.js.map} +3 -3
- package/dist/cli/{chunk-mqc662a6.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-3em5wd2y.js} +21 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5f86nr5m.js} +15 -15
- package/dist/cli/{chunk-hr8ne106.js → chunk-5m5nmvyq.js} +68 -43
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-5xvm6tfj.js} +14 -14
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-6dsbexzp.js} +14 -14
- package/dist/cli/{chunk-00gs3wqs.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-j85scx15.js → chunk-9t7a85s3.js} +130 -44
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-nfcyttvj.js → chunk-acanzt5p.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-acanzt5p.js.map} +1 -1
- package/dist/cli/{chunk-wdrt2k2v.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-xh43dwgw.js → chunk-c8chx29p.js} +42 -29
- package/dist/cli/{chunk-xh43dwgw.js.map → chunk-c8chx29p.js.map} +9 -9
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-crgn1q09.js} +21 -12
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-4e9b9ra6.js → chunk-ey84smr6.js} +3 -3
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-g4hq16wv.js} +13 -13
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-hm3vjy5s.js} +64 -45
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-5r8g91qn.js → chunk-j85vccga.js} +306 -107
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-7vtckvaw.js → chunk-p73c0m7w.js} +14 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-p73c0m7w.js.map} +1 -1
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-pehfxfta.js} +3 -3
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-pv29h0wf.js} +1223 -978
- package/dist/cli/{chunk-v6ya5kcb.js.map → chunk-pv29h0wf.js.map} +47 -46
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6dtt0zfn.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-r9rcc4w7.js} +7 -7
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-3yce002v.js → chunk-vg9r4eb9.js} +6 -6
- package/dist/cli/{chunk-3yce002v.js.map → chunk-vg9r4eb9.js.map} +4 -4
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-wrr3j9w9.js} +16 -17
- package/dist/cli/{chunk-hqp2ajnh.js.map → chunk-wrr3j9w9.js.map} +7 -7
- package/dist/cli/{chunk-ddndchfr.js → chunk-yfyb25rh.js} +38 -26
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +20 -18
- package/dist/cli/index.js.map +3 -3
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +9 -7
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/core/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +9 -7
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/meta.d.ts +8 -0
- package/dist/types/core/schema.d.ts +7 -0
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -0
- package/dist/types/deploy/platforms/types.d.ts +8 -0
- package/dist/types/deploy/redirects.d.ts +24 -13
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/docs/02-deployment.mdx +4 -2
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/cli/audit.mdx +17 -1
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +20 -1
- package/docs/configuration/customization.mdx +2 -0
- package/docs/configuration/search.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/i18n.mdx +2 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +3 -1
- package/docs/content/sources.mdx +1 -5
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/index.mdx +2 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +1 -1
- package/docs/references/openapi.mdx +5 -3
- package/package.json +1 -1
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +7 -2
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/link-headers.ts +14 -5
- package/src/ai/llms.ts +18 -7
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/tar.ts +139 -12
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +1 -1
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +81 -37
- package/src/audit/checks/links.ts +8 -1
- package/src/audit/graph.ts +3 -1
- package/src/audit/run.ts +12 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/cli/args.ts +32 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/Badge.astro +5 -3
- package/src/components/content/Component.astro +2 -2
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -0
- package/src/components/content/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- package/src/components/islands/assistant.tsx +21 -4
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +35 -19
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +2 -2
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/NavSelector.astro +2 -2
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +7 -6
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/RootLayout.astro +5 -5
- package/src/components/layout/Search.astro +33 -13
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/Operation.astro +2 -0
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestPanel.astro +5 -2
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/snippets.ts +85 -6
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +62 -16
- package/src/core/config-input.ts +9 -7
- package/src/core/diagnostics.ts +2 -0
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n.ts +31 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/locale-links.ts +5 -1
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +28 -8
- package/src/core/project-graph.ts +27 -5
- package/src/core/schema.ts +24 -4
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +325 -90
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +27 -6
- package/src/deploy/cloudflare-negotiation.ts +4 -12
- package/src/deploy/headers.ts +8 -4
- package/src/deploy/node-headers.ts +1 -1
- package/src/deploy/platforms/cloudflare.ts +12 -3
- package/src/deploy/platforms/netlify.ts +1 -0
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +1 -0
- package/src/deploy/redirects.ts +54 -18
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +7 -6
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -4
- package/src/og/card.ts +129 -5
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +22 -14
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +41 -2
- package/src/registry/eject.ts +181 -24
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +67 -26
- package/src/search/popular.ts +2 -1
- package/src/seo/jsonld.ts +7 -3
- package/src/translate/meta.ts +68 -24
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +4 -1
- package/src/translate/work-list.ts +56 -17
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- /package/dist/cli/{chunk-6k4ftwze.js.map → chunk-1d7ve1dm.js.map} +0 -0
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-mqc662a6.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5f86nr5m.js.map} +0 -0
- /package/dist/cli/{chunk-91ws1n6j.js.map → chunk-5xvm6tfj.js.map} +0 -0
- /package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-6dsbexzp.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-4e9b9ra6.js.map → chunk-ey84smr6.js.map} +0 -0
- /package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-g4hq16wv.js.map} +0 -0
- /package/dist/cli/{chunk-qkb5a8sa.js.map → chunk-pehfxfta.js.map} +0 -0
- /package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-r9rcc4w7.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
+
import data from "blume:data";
|
|
3
|
+
|
|
2
4
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
5
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
6
|
import type { Navigation } from "../../core/types.ts";
|
|
7
|
+
import { scopesVersions } from "../../search/adapters/version-scope.ts";
|
|
5
8
|
import { resolvePopularIconMarkup } from "../../search/popular-icon.ts";
|
|
6
9
|
import Icon from "../Icon.astro";
|
|
10
|
+
import { withBase, withMountedBase } from "../islands/base-path.ts";
|
|
7
11
|
import { flattenPages } from "./nav-utils.ts";
|
|
8
12
|
|
|
9
13
|
// The provider-specific loader is generated per project into `blume:search-client`
|
|
@@ -21,7 +25,8 @@ interface Props {
|
|
|
21
25
|
locale?: string;
|
|
22
26
|
/**
|
|
23
27
|
* Docs version to filter results to (`""` = the current docs — a meaningful
|
|
24
|
-
* value, so `null`/omitted is what disables version filtering).
|
|
28
|
+
* value, so `null`/omitted is what disables version filtering). Ignored when
|
|
29
|
+
* the configured provider can't scope by version.
|
|
25
30
|
*/
|
|
26
31
|
version?: string | null;
|
|
27
32
|
}
|
|
@@ -32,27 +37,41 @@ const {
|
|
|
32
37
|
popularPages,
|
|
33
38
|
strings,
|
|
34
39
|
locale,
|
|
35
|
-
version = null,
|
|
40
|
+
version: viewedVersion = null,
|
|
36
41
|
} = Astro.props;
|
|
42
|
+
// Pagefind, Orama Cloud, and Mixedbread search every version whatever the
|
|
43
|
+
// dialog asks, so on those an "All versions" toggle would change nothing:
|
|
44
|
+
// treat the site as unversioned for search instead.
|
|
45
|
+
const version = scopesVersions(data.config.search.provider)
|
|
46
|
+
? viewedVersion
|
|
47
|
+
: null;
|
|
37
48
|
// Merge over the English baseline per key (rather than `strings ?? …`) so a
|
|
38
49
|
// partial — or empty `{}` — strings object still resolves every label to a
|
|
39
50
|
// default, matching the pattern PageActions uses for its own dictionary.
|
|
40
51
|
const s = { ...EN_UI.search, ...strings };
|
|
41
52
|
|
|
42
53
|
// Resolve popular icons to markup here (icon set is server-only). Unknown
|
|
43
|
-
// names fall through to the island's file glyph.
|
|
54
|
+
// names fall through to the island's file glyph. Each entry's served href is
|
|
55
|
+
// resolved here too: a configured entry is authored, so a base written by hand
|
|
56
|
+
// is kept (`withBase`); the sidebar fallback links generated routes, which
|
|
57
|
+
// always go under the base (`withMountedBase`).
|
|
44
58
|
const popular =
|
|
45
59
|
popularPages && popularPages.length > 0
|
|
46
60
|
? popularPages.map((page) => ({
|
|
61
|
+
href: withBase(page.route),
|
|
47
62
|
icon: resolvePopularIconMarkup(
|
|
48
63
|
page.icon,
|
|
49
64
|
import.meta.env.BASE_URL ?? "/"
|
|
50
65
|
),
|
|
51
66
|
label: page.label,
|
|
52
|
-
route: page.route,
|
|
53
67
|
}))
|
|
54
68
|
: navigation
|
|
55
|
-
? flattenPages(navigation.sidebar)
|
|
69
|
+
? flattenPages(navigation.sidebar)
|
|
70
|
+
.slice(0, 6)
|
|
71
|
+
.map((page) => ({
|
|
72
|
+
href: withMountedBase(page.route),
|
|
73
|
+
label: page.label,
|
|
74
|
+
}))
|
|
56
75
|
: [];
|
|
57
76
|
|
|
58
77
|
const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
@@ -220,7 +239,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
220
239
|
<script>
|
|
221
240
|
import { navigate } from "astro:transitions/client";
|
|
222
241
|
import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
|
|
223
|
-
import {
|
|
242
|
+
import { mountBase } from "../islands/base-path.ts";
|
|
224
243
|
import { escape as escapeHtml } from "html-escaper";
|
|
225
244
|
|
|
226
245
|
import { highlight, matchSnippet } from "./search/types.ts";
|
|
@@ -234,10 +253,11 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
234
253
|
}
|
|
235
254
|
|
|
236
255
|
interface PopularPage {
|
|
256
|
+
/** The served URL, base included (resolved server-side). */
|
|
257
|
+
href: string;
|
|
237
258
|
/** Pre-resolved inline SVG from the server; absent falls back to `file`. */
|
|
238
259
|
icon?: string;
|
|
239
260
|
label: string;
|
|
240
|
-
route: string;
|
|
241
261
|
}
|
|
242
262
|
|
|
243
263
|
const GRID_COLS = "md:grid-cols-[22rem_minmax(0,1fr)]";
|
|
@@ -671,7 +691,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
671
691
|
if (this.popular.length > 0) {
|
|
672
692
|
const group = this.addGroup(this.popularMsg);
|
|
673
693
|
for (const page of this.popular) {
|
|
674
|
-
const item = this.createLinkRow(page.
|
|
694
|
+
const item = this.createLinkRow(page.href, page.label, page.icon);
|
|
675
695
|
group.appendChild(item.el);
|
|
676
696
|
this.selectables.push(item);
|
|
677
697
|
}
|
|
@@ -755,9 +775,10 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
755
775
|
|
|
756
776
|
createHitRow(hit: SearchHit, query: string): Selectable {
|
|
757
777
|
const el = document.createElement("a");
|
|
758
|
-
// Index URLs are base-less logical routes;
|
|
759
|
-
// clicking a result lands on the page's real served
|
|
760
|
-
|
|
778
|
+
// Index URLs are base-less logical routes; mount them under the
|
|
779
|
+
// deployment base so clicking a result lands on the page's real served
|
|
780
|
+
// URL, even when the route starts with the base's own name.
|
|
781
|
+
const href = mountBase(import.meta.env.BASE_URL, hit.url);
|
|
761
782
|
el.href = href;
|
|
762
783
|
el.className = `${ROW_CLASS} ${MARK}`;
|
|
763
784
|
// `line-clamp-2` already sets `display`, so no `block` here (it would
|
|
@@ -782,9 +803,8 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
782
803
|
return item;
|
|
783
804
|
}
|
|
784
805
|
|
|
785
|
-
createLinkRow(
|
|
806
|
+
createLinkRow(href: string, label: string, icon?: string): Selectable {
|
|
786
807
|
const el = document.createElement("a");
|
|
787
|
-
const href = prefixBase(import.meta.env.BASE_URL, url);
|
|
788
808
|
el.href = href;
|
|
789
809
|
el.className = ROW_CLASS;
|
|
790
810
|
// `icon` is server-resolved markup (built-in Lucide, `<img>`, or
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
3
3
|
import Icon from "../Icon.astro";
|
|
4
4
|
|
|
5
5
|
// The "you're viewing an old version" notice, shared by RootLayout and
|
|
@@ -29,7 +29,7 @@ const { notice } = Astro.props;
|
|
|
29
29
|
<span>{notice.message}</span>
|
|
30
30
|
<a
|
|
31
31
|
class="inline-flex items-center gap-1 font-medium underline underline-offset-2"
|
|
32
|
-
href={
|
|
32
|
+
href={withMountedBase(notice.latestHref)}
|
|
33
33
|
>
|
|
34
34
|
{notice.latestLabel}
|
|
35
35
|
<Icon name="arrow-right" size={14} />
|
|
@@ -40,6 +40,9 @@ interface AnalyticsWindow {
|
|
|
40
40
|
pirsch?: (event: string, options?: { meta?: TrackProps }) => void;
|
|
41
41
|
plausible?: (event: string, options?: { props?: TrackProps }) => void;
|
|
42
42
|
posthog?: { capture?: (event: string, props?: TrackProps) => void };
|
|
43
|
+
stonks?: {
|
|
44
|
+
event?: (event: string, props?: Record<string, string>) => void;
|
|
45
|
+
};
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/** Run one provider call; its failure must not reach the others or the caller. */
|
|
@@ -85,6 +88,15 @@ const trackGlobals = (
|
|
|
85
88
|
attempt(() => w.plausible?.(event, { props }));
|
|
86
89
|
attempt(() => w.databuddy?.track?.(event, props));
|
|
87
90
|
attempt(() => w.fathom?.trackEvent?.(event));
|
|
91
|
+
// OneDollarStats only takes string property values.
|
|
92
|
+
attempt(() =>
|
|
93
|
+
w.stonks?.event?.(
|
|
94
|
+
event,
|
|
95
|
+
Object.fromEntries(
|
|
96
|
+
Object.entries(props).map(([key, value]) => [key, String(value)])
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
);
|
|
88
100
|
// Pirsch stringifies `meta` values in place, so it gets its own copy and
|
|
89
101
|
// the `blume:track` listeners still see the original types.
|
|
90
102
|
attempt(() => w.pirsch?.(event, { meta: { ...props } }));
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scrollspy's choice of the active "On this page" entry, kept apart from
|
|
3
|
+
* the `<blume-toc>` element (toc-element.ts) so it runs without a DOM.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The two measurements the scrollspy takes of a heading. */
|
|
7
|
+
export interface MeasuredHeading {
|
|
8
|
+
getBoundingClientRect: () => { top: number };
|
|
9
|
+
getClientRects: () => { length: number };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The entry for the section being read: the last heading at or above the
|
|
14
|
+
* trigger line (the first heading before any has reached it), or the last
|
|
15
|
+
* heading once the page is scrolled to the bottom.
|
|
16
|
+
*
|
|
17
|
+
* Only rendered headings compete. A heading inside a hidden tab panel
|
|
18
|
+
* (`display: none`) has no boxes and measures `top: 0`, so it would always
|
|
19
|
+
* read as scrolled past and hold the highlight from the top of the page.
|
|
20
|
+
*/
|
|
21
|
+
export const activeTocEntry = <Entry extends { heading: MeasuredHeading }>(
|
|
22
|
+
entries: readonly Entry[],
|
|
23
|
+
triggerOffset: number,
|
|
24
|
+
scrolledToBottom: boolean
|
|
25
|
+
): Entry | null => {
|
|
26
|
+
const rendered = entries.filter(
|
|
27
|
+
({ heading }) => heading.getClientRects().length > 0
|
|
28
|
+
);
|
|
29
|
+
if (scrolledToBottom) {
|
|
30
|
+
return rendered.at(-1) ?? null;
|
|
31
|
+
}
|
|
32
|
+
// Headings are in document order, so the last one whose top has reached the
|
|
33
|
+
// trigger line is the section currently being read; default to the first.
|
|
34
|
+
let active = rendered[0] ?? null;
|
|
35
|
+
for (const entry of rendered) {
|
|
36
|
+
if (entry.heading.getBoundingClientRect().top <= triggerOffset) {
|
|
37
|
+
active = entry;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return active;
|
|
41
|
+
};
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* The active heading is chosen by rect (the last heading at or above a trigger
|
|
8
8
|
* line just below the sticky header), so it stays correct for sections taller
|
|
9
|
-
* than the viewport and at the bottom of the page
|
|
9
|
+
* than the viewport and at the bottom of the page; headings that aren't rendered
|
|
10
|
+
* (inside a hidden tab panel) are skipped (see toc-active.ts). An IntersectionObserver is the
|
|
10
11
|
* cheap primary trigger — it only fires as headings cross the band near the top —
|
|
11
12
|
* and a passive, rAF-throttled scroll listener covers the one case it can't: a
|
|
12
13
|
* final section too short to push its heading past the trigger line.
|
|
@@ -14,6 +15,8 @@
|
|
|
14
15
|
* Imported for its side effect (registers the element) from RootLayout's script.
|
|
15
16
|
*/
|
|
16
17
|
|
|
18
|
+
import { activeTocEntry } from "./toc-active.ts";
|
|
19
|
+
|
|
17
20
|
interface TocEntry {
|
|
18
21
|
heading: HTMLElement;
|
|
19
22
|
link: HTMLAnchorElement;
|
|
@@ -93,19 +96,10 @@ class BlumeToc extends HTMLElement {
|
|
|
93
96
|
const scrolledToBottom =
|
|
94
97
|
window.innerHeight + window.scrollY >=
|
|
95
98
|
document.documentElement.scrollHeight - 2;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// Headings are in document order, so the last one whose top has reached the
|
|
101
|
-
// trigger line is the section currently being read; default to the first.
|
|
102
|
-
let active = this.#entries[0]?.link ?? null;
|
|
103
|
-
for (const { heading, link } of this.#entries) {
|
|
104
|
-
if (heading.getBoundingClientRect().top <= TRIGGER_OFFSET) {
|
|
105
|
-
active = link;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return active;
|
|
99
|
+
return (
|
|
100
|
+
activeTocEntry(this.#entries, TRIGGER_OFFSET, scrolledToBottom)?.link ??
|
|
101
|
+
null
|
|
102
|
+
);
|
|
109
103
|
}
|
|
110
104
|
}
|
|
111
105
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import specs from "blume:openapi";
|
|
3
3
|
import { specOf } from "../../openapi/model.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
5
5
|
import MethodBadge from "./MethodBadge.astro";
|
|
6
6
|
|
|
7
7
|
// The operation-link list for one tag section of an API overview page. The
|
|
@@ -27,7 +27,7 @@ const operations = Object.values(specOf(specs, source)?.operations ?? {}).filter
|
|
|
27
27
|
<li>
|
|
28
28
|
<a
|
|
29
29
|
class="flex items-start gap-3 rounded-blume border border-border p-3 text-inherit no-underline! transition-colors hover:border-accent hover:bg-muted hover:no-underline!"
|
|
30
|
-
href={
|
|
30
|
+
href={withMountedBase(operation.route)}
|
|
31
31
|
>
|
|
32
32
|
<MethodBadge class="mt-0.5 shrink-0" method={operation.method} />
|
|
33
33
|
{/* Title over path, stacked, so a long summary and a long route each
|
|
@@ -70,7 +70,7 @@ const security = resolveAsyncApiSecurity(
|
|
|
70
70
|
);
|
|
71
71
|
const parameters = channelParameters(channel, document);
|
|
72
72
|
const messages = operation ? operationMessages(operation, channel, document) : [];
|
|
73
|
-
const protocol = protocolOf(operation, channel, servers);
|
|
73
|
+
const protocol = protocolOf(operation, channel, servers, document.components);
|
|
74
74
|
|
|
75
75
|
/** Declared example first, else a sampled value from the payload schema. */
|
|
76
76
|
const exampleOf = (message: (typeof messages)[number]["message"]): unknown =>
|
|
@@ -115,8 +115,8 @@ const samplePanels = sample
|
|
|
115
115
|
? await languageSamplePanels(languages, sample, data.config.codeThemes)
|
|
116
116
|
: [];
|
|
117
117
|
|
|
118
|
-
const operationBindings = bindingGroups(operation?.bindings);
|
|
119
|
-
const channelBindings = bindingGroups(channel?.bindings);
|
|
118
|
+
const operationBindings = bindingGroups(operation?.bindings, document.components);
|
|
119
|
+
const channelBindings = bindingGroups(channel?.bindings, document.components);
|
|
120
120
|
---
|
|
121
121
|
|
|
122
122
|
{
|
|
@@ -144,7 +144,10 @@ const channelBindings = bindingGroups(channel?.bindings);
|
|
|
144
144
|
// Headers may be multi-format too; undefined with `headers`
|
|
145
145
|
// present means an inline-unrenderable schema format.
|
|
146
146
|
const headers = schemaOf(named.message.headers);
|
|
147
|
-
const messageBindings = bindingGroups(
|
|
147
|
+
const messageBindings = bindingGroups(
|
|
148
|
+
named.message.bindings,
|
|
149
|
+
document.components
|
|
150
|
+
);
|
|
148
151
|
return (
|
|
149
152
|
<section class="mt-6">
|
|
150
153
|
<div
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A schema name (or full type display like `[Pet!]!`) rendered as a link when
|
|
@@ -21,7 +21,7 @@ const { name, route, class: className } = Astro.props;
|
|
|
21
21
|
route ? (
|
|
22
22
|
<a
|
|
23
23
|
class:list={["font-mono text-accent hover:underline", className]}
|
|
24
|
-
href={
|
|
24
|
+
href={withMountedBase(route)}
|
|
25
25
|
>
|
|
26
26
|
{name}
|
|
27
27
|
</a>
|
|
@@ -127,6 +127,7 @@ const security = resolveSecurity(
|
|
|
127
127
|
const model =
|
|
128
128
|
ref && operation
|
|
129
129
|
? operationModel({
|
|
130
|
+
components,
|
|
130
131
|
method: ref.method,
|
|
131
132
|
parameters: params,
|
|
132
133
|
path: ref.path,
|
|
@@ -195,6 +196,7 @@ const languages = sampleLanguages(spec?.codeSamples ?? []);
|
|
|
195
196
|
/>
|
|
196
197
|
)}
|
|
197
198
|
<RequestPanel
|
|
199
|
+
components={components}
|
|
198
200
|
languages={languages}
|
|
199
201
|
responses={responses}
|
|
200
202
|
sample={sample}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { stableId } from "../content/tooltip-id.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
4
4
|
|
|
5
5
|
import { paramKey, type PlaygroundModel } from "./request.ts";
|
|
6
6
|
|
|
@@ -25,9 +25,9 @@ interface Props {
|
|
|
25
25
|
const { model, proxy, slug, operation } = Astro.props;
|
|
26
26
|
|
|
27
27
|
// The built-in proxy route carries the site `basePath` but, like every
|
|
28
|
-
// internal URL in the spec data, not `deployment.base`; `
|
|
29
|
-
// and passes an external proxy URL through untouched.
|
|
30
|
-
const proxyUrl = proxy ?
|
|
28
|
+
// internal URL in the spec data, not `deployment.base`; `withMountedBase`
|
|
29
|
+
// adds it and passes an external proxy URL through untouched.
|
|
30
|
+
const proxyUrl = proxy ? withMountedBase(proxy) : undefined;
|
|
31
31
|
|
|
32
32
|
// `</script>`-proof embedding: `<` only ever occurs inside JSON strings,
|
|
33
33
|
// where the \u escape is equivalent, so a spec description can never close
|
|
@@ -3,6 +3,7 @@ import data from "blume:data";
|
|
|
3
3
|
|
|
4
4
|
import { highlightCode } from "../../markdown/index.ts";
|
|
5
5
|
import {
|
|
6
|
+
type ComponentsLike,
|
|
6
7
|
responseExample,
|
|
7
8
|
type SchemaLike,
|
|
8
9
|
type SpecValue,
|
|
@@ -24,13 +25,15 @@ interface ResponseLike {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
interface Props {
|
|
28
|
+
/** The document's `components`, which `$ref`'d examples resolve against. */
|
|
29
|
+
components?: ComponentsLike;
|
|
27
30
|
sample: RequestSample;
|
|
28
31
|
languages: SampleLanguage[];
|
|
29
32
|
responses: Record<string, ResponseLike>;
|
|
30
33
|
schemas: Record<string, SchemaLike>;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
const { sample, languages, responses, schemas } = Astro.props;
|
|
36
|
+
const { components, sample, languages, responses, schemas } = Astro.props;
|
|
34
37
|
|
|
35
38
|
const requestPanels = await languageSamplePanels(
|
|
36
39
|
languages,
|
|
@@ -44,7 +47,7 @@ const responsePanels = await Promise.all(
|
|
|
44
47
|
Object.entries(response.content ?? {}).find(([type]) =>
|
|
45
48
|
type.includes("json")
|
|
46
49
|
)?.[1] ?? Object.values(response.content ?? {})[0];
|
|
47
|
-
const example = media ? responseExample(media, schemas) : undefined;
|
|
50
|
+
const example = media ? responseExample(media, schemas, components) : undefined;
|
|
48
51
|
const html =
|
|
49
52
|
example === undefined || example === null
|
|
50
53
|
? null
|
|
@@ -35,6 +35,13 @@ const node = planned ?? schemaTree(schema, schemas);
|
|
|
35
35
|
</div>
|
|
36
36
|
) : node.kind === "branches" ? (
|
|
37
37
|
<div class="flex flex-col gap-3">
|
|
38
|
+
{node.rows && (
|
|
39
|
+
<div class="not-prose">
|
|
40
|
+
{node.rows.map((row) => (
|
|
41
|
+
<SchemaProperty row={row} schemas={schemas} expandAll={expandAll} />
|
|
42
|
+
))}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
38
45
|
<div class="text-muted-foreground text-xs">{node.label}:</div>
|
|
39
46
|
{node.branches.map((branch) => (
|
|
40
47
|
<div class="rounded-blume border border-border p-3">
|
|
@@ -265,6 +265,32 @@ export const channelServers = (
|
|
|
265
265
|
return servers;
|
|
266
266
|
};
|
|
267
267
|
|
|
268
|
+
/** A protocol-keyed `bindings` map, whatever object it sits on. */
|
|
269
|
+
type BindingMap = Record<string, AsyncApiSpecValue>;
|
|
270
|
+
|
|
271
|
+
const BINDINGS_REF =
|
|
272
|
+
/^#\/components\/(?<section>(?:server|channel|operation|message)Bindings)\/(?<name>[^/]+)$/u;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* A `bindings` map as declared: inline, or — which AsyncAPI allows in its
|
|
276
|
+
* place — a `$ref` to a `#/components/*Bindings` entry, resolved here. Read
|
|
277
|
+
* as-is, a referenced map's only key is `$ref`, which would pass for the
|
|
278
|
+
* protocol. `undefined` when a ref resolves to nothing.
|
|
279
|
+
*/
|
|
280
|
+
export const resolveBindings = (
|
|
281
|
+
bindings: BindingMap | undefined,
|
|
282
|
+
components?: Components
|
|
283
|
+
): BindingMap | undefined => {
|
|
284
|
+
const ref = bindings?.$ref;
|
|
285
|
+
if (!isString(ref)) {
|
|
286
|
+
return bindings;
|
|
287
|
+
}
|
|
288
|
+
const groups = BINDINGS_REF.exec(ref)?.groups;
|
|
289
|
+
const target =
|
|
290
|
+
components?.[groups?.section ?? ""]?.[unescapePointer(groups?.name ?? "")];
|
|
291
|
+
return isObject(target) ? target : undefined;
|
|
292
|
+
};
|
|
293
|
+
|
|
268
294
|
/** Normalize protocol spellings onto the binding key they document. */
|
|
269
295
|
const PROTOCOL_ALIASES = {
|
|
270
296
|
"kafka-secure": "kafka",
|
|
@@ -277,15 +303,17 @@ const PROTOCOL_ALIASES = {
|
|
|
277
303
|
/**
|
|
278
304
|
* The protocol an operation speaks, for binding-aware code samples: the first
|
|
279
305
|
* operation/channel binding key, else the first relevant server's `protocol`.
|
|
306
|
+
* `components` resolves bindings given as a `$ref`.
|
|
280
307
|
*/
|
|
281
308
|
export const protocolOf = (
|
|
282
309
|
operation: AsyncApiOperationObject | undefined,
|
|
283
310
|
channel: AsyncApiChannelObject | undefined,
|
|
284
|
-
servers: AsyncApiServerObject[]
|
|
311
|
+
servers: AsyncApiServerObject[],
|
|
312
|
+
components?: Components
|
|
285
313
|
): string | undefined => {
|
|
286
314
|
const declared =
|
|
287
|
-
Object.keys(operation?.bindings ?? {})[0] ??
|
|
288
|
-
Object.keys(channel?.bindings ?? {})[0] ??
|
|
315
|
+
Object.keys(resolveBindings(operation?.bindings, components) ?? {})[0] ??
|
|
316
|
+
Object.keys(resolveBindings(channel?.bindings, components) ?? {})[0] ??
|
|
289
317
|
servers.find((server) => isString(server.protocol))?.protocol;
|
|
290
318
|
if (!isString(declared) || declared === "") {
|
|
291
319
|
return undefined;
|
|
@@ -338,13 +366,17 @@ export interface BindingGroup {
|
|
|
338
366
|
/**
|
|
339
367
|
* Binding maps flattened for display, `bindingVersion` (metadata, not
|
|
340
368
|
* behavior) dropped. Values stay unformatted — the component renders schema-ish
|
|
341
|
-
* objects as nested schema tables and everything else as code.
|
|
369
|
+
* objects as nested schema tables and everything else as code. `components`
|
|
370
|
+
* resolves a map given as a `$ref`.
|
|
342
371
|
*/
|
|
343
372
|
export const bindingGroups = (
|
|
344
|
-
bindings?:
|
|
373
|
+
bindings?: BindingMap,
|
|
374
|
+
components?: Components
|
|
345
375
|
): BindingGroup[] => {
|
|
346
376
|
const groups: BindingGroup[] = [];
|
|
347
|
-
for (const [protocol, fields] of Object.entries(
|
|
377
|
+
for (const [protocol, fields] of Object.entries(
|
|
378
|
+
resolveBindings(bindings, components) ?? {}
|
|
379
|
+
)) {
|
|
348
380
|
if (!isObject(fields)) {
|
|
349
381
|
continue;
|
|
350
382
|
}
|
|
@@ -335,12 +335,17 @@ const isRecord = (value: SpecValue): value is Record<string, SpecValue> =>
|
|
|
335
335
|
|
|
336
336
|
/**
|
|
337
337
|
* The example a parameter or media type declares: its `example`, else the
|
|
338
|
-
* first
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
338
|
+
* first `value` in its `examples` map — inline, or behind a `$ref` to
|
|
339
|
+
* `#/components/examples`, resolved against `components`. Scalar's upgrade
|
|
340
|
+
* moves every OpenAPI 3.0 and Swagger 2.0 `example` into
|
|
341
|
+
* `examples.default.value`, and 3.1 authors write that map directly, so
|
|
342
|
+
* reading `example` alone would drop them all. `undefined` when nothing is
|
|
343
|
+
* declared.
|
|
342
344
|
*/
|
|
343
|
-
export const declaredExample = (
|
|
345
|
+
export const declaredExample = (
|
|
346
|
+
carrier: ExampleCarrier,
|
|
347
|
+
components?: ComponentsLike
|
|
348
|
+
): SpecValue => {
|
|
344
349
|
if (carrier.example !== undefined) {
|
|
345
350
|
return carrier.example;
|
|
346
351
|
}
|
|
@@ -349,8 +354,11 @@ export const declaredExample = (carrier: ExampleCarrier): SpecValue => {
|
|
|
349
354
|
return undefined;
|
|
350
355
|
}
|
|
351
356
|
for (const entry of Object.values(examples)) {
|
|
352
|
-
|
|
353
|
-
|
|
357
|
+
const example = isRecord(entry)
|
|
358
|
+
? resolveComponentRef(entry, components, "examples")
|
|
359
|
+
: undefined;
|
|
360
|
+
if (example?.value !== undefined) {
|
|
361
|
+
return example.value;
|
|
354
362
|
}
|
|
355
363
|
}
|
|
356
364
|
return undefined;
|
|
@@ -363,9 +371,11 @@ export const declaredExample = (carrier: ExampleCarrier): SpecValue => {
|
|
|
363
371
|
*/
|
|
364
372
|
export const responseExample = (
|
|
365
373
|
media: ExampleCarrier & { schema?: SchemaLike },
|
|
366
|
-
schemas: Record<string, SchemaLike
|
|
374
|
+
schemas: Record<string, SchemaLike>,
|
|
375
|
+
components?: ComponentsLike
|
|
367
376
|
): SpecValue =>
|
|
368
|
-
declaredExample(media) ??
|
|
377
|
+
declaredExample(media, components) ??
|
|
378
|
+
exampleValue(media.schema, schemas, "response");
|
|
369
379
|
|
|
370
380
|
/** Pretty-print a JSON value for an example/code block. */
|
|
371
381
|
export const toJson = <T>(value: T): string => JSON.stringify(value, null, 2);
|
|
@@ -171,7 +171,12 @@ export const initComposer = (root: HTMLElement): void => {
|
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
173
|
if (state === "error") {
|
|
174
|
-
|
|
174
|
+
// A detail is the browser refusing the URL before dialing; a failed
|
|
175
|
+
// handshake carries none.
|
|
176
|
+
setStatus(
|
|
177
|
+
detail ? `Couldn't connect: ${detail}` : ERROR_MESSAGE,
|
|
178
|
+
ERROR_TEXT
|
|
179
|
+
);
|
|
175
180
|
return;
|
|
176
181
|
}
|
|
177
182
|
setStatus(
|
|
@@ -87,6 +87,17 @@ export const defaultMessageValues = (model: MessageModel): MessageValues => ({
|
|
|
87
87
|
|
|
88
88
|
const PARAM_TEMPLATE = /\{(?<name>[^{}]+)\}/gu;
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Protocols whose channel address is a URL path, where a parameter value must
|
|
92
|
+
* be percent-encoded. Everywhere else — a Kafka or MQTT topic, an AMQP routing
|
|
93
|
+
* key — the address is a name the value goes into as written: `sensor 1`,
|
|
94
|
+
* not `sensor%201`.
|
|
95
|
+
*/
|
|
96
|
+
const URL_PROTOCOLS = { http: true, https: true, ws: true } satisfies Record<
|
|
97
|
+
string,
|
|
98
|
+
true
|
|
99
|
+
>;
|
|
100
|
+
|
|
90
101
|
/**
|
|
91
102
|
* A free-text base URL lowered onto the server shape the snippet builders and
|
|
92
103
|
* the connect URL read. A full URL contributes its scheme, host, and path.
|
|
@@ -114,7 +125,8 @@ const customServer = (url: string): AsyncApiServerObject => {
|
|
|
114
125
|
/**
|
|
115
126
|
* THE one message builder: the composer's samples, its copy buttons, and the
|
|
116
127
|
* live WebSocket frame all consume its output. Parameter values are
|
|
117
|
-
* URL-encoded into the address
|
|
128
|
+
* URL-encoded into the address when the protocol addresses channels by URL
|
|
129
|
+
* (see {@link URL_PROTOCOLS}); a parameter left blank keeps its `{name}`
|
|
118
130
|
* template so the sample still reads as a template rather than a broken
|
|
119
131
|
* address. Payload text that doesn't parse yields no payload — the editor is
|
|
120
132
|
* showing the reader a validation error at that moment, and neither a sample
|
|
@@ -126,6 +138,9 @@ export const buildMessage = (
|
|
|
126
138
|
): MessageSample => {
|
|
127
139
|
const custom = values.customUrl.trim();
|
|
128
140
|
const picked = model.servers[values.server] ?? model.servers[0];
|
|
141
|
+
const encode = Object.hasOwn(URL_PROTOCOLS, model.protocol ?? "")
|
|
142
|
+
? encodeURIComponent
|
|
143
|
+
: (value: string) => value;
|
|
129
144
|
let payload: unknown;
|
|
130
145
|
try {
|
|
131
146
|
// SAFETY: JSON.parse returns `any`; widening it to `unknown` claims
|
|
@@ -138,7 +153,7 @@ export const buildMessage = (
|
|
|
138
153
|
action: model.action,
|
|
139
154
|
address: model.address.replaceAll(PARAM_TEMPLATE, (template, name) => {
|
|
140
155
|
const value = values.params[String(name)] ?? "";
|
|
141
|
-
return value === "" ? template :
|
|
156
|
+
return value === "" ? template : encode(value);
|
|
142
157
|
}),
|
|
143
158
|
payload,
|
|
144
159
|
server: custom === "" ? picked?.server : customServer(custom),
|