blume 2.0.3 → 2.1.0
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/AGENTS.md +1 -0
- package/CHANGELOG.md +48 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-1d7ve1dm.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-yfyb25rh.js → chunk-2f3ndkpw.js} +24 -8
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-r20tn01b.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-35d4wj9f.js → chunk-2n4ps1b3.js} +7 -7
- package/dist/cli/{chunk-j85vccga.js → chunk-43zcf4gz.js} +97 -57
- package/dist/cli/{chunk-j85vccga.js.map → chunk-43zcf4gz.js.map} +6 -6
- package/dist/cli/{chunk-e04dxsz1.js → chunk-4ejxbzee.js} +1 -1
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-4h8z9vv5.js} +233 -39
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-5f86nr5m.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-tzmab476.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-2z928egk.js → chunk-5wj2qwn2.js} +5 -5
- package/dist/cli/chunk-6bm0xpgd.js +18 -0
- package/dist/cli/chunk-6bm0xpgd.js.map +10 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-713mngdq.js} +12 -5
- package/dist/cli/{chunk-vg9r4eb9.js.map → chunk-713mngdq.js.map} +3 -3
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-9xr4dgee.js} +26 -17
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-b07cmahc.js → chunk-amrb6hgh.js} +2 -2
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-cmtcnh9f.js} +2 -2
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-e7vvk5ya.js} +4 -4
- package/dist/cli/{chunk-pehfxfta.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-h2a48yhd.js} +186 -63
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-jnwj5kjj.js} +14 -9
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-jnwj5kjj.js.map} +4 -4
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-nd1821sk.js} +18 -16
- package/dist/cli/{chunk-2hsdwb9n.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-6dsbexzp.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-ey84smr6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-py62fewh.js.map} +1 -1
- package/dist/cli/chunk-q3wxxq39.js +126 -0
- package/dist/cli/chunk-q3wxxq39.js.map +11 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-rekqz4qg.js} +23 -13
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-pv29h0wf.js → chunk-xkz5xbtv.js} +3476 -822
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-y5h4r2y0.js} +4 -4
- package/dist/cli/{chunk-acanzt5p.js → chunk-yj17vs39.js} +6 -6
- package/dist/cli/{chunk-p73c0m7w.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-3em5wd2y.js → chunk-zccbjcj0.js} +4 -3
- package/dist/cli/{chunk-3em5wd2y.js.map → chunk-zccbjcj0.js.map} +2 -2
- package/dist/cli/{chunk-c8chx29p.js → chunk-zf748avv.js} +384 -207
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +26 -20
- package/dist/cli/index.js.map +5 -5
- package/dist/types/ai/agent-surface.d.ts +8 -7
- package/dist/types/ai/ai-catalog.d.ts +7 -0
- package/dist/types/ai/ask-context.d.ts +22 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +16 -0
- package/dist/types/astro/integration.d.ts +8 -0
- package/dist/types/captcha/hcaptcha.d.ts +27 -0
- package/dist/types/captcha/index.d.ts +28 -0
- package/dist/types/captcha/schema.d.ts +29 -0
- package/dist/types/captcha/turnstile.d.ts +28 -0
- package/dist/types/components/content/api-field.d.ts +59 -0
- package/dist/types/components/content/api-page.d.ts +95 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/helpers.d.ts +137 -0
- package/dist/types/components/openapi/security.d.ts +85 -0
- package/dist/types/consent/ethyca.d.ts +48 -0
- package/dist/types/consent/index.d.ts +37 -0
- package/dist/types/consent/native.d.ts +28 -0
- package/dist/types/consent/osano.d.ts +42 -0
- package/dist/types/consent/schema.d.ts +110 -0
- package/dist/types/core/config-input.d.ts +187 -20
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/redirect-patterns.d.ts +144 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +551 -56
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +5 -0
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +39 -11
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/features.d.ts +3 -2
- package/dist/types/narration/build.d.ts +70 -0
- package/dist/types/narration/provider.d.ts +26 -0
- package/dist/types/narration/script.d.ts +154 -0
- package/dist/types/openapi/model.d.ts +12 -6
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +13 -0
- package/dist/types/openapi/references.d.ts +14 -4
- package/dist/types/ratelimit/cloudflare.d.ts +39 -0
- package/dist/types/ratelimit/index.d.ts +27 -0
- package/dist/types/ratelimit/memory.d.ts +36 -0
- package/dist/types/ratelimit/schema.d.ts +111 -0
- package/dist/types/ratelimit/upstash.d.ts +21 -0
- package/dist/types/ratelimit/wrangler.d.ts +23 -0
- package/dist/types/reference/asyncapi.d.ts +10 -10
- package/dist/types/reference/graphql.d.ts +13 -10
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +45 -5
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +60 -36
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +4 -0
- package/dist/types/seo/metatags.d.ts +20 -0
- package/docs/02-deployment.mdx +41 -5
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +21 -1
- package/docs/cli/index.mdx +1 -0
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +4 -4
- package/docs/configuration/index.mdx +63 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +69 -0
- package/docs/configuration/search.mdx +29 -1
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +106 -0
- package/docs/content/frontmatter.mdx +76 -0
- package/docs/content/i18n.mdx +38 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/meta.mdx +1 -0
- package/docs/content/meta.ts +1 -0
- package/docs/content/navigation.mdx +28 -6
- package/docs/content/sources/contentful.mdx +32 -0
- package/docs/content/sources/custom.mdx +39 -0
- package/docs/content/sources/github-releases.mdx +31 -0
- package/docs/content/sources/index.mdx +91 -0
- package/docs/content/sources/meta.ts +16 -0
- package/docs/content/sources/notion.mdx +32 -0
- package/docs/content/sources/obsidian.mdx +37 -0
- package/docs/content/sources/payload.mdx +30 -0
- package/docs/content/sources/remote-mdx.mdx +40 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +31 -0
- package/docs/content/syntax.mdx +67 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +102 -3
- package/docs/references/scalar.mdx +3 -1
- package/package.json +36 -1
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +5 -5
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +60 -14
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +2 -1
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +6 -3
- package/src/ai/ask-context.ts +73 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/component-markdown.ts +75 -0
- package/src/ai/cors.ts +2 -0
- package/src/ai/index.ts +12 -0
- package/src/ai/llms.ts +6 -2
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/openapi-components.ts +9 -3
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +35 -2
- package/src/ai/write-skill.ts +97 -0
- package/src/astro/generate.ts +132 -18
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +35 -0
- package/src/astro/module-types.ts +6 -0
- package/src/astro/templates.ts +283 -30
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +19 -0
- package/src/audit/checks/contrast.ts +183 -0
- package/src/audit/checks/i18n.ts +2 -3
- package/src/audit/checks/indexability.ts +2 -3
- package/src/audit/checks/links.ts +5 -12
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +2 -0
- package/src/audit/types.ts +2 -1
- package/src/blume-modules.d.ts +7 -0
- package/src/captcha/client.ts +152 -0
- package/src/captcha/hcaptcha.ts +36 -0
- package/src/captcha/index.ts +28 -0
- package/src/captcha/schema.ts +32 -0
- package/src/captcha/turnstile.ts +38 -0
- package/src/captcha/verify.ts +71 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/doctor.ts +22 -16
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/index.ts +5 -0
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/content/ApiEndpoint.astro +18 -0
- package/src/components/content/ApiField.astro +79 -0
- package/src/components/content/ApiPlayground.astro +62 -0
- package/src/components/content/ApiRail.astro +19 -0
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/FileTree.astro +1 -0
- package/src/components/content/GithubInfo.astro +1 -0
- package/src/components/content/Panel.astro +1 -0
- package/src/components/content/ParamField.astro +25 -0
- package/src/components/content/RequestExample.astro +17 -0
- package/src/components/content/ResponseExample.astro +17 -0
- package/src/components/content/ResponseField.astro +32 -0
- package/src/components/content/Step.astro +1 -0
- package/src/components/content/Tile.astro +1 -0
- package/src/components/content/Tooltip.astro +1 -0
- package/src/components/content/Tree.astro +1 -0
- package/src/components/content/TypeTable.astro +4 -1
- package/src/components/content/View.astro +21 -0
- package/src/components/content/ViewSwitcher.astro +168 -0
- package/src/components/content/api-field.ts +100 -0
- package/src/components/content/api-page.ts +224 -0
- package/src/components/content/view-switcher.ts +71 -0
- package/src/components/index.ts +2 -0
- package/src/components/islands/Assistant.astro +10 -1
- package/src/components/islands/assistant.tsx +104 -14
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +110 -21
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +6 -2
- package/src/components/layout/ConsentBanner.astro +62 -0
- package/src/components/layout/ConsentHead.astro +38 -0
- package/src/components/layout/Directory.astro +94 -0
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +122 -80
- package/src/components/layout/LanguageSwitcher.astro +48 -15
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +33 -7
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +71 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +308 -45
- package/src/components/layout/Search.astro +31 -17
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/consent/native.ts +79 -0
- package/src/components/layout/consent/types.ts +37 -0
- package/src/components/layout/footer-links.ts +53 -0
- package/src/components/layout/footer-socials.ts +87 -0
- package/src/components/layout/locale-redirect.ts +107 -0
- package/src/components/layout/nav-utils.ts +83 -0
- package/src/components/layout/page-feedback.ts +96 -0
- package/src/components/layout/search/analytics.ts +98 -0
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/types.ts +20 -0
- package/src/components/layout/search/typesense.ts +5 -1
- package/src/components/openapi/Operation.astro +161 -13
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +869 -8
- package/src/components/props.ts +6 -0
- package/src/consent/client.ts +95 -0
- package/src/consent/clients.ts +19 -0
- package/src/consent/ethyca.ts +71 -0
- package/src/consent/head.ts +24 -0
- package/src/consent/index.ts +37 -0
- package/src/consent/init.ts +29 -0
- package/src/consent/native.ts +36 -0
- package/src/consent/osano.ts +59 -0
- package/src/consent/schema.ts +30 -0
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +194 -20
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +3 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/includes.ts +20 -3
- package/src/core/links.ts +16 -2
- package/src/core/localizable.ts +26 -0
- package/src/core/navigation.ts +43 -12
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +111 -5
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +318 -64
- package/src/core/sources/normalize.ts +2 -1
- package/src/core/sources/read.ts +8 -3
- package/src/core/types.ts +1 -1
- package/src/core/ui-packs/ar.ts +43 -0
- package/src/core/ui-packs/bg.ts +45 -0
- package/src/core/ui-packs/bn.ts +43 -0
- package/src/core/ui-packs/ca.ts +46 -0
- package/src/core/ui-packs/cs.ts +44 -0
- package/src/core/ui-packs/da.ts +45 -0
- package/src/core/ui-packs/de.ts +46 -0
- package/src/core/ui-packs/el.ts +45 -0
- package/src/core/ui-packs/es.ts +46 -0
- package/src/core/ui-packs/fa.ts +45 -0
- package/src/core/ui-packs/fi.ts +45 -0
- package/src/core/ui-packs/fr.ts +46 -0
- package/src/core/ui-packs/he.ts +43 -0
- package/src/core/ui-packs/hi.ts +44 -0
- package/src/core/ui-packs/hr.ts +45 -0
- package/src/core/ui-packs/hu.ts +44 -0
- package/src/core/ui-packs/id.ts +45 -0
- package/src/core/ui-packs/it.ts +45 -0
- package/src/core/ui-packs/ja.ts +45 -0
- package/src/core/ui-packs/ko.ts +44 -0
- package/src/core/ui-packs/nl.ts +46 -0
- package/src/core/ui-packs/no.ts +44 -0
- package/src/core/ui-packs/pl.ts +45 -0
- package/src/core/ui-packs/pt-br.ts +46 -0
- package/src/core/ui-packs/pt.ts +45 -0
- package/src/core/ui-packs/ro.ts +45 -0
- package/src/core/ui-packs/ru.ts +45 -0
- package/src/core/ui-packs/sk.ts +44 -0
- package/src/core/ui-packs/sr.ts +45 -0
- package/src/core/ui-packs/sv.ts +44 -0
- package/src/core/ui-packs/th.ts +43 -0
- package/src/core/ui-packs/tr.ts +45 -0
- package/src/core/ui-packs/uk.ts +45 -0
- package/src/core/ui-packs/vi.ts +45 -0
- package/src/core/ui-packs/zh-tw.ts +43 -0
- package/src/core/ui-packs/zh.ts +43 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/artifacts.ts +77 -8
- package/src/deploy/cloudflare-negotiation.ts +35 -5
- package/src/deploy/headers.ts +2 -1
- package/src/deploy/node-headers.ts +63 -9
- package/src/deploy/platforms/cloudflare.ts +32 -0
- package/src/deploy/platforms/netlify.ts +33 -1
- package/src/deploy/platforms/vercel.ts +37 -2
- package/src/deploy/redirects.ts +91 -24
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/features.ts +3 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +28 -5
- package/src/markdown/math.ts +4 -4
- package/src/markdown/variables.ts +56 -0
- package/src/markdown/views.ts +82 -0
- package/src/narration/build.ts +412 -0
- package/src/narration/player.ts +249 -0
- package/src/narration/provider.ts +29 -0
- package/src/narration/script.ts +592 -0
- package/src/og/card.ts +7 -6
- package/src/openapi/model.ts +57 -24
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +1 -1
- package/src/ratelimit/cloudflare.ts +54 -0
- package/src/ratelimit/index.ts +30 -0
- package/src/ratelimit/memory.ts +45 -0
- package/src/ratelimit/runtime.ts +229 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +30 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +5 -2
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +51 -7
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +20 -4
- package/src/registry/registry.ts +7 -0
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/sync/algolia.ts +15 -4
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +5 -0
- package/src/seo/metatags.ts +67 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +56 -3
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +8 -2
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-364znk6q.js +0 -75
- package/dist/cli/chunk-364znk6q.js.map +0 -11
- package/dist/cli/chunk-5m5nmvyq.js.map +0 -13
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-pehfxfta.js.map +0 -10
- package/dist/cli/chunk-pv29h0wf.js.map +0 -190
- package/dist/cli/chunk-q58y5e6a.js.map +0 -10
- package/dist/cli/chunk-wrr3j9w9.js.map +0 -35
- package/dist/cli/chunk-yfyb25rh.js.map +0 -14
- package/docs/content/sources.mdx +0 -334
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-35d4wj9f.js.map → chunk-2n4ps1b3.js.map} +0 -0
- /package/dist/cli/{chunk-e04dxsz1.js.map → chunk-4ejxbzee.js.map} +0 -0
- /package/dist/cli/{chunk-tzmab476.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-b07cmahc.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-cmtcnh9f.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-e7vvk5ya.js.map} +0 -0
- /package/dist/cli/{chunk-tkacnehg.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-8ktnccpt.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-y4ykbark.js.map} +0 -0
- /package/dist/cli/{chunk-p3v96n38.js.map → chunk-y5h4r2y0.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-yj17vs39.js.map} +0 -0
package/src/reference/scalar.ts
CHANGED
|
@@ -2,7 +2,13 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
import type { AdapterDescriptor, JsonValue } from "../core/adapter.ts";
|
|
4
4
|
import { adapterDescriptorSchema } from "../core/adapter.ts";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
hasSources,
|
|
7
|
+
missingSourcesIssue,
|
|
8
|
+
overlaysHaveSpec,
|
|
9
|
+
overlaysNeedSpecIssue,
|
|
10
|
+
overlaysSchema,
|
|
11
|
+
} from "./options.ts";
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* What the generated Scalar page imports. Declared on every `scalar()`
|
|
@@ -22,6 +28,11 @@ export const scalarSourceSchema = z.strictObject({
|
|
|
22
28
|
label: z.string().optional(),
|
|
23
29
|
/** Emit noindex metadata and omit the page from the sitemap. */
|
|
24
30
|
noindex: z.boolean().default(false),
|
|
31
|
+
/**
|
|
32
|
+
* OpenAPI Overlay documents applied in order to the document before it's
|
|
33
|
+
* embedded (inlined, so a remote spec with overlays is fetched at build).
|
|
34
|
+
*/
|
|
35
|
+
overlays: overlaysSchema,
|
|
25
36
|
/** Per-source route; defaults to the adapter's `route` (or a derived path). */
|
|
26
37
|
route: z.string().optional(),
|
|
27
38
|
/** Local path or `http(s)` URL to the OpenAPI or AsyncAPI document. */
|
|
@@ -34,6 +45,8 @@ export interface ScalarSourceOptions {
|
|
|
34
45
|
label?: string;
|
|
35
46
|
/** Emit noindex metadata and omit the page from the sitemap. Defaults to `false`. */
|
|
36
47
|
noindex?: boolean;
|
|
48
|
+
/** OpenAPI Overlay documents applied in order to the document before it's embedded. */
|
|
49
|
+
overlays?: string[];
|
|
37
50
|
/** Per-source route; defaults to the adapter's `route` (or a derived path). */
|
|
38
51
|
route?: string;
|
|
39
52
|
/** Local path or `http(s)` URL to the OpenAPI or AsyncAPI document. */
|
|
@@ -55,6 +68,8 @@ export type ResolvedScalarOptions = {
|
|
|
55
68
|
|
|
56
69
|
/** The options {@link scalar} maps itself. */
|
|
57
70
|
export interface ScalarNamedOptions {
|
|
71
|
+
/** OpenAPI Overlay documents for `spec`; with `sources`, each takes its own. */
|
|
72
|
+
overlays?: string[];
|
|
58
73
|
/** Where the reference mounts. Defaults to `/reference`. */
|
|
59
74
|
route?: string;
|
|
60
75
|
/** One or more documents; each renders on its own route by default. */
|
|
@@ -87,6 +102,8 @@ export type ScalarOptions = ScalarNamedOptions & {
|
|
|
87
102
|
*/
|
|
88
103
|
export const scalarOptionsSchema = z
|
|
89
104
|
.object({
|
|
105
|
+
/** Overlays for the `spec` shorthand. */
|
|
106
|
+
overlays: z.array(z.string()).optional(),
|
|
90
107
|
/** Where the reference mounts. */
|
|
91
108
|
route: z.string().default("/reference"),
|
|
92
109
|
/** One or more documents; each renders on its own route by default. */
|
|
@@ -97,13 +114,21 @@ export const scalarOptionsSchema = z
|
|
|
97
114
|
theme: z.string().optional(),
|
|
98
115
|
})
|
|
99
116
|
.catchall(z.json())
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
sources:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
117
|
+
.refine(overlaysHaveSpec, overlaysNeedSpecIssue)
|
|
118
|
+
.transform(
|
|
119
|
+
({ overlays, spec, sources, ...options }): ResolvedScalarOptions => ({
|
|
120
|
+
...options,
|
|
121
|
+
sources:
|
|
122
|
+
spec === undefined
|
|
123
|
+
? sources
|
|
124
|
+
: [
|
|
125
|
+
scalarSourceSchema.parse(
|
|
126
|
+
overlays ? { overlays, spec } : { spec }
|
|
127
|
+
),
|
|
128
|
+
...sources,
|
|
129
|
+
],
|
|
130
|
+
})
|
|
131
|
+
)
|
|
107
132
|
.refine(hasSources, missingSourcesIssue("scalar"));
|
|
108
133
|
|
|
109
134
|
export type ScalarAdapter = AdapterDescriptor<"scalar", ScalarOptions>;
|
package/src/registry/eject.ts
CHANGED
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
rssEndpointTemplate,
|
|
64
64
|
runtimeDependencies,
|
|
65
65
|
runtimeTsconfigTemplate,
|
|
66
|
+
consentClientTemplate,
|
|
66
67
|
searchClientTemplate,
|
|
67
68
|
searchEndpointTemplate,
|
|
68
69
|
staticJsonEndpointTemplate,
|
|
@@ -162,9 +163,12 @@ const askFiles = async (
|
|
|
162
163
|
const files = [
|
|
163
164
|
{
|
|
164
165
|
content: askEndpointTemplate(backend, {
|
|
166
|
+
captcha: assistant.captcha,
|
|
165
167
|
cors: assistant.cors,
|
|
166
168
|
instructions: assistant.instructions,
|
|
169
|
+
rateLimit: project.config.rateLimit,
|
|
167
170
|
retrieval: assistant.retrieval,
|
|
171
|
+
tools: assistant.tools ?? backend.toolsByDefault,
|
|
168
172
|
}),
|
|
169
173
|
path: join(srcDir, "pages", "api", "ask.ts"),
|
|
170
174
|
},
|
|
@@ -243,7 +247,10 @@ const playgroundProxyFiles = (
|
|
|
243
247
|
needsPlaygroundProxy(config)
|
|
244
248
|
? [
|
|
245
249
|
{
|
|
246
|
-
content: playgroundProxyTemplate(
|
|
250
|
+
content: playgroundProxyTemplate(
|
|
251
|
+
specOrigins(openApiData),
|
|
252
|
+
config.rateLimit
|
|
253
|
+
),
|
|
247
254
|
path: join(srcDir, PLAYGROUND_PROXY_ENTRY),
|
|
248
255
|
},
|
|
249
256
|
]
|
|
@@ -667,6 +674,7 @@ export const eject = async (
|
|
|
667
674
|
content: astroConfigTemplate({
|
|
668
675
|
askPath: "./src/generated/Ask.astro",
|
|
669
676
|
config,
|
|
677
|
+
consentClientPath: "./src/generated/consent-client.ts",
|
|
670
678
|
contentRoot: relContext.contentRoot,
|
|
671
679
|
contentRoutes: project.manifest.routes.map((route) => route.path),
|
|
672
680
|
context: relContext,
|
|
@@ -869,8 +877,9 @@ export const eject = async (
|
|
|
869
877
|
);
|
|
870
878
|
}
|
|
871
879
|
|
|
872
|
-
// The client-feature loaders behind the `blume:features` alias,
|
|
873
|
-
// provider-specific client loader behind `blume:search-client
|
|
880
|
+
// The client-feature loaders behind the `blume:features` alias, the
|
|
881
|
+
// provider-specific client loader behind `blume:search-client`, and the
|
|
882
|
+
// consent adapter's browser module behind `blume:consent-client`.
|
|
874
883
|
files.push(
|
|
875
884
|
{
|
|
876
885
|
content: featuresTemplate(features),
|
|
@@ -879,6 +888,10 @@ export const eject = async (
|
|
|
879
888
|
{
|
|
880
889
|
content: searchClientTemplate(config),
|
|
881
890
|
path: join(genDir, "search-client.ts"),
|
|
891
|
+
},
|
|
892
|
+
{
|
|
893
|
+
content: consentClientTemplate(config.consent),
|
|
894
|
+
path: join(genDir, "consent-client.ts"),
|
|
882
895
|
}
|
|
883
896
|
);
|
|
884
897
|
|
|
@@ -899,7 +912,10 @@ export const eject = async (
|
|
|
899
912
|
|
|
900
913
|
if (searchAdapter.kind === "mixedbread") {
|
|
901
914
|
files.push({
|
|
902
|
-
content: mixedbreadSearchEndpointTemplate(
|
|
915
|
+
content: mixedbreadSearchEndpointTemplate(
|
|
916
|
+
searchAdapter.options,
|
|
917
|
+
config.rateLimit
|
|
918
|
+
),
|
|
903
919
|
path: join(srcDir, "pages", "api", "search.ts"),
|
|
904
920
|
});
|
|
905
921
|
}
|
package/src/registry/registry.ts
CHANGED
|
@@ -262,6 +262,13 @@ export const registry: RegistryItem[] = [
|
|
|
262
262
|
name: "feedback",
|
|
263
263
|
slot: "Feedback",
|
|
264
264
|
}),
|
|
265
|
+
layoutComponent({
|
|
266
|
+
description:
|
|
267
|
+
"The site footer: a row of links, social icons, the theme toggle, and the Cookie settings link.",
|
|
268
|
+
file: "SiteFooter.astro",
|
|
269
|
+
name: "footer",
|
|
270
|
+
slot: "Footer",
|
|
271
|
+
}),
|
|
265
272
|
...CONTENT_COMPONENTS.map(contentComponent),
|
|
266
273
|
];
|
|
267
274
|
|
package/src/search/documents.ts
CHANGED
|
@@ -54,6 +54,10 @@ export interface SearchDocument {
|
|
|
54
54
|
* Filterable through the MCP tools' `filters` input.
|
|
55
55
|
*/
|
|
56
56
|
facets?: Record<string, string>;
|
|
57
|
+
/** Frontmatter `search.boost`: the page's relevance multiplier, when set. */
|
|
58
|
+
boost?: number;
|
|
59
|
+
/** Frontmatter `search.keywords`: extra terms the page is found by. */
|
|
60
|
+
keywords?: string[];
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
/**
|
|
@@ -77,6 +81,13 @@ export interface SearchRecord {
|
|
|
77
81
|
version: string;
|
|
78
82
|
/** Single faceting tag (the first frontmatter tag, when present). */
|
|
79
83
|
tag?: string;
|
|
84
|
+
/**
|
|
85
|
+
* The page's relevance multiplier, 1 unless `search.boost` sets it: a
|
|
86
|
+
* ranking attribute every record carries, so the backend can sort by it.
|
|
87
|
+
*/
|
|
88
|
+
boost: number;
|
|
89
|
+
/** Extra terms the page is found by (`search.keywords`). */
|
|
90
|
+
keywords?: string[];
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
// Tag-shaped only: a name (or closing slash/fragment) right after `<`, and no
|
|
@@ -414,6 +425,17 @@ const pageBody = async (
|
|
|
414
425
|
});
|
|
415
426
|
};
|
|
416
427
|
|
|
428
|
+
/** The ranking a page asks for: its `search.boost` and `search.keywords`. */
|
|
429
|
+
const rankingFields = (
|
|
430
|
+
search: PageRecord["meta"]["search"] | undefined
|
|
431
|
+
): Pick<SearchDocument, "boost" | "keywords"> => {
|
|
432
|
+
const { boost, keywords } = search ?? {};
|
|
433
|
+
return {
|
|
434
|
+
...(boost !== undefined && boost !== 1 && { boost }),
|
|
435
|
+
...(keywords && keywords.length > 0 && { keywords }),
|
|
436
|
+
};
|
|
437
|
+
};
|
|
438
|
+
|
|
417
439
|
/**
|
|
418
440
|
* Build search documents from the content graph. Only indexable pages are
|
|
419
441
|
* included (per the route manifest), and content comes from the source files,
|
|
@@ -531,7 +553,7 @@ export const buildSearchDocuments = async (
|
|
|
531
553
|
if (facets) {
|
|
532
554
|
document.facets = facets;
|
|
533
555
|
}
|
|
534
|
-
return document;
|
|
556
|
+
return { ...document, ...rankingFields(page?.meta?.search) };
|
|
535
557
|
})
|
|
536
558
|
);
|
|
537
559
|
};
|
|
@@ -539,13 +561,15 @@ export const buildSearchDocuments = async (
|
|
|
539
561
|
/**
|
|
540
562
|
* Map per-page search documents to the flat record shape hosted backends
|
|
541
563
|
* ingest. One record per page, keyed by route; the first tag becomes the
|
|
542
|
-
* faceting `tag
|
|
564
|
+
* faceting `tag`, and every record carries a `boost` (1 by default).
|
|
543
565
|
*/
|
|
544
566
|
export const toSearchRecords = (documents: SearchDocument[]): SearchRecord[] =>
|
|
545
567
|
documents.map((doc) => ({
|
|
546
568
|
_id: doc.route,
|
|
569
|
+
boost: doc.boost ?? 1,
|
|
547
570
|
content: doc.content,
|
|
548
571
|
description: doc.description,
|
|
572
|
+
...(doc.keywords && { keywords: doc.keywords }),
|
|
549
573
|
locale: doc.locale,
|
|
550
574
|
tag: doc.tags?.[0],
|
|
551
575
|
title: doc.title,
|
|
@@ -31,6 +31,10 @@ export interface OramaDoc {
|
|
|
31
31
|
* but not indexed, so they ride along on the returned document untouched. */
|
|
32
32
|
breadcrumb?: string[];
|
|
33
33
|
section?: string;
|
|
34
|
+
/** The page's relevance multiplier (`search.boost`); stored, not indexed. */
|
|
35
|
+
boost?: number;
|
|
36
|
+
/** Extra terms the page is found by (`search.keywords`). */
|
|
37
|
+
keywords?: string[];
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
const SCHEMA = {
|
|
@@ -42,6 +46,7 @@ const SCHEMA = {
|
|
|
42
46
|
// serves every project's facet keys, with `containsAll` matching a filter
|
|
43
47
|
// set. Derived from `facets` at insert time.
|
|
44
48
|
facetTerms: "enum[]",
|
|
49
|
+
keywords: "string[]",
|
|
45
50
|
locale: "enum",
|
|
46
51
|
route: "string",
|
|
47
52
|
title: "string",
|
|
@@ -52,8 +57,21 @@ const SCHEMA = {
|
|
|
52
57
|
const toFacetTerms = (facets: Record<string, string>): string[] =>
|
|
53
58
|
Object.entries(facets).map(([key, value]) => `${key}:${value}`);
|
|
54
59
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Title and description outrank body text, matching the search dialog; a
|
|
62
|
+
* page's keywords count as much as its title, since its author chose them.
|
|
63
|
+
*/
|
|
64
|
+
const BOOST = { description: 2, keywords: 3, title: 3 };
|
|
65
|
+
|
|
66
|
+
/** Databases holding a boosted page, which rank by boosted score. */
|
|
67
|
+
const boostedDatabases = new WeakSet<AnyOrama>();
|
|
68
|
+
|
|
69
|
+
/** A search match as Orama's sort function sees it: id, score, document. */
|
|
70
|
+
type RankedMatch = [unknown, number, OramaDoc];
|
|
71
|
+
|
|
72
|
+
/** Rank matches by relevance times the page's `search.boost`. */
|
|
73
|
+
const byBoostedScore = (a: RankedMatch, b: RankedMatch): number =>
|
|
74
|
+
b[1] * (b[2].boost ?? 1) - a[1] * (a[2].boost ?? 1);
|
|
57
75
|
|
|
58
76
|
/**
|
|
59
77
|
* The script whose text Orama's default tokenizer keeps mostly intact. Its
|
|
@@ -322,6 +340,9 @@ export const buildOramaIndex = async (
|
|
|
322
340
|
doc.facets ? { ...doc, facetTerms: toFacetTerms(doc.facets) } : doc
|
|
323
341
|
)
|
|
324
342
|
);
|
|
343
|
+
if (documents.some((doc) => (doc.boost ?? 1) !== 1)) {
|
|
344
|
+
boostedDatabases.add(db);
|
|
345
|
+
}
|
|
325
346
|
return db;
|
|
326
347
|
};
|
|
327
348
|
|
|
@@ -391,10 +412,14 @@ export const queryOramaIndex = async (
|
|
|
391
412
|
if (facetTerms.length > 0) {
|
|
392
413
|
where.facetTerms = { containsAll: facetTerms };
|
|
393
414
|
}
|
|
415
|
+
// Boosts multiply every match's score before the limit applies (Orama
|
|
416
|
+
// sorts the whole match set with a sort function), so a boosted page can
|
|
417
|
+
// rise past matches outside the first page.
|
|
394
418
|
const unfiltered = {
|
|
395
419
|
boost: BOOST,
|
|
396
420
|
limit,
|
|
397
|
-
properties: ["title", "description", "content"],
|
|
421
|
+
properties: ["title", "keywords", "description", "content"],
|
|
422
|
+
...(boostedDatabases.has(db) && { sortBy: byBoostedScore }),
|
|
398
423
|
term,
|
|
399
424
|
};
|
|
400
425
|
const params =
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page's `search.boost` and `search.keywords`, as Pagefind reads them from
|
|
3
|
+
* the built page. Pagefind has no page-level boost, only element weights, so
|
|
4
|
+
* the page's body carries its boost as its weight: a boosted page's matches
|
|
5
|
+
* count for more and it ranks higher. Pagefind damps repeated matches, so the
|
|
6
|
+
* lift is smaller than the full multiplier: a boost of 5 scored its page about
|
|
7
|
+
* 1.5 to 2 times higher in testing. A boost past Pagefind's largest weight,
|
|
8
|
+
* 10, counts as 10.
|
|
9
|
+
* Keywords go on an empty element as an indexed attribute, found like text
|
|
10
|
+
* and never shown.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** The largest weight Pagefind takes. */
|
|
14
|
+
const MAX_WEIGHT = 10;
|
|
15
|
+
|
|
16
|
+
/** The attribute a page's keywords ride on. */
|
|
17
|
+
export const KEYWORDS_ATTRIBUTE = "data-blume-keywords";
|
|
18
|
+
|
|
19
|
+
/** What the page body carries for Pagefind. */
|
|
20
|
+
export interface PagefindRanking {
|
|
21
|
+
/** `data-pagefind-weight` for the body, when the page is boosted. */
|
|
22
|
+
weight?: string;
|
|
23
|
+
/** The keywords, space-separated, when the page sets any. */
|
|
24
|
+
keywords?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const pagefindRanking = (search: {
|
|
28
|
+
boost?: number;
|
|
29
|
+
keywords?: string[];
|
|
30
|
+
}): PagefindRanking => {
|
|
31
|
+
const { boost, keywords } = search;
|
|
32
|
+
return {
|
|
33
|
+
...(boost !== undefined &&
|
|
34
|
+
boost !== 1 && {
|
|
35
|
+
weight: String(Math.min(boost, MAX_WEIGHT)),
|
|
36
|
+
}),
|
|
37
|
+
...(keywords && keywords.length > 0 && { keywords: keywords.join(" ") }),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
@@ -16,6 +16,9 @@ export type AlgoliaSyncConfig = Pick<AlgoliaOptions, "appId" | "indexName">;
|
|
|
16
16
|
*/
|
|
17
17
|
const FILTER_ATTRIBUTES = ["locale", "version"] as const;
|
|
18
18
|
|
|
19
|
+
/** The custom ranking rule that orders close matches by `search.boost`. */
|
|
20
|
+
const BOOST_RANKING = "desc(boost)";
|
|
21
|
+
|
|
19
22
|
/** A faceting declaration's attribute: `filterOnly(locale)` → `locale`. */
|
|
20
23
|
const facetAttribute = (declaration: string): string =>
|
|
21
24
|
declaration.replace(/^\w+\((?<name>.*)\)$/u, "$<name>");
|
|
@@ -48,12 +51,17 @@ export const syncAlgolia = async (
|
|
|
48
51
|
indexName,
|
|
49
52
|
objects: records.map((record) => ({ ...record, objectID: record._id })),
|
|
50
53
|
});
|
|
51
|
-
const { attributesForFaceting = []
|
|
52
|
-
indexName
|
|
53
|
-
});
|
|
54
|
+
const { attributesForFaceting = [], customRanking = [] } =
|
|
55
|
+
await client.getSettings({ indexName });
|
|
54
56
|
const declared = new Set(attributesForFaceting.map(facetAttribute));
|
|
55
57
|
const missing = FILTER_ATTRIBUTES.filter((name) => !declared.has(name));
|
|
56
|
-
|
|
58
|
+
// Every record carries `boost` (search.boost, 1 by default): ranking by it
|
|
59
|
+
// after textual relevance puts boosted pages first among close matches.
|
|
60
|
+
// A ranking the site set in the dashboard keeps its place ahead of it.
|
|
61
|
+
const ranksByBoost = customRanking.some((rule) =>
|
|
62
|
+
/^(?:asc|desc)\(boost\)$/u.test(rule)
|
|
63
|
+
);
|
|
64
|
+
if (missing.length === 0 && ranksByBoost) {
|
|
57
65
|
return;
|
|
58
66
|
}
|
|
59
67
|
const { taskID } = await client.setSettings({
|
|
@@ -63,6 +71,9 @@ export const syncAlgolia = async (
|
|
|
63
71
|
...attributesForFaceting,
|
|
64
72
|
...missing.map((name) => `filterOnly(${name})`),
|
|
65
73
|
],
|
|
74
|
+
customRanking: ranksByBoost
|
|
75
|
+
? customRanking
|
|
76
|
+
: [...customRanking, BOOST_RANKING],
|
|
66
77
|
},
|
|
67
78
|
});
|
|
68
79
|
await client.waitForTask({ indexName, taskID });
|
|
@@ -31,9 +31,12 @@ export const syncOramaCloud = async (
|
|
|
31
31
|
const index = manager.index(config.indexId);
|
|
32
32
|
await index.snapshot(
|
|
33
33
|
records.map((record) => ({
|
|
34
|
+
// The dialog multiplies each hit's score by it (search.boost).
|
|
35
|
+
boost: record.boost,
|
|
34
36
|
content: record.content,
|
|
35
37
|
description: record.description,
|
|
36
38
|
id: record._id,
|
|
39
|
+
keywords: record.keywords ?? [],
|
|
37
40
|
// Carried so an i18n site can filter hosted results per language.
|
|
38
41
|
locale: record.locale,
|
|
39
42
|
tag: record.tag,
|
|
@@ -57,6 +57,9 @@ export const syncTypesense = async (
|
|
|
57
57
|
{ name: "description", optional: true, type: "string" },
|
|
58
58
|
{ name: "content", type: "string" },
|
|
59
59
|
{ name: "url", type: "string" },
|
|
60
|
+
{ name: "keywords", optional: true, type: "string[]" },
|
|
61
|
+
// The dialog sorts close matches by it (search.boost, 1 by default).
|
|
62
|
+
{ name: "boost", type: "float" },
|
|
60
63
|
{ facet: true, name: "tag", optional: true, type: "string" },
|
|
61
64
|
// Carried as facets so hosted results can filter per language and per
|
|
62
65
|
// docs version (the SearchRecord contract; current docs = "current").
|
|
@@ -67,9 +70,11 @@ export const syncTypesense = async (
|
|
|
67
70
|
});
|
|
68
71
|
|
|
69
72
|
const documents = records.map((record) => ({
|
|
73
|
+
boost: record.boost,
|
|
70
74
|
content: record.content,
|
|
71
75
|
description: record.description,
|
|
72
76
|
id: record._id,
|
|
77
|
+
keywords: record.keywords ?? [],
|
|
73
78
|
locale: record.locale,
|
|
74
79
|
tag: record.tag,
|
|
75
80
|
title: record.title,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site-wide meta tags (`seo.metatags`): tags written into every page's head,
|
|
3
|
+
* for what Blume has no setting of its own for, such as site-verification
|
|
4
|
+
* tokens, `theme-color`, or an app banner. The tags Blume writes itself are
|
|
5
|
+
* refused, each pointing at the setting that controls it, so a page never
|
|
6
|
+
* carries two values for one tag.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const IMAGE =
|
|
10
|
+
"`seo.og` for the generated cards, or `seo.image` in a page's frontmatter";
|
|
11
|
+
const TITLE = "`title`, or `seo.title` in a page's frontmatter";
|
|
12
|
+
const DESCRIPTION =
|
|
13
|
+
"`description` in a page's frontmatter or the config, or a page's `seo.description`";
|
|
14
|
+
const FROM_PAGE = "Blume sets it from each page's type and dates";
|
|
15
|
+
|
|
16
|
+
/** The tags Blume writes itself, by lowercase name, and what sets each. */
|
|
17
|
+
const OWNED = new Map([
|
|
18
|
+
["article:modified_time", FROM_PAGE],
|
|
19
|
+
["article:published_time", FROM_PAGE],
|
|
20
|
+
["description", DESCRIPTION],
|
|
21
|
+
["og:description", DESCRIPTION],
|
|
22
|
+
["og:site_name", "the config's `title`"],
|
|
23
|
+
["og:title", TITLE],
|
|
24
|
+
["og:type", FROM_PAGE],
|
|
25
|
+
["og:url", "`deployment.site`, which sets each page's canonical URL"],
|
|
26
|
+
["robots", "`seo.noindex` in a page's frontmatter"],
|
|
27
|
+
["twitter:card", "Blume sets it from each page's image"],
|
|
28
|
+
["twitter:creator", "`seo.x.creator` in the config or a page's frontmatter"],
|
|
29
|
+
["twitter:description", DESCRIPTION],
|
|
30
|
+
["twitter:site", "`seo.x.handle`"],
|
|
31
|
+
["twitter:title", TITLE],
|
|
32
|
+
["viewport", "Blume sets it for every page"],
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
/** `og:image` and `twitter:image`, and their sub-properties. */
|
|
36
|
+
const IMAGE_TAG = /^(?:og|twitter):image(?::|$)/u;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* What sets a tag Blume writes itself, or `undefined` for a tag the site can
|
|
40
|
+
* add.
|
|
41
|
+
*/
|
|
42
|
+
export const ownedMetatag = (name: string): string | undefined => {
|
|
43
|
+
const key = name.trim().toLowerCase();
|
|
44
|
+
return IMAGE_TAG.test(key) ? IMAGE : OWNED.get(key);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The Open Graph families, which the protocol names with `property`; every
|
|
49
|
+
* other tag takes `name`.
|
|
50
|
+
*/
|
|
51
|
+
const PROPERTY_TAG =
|
|
52
|
+
/^(?:og|fb|article|book|profile|music|video|al|product):/iu;
|
|
53
|
+
|
|
54
|
+
/** One site-wide tag, as its `<meta>` element's attributes. */
|
|
55
|
+
export interface MetatagAttributes {
|
|
56
|
+
content: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
property?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The site's `seo.metatags` as `<meta>` attributes, in the order written. */
|
|
62
|
+
export const metatagAttributes = (
|
|
63
|
+
tags?: Readonly<Record<string, string>> | null
|
|
64
|
+
): MetatagAttributes[] =>
|
|
65
|
+
Object.entries(tags ?? {}).map(([name, content]) =>
|
|
66
|
+
PROPERTY_TAG.test(name) ? { content, property: name } : { content, name }
|
|
67
|
+
);
|
|
@@ -19,6 +19,7 @@ interface ChromeIconSet {
|
|
|
19
19
|
|
|
20
20
|
export const chromeIcons: ChromeIconSet = {
|
|
21
21
|
check: '<path d="M20 6 9 17l-5-5"/>',
|
|
22
|
+
"chevron-down": '<path d="m6 9 6 6 6-6"/>',
|
|
22
23
|
copy: '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
|
|
23
24
|
file: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
|
|
24
25
|
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
|