blume 2.0.1 → 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 +147 -0
- package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
- package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
- package/dist/cli/chunk-35d4wj9f.js.map +15 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
- package/dist/cli/chunk-5f86nr5m.js.map +11 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
- package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
- package/dist/cli/chunk-6dsbexzp.js.map +10 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
- package/dist/cli/chunk-acanzt5p.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
- package/dist/cli/chunk-c8chx29p.js.map +36 -0
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
- 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-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
- package/dist/cli/chunk-ey84smr6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
- package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
- package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
- package/dist/cli/chunk-p73c0m7w.js.map +11 -0
- package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
- package/dist/cli/chunk-pehfxfta.js.map +10 -0
- package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
- package/dist/cli/chunk-pv29h0wf.js.map +190 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
- package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
- package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
- package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
- package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
- package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
- package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +162 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +7 -0
- package/dist/types/ai/component-markdown.d.ts +4 -4
- 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 +11 -4
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +4 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +28 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +24 -1
- package/dist/types/core/config-input.d.ts +13 -11
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +9 -1
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +13 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +59 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +38 -13
- package/dist/types/core/sources/normalize.d.ts +23 -2
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +36 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +15 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +31 -15
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- 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/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +20 -9
- package/docs/08-faq.mdx +10 -3
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +7 -5
- package/docs/cli/audit.mdx +19 -3
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +4 -4
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +42 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +6 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -1
- package/docs/content/index.mdx +8 -4
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +7 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -16
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +2 -1
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +4 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +6 -4
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +8 -4
- package/docs/discoverability/rss.mdx +4 -2
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +6 -4
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +17 -4
- package/src/ai/api-catalog.ts +13 -3
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +22 -8
- package/src/ai/llms.ts +22 -10
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +80 -9
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +99 -15
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/tar.ts +139 -12
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +8 -0
- package/src/analytics/index.ts +7 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +4 -0
- package/src/astro/generate.ts +72 -28
- 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 +139 -57
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +22 -1
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/graph.ts +3 -1
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +13 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/args.ts +32 -0
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +11 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +20 -18
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +18 -18
- package/src/cli/commands/upgrade.ts +11 -0
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +7 -12
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +11 -5
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +58 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +20 -0
- package/src/components/content/base-href.ts +14 -22
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +37 -6
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +42 -21
- package/src/components/islands/webmcp.ts +16 -10
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +23 -10
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +8 -3
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +31 -12
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +28 -13
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +25 -12
- package/src/components/layout/Search.astro +38 -14
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +14 -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/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +23 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +13 -6
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +34 -52
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +100 -12
- package/src/components/openapi/message-composer.ts +14 -1
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +21 -3
- package/src/components/openapi/operation-model.ts +119 -17
- package/src/components/openapi/panel.ts +31 -5
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +110 -23
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +192 -26
- package/src/components/openapi/schema-tree.ts +209 -0
- package/src/components/openapi/snippets.ts +113 -19
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +69 -8
- package/src/core/config-input.ts +13 -11
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +219 -30
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +120 -11
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +44 -5
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +22 -18
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +89 -12
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +272 -83
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +50 -6
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +94 -18
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +28 -20
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +25 -3
- package/src/core/sources/github-releases.ts +131 -11
- package/src/core/sources/lexical.ts +30 -20
- package/src/core/sources/lower.ts +276 -56
- package/src/core/sources/mdx-remote.ts +52 -16
- package/src/core/sources/normalize.ts +456 -119
- package/src/core/sources/notion.ts +77 -34
- package/src/core/sources/obsidian.ts +40 -9
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +85 -49
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +28 -19
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +13 -0
- package/src/core/ui-packs/bg.ts +13 -0
- package/src/core/ui-packs/bn.ts +13 -0
- package/src/core/ui-packs/ca.ts +13 -0
- package/src/core/ui-packs/cs.ts +13 -0
- package/src/core/ui-packs/da.ts +13 -0
- package/src/core/ui-packs/de.ts +13 -0
- package/src/core/ui-packs/el.ts +13 -0
- package/src/core/ui-packs/es.ts +13 -0
- package/src/core/ui-packs/fa.ts +13 -0
- package/src/core/ui-packs/fi.ts +13 -0
- package/src/core/ui-packs/fr.ts +13 -0
- package/src/core/ui-packs/he.ts +13 -0
- package/src/core/ui-packs/hi.ts +13 -0
- package/src/core/ui-packs/hr.ts +13 -0
- package/src/core/ui-packs/hu.ts +13 -0
- package/src/core/ui-packs/id.ts +13 -0
- package/src/core/ui-packs/it.ts +13 -0
- package/src/core/ui-packs/ja.ts +13 -0
- package/src/core/ui-packs/ko.ts +13 -0
- package/src/core/ui-packs/nl.ts +13 -0
- package/src/core/ui-packs/no.ts +13 -0
- package/src/core/ui-packs/pl.ts +13 -0
- package/src/core/ui-packs/pt-br.ts +13 -0
- package/src/core/ui-packs/pt.ts +13 -0
- package/src/core/ui-packs/ro.ts +13 -0
- package/src/core/ui-packs/ru.ts +13 -0
- package/src/core/ui-packs/sk.ts +13 -0
- package/src/core/ui-packs/sr.ts +13 -0
- package/src/core/ui-packs/sv.ts +13 -0
- package/src/core/ui-packs/th.ts +13 -0
- package/src/core/ui-packs/tr.ts +13 -0
- package/src/core/ui-packs/uk.ts +13 -0
- package/src/core/ui-packs/vi.ts +13 -0
- package/src/core/ui-packs/zh-tw.ts +13 -0
- package/src/core/ui-packs/zh.ts +13 -0
- package/src/core/version-cut.ts +69 -17
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +52 -11
- package/src/deploy/cloudflare-negotiation.ts +27 -16
- package/src/deploy/headers.ts +75 -55
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +13 -3
- package/src/deploy/platforms/netlify.ts +83 -5
- package/src/deploy/platforms/node.ts +8 -5
- package/src/deploy/platforms/static.ts +2 -0
- package/src/deploy/platforms/types.ts +15 -0
- package/src/deploy/platforms/vercel.ts +10 -3
- package/src/deploy/redirects.ts +68 -21
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +74 -29
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +13 -9
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +15 -27
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +149 -9
- package/src/og/derive.ts +156 -5
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +99 -27
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +51 -3
- package/src/registry/eject.ts +297 -43
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +89 -27
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/seo/jsonld.ts +7 -3
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +76 -24
- package/src/translate/run.ts +14 -8
- package/src/translate/validate.ts +14 -2
- package/src/translate/work-list.ts +91 -19
- package/src/upgrade/upgrade.ts +36 -4
- package/dist/cli/chunk-27g6wdth.js.map +0 -10
- package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
- package/dist/cli/chunk-6crbhc3x.js.map +0 -14
- package/dist/cli/chunk-6hsn950k.js.map +0 -10
- package/dist/cli/chunk-79jhk4py.js.map +0 -35
- package/dist/cli/chunk-82bbrxdn.js +0 -51
- package/dist/cli/chunk-82bbrxdn.js.map +0 -10
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-ce574jw2.js +0 -23
- package/dist/cli/chunk-ce574jw2.js.map +0 -10
- package/dist/cli/chunk-dh8cwk36.js.map +0 -10
- package/dist/cli/chunk-epjnccmv.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-fs23ddbb.js.map +0 -35
- package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
- package/dist/cli/chunk-jwyddg7y.js.map +0 -15
- package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
- package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
- package/dist/cli/chunk-mb2919y2.js.map +0 -10
- package/dist/cli/chunk-q5163e60.js.map +0 -11
- package/dist/cli/chunk-qkqwkpte.js.map +0 -182
- package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
- package/dist/cli/chunk-s1p84fyh.js.map +0 -10
- package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
- package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
- package/dist/cli/chunk-yw7dm696.js.map +0 -14
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -16,23 +16,50 @@
|
|
|
16
16
|
/** How long the copied confirmation holds before reverting. */
|
|
17
17
|
const HOLD_MS = 1500;
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* How long a just-inserted live region waits before its first message. Screen
|
|
21
|
+
* readers start watching a region once it is in the document, so text written
|
|
22
|
+
* in the same task as the insertion is often never announced.
|
|
23
|
+
*/
|
|
24
|
+
const REGISTER_MS = 100;
|
|
25
|
+
|
|
19
26
|
/** The shared visually-hidden live region, created on first announcement. */
|
|
20
27
|
let region: HTMLElement | null = null;
|
|
28
|
+
/** Pending while a fresh region registers; its first message waits for it. */
|
|
29
|
+
let registering: ReturnType<typeof setTimeout> | undefined;
|
|
30
|
+
/** The newest message asked for while the region was registering. */
|
|
31
|
+
let queued = "";
|
|
32
|
+
|
|
33
|
+
const write = (target: HTMLElement, message: string): void => {
|
|
34
|
+
// Clear first so repeating the same message is re-announced.
|
|
35
|
+
target.textContent = "";
|
|
36
|
+
target.textContent = message;
|
|
37
|
+
};
|
|
21
38
|
|
|
22
39
|
/**
|
|
23
40
|
* Announce `message` to screen readers. The region is re-created if a swap
|
|
24
|
-
* (view transition, client router) disconnected it
|
|
41
|
+
* (view transition, client router) disconnected it — the router replaces
|
|
42
|
+
* `<body>` on every navigation — and a fresh region gets its first message
|
|
43
|
+
* only after it has had time to register.
|
|
25
44
|
*/
|
|
26
45
|
export const announceCopied = (message: string): void => {
|
|
27
46
|
if (!region?.isConnected) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
document.body.append(
|
|
47
|
+
const fresh = document.createElement("div");
|
|
48
|
+
fresh.setAttribute("role", "status");
|
|
49
|
+
fresh.className = "sr-only";
|
|
50
|
+
document.body.append(fresh);
|
|
51
|
+
region = fresh;
|
|
52
|
+
clearTimeout(registering);
|
|
53
|
+
registering = setTimeout(() => {
|
|
54
|
+
registering = undefined;
|
|
55
|
+
write(fresh, queued);
|
|
56
|
+
}, REGISTER_MS);
|
|
57
|
+
}
|
|
58
|
+
if (registering === undefined) {
|
|
59
|
+
write(region, message);
|
|
60
|
+
} else {
|
|
61
|
+
queued = message;
|
|
32
62
|
}
|
|
33
|
-
// Clear first so repeating the same message is re-announced.
|
|
34
|
-
region.textContent = "";
|
|
35
|
-
region.textContent = message;
|
|
36
63
|
};
|
|
37
64
|
|
|
38
65
|
/**
|
|
@@ -6,7 +6,7 @@ import { createPortal } from "react-dom";
|
|
|
6
6
|
|
|
7
7
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
8
8
|
import { copyText } from "../copy-feedback.ts";
|
|
9
|
-
import { joinBase,
|
|
9
|
+
import { joinBase, mountBase } from "./base-path.ts";
|
|
10
10
|
import { useAssistant } from "./hooks.ts";
|
|
11
11
|
|
|
12
12
|
/** A resolved empty-state prompt; `icon` is ready-to-inline SVG (or null). */
|
|
@@ -65,11 +65,12 @@ const markdown = new Marked({
|
|
|
65
65
|
breaks: true,
|
|
66
66
|
gfm: true,
|
|
67
67
|
// The model cites pages as base-less logical routes (`[Title](/route)`); rewrite
|
|
68
|
-
// link targets to served URLs so citations resolve under `deployment.base
|
|
69
|
-
//
|
|
68
|
+
// link targets to served URLs so citations resolve under `deployment.base`,
|
|
69
|
+
// even a route that starts with the base's own name. `mountBase` leaves
|
|
70
|
+
// external URLs and fragments untouched.
|
|
70
71
|
walkTokens: (token) => {
|
|
71
72
|
if (token.type === "link") {
|
|
72
|
-
token.href =
|
|
73
|
+
token.href = mountBase(import.meta.env.BASE_URL, token.href);
|
|
73
74
|
}
|
|
74
75
|
},
|
|
75
76
|
});
|
|
@@ -77,9 +78,18 @@ const markdown = new Marked({
|
|
|
77
78
|
const renderMarkdown = (content: string): string =>
|
|
78
79
|
DOMPurify.sanitize(markdown.parse(content, { async: false }));
|
|
79
80
|
|
|
80
|
-
const Glyph = ({
|
|
81
|
+
const Glyph = ({
|
|
82
|
+
className,
|
|
83
|
+
path,
|
|
84
|
+
size = 16,
|
|
85
|
+
}: {
|
|
86
|
+
className?: string;
|
|
87
|
+
path: string;
|
|
88
|
+
size?: number;
|
|
89
|
+
}) => (
|
|
81
90
|
<svg
|
|
82
91
|
aria-hidden="true"
|
|
92
|
+
className={className}
|
|
83
93
|
// `path` is a trusted, server-resolved Lucide glyph body (inline SVG),
|
|
84
94
|
// not user content; it must be injected as markup to render the icon.
|
|
85
95
|
// oxlint-disable-next-line react/no-danger -- trusted server-resolved inline SVG glyph
|
|
@@ -253,6 +263,22 @@ const Assistant = ({
|
|
|
253
263
|
};
|
|
254
264
|
}, [open]);
|
|
255
265
|
|
|
266
|
+
// Stamp the incoming body before the swap installs it, as the theme and
|
|
267
|
+
// banner scripts do for <html>. Stamped only after the swap (below, in a
|
|
268
|
+
// passive effect), the new body would first paint without the push padding,
|
|
269
|
+
// replaying its transition on every navigation with the panel docked.
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
if (!open) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const onBeforeSwap = (event: DocumentEventMap["astro:before-swap"]) => {
|
|
275
|
+
event.newDocument.body.dataset.blumeAssistant = "open";
|
|
276
|
+
};
|
|
277
|
+
document.addEventListener("astro:before-swap", onBeforeSwap);
|
|
278
|
+
return () =>
|
|
279
|
+
document.removeEventListener("astro:before-swap", onBeforeSwap);
|
|
280
|
+
}, [open]);
|
|
281
|
+
|
|
256
282
|
// Re-stamp the push attribute after a swap while the panel is open — the new
|
|
257
283
|
// body arrives without it. Deliberately separate from the effect above: a
|
|
258
284
|
// navigation must not re-run the focus handling and yank focus out of the
|
|
@@ -383,6 +409,9 @@ const Assistant = ({
|
|
|
383
409
|
<aside
|
|
384
410
|
aria-hidden={open ? undefined : "true"}
|
|
385
411
|
aria-label={t.title}
|
|
412
|
+
// Print hides the chrome by this marker, not by tag: content renders
|
|
413
|
+
// <aside> too (Callout, Panel), and that must print.
|
|
414
|
+
data-blume-assistant-panel=""
|
|
386
415
|
ref={panelRef}
|
|
387
416
|
// The closed panel is only translated off-screen; `inert` drops its
|
|
388
417
|
// buttons/textarea from the tab order and the accessibility tree.
|
|
@@ -418,7 +447,9 @@ const Assistant = ({
|
|
|
418
447
|
onClick={() => setOpen(false)}
|
|
419
448
|
type="button"
|
|
420
449
|
>
|
|
421
|
-
|
|
450
|
+
{/* Chevrons toward the inline end, where the panel docks: they
|
|
451
|
+
mirror with the panel in a right-to-left locale. */}
|
|
452
|
+
<Glyph className="rtl:-scale-x-100" path={icons.close} size={18} />
|
|
422
453
|
</button>
|
|
423
454
|
</div>
|
|
424
455
|
</header>
|
|
@@ -13,20 +13,50 @@ export const withTrailingSlash = (base: string): string =>
|
|
|
13
13
|
export const joinBase = (base: string, path: string): string =>
|
|
14
14
|
`${withTrailingSlash(base)}${path}`;
|
|
15
15
|
|
|
16
|
+
/** A root-relative internal target (`/x`) — never `//host` or a URL. */
|
|
17
|
+
const isRootRelative = (route: string): boolean =>
|
|
18
|
+
route.startsWith("/") && !route.startsWith("//");
|
|
19
|
+
|
|
20
|
+
/** A target's path, without its `?query` or `#fragment`. */
|
|
21
|
+
const pathOf = (route: string): string => {
|
|
22
|
+
const at = route.search(/[#?]/u);
|
|
23
|
+
return at === -1 ? route : route.slice(0, at);
|
|
24
|
+
};
|
|
25
|
+
|
|
16
26
|
/**
|
|
17
|
-
* Prefix a root-relative internal
|
|
18
|
-
* (`/guide` under base `/sub` -> `/sub/guide`), so
|
|
27
|
+
* Prefix a root-relative internal link an author wrote with the deployment
|
|
28
|
+
* base (`/guide` under base `/sub` -> `/sub/guide`), so it points at the
|
|
19
29
|
* page's real served URL. External URLs, protocol-relative URLs, and fragments
|
|
20
|
-
* pass through untouched, and it's idempotent
|
|
21
|
-
*
|
|
22
|
-
*
|
|
30
|
+
* pass through untouched, and it's idempotent: a link whose path is already
|
|
31
|
+
* under the base (`/sub/guide`, `/sub#install`) is returned unchanged, so a
|
|
32
|
+
* base written by hand isn't doubled. A route Blume generates takes
|
|
33
|
+
* {@link mountBase} instead. This is applied only where a URL is *emitted* —
|
|
34
|
+
* the navigation model and active-route matching stay in base-less logical
|
|
35
|
+
* space.
|
|
23
36
|
*/
|
|
24
37
|
export const prefixBase = (base: string, route: string): string => {
|
|
25
|
-
if (!
|
|
38
|
+
if (!isRootRelative(route)) {
|
|
26
39
|
return route;
|
|
27
40
|
}
|
|
28
41
|
const trimmed = base.replace(/\/+$/u, "");
|
|
29
|
-
|
|
42
|
+
const path = pathOf(route);
|
|
43
|
+
if (!trimmed || path === trimmed || path.startsWith(`${trimmed}/`)) {
|
|
44
|
+
return route;
|
|
45
|
+
}
|
|
46
|
+
return route === "/" ? trimmed : `${trimmed}${route}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Prefix a route Blume generates (a page, a sidebar entry, a Markdown mirror,
|
|
51
|
+
* an agent artifact) with the deployment base, unconditionally. Unlike
|
|
52
|
+
* {@link prefixBase}, a route whose first segment matches the base is still
|
|
53
|
+
* prefixed: under base `/guides`, the page `guides/setup.md` is served at
|
|
54
|
+
* `/guides/guides/setup`, not at `/guides/setup`. External and
|
|
55
|
+
* protocol-relative URLs pass through untouched.
|
|
56
|
+
*/
|
|
57
|
+
export const mountBase = (base: string, route: string): string => {
|
|
58
|
+
const trimmed = base.replace(/\/+$/u, "");
|
|
59
|
+
if (!(trimmed && isRootRelative(route))) {
|
|
30
60
|
return route;
|
|
31
61
|
}
|
|
32
62
|
return route === "/" ? trimmed : `${trimmed}${route}`;
|
|
@@ -34,13 +64,20 @@ export const prefixBase = (base: string, route: string): string => {
|
|
|
34
64
|
|
|
35
65
|
/**
|
|
36
66
|
* {@link prefixBase} bound to the build-time `BASE_URL` (the resolved
|
|
37
|
-
* `deployment.base`). The ergonomic form for
|
|
38
|
-
* withBase(
|
|
39
|
-
* is bundled into the site.
|
|
67
|
+
* `deployment.base`), for links an author wrote. The ergonomic form for
|
|
68
|
+
* `.astro` templates — `href={withBase(href)}` — since `BASE_URL` is inlined
|
|
69
|
+
* by Vite wherever this module is bundled into the site.
|
|
40
70
|
*/
|
|
41
71
|
export const withBase = (route: string): string =>
|
|
42
72
|
prefixBase(import.meta.env.BASE_URL ?? "/", route);
|
|
43
73
|
|
|
74
|
+
/**
|
|
75
|
+
* {@link mountBase} bound to the build-time `BASE_URL`, for routes Blume
|
|
76
|
+
* generates: `href={withMountedBase(page.route)}`.
|
|
77
|
+
*/
|
|
78
|
+
export const withMountedBase = (route: string): string =>
|
|
79
|
+
mountBase(import.meta.env.BASE_URL ?? "/", route);
|
|
80
|
+
|
|
44
81
|
/**
|
|
45
82
|
* A pathname with the deployment base stripped (`/docs/guide` -> `/guide`),
|
|
46
83
|
* for page-context lookups against base-less document routes.
|
|
@@ -23,27 +23,29 @@ import { joinBase, stripBase } from "./base-path.ts";
|
|
|
23
23
|
|
|
24
24
|
export type { BlumeClientData } from "../../core/data.ts";
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The last parsed snapshot, keyed by the script text it came from. Each page
|
|
28
|
+
* renders its own snapshot (its route and title, its locale's navigation), and
|
|
29
|
+
* a client-router navigation swaps in the new page's tag, so the cache is only
|
|
30
|
+
* reused while the tag's text is unchanged.
|
|
31
|
+
*/
|
|
32
|
+
let cached: { data: BlumeClientData; text: string } | null = null;
|
|
27
33
|
|
|
28
|
-
/** Read + parse the injected snapshot (memoized
|
|
34
|
+
/** Read + parse the current page's injected snapshot (memoized per text). */
|
|
29
35
|
const readClientData = (): BlumeClientData | null => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
// `document` is undeclared on the server; probing `globalThis` avoids both
|
|
34
|
-
// the bare-reference ReferenceError and a `typeof` sniff.
|
|
35
|
-
if (!("document" in globalThis)) {
|
|
36
|
+
const text = document.querySelector("#blume-client-data")?.textContent;
|
|
37
|
+
if (!text) {
|
|
36
38
|
return null;
|
|
37
39
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return null;
|
|
40
|
+
if (cached?.text === text) {
|
|
41
|
+
return cached.data;
|
|
41
42
|
}
|
|
42
43
|
try {
|
|
43
44
|
// SAFETY: the layout serialized this script tag's JSON from the same
|
|
44
45
|
// `BlumeClientData` snapshot this reads back.
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
const data = JSON.parse(text) as BlumeClientData;
|
|
47
|
+
cached = { data, text };
|
|
48
|
+
return data;
|
|
47
49
|
} catch {
|
|
48
50
|
return null;
|
|
49
51
|
}
|
|
@@ -51,15 +53,29 @@ const readClientData = (): BlumeClientData | null => {
|
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* Read the injected snapshot after mount. `null` on the server and on the first
|
|
54
|
-
* client render (so hydration matches), then the data once mounted
|
|
56
|
+
* client render (so hydration matches), then the data once mounted, re-read
|
|
57
|
+
* after every client-router swap.
|
|
55
58
|
*/
|
|
56
59
|
const useClientData = (): BlumeClientData | null => {
|
|
57
60
|
const [data, setData] = useState<BlumeClientData | null>(null);
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
// `document` is undeclared on the server; probing `globalThis` avoids both
|
|
63
|
+
// the bare-reference ReferenceError and a `typeof` sniff.
|
|
64
|
+
if (!("document" in globalThis)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const sync = () => setData(readClientData());
|
|
68
|
+
// Intentional post-mount hydration guard: `null` on the server and first
|
|
69
|
+
// client render so hydration matches, then the snapshot once mounted. The
|
|
70
|
+
// extra render is required; do not seed the initial value from the DOM.
|
|
71
|
+
// oxlint-disable-next-line react/react-compiler, react/set-state-in-effect, react-doctor/no-initialize-state -- deliberate SSR hydration guard
|
|
72
|
+
sync();
|
|
73
|
+
// An island kept across navigations (`transition:persist`) outlives the
|
|
74
|
+
// page it mounted on; the swap brings the next page's snapshot, so read
|
|
75
|
+
// it again or the hook keeps answering with the first page's data.
|
|
76
|
+
document.addEventListener("astro:after-swap", sync);
|
|
77
|
+
return () => document.removeEventListener("astro:after-swap", sync);
|
|
78
|
+
}, []);
|
|
63
79
|
return data;
|
|
64
80
|
};
|
|
65
81
|
|
|
@@ -308,12 +324,17 @@ export const useAssistant = (
|
|
|
308
324
|
}
|
|
309
325
|
}
|
|
310
326
|
if (live()) {
|
|
311
|
-
// A 200 with nothing in it (no body, an empty stream)
|
|
312
|
-
//
|
|
327
|
+
// A 200 with nothing in it (no body, an empty stream) is not an
|
|
328
|
+
// answer. It is also how a provider failure after the 200 arrives
|
|
329
|
+
// (a bad key, a rate limit, an unknown model): the route's text
|
|
330
|
+
// stream carries only text, so the error ends it empty. Show the
|
|
331
|
+
// error notice instead of leaving a pulsing blank bubble.
|
|
313
332
|
if (assistant.content) {
|
|
314
333
|
outcome("ask_answer", { chars: assistant.content.length });
|
|
315
334
|
} else {
|
|
316
335
|
outcome("ask_error", { status });
|
|
336
|
+
assistant.content = errorMessage;
|
|
337
|
+
setMessages([...history, { ...assistant }]);
|
|
317
338
|
}
|
|
318
339
|
}
|
|
319
340
|
} catch {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { unescape } from "html-escaper";
|
|
2
|
+
|
|
1
3
|
import type { SearchFn } from "../layout/search/types.ts";
|
|
2
|
-
import { joinBase, prefixBase } from "./base-path.ts";
|
|
4
|
+
import { joinBase, mountBase, prefixBase } from "./base-path.ts";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* WebMCP (W3C Web Machine Learning CG): in-page tools an agentic browser can
|
|
@@ -80,13 +82,15 @@ const isCallable = <T extends (...args: never[]) => void>(
|
|
|
80
82
|
const TAG = /<[^>]*>?/gu;
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
85
|
+
* Search-hit HTML as the plain text an agent reads: the `<mark>`
|
|
86
|
+
* highlighting (and any other markup) dropped, then the entities the
|
|
87
|
+
* highlighter escaped decoded (`<T> &` → `<T> &`). The closing `>`
|
|
88
|
+
* is optional so every `<` starts a strip: a dangling `<script` fragment
|
|
89
|
+
* can't survive the way it would if a full `<...>` pair were required. The
|
|
90
|
+
* input is HTML, where a literal `<` is `<`, so consuming from every raw
|
|
91
|
+
* `<` loses nothing legitimate. Stripping repeats to a fixed point so the
|
|
92
|
+
* no-fragment guarantee is explicit rather than an artifact of the regex
|
|
93
|
+
* shape; decoding comes last, so a decoded `<` is text, never a tag.
|
|
90
94
|
*/
|
|
91
95
|
const plain = (html: string): string => {
|
|
92
96
|
let stripped = html;
|
|
@@ -95,7 +99,7 @@ const plain = (html: string): string => {
|
|
|
95
99
|
previous = stripped;
|
|
96
100
|
stripped = stripped.replaceAll(TAG, "");
|
|
97
101
|
} while (stripped !== previous);
|
|
98
|
-
return stripped;
|
|
102
|
+
return unescape(stripped);
|
|
99
103
|
};
|
|
100
104
|
|
|
101
105
|
export interface WebMcpToolOptions {
|
|
@@ -140,7 +144,7 @@ export const buildWebMcpTools = (options: WebMcpToolOptions): WebMcpTool[] => {
|
|
|
140
144
|
hits
|
|
141
145
|
.map(
|
|
142
146
|
(hit) =>
|
|
143
|
-
`${plain(hit.title)} — ${
|
|
147
|
+
`${plain(hit.title)} — ${mountBase(options.base, hit.url)}\n${plain(hit.excerpt)}`
|
|
144
148
|
)
|
|
145
149
|
.join("\n\n")
|
|
146
150
|
);
|
|
@@ -171,6 +175,8 @@ export const buildWebMcpTools = (options: WebMcpToolOptions): WebMcpTool[] => {
|
|
|
171
175
|
}
|
|
172
176
|
const trimmed = route.length > 1 ? route.replace(/\/+$/u, "") : route;
|
|
173
177
|
const target = trimmed === "/" ? "/index" : trimmed;
|
|
178
|
+
// The agent may hand back a served URL `search_docs` returned, base
|
|
179
|
+
// included, so a path already under the base keeps it (`prefixBase`).
|
|
174
180
|
const response = await fetchFn(
|
|
175
181
|
`${prefixBase(options.base, target)}.md`
|
|
176
182
|
).catch(() => null);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import data from "blume:data";
|
|
2
3
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
4
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
5
|
import { withBase } from "../islands/base-path.ts";
|
|
@@ -18,13 +19,31 @@ interface Props {
|
|
|
18
19
|
| null;
|
|
19
20
|
/** Localized banner labels. */
|
|
20
21
|
strings?: UIStrings["banner"];
|
|
22
|
+
/**
|
|
23
|
+
* Active locale of the page. The link follows it the way the sidebar's
|
|
24
|
+
* featured links do (`/guide` -> `/fr/guide` when French serves it).
|
|
25
|
+
* Absent, the link resolves for the default locale.
|
|
26
|
+
*/
|
|
27
|
+
locale?: string;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
|
-
const { banner, strings } = Astro.props;
|
|
30
|
+
const { banner, strings, locale } = Astro.props;
|
|
24
31
|
// Merge over the English baseline per key (rather than `strings ?? …`) so a
|
|
25
32
|
// partial — or missing — strings object still resolves every label to a
|
|
26
33
|
// default, matching the pattern PageFeedback and Search use.
|
|
27
34
|
const b = { ...EN_UI.banner, ...strings };
|
|
35
|
+
// The configured `banner.link.href` is authored as if the site were mounted at
|
|
36
|
+
// root, in the default locale's path space; the locale's navigation carries it
|
|
37
|
+
// localized and mounted under `basePath` (the default navigation on a
|
|
38
|
+
// single-locale site). A banner a custom page passes with a link of its own
|
|
39
|
+
// renders that link as given.
|
|
40
|
+
const navigation =
|
|
41
|
+
(locale === undefined ? undefined : data.navigationByLocale[locale]) ??
|
|
42
|
+
data.navigation;
|
|
43
|
+
const linkHref =
|
|
44
|
+
banner?.link && banner.link.href === data.config.banner?.link?.href
|
|
45
|
+
? (navigation.bannerHref ?? banner.link.href)
|
|
46
|
+
: banner?.link?.href;
|
|
28
47
|
---
|
|
29
48
|
|
|
30
49
|
{
|
|
@@ -37,13 +56,13 @@ const b = { ...EN_UI.banner, ...strings };
|
|
|
37
56
|
{/* The message is authored in one language, whatever the page's
|
|
38
57
|
direction: `dir="auto"` keeps its punctuation where it was typed. */}
|
|
39
58
|
<span dir="auto">{banner.content}</span>
|
|
40
|
-
{banner.link && (
|
|
59
|
+
{banner.link && linkHref && (
|
|
41
60
|
<a
|
|
42
61
|
class="inline-flex items-center gap-1 font-medium underline underline-offset-2"
|
|
43
|
-
href={withBase(
|
|
62
|
+
href={withBase(linkHref)}
|
|
44
63
|
>
|
|
45
64
|
{banner.link.text}
|
|
46
|
-
<Icon name="arrow-right" size={14} />
|
|
65
|
+
<Icon class="rtl:-scale-x-100" name="arrow-right" size={14} />
|
|
47
66
|
</a>
|
|
48
67
|
)}
|
|
49
68
|
{banner.dismissible && (
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
3
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
|
-
import {
|
|
4
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
5
5
|
import type { Crumb } from "./nav-utils.ts";
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
@@ -35,7 +35,10 @@ const eyebrowCrumb = crumbs.length > 1 ? crumbs[crumbs.length - 2] : null;
|
|
|
35
35
|
]}
|
|
36
36
|
>
|
|
37
37
|
{eyebrowCrumb.route ? (
|
|
38
|
-
<a
|
|
38
|
+
<a
|
|
39
|
+
class="hover:text-foreground"
|
|
40
|
+
href={withMountedBase(eyebrowCrumb.route)}
|
|
41
|
+
>
|
|
39
42
|
{eyebrowCrumb.label}
|
|
40
43
|
</a>
|
|
41
44
|
) : (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { DiscoveryHeadLinks } from "../../core/data.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
4
4
|
// Emits the agent-discovery <link>s for the document <head>, on every page and
|
|
5
5
|
// not just the root: an agent that enters on a deep page (a search result, a
|
|
6
6
|
// shared link) never sees the homepage HTTP `Link` header (see
|
|
@@ -32,7 +32,7 @@ const { discovery, markdownMirror } = Astro.props;
|
|
|
32
32
|
{
|
|
33
33
|
discovery?.agentReadability && (
|
|
34
34
|
<link
|
|
35
|
-
href={
|
|
35
|
+
href={withMountedBase("/agent-readability.json")}
|
|
36
36
|
rel="describedby"
|
|
37
37
|
type="application/json"
|
|
38
38
|
/>
|
|
@@ -40,7 +40,11 @@ const { discovery, markdownMirror } = Astro.props;
|
|
|
40
40
|
}
|
|
41
41
|
{
|
|
42
42
|
discovery?.llmsTxt && (
|
|
43
|
-
<link
|
|
43
|
+
<link
|
|
44
|
+
href={withMountedBase("/llms.txt")}
|
|
45
|
+
rel="describedby"
|
|
46
|
+
type="text/plain"
|
|
47
|
+
/>
|
|
44
48
|
)
|
|
45
49
|
}
|
|
46
50
|
{/* The AI Catalog / ARD manifest under both relations its two specs define:
|
|
@@ -50,12 +54,12 @@ const { discovery, markdownMirror } = Astro.props;
|
|
|
50
54
|
discovery?.aiCatalog && (
|
|
51
55
|
<>
|
|
52
56
|
<link
|
|
53
|
-
href={
|
|
57
|
+
href={withMountedBase("/.well-known/ai-catalog.json")}
|
|
54
58
|
rel="ai-catalog"
|
|
55
59
|
type="application/ai-catalog+json"
|
|
56
60
|
/>
|
|
57
61
|
<link
|
|
58
|
-
href={
|
|
62
|
+
href={withMountedBase("/.well-known/ard.json")}
|
|
59
63
|
rel="ard"
|
|
60
64
|
type="application/json"
|
|
61
65
|
/>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import Ask from "blume:ask";
|
|
3
3
|
import data from "blume:data";
|
|
4
|
-
import { withBase } from "../islands/base-path.ts";
|
|
4
|
+
import { withBase, withMountedBase } from "../islands/base-path.ts";
|
|
5
5
|
import { NEW_TAB_ATTRS, newTabAttrs } from "../../core/new-tab.ts";
|
|
6
6
|
import type { ComponentOverride } from "../../core/define-components.ts";
|
|
7
7
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
@@ -17,6 +17,7 @@ import LanguageSwitcher from "./LanguageSwitcher.astro";
|
|
|
17
17
|
import Logo from "./Logo.astro";
|
|
18
18
|
import { currentTabForRoute } from "./nav-utils.ts";
|
|
19
19
|
import NavSelector from "./NavSelector.astro";
|
|
20
|
+
import NavTabMenu from "./NavTabMenu.astro";
|
|
20
21
|
import { resolveSlot } from "./overrides.ts";
|
|
21
22
|
import Search from "./Search.astro";
|
|
22
23
|
|
|
@@ -216,15 +217,27 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
|
|
|
216
217
|
{
|
|
217
218
|
navigation.tabs.length > 0 && (
|
|
218
219
|
<nav aria-label={n.sections} class={tabsNavClass}>
|
|
219
|
-
{navigation.tabs.map((tab) =>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
220
|
+
{navigation.tabs.map((tab) =>
|
|
221
|
+
tab.items && tab.items.length > 0 ? (
|
|
222
|
+
<NavTabMenu
|
|
223
|
+
active={tab === activeTab}
|
|
224
|
+
route={route}
|
|
225
|
+
tab={tab}
|
|
226
|
+
variant="bar"
|
|
227
|
+
/>
|
|
228
|
+
) : (
|
|
229
|
+
<a
|
|
230
|
+
aria-current={tab === activeTab ? "page" : undefined}
|
|
231
|
+
class="inline-flex items-center gap-1.5 rounded-full px-3 py-1.5 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:text-foreground"
|
|
232
|
+
href={withMountedBase(tab.href ?? tab.path)}
|
|
233
|
+
>
|
|
234
|
+
{tab.icon && (
|
|
235
|
+
<Icon class="shrink-0" name={tab.icon} size={14} />
|
|
236
|
+
)}
|
|
237
|
+
{tab.label}
|
|
238
|
+
</a>
|
|
239
|
+
)
|
|
240
|
+
)}
|
|
228
241
|
</nav>
|
|
229
242
|
)
|
|
230
243
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
3
3
|
import type { LocaleSwitchOption } from "../../core/types.ts";
|
|
4
4
|
import Icon from "../Icon.astro";
|
|
5
5
|
|
|
@@ -42,7 +42,7 @@ const menuRowClass =
|
|
|
42
42
|
<a
|
|
43
43
|
aria-current={option.current ? "true" : undefined}
|
|
44
44
|
class={menuRowClass}
|
|
45
|
-
href={
|
|
45
|
+
href={withMountedBase(option.href)}
|
|
46
46
|
hreflang={option.code}
|
|
47
47
|
lang={option.code}
|
|
48
48
|
>
|
|
@@ -47,9 +47,14 @@ const brandHref =
|
|
|
47
47
|
// it's omitted. An explicit `""` hides it (an image-only logo whose mark already
|
|
48
48
|
// carries the wordmark).
|
|
49
49
|
const brandText = logo?.text ?? site.title;
|
|
50
|
+
// Without a wordmark the link's only content is the mark, and an inline SVG
|
|
51
|
+
// mark is decorative (`aria-hidden`), so the link would have no accessible
|
|
52
|
+
// name. Name it after the image's `alt`, else the site title.
|
|
53
|
+
const linkLabel = brandText ? undefined : logoAlt || site.title;
|
|
50
54
|
---
|
|
51
55
|
|
|
52
56
|
<a
|
|
57
|
+
aria-label={linkLabel}
|
|
53
58
|
class="inline-flex min-w-0 items-center gap-2 font-semibold text-base text-foreground"
|
|
54
59
|
href={withBase(brandHref)}
|
|
55
60
|
>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
3
3
|
// A top-level navigation selector: a dropdown that switches
|
|
4
4
|
// between partitions of the site — a product, a version, or any grouped set of
|
|
5
5
|
// destinations (`navigation.selectors` in the config). Built on
|
|
@@ -38,6 +38,11 @@ const active =
|
|
|
38
38
|
// it switches. Longer product names still collapse to the icon there.
|
|
39
39
|
const label = active?.label ?? selector.label;
|
|
40
40
|
const labelClass = label.length <= 6 ? undefined : "max-sm:hidden";
|
|
41
|
+
// The accessible name carries the visible active label too (WCAG 2.5.3,
|
|
42
|
+
// label in name), so a voice-control user can say "v2" to open it — the
|
|
43
|
+
// language switcher's pattern.
|
|
44
|
+
const summaryLabel =
|
|
45
|
+
label === selector.label ? selector.label : `${selector.label}: ${label}`;
|
|
41
46
|
|
|
42
47
|
const iconButton =
|
|
43
48
|
"inline-flex h-9 cursor-pointer items-center gap-1.5 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground";
|
|
@@ -49,7 +54,7 @@ const menuRowClass =
|
|
|
49
54
|
selector.items.length > 0 && (
|
|
50
55
|
<details class="group relative" data-blume-dropdown>
|
|
51
56
|
<summary
|
|
52
|
-
aria-label={
|
|
57
|
+
aria-label={summaryLabel}
|
|
53
58
|
class={`${iconButton} list-none [&::-webkit-details-marker]:hidden`}
|
|
54
59
|
>
|
|
55
60
|
{active?.icon && <Icon name={active.icon} size={16} />}
|
|
@@ -68,7 +73,7 @@ const menuRowClass =
|
|
|
68
73
|
<a
|
|
69
74
|
aria-current={item.path === active?.path ? "true" : undefined}
|
|
70
75
|
class={menuRowClass}
|
|
71
|
-
href={
|
|
76
|
+
href={withMountedBase(item.path)}
|
|
72
77
|
>
|
|
73
78
|
{item.icon && <Icon class="mt-0.5" name={item.icon} size={16} />}
|
|
74
79
|
<span class="flex-1">
|