blume 2.0.3 → 2.1.1
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 +142 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-5f86nr5m.js → chunk-073rdvgb.js} +21 -48
- package/dist/cli/{chunk-5f86nr5m.js.map → chunk-073rdvgb.js.map} +4 -5
- package/dist/cli/{chunk-ga0pf4aj.js → chunk-0n9xkp3d.js} +12 -3
- package/dist/cli/{chunk-ga0pf4aj.js.map → chunk-0n9xkp3d.js.map} +3 -3
- package/dist/cli/chunk-150jk7km.js +52 -0
- package/dist/cli/chunk-150jk7km.js.map +10 -0
- package/dist/cli/{chunk-9t7a85s3.js → chunk-313vnn40.js} +30 -19
- package/dist/cli/chunk-313vnn40.js.map +10 -0
- package/dist/cli/{chunk-tkacnehg.js → chunk-3v3ffbrj.js} +2 -2
- package/dist/cli/{chunk-q58y5e6a.js → chunk-3x6x3pph.js} +46 -24
- package/dist/cli/chunk-3x6x3pph.js.map +10 -0
- package/dist/cli/{chunk-1d7ve1dm.js → chunk-42t4jxdx.js} +37 -5
- package/dist/cli/chunk-42t4jxdx.js.map +12 -0
- package/dist/cli/{chunk-8ktnccpt.js → chunk-54ztw1nj.js} +21 -2
- package/dist/cli/chunk-54ztw1nj.js.map +10 -0
- package/dist/cli/{chunk-hm3vjy5s.js → chunk-5vjtgs6e.js} +28 -10
- package/dist/cli/{chunk-hm3vjy5s.js.map → chunk-5vjtgs6e.js.map} +4 -4
- package/dist/cli/chunk-66w9j7p6.js +322 -0
- package/dist/cli/chunk-66w9j7p6.js.map +14 -0
- package/dist/cli/{chunk-2hsdwb9n.js → chunk-7ppf1rva.js} +34 -18
- package/dist/cli/chunk-7ppf1rva.js.map +10 -0
- package/dist/cli/{chunk-r9rcc4w7.js → chunk-8y2kmjv8.js} +7 -7
- package/dist/cli/{chunk-r20tn01b.js → chunk-c1y538kd.js} +8 -2
- package/dist/cli/{chunk-r20tn01b.js.map → chunk-c1y538kd.js.map} +3 -3
- package/dist/cli/{chunk-2eytanqx.js → chunk-c887fj0s.js} +2 -2
- package/dist/cli/{chunk-p3v96n38.js → chunk-cfyg47zg.js} +16 -6
- package/dist/cli/chunk-cfyg47zg.js.map +10 -0
- package/dist/cli/chunk-cm89scdv.js +443 -0
- package/dist/cli/chunk-cm89scdv.js.map +11 -0
- package/dist/cli/{chunk-akbpwfxc.js → chunk-dzwehpy7.js} +2 -2
- package/dist/cli/{chunk-wrr3j9w9.js → chunk-e6atmn4h.js} +299 -72
- package/dist/cli/chunk-e6atmn4h.js.map +36 -0
- package/dist/cli/{chunk-tzmab476.js → chunk-e8xw9qkh.js} +8 -6
- package/dist/cli/{chunk-tzmab476.js.map → chunk-e8xw9qkh.js.map} +3 -3
- package/dist/cli/{chunk-6dsbexzp.js → chunk-e9q7a2ak.js} +17 -15
- package/dist/cli/{chunk-6dsbexzp.js.map → chunk-e9q7a2ak.js.map} +2 -2
- package/dist/cli/{chunk-pv29h0wf.js → chunk-ehweg4fr.js} +5155 -1249
- package/dist/cli/chunk-ehweg4fr.js.map +218 -0
- package/dist/cli/{chunk-c8chx29p.js → chunk-ep6nd0rn.js} +390 -318
- package/dist/cli/chunk-ep6nd0rn.js.map +36 -0
- package/dist/cli/{chunk-p73c0m7w.js → chunk-fdrkhpjy.js} +18 -15
- package/dist/cli/{chunk-p73c0m7w.js.map → chunk-fdrkhpjy.js.map} +3 -3
- package/dist/cli/{chunk-2z928egk.js → chunk-ft9vfys6.js} +5 -5
- package/dist/cli/{chunk-5m5nmvyq.js → chunk-gb420vvz.js} +316 -79
- package/dist/cli/chunk-gb420vvz.js.map +17 -0
- package/dist/cli/{chunk-vg9r4eb9.js → chunk-hebn0tpx.js} +86 -13
- package/dist/cli/chunk-hebn0tpx.js.map +14 -0
- package/dist/cli/{chunk-35d4wj9f.js → chunk-htrwgabr.js} +41 -23
- package/dist/cli/chunk-htrwgabr.js.map +15 -0
- package/dist/cli/{chunk-g4hq16wv.js → chunk-hx3xqnk9.js} +16 -14
- package/dist/cli/{chunk-g4hq16wv.js.map → chunk-hx3xqnk9.js.map} +2 -2
- package/dist/cli/chunk-j134vrp2.js +65 -0
- package/dist/cli/chunk-j134vrp2.js.map +10 -0
- package/dist/cli/{chunk-a58773jm.js → chunk-kma9f6sf.js} +2 -2
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-ey84smr6.js → chunk-mrcccgnh.js} +6 -6
- package/dist/cli/{chunk-ey84smr6.js.map → chunk-mrcccgnh.js.map} +1 -1
- package/dist/cli/{chunk-yfyb25rh.js → chunk-n0v7m8ry.js} +24 -8
- package/dist/cli/chunk-n0v7m8ry.js.map +14 -0
- package/dist/cli/{chunk-j85vccga.js → chunk-ncj7w596.js} +576 -367
- package/dist/cli/chunk-ncj7w596.js.map +35 -0
- package/dist/cli/{chunk-5xvm6tfj.js → chunk-nq1d20w2.js} +17 -15
- package/dist/cli/{chunk-5xvm6tfj.js.map → chunk-nq1d20w2.js.map} +2 -2
- package/dist/cli/{chunk-pehfxfta.js → chunk-nrns10mj.js} +6 -98
- package/dist/cli/chunk-nrns10mj.js.map +10 -0
- package/dist/cli/{chunk-crgn1q09.js → chunk-sds923fa.js} +4 -4
- package/dist/cli/chunk-t2m5j4pr.js +104 -0
- package/dist/cli/chunk-t2m5j4pr.js.map +11 -0
- package/dist/cli/{chunk-acanzt5p.js → chunk-tdqhyxfz.js} +6 -6
- package/dist/cli/chunk-tphac74f.js +126 -0
- package/dist/cli/chunk-tphac74f.js.map +11 -0
- package/dist/cli/chunk-w1r4vase.js +18 -0
- package/dist/cli/chunk-w1r4vase.js.map +10 -0
- 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/index.js +31 -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 +26 -0
- package/dist/types/ai/ask.d.ts +271 -58
- package/dist/types/ai/component-markdown.d.ts +2 -0
- package/dist/types/ai/index.d.ts +2 -2
- package/dist/types/ai/openapi-components.d.ts +4 -1
- 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 +9 -0
- package/dist/types/astro/markdown-negotiation.d.ts +3 -0
- package/dist/types/astro/pages.d.ts +10 -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/cli/init/scaffold.d.ts +1 -1
- package/dist/types/components/content/api-field.d.ts +62 -0
- package/dist/types/components/content/api-page.d.ts +100 -0
- package/dist/types/components/layout/nav-utils.d.ts +26 -0
- package/dist/types/components/openapi/code-samples.d.ts +29 -0
- package/dist/types/components/openapi/helpers.d.ts +158 -0
- package/dist/types/components/openapi/security.d.ts +95 -0
- package/dist/types/components/openapi/snippets.d.ts +60 -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/changelog-index.d.ts +8 -0
- package/dist/types/core/config-input.d.ts +217 -23
- package/dist/types/core/data.d.ts +53 -4
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/i18n-ui.d.ts +88 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/links.d.ts +8 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/page-modes.d.ts +29 -0
- package/dist/types/core/probe.d.ts +2 -0
- package/dist/types/core/project-graph.d.ts +6 -0
- package/dist/types/core/redirect-patterns.d.ts +152 -0
- package/dist/types/core/related.d.ts +40 -0
- package/dist/types/core/schema.d.ts +670 -56
- package/dist/types/core/sources/cache.d.ts +3 -0
- package/dist/types/core/sources/contentful-rich-text.d.ts +11 -2
- package/dist/types/core/sources/contentful.d.ts +17 -1
- package/dist/types/core/sources/lexical.d.ts +5 -0
- package/dist/types/core/sources/normalize.d.ts +20 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/remote.d.ts +37 -10
- package/dist/types/core/sources/sanity.d.ts +1 -1
- package/dist/types/core/types.d.ts +18 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/adapter-output.d.ts +2 -1
- package/dist/types/deploy/adapters/cloudflare.d.ts +7 -5
- package/dist/types/deploy/adapters/node.d.ts +58 -23
- package/dist/types/deploy/adapters/registry.d.ts +10 -0
- package/dist/types/deploy/adapters/types.d.ts +3 -2
- package/dist/types/deploy/node-headers.d.ts +13 -2
- package/dist/types/deploy/platforms/cloudflare.d.ts +15 -7
- package/dist/types/deploy/platforms/netlify.d.ts +7 -0
- package/dist/types/deploy/platforms/node.d.ts +3 -1
- package/dist/types/deploy/platforms/types.d.ts +18 -7
- package/dist/types/deploy/platforms/vercel.d.ts +26 -1
- package/dist/types/deploy/redirects.d.ts +55 -11
- package/dist/types/deploy/sitemap.d.ts +2 -1
- package/dist/types/deploy/vercel-negotiation.d.ts +51 -21
- 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/checks.d.ts +27 -0
- package/dist/types/openapi/graphql.d.ts +6 -0
- package/dist/types/openapi/model.d.ts +27 -10
- package/dist/types/openapi/overlay.d.ts +54 -0
- package/dist/types/openapi/parse.d.ts +16 -0
- package/dist/types/openapi/references.d.ts +30 -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 +119 -0
- package/dist/types/ratelimit/upstash.d.ts +46 -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 +89 -11
- package/dist/types/reference/index.d.ts +1 -1
- package/dist/types/reference/openapi.d.ts +33 -12
- package/dist/types/reference/options.d.ts +82 -6
- package/dist/types/reference/scalar.d.ts +13 -0
- package/dist/types/reference/schema.d.ts +144 -36
- package/dist/types/search/adapters/registry.d.ts +3 -0
- package/dist/types/search/adapters/typesense.d.ts +14 -4
- package/dist/types/search/build.d.ts +3 -1
- package/dist/types/search/documents.d.ts +12 -1
- package/dist/types/search/orama-index.d.ts +13 -9
- package/dist/types/search/sync/algolia.d.ts +5 -2
- package/dist/types/search/sync/index.d.ts +3 -1
- package/dist/types/search/sync/typesense.d.ts +11 -7
- package/dist/types/seo/metatags.d.ts +20 -0
- package/dist/types/sources/contentful.d.ts +16 -0
- package/dist/types/sources/github-releases.d.ts +8 -1
- package/dist/types/sources/payload.d.ts +2 -0
- package/dist/types/sources/registry.d.ts +54 -0
- package/dist/types/sources/shared.d.ts +3 -0
- package/dist/types/sources/strapi.d.ts +2 -0
- package/docs/01-quickstart.mdx +4 -2
- package/docs/02-deployment.mdx +71 -17
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/advanced/changelog.mdx +13 -2
- package/docs/advanced/skills.mdx +15 -1
- package/docs/cli/audit.mdx +51 -8
- package/docs/cli/doctor.mdx +5 -1
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +1 -0
- package/docs/cli/validate.mdx +4 -3
- package/docs/configuration/analytics.mdx +3 -1
- package/docs/configuration/assistant.mdx +194 -38
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +8 -6
- package/docs/configuration/export.mdx +5 -3
- package/docs/configuration/index.mdx +64 -5
- package/docs/configuration/meta.ts +3 -0
- package/docs/configuration/narration.mdx +119 -0
- package/docs/configuration/rate-limiting.mdx +90 -0
- package/docs/configuration/search.mdx +63 -12
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +108 -2
- package/docs/content/frontmatter.mdx +80 -2
- package/docs/content/i18n.mdx +39 -4
- 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 +32 -10
- package/docs/content/sources/contentful.mdx +35 -0
- package/docs/content/sources/custom.mdx +43 -0
- package/docs/content/sources/github-releases.mdx +39 -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 +31 -0
- package/docs/content/sources/remote-mdx.mdx +44 -0
- package/docs/content/sources/sanity.mdx +29 -0
- package/docs/content/sources/strapi.mdx +32 -0
- package/docs/content/syntax.mdx +69 -4
- package/docs/content/variables.mdx +41 -0
- package/docs/discoverability/agent-discovery.mdx +29 -2
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/json-api.mdx +2 -0
- package/docs/discoverability/llms-txt.mdx +2 -2
- package/docs/discoverability/markdown.mdx +3 -1
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +4 -2
- package/docs/references/graphql.mdx +14 -2
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +148 -7
- package/docs/references/scalar.mdx +3 -1
- package/package.json +43 -8
- package/skills/blume/SKILL.md +3 -0
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +7 -7
- package/skills/blume-migrate/references/fumadocs.md +17 -13
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +10 -7
- package/skills/blume-migrate/references/starlight.md +11 -8
- 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 +103 -20
- package/src/ai/ask-data.ts +6 -0
- package/src/ai/ask-limits.ts +13 -0
- package/src/ai/ask-tools.ts +142 -0
- package/src/ai/ask.ts +354 -76
- package/src/ai/changelog-markdown.ts +11 -6
- package/src/ai/component-markdown.ts +76 -1
- package/src/ai/cors.ts +14 -3
- 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/mcp/query.ts +14 -6
- package/src/ai/openapi-components.ts +36 -6
- package/src/ai/relative-links.ts +4 -38
- 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 +210 -66
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +42 -4
- package/src/astro/markdown-negotiation.ts +3 -0
- package/src/astro/module-types.ts +9 -1
- package/src/astro/pages.ts +23 -0
- package/src/astro/templates.ts +406 -108
- package/src/astro/variables.ts +31 -0
- package/src/audit/catalog.ts +38 -3
- 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 +19 -13
- package/src/audit/checks/network.ts +110 -6
- package/src/audit/checks/redirects.ts +6 -4
- package/src/audit/checks/robots.ts +58 -2
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +5 -0
- package/src/audit/types.ts +4 -1
- package/src/audit/url.ts +14 -5
- package/src/blume-modules.d.ts +12 -1
- 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/args.ts +51 -0
- package/src/cli/build-failure.ts +23 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +10 -1
- package/src/cli/commands/dev.ts +18 -2
- package/src/cli/commands/doctor.ts +67 -28
- package/src/cli/commands/eval.ts +36 -4
- package/src/cli/commands/init.ts +6 -4
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/validate.ts +10 -1
- package/src/cli/index.ts +5 -0
- package/src/cli/init/scaffold.ts +3 -3
- package/src/cli/prepare.ts +6 -3
- package/src/cli/required-secrets.ts +72 -30
- 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 +66 -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 +103 -0
- package/src/components/content/api-page.ts +248 -0
- package/src/components/content/mermaid-element.ts +15 -1
- 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 +136 -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/PageActions.astro +139 -24
- 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 +318 -47
- package/src/components/layout/Search.astro +47 -19
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/TableOfContents.astro +24 -17
- 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/head-scripts.ts +6 -1
- 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/algolia.ts +10 -1
- package/src/components/layout/search/analytics.ts +126 -0
- package/src/components/layout/search/endpoint.ts +48 -5
- package/src/components/layout/search/flexsearch.ts +17 -5
- package/src/components/layout/search/orama-cloud.ts +26 -14
- package/src/components/layout/search/pagefind.ts +114 -5
- package/src/components/layout/search/types.ts +32 -0
- package/src/components/layout/search/typesense.ts +19 -3
- package/src/components/openapi/AsyncApiOperation.astro +14 -7
- package/src/components/openapi/Authorization.astro +10 -0
- package/src/components/openapi/GraphqlOperation.astro +5 -0
- package/src/components/openapi/Operation.astro +184 -13
- package/src/components/openapi/ParametersTable.astro +8 -4
- package/src/components/openapi/Playground.astro +7 -1
- package/src/components/openapi/RequestBody.astro +24 -5
- package/src/components/openapi/RequestPanel.astro +59 -7
- package/src/components/openapi/Responses.astro +7 -4
- package/src/components/openapi/SchemaTable.astro +15 -3
- package/src/components/openapi/async-snippets.ts +65 -9
- package/src/components/openapi/async.ts +123 -29
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/graphql-helpers.ts +35 -12
- package/src/components/openapi/helpers.ts +36 -2
- package/src/components/openapi/message-model.ts +54 -11
- package/src/components/openapi/message.ts +14 -4
- package/src/components/openapi/operation-model.ts +16 -7
- package/src/components/openapi/playground-client.ts +24 -5
- package/src/components/openapi/playground-schema.ts +1 -1
- package/src/components/openapi/schema-tree.ts +44 -14
- package/src/components/openapi/security.ts +16 -3
- package/src/components/openapi/snippets.ts +873 -10
- package/src/components/openapi/validate-json.ts +4 -3
- 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/changelog-index.ts +31 -1
- package/src/core/client-address.ts +19 -0
- package/src/core/component-diagnostics.ts +53 -0
- package/src/core/component-overrides.ts +32 -0
- package/src/core/config-input.ts +225 -23
- package/src/core/data.ts +46 -4
- package/src/core/diagnostics.ts +8 -2
- package/src/core/footer.ts +26 -0
- package/src/core/graph.ts +39 -36
- package/src/core/i18n-ui.ts +84 -2
- package/src/core/includes.ts +21 -4
- package/src/core/links.ts +63 -19
- 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/probe.ts +26 -6
- package/src/core/project-graph.ts +163 -16
- package/src/core/redirect-patterns.ts +520 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +339 -66
- package/src/core/sources/cache.ts +6 -0
- package/src/core/sources/contentful-rich-text.ts +29 -6
- package/src/core/sources/contentful.ts +135 -25
- package/src/core/sources/filesystem.ts +26 -4
- package/src/core/sources/github-releases.ts +12 -1
- package/src/core/sources/lexical.ts +15 -10
- package/src/core/sources/mdx-remote.ts +62 -10
- package/src/core/sources/normalize.ts +228 -41
- package/src/core/sources/notion.ts +7 -1
- package/src/core/sources/obsidian.ts +16 -3
- package/src/core/sources/payload.ts +38 -15
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/remote.ts +72 -15
- package/src/core/sources/resolve.ts +11 -1
- package/src/core/sources/sanity.ts +43 -25
- package/src/core/sources/strapi.ts +2 -2
- package/src/core/types.ts +19 -1
- package/src/core/ui-packs/ar.ts +44 -0
- package/src/core/ui-packs/bg.ts +46 -0
- package/src/core/ui-packs/bn.ts +44 -0
- package/src/core/ui-packs/ca.ts +47 -0
- package/src/core/ui-packs/cs.ts +45 -0
- package/src/core/ui-packs/da.ts +46 -0
- package/src/core/ui-packs/de.ts +47 -0
- package/src/core/ui-packs/el.ts +46 -0
- package/src/core/ui-packs/es.ts +47 -0
- package/src/core/ui-packs/fa.ts +46 -0
- package/src/core/ui-packs/fi.ts +46 -0
- package/src/core/ui-packs/fr.ts +47 -0
- package/src/core/ui-packs/he.ts +44 -0
- package/src/core/ui-packs/hi.ts +45 -0
- package/src/core/ui-packs/hr.ts +46 -0
- package/src/core/ui-packs/hu.ts +45 -0
- package/src/core/ui-packs/id.ts +46 -0
- package/src/core/ui-packs/it.ts +46 -0
- package/src/core/ui-packs/ja.ts +46 -0
- package/src/core/ui-packs/ko.ts +45 -0
- package/src/core/ui-packs/nl.ts +47 -0
- package/src/core/ui-packs/no.ts +45 -0
- package/src/core/ui-packs/pl.ts +46 -0
- package/src/core/ui-packs/pt-br.ts +47 -0
- package/src/core/ui-packs/pt.ts +46 -0
- package/src/core/ui-packs/ro.ts +46 -0
- package/src/core/ui-packs/ru.ts +46 -0
- package/src/core/ui-packs/sk.ts +45 -0
- package/src/core/ui-packs/sr.ts +46 -0
- package/src/core/ui-packs/sv.ts +45 -0
- package/src/core/ui-packs/th.ts +44 -0
- package/src/core/ui-packs/tr.ts +46 -0
- package/src/core/ui-packs/uk.ts +46 -0
- package/src/core/ui-packs/vi.ts +46 -0
- package/src/core/ui-packs/zh-tw.ts +44 -0
- package/src/core/ui-packs/zh.ts +44 -0
- package/src/core/variables.ts +103 -0
- package/src/deploy/adapter-output.ts +7 -2
- package/src/deploy/adapters/cloudflare.ts +7 -5
- package/src/deploy/adapters/node.ts +47 -9
- package/src/deploy/adapters/types.ts +7 -3
- package/src/deploy/artifacts.ts +79 -9
- 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 +45 -9
- package/src/deploy/platforms/netlify.ts +35 -2
- package/src/deploy/platforms/node.ts +12 -2
- package/src/deploy/platforms/types.ts +21 -7
- package/src/deploy/platforms/vercel.ts +149 -17
- package/src/deploy/redirects.ts +151 -24
- package/src/deploy/rss.ts +3 -1
- package/src/deploy/sitemap.ts +39 -5
- package/src/deploy/vercel-negotiation.ts +326 -102
- package/src/eval/agents.ts +18 -1
- package/src/eval/prompts.ts +6 -3
- package/src/eval/report.ts +34 -13
- package/src/eval/run.ts +5 -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/relative-links.ts +3 -2
- 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/checks.ts +273 -0
- package/src/openapi/graphql-build.ts +21 -0
- package/src/openapi/graphql.ts +11 -0
- package/src/openapi/model.ts +72 -27
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +182 -4
- package/src/openapi/references.ts +64 -8
- package/src/openapi/render-mdx.ts +5 -1
- package/src/openapi/scalar.ts +40 -6
- package/src/openapi/source.ts +40 -15
- 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 +247 -0
- package/src/ratelimit/schema.ts +28 -0
- package/src/ratelimit/upstash.ts +60 -0
- package/src/ratelimit/wrangler.ts +70 -0
- package/src/reference/asyncapi.ts +2 -2
- package/src/reference/graphql.ts +20 -3
- package/src/reference/index.ts +1 -0
- package/src/reference/openapi.ts +21 -7
- package/src/reference/options.ts +80 -8
- package/src/reference/scalar.ts +33 -8
- package/src/registry/eject.ts +23 -5
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/locale-scope.ts +30 -0
- package/src/search/adapters/typesense.ts +14 -4
- package/src/search/build.ts +21 -3
- package/src/search/documents.ts +26 -2
- package/src/search/orama-index.ts +191 -34
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/source-pages.ts +29 -0
- package/src/search/sync/algolia.ts +111 -10
- package/src/search/sync/index.ts +52 -13
- package/src/search/sync/orama-cloud.ts +3 -0
- package/src/search/sync/typesense.ts +76 -26
- package/src/seo/metatags.ts +67 -0
- package/src/sources/contentful.ts +12 -0
- package/src/sources/github-releases.ts +7 -1
- package/src/sources/shared.ts +3 -0
- package/src/theme/chrome-icons.ts +1 -0
- package/src/theme/color.ts +500 -0
- package/src/theme/entry.ts +85 -6
- package/src/theme/palette.ts +121 -45
- package/src/translate/prompts.ts +1 -0
- package/src/translate/validate.ts +31 -3
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-1d7ve1dm.js.map +0 -12
- package/dist/cli/chunk-2hsdwb9n.js.map +0 -10
- package/dist/cli/chunk-35d4wj9f.js.map +0 -15
- 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-8ktnccpt.js.map +0 -10
- package/dist/cli/chunk-9t7a85s3.js.map +0 -10
- package/dist/cli/chunk-b07cmahc.js +0 -145
- package/dist/cli/chunk-b07cmahc.js.map +0 -11
- package/dist/cli/chunk-c8chx29p.js.map +0 -36
- package/dist/cli/chunk-e04dxsz1.js +0 -39
- package/dist/cli/chunk-e04dxsz1.js.map +0 -10
- package/dist/cli/chunk-j85vccga.js.map +0 -36
- package/dist/cli/chunk-p3v96n38.js.map +0 -10
- 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-vg9r4eb9.js.map +0 -14
- 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-tkacnehg.js.map → chunk-3v3ffbrj.js.map} +0 -0
- /package/dist/cli/{chunk-r9rcc4w7.js.map → chunk-8y2kmjv8.js.map} +0 -0
- /package/dist/cli/{chunk-2eytanqx.js.map → chunk-c887fj0s.js.map} +0 -0
- /package/dist/cli/{chunk-akbpwfxc.js.map → chunk-dzwehpy7.js.map} +0 -0
- /package/dist/cli/{chunk-2z928egk.js.map → chunk-ft9vfys6.js.map} +0 -0
- /package/dist/cli/{chunk-a58773jm.js.map → chunk-kma9f6sf.js.map} +0 -0
- /package/dist/cli/{chunk-crgn1q09.js.map → chunk-sds923fa.js.map} +0 -0
- /package/dist/cli/{chunk-acanzt5p.js.map → chunk-tdqhyxfz.js.map} +0 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narration's reading script: what "Listen to this page" says, in order, and
|
|
3
|
+
* where each spoken sentence sits in the page's text.
|
|
4
|
+
*
|
|
5
|
+
* One walker serves both sides. `blume build` runs it over the built HTML
|
|
6
|
+
* (parsed with node-html-parser) to generate a clip per sentence, and the
|
|
7
|
+
* player runs it over the live `<article>` to highlight the sentence being
|
|
8
|
+
* read. Both see the same markup and apply the same rules, so they agree on
|
|
9
|
+
* every block's text; the sentence split is only ever computed once per
|
|
10
|
+
* engine (by the build for generated audio, by the browser for its own
|
|
11
|
+
* voices), so an ICU difference between Node and the browser can't move a
|
|
12
|
+
* highlight.
|
|
13
|
+
*
|
|
14
|
+
* The rules follow what reads well aloud: the title, description, headings,
|
|
15
|
+
* and prose are read in order; callouts, steps, tabs, and collapsible
|
|
16
|
+
* sections are announced with a short spoken cue; code, tables, media,
|
|
17
|
+
* diagrams, math, API field tables, and anything marked
|
|
18
|
+
* `data-blume-narration="skip"` are left out.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The slice of a DOM-like tree the walker reads. The browser DOM and
|
|
23
|
+
* node-html-parser both satisfy it through a small adapter, so the walker
|
|
24
|
+
* never touches either API directly.
|
|
25
|
+
*/
|
|
26
|
+
export interface NarrationTree<N> {
|
|
27
|
+
/** An element's attribute, or `null` when it is absent. */
|
|
28
|
+
attr: (node: N, name: string) => string | null;
|
|
29
|
+
children: (node: N) => readonly N[];
|
|
30
|
+
/** An element's lowercase tag name; `null` for text and other nodes. */
|
|
31
|
+
tag: (node: N) => string | null;
|
|
32
|
+
/** A text node's decoded text; `null` for elements and other nodes. */
|
|
33
|
+
text: (node: N) => string | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The spoken cues, already localized for the page. `{n}` in `step` is the
|
|
38
|
+
* step's number and `{title}` in `tab` is the tab's label. An empty string
|
|
39
|
+
* silences that cue.
|
|
40
|
+
*/
|
|
41
|
+
export interface NarrationCues {
|
|
42
|
+
danger: string;
|
|
43
|
+
info: string;
|
|
44
|
+
note: string;
|
|
45
|
+
section: string;
|
|
46
|
+
step: string;
|
|
47
|
+
success: string;
|
|
48
|
+
tab: string;
|
|
49
|
+
tip: string;
|
|
50
|
+
warning: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* A run of text read as one unit: a paragraph, heading, list item, or any
|
|
55
|
+
* other block. `nodes` are the text nodes it was read from; `nodeOf[i]` and
|
|
56
|
+
* `offsetOf[i]` locate character `i` of `text` in them, so a sentence's
|
|
57
|
+
* `[start, end)` maps back to a DOM range for highlighting.
|
|
58
|
+
*/
|
|
59
|
+
export interface NarrationBlock<N> {
|
|
60
|
+
nodeOf: number[];
|
|
61
|
+
nodes: N[];
|
|
62
|
+
offsetOf: number[];
|
|
63
|
+
text: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** One step of the reading order: a spoken cue, or a block of page text. */
|
|
67
|
+
export type NarrationItem =
|
|
68
|
+
| { block: number; kind: "block" }
|
|
69
|
+
| { kind: "cue"; text: string };
|
|
70
|
+
|
|
71
|
+
/** What {@link extractNarration} reads from a page. */
|
|
72
|
+
export interface NarrationExtract<N> {
|
|
73
|
+
blocks: NarrationBlock<N>[];
|
|
74
|
+
items: NarrationItem[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* One spoken unit. A sentence carries its block and its `[start, end)` in
|
|
79
|
+
* that block's text; a cue carries `block: null`. `text` is what is spoken.
|
|
80
|
+
*/
|
|
81
|
+
export interface NarrationSegment {
|
|
82
|
+
block: number | null;
|
|
83
|
+
end: number;
|
|
84
|
+
start: number;
|
|
85
|
+
text: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The per-page manifest `blume build` writes for generated narration. The
|
|
90
|
+
* player aligns `blocks` (the build's block texts) with the live page by
|
|
91
|
+
* text, then plays one clip per segment. A sentence names its block and range;
|
|
92
|
+
* a cue carries its spoken `text` instead.
|
|
93
|
+
*/
|
|
94
|
+
export interface NarrationManifest {
|
|
95
|
+
blocks: string[];
|
|
96
|
+
segments: NarrationManifestSegment[];
|
|
97
|
+
version: 1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** One clip of a {@link NarrationManifest}: a sentence or a cue. */
|
|
101
|
+
export type NarrationManifestSegment =
|
|
102
|
+
| { audio: string; block: number; end: number; start: number }
|
|
103
|
+
| { audio: string; text: string };
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A page needs at least this many spoken characters (title and description
|
|
107
|
+
* included) to offer narration: roughly 50 English words. Shorter pages, and
|
|
108
|
+
* pages that are mostly code, tables, or API fields, get no player.
|
|
109
|
+
*/
|
|
110
|
+
export const NARRATION_MIN_CHARS = 280;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Sentences longer than this are split at a clause, then a word boundary.
|
|
114
|
+
* Browser speech engines drop an utterance that runs past about 15 seconds,
|
|
115
|
+
* and shorter clips also keep the highlight moving.
|
|
116
|
+
*/
|
|
117
|
+
export const NARRATION_MAX_SEGMENT_CHARS = 220;
|
|
118
|
+
|
|
119
|
+
/** The attribute that keeps an element (and everything in it) out of narration. */
|
|
120
|
+
export const NARRATION_SKIP_ATTRIBUTE = "data-blume-narration";
|
|
121
|
+
|
|
122
|
+
// Inline elements continue the current block; every other element starts and
|
|
123
|
+
// ends one, so a paragraph's text never runs into the heading after it. The
|
|
124
|
+
// replaced and form elements are inline too: an icon or a button mid-sentence
|
|
125
|
+
// is skipped without splitting the sentence around it.
|
|
126
|
+
const INLINE_TAGS = new Set([
|
|
127
|
+
"a",
|
|
128
|
+
"abbr",
|
|
129
|
+
"audio",
|
|
130
|
+
"button",
|
|
131
|
+
"canvas",
|
|
132
|
+
"embed",
|
|
133
|
+
"iframe",
|
|
134
|
+
"img",
|
|
135
|
+
"input",
|
|
136
|
+
"math",
|
|
137
|
+
"noscript",
|
|
138
|
+
"object",
|
|
139
|
+
"picture",
|
|
140
|
+
"script",
|
|
141
|
+
"select",
|
|
142
|
+
"style",
|
|
143
|
+
"svg",
|
|
144
|
+
"template",
|
|
145
|
+
"textarea",
|
|
146
|
+
"video",
|
|
147
|
+
"b",
|
|
148
|
+
"bdi",
|
|
149
|
+
"bdo",
|
|
150
|
+
"cite",
|
|
151
|
+
"code",
|
|
152
|
+
"data",
|
|
153
|
+
"del",
|
|
154
|
+
"dfn",
|
|
155
|
+
"em",
|
|
156
|
+
"font",
|
|
157
|
+
"i",
|
|
158
|
+
"ins",
|
|
159
|
+
"kbd",
|
|
160
|
+
"label",
|
|
161
|
+
"mark",
|
|
162
|
+
"q",
|
|
163
|
+
"s",
|
|
164
|
+
"samp",
|
|
165
|
+
"small",
|
|
166
|
+
"span",
|
|
167
|
+
"strong",
|
|
168
|
+
"sub",
|
|
169
|
+
"sup",
|
|
170
|
+
"time",
|
|
171
|
+
"u",
|
|
172
|
+
"var",
|
|
173
|
+
"wbr",
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
// Content that doesn't read well aloud, or isn't content at all.
|
|
177
|
+
const SKIPPED_TAGS = new Set([
|
|
178
|
+
"audio",
|
|
179
|
+
"blume-diff",
|
|
180
|
+
"blume-mermaid",
|
|
181
|
+
"button",
|
|
182
|
+
"canvas",
|
|
183
|
+
"dialog",
|
|
184
|
+
"embed",
|
|
185
|
+
"figure",
|
|
186
|
+
"iframe",
|
|
187
|
+
"img",
|
|
188
|
+
"input",
|
|
189
|
+
"math",
|
|
190
|
+
"nav",
|
|
191
|
+
"noscript",
|
|
192
|
+
"object",
|
|
193
|
+
"picture",
|
|
194
|
+
"pre",
|
|
195
|
+
"script",
|
|
196
|
+
"select",
|
|
197
|
+
"style",
|
|
198
|
+
"svg",
|
|
199
|
+
"table",
|
|
200
|
+
"template",
|
|
201
|
+
"textarea",
|
|
202
|
+
"video",
|
|
203
|
+
]);
|
|
204
|
+
|
|
205
|
+
// Classes whose text is for screen readers or renderers, not for listening:
|
|
206
|
+
// visually hidden hints, and KaTeX's glyph layer (its MathML is `math`).
|
|
207
|
+
const SKIPPED_CLASSES = ["sr-only", "katex"];
|
|
208
|
+
|
|
209
|
+
const CALLOUT_KINDS = new Set([
|
|
210
|
+
"danger",
|
|
211
|
+
"info",
|
|
212
|
+
"note",
|
|
213
|
+
"success",
|
|
214
|
+
"tip",
|
|
215
|
+
"warning",
|
|
216
|
+
]);
|
|
217
|
+
|
|
218
|
+
const isCalloutKind = (
|
|
219
|
+
kind: string
|
|
220
|
+
): kind is "danger" | "info" | "note" | "success" | "tip" | "warning" =>
|
|
221
|
+
CALLOUT_KINDS.has(kind);
|
|
222
|
+
|
|
223
|
+
const WHITESPACE = /\s/u;
|
|
224
|
+
|
|
225
|
+
const hasClass = (classes: string | null, name: string): boolean =>
|
|
226
|
+
classes !== null && classes.split(/\s+/u).includes(name);
|
|
227
|
+
|
|
228
|
+
const isSkipped = <N>(
|
|
229
|
+
tree: NarrationTree<N>,
|
|
230
|
+
node: N,
|
|
231
|
+
tag: string
|
|
232
|
+
): boolean => {
|
|
233
|
+
if (SKIPPED_TAGS.has(tag)) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
if (tree.attr(node, NARRATION_SKIP_ATTRIBUTE) === "skip") {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
if (tree.attr(node, "aria-hidden") === "true") {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
const classes = tree.attr(node, "class");
|
|
243
|
+
return SKIPPED_CLASSES.some((name) => hasClass(classes, name));
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/** The spoken cue that introduces an element, or `null` for none. */
|
|
247
|
+
const cueFor = <N>(
|
|
248
|
+
tree: NarrationTree<N>,
|
|
249
|
+
node: N,
|
|
250
|
+
tag: string,
|
|
251
|
+
cues: NarrationCues,
|
|
252
|
+
stepNumber: number
|
|
253
|
+
): string | null => {
|
|
254
|
+
const callout = tree.attr(node, "data-blume-callout");
|
|
255
|
+
if (callout !== null) {
|
|
256
|
+
return isCalloutKind(callout) ? cues[callout] : cues.note;
|
|
257
|
+
}
|
|
258
|
+
if (tree.attr(node, "data-blume-step") !== null) {
|
|
259
|
+
return cues.step.replaceAll("{n}", String(stepNumber));
|
|
260
|
+
}
|
|
261
|
+
if (tree.attr(node, "data-blume-tab-panel") !== null) {
|
|
262
|
+
const title = tree.attr(node, "data-title");
|
|
263
|
+
return title ? cues.tab.replaceAll("{title}", title) : null;
|
|
264
|
+
}
|
|
265
|
+
return tag === "details" ? cues.section : null;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Walk `root` and return its reading order: each block of text, with the
|
|
270
|
+
* cues that introduce callouts, steps, tabs, and collapsible sections.
|
|
271
|
+
* Whitespace collapses to single spaces, as it renders.
|
|
272
|
+
*/
|
|
273
|
+
export const extractNarration = <N>(
|
|
274
|
+
root: N,
|
|
275
|
+
tree: NarrationTree<N>,
|
|
276
|
+
cues: NarrationCues
|
|
277
|
+
): NarrationExtract<N> => {
|
|
278
|
+
const blocks: NarrationBlock<N>[] = [];
|
|
279
|
+
const items: NarrationItem[] = [];
|
|
280
|
+
|
|
281
|
+
let text = "";
|
|
282
|
+
let nodes: N[] = [];
|
|
283
|
+
let nodeOf: number[] = [];
|
|
284
|
+
let offsetOf: number[] = [];
|
|
285
|
+
let pendingSpace = false;
|
|
286
|
+
// Cues wait here until their element yields text: a tab of nothing but
|
|
287
|
+
// code, or a step holding only a snippet, isn't announced into silence.
|
|
288
|
+
let pendingCues: string[] = [];
|
|
289
|
+
|
|
290
|
+
const flush = (): void => {
|
|
291
|
+
if (text) {
|
|
292
|
+
for (const cue of pendingCues) {
|
|
293
|
+
items.push({ kind: "cue", text: cue });
|
|
294
|
+
}
|
|
295
|
+
pendingCues = [];
|
|
296
|
+
items.push({ block: blocks.length, kind: "block" });
|
|
297
|
+
blocks.push({ nodeOf, nodes, offsetOf, text });
|
|
298
|
+
}
|
|
299
|
+
text = "";
|
|
300
|
+
nodes = [];
|
|
301
|
+
nodeOf = [];
|
|
302
|
+
offsetOf = [];
|
|
303
|
+
pendingSpace = false;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const append = (node: N, value: string): void => {
|
|
307
|
+
let index = -1;
|
|
308
|
+
for (let offset = 0; offset < value.length; offset += 1) {
|
|
309
|
+
const char = value.charAt(offset);
|
|
310
|
+
if (WHITESPACE.test(char)) {
|
|
311
|
+
pendingSpace = text.length > 0;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (index === -1) {
|
|
315
|
+
index = nodes.length;
|
|
316
|
+
nodes.push(node);
|
|
317
|
+
}
|
|
318
|
+
// A collapsed run of whitespace maps to the character after it, so a
|
|
319
|
+
// range that starts on the space starts on the word instead.
|
|
320
|
+
if (pendingSpace) {
|
|
321
|
+
text += " ";
|
|
322
|
+
nodeOf.push(index);
|
|
323
|
+
offsetOf.push(offset);
|
|
324
|
+
pendingSpace = false;
|
|
325
|
+
}
|
|
326
|
+
text += char;
|
|
327
|
+
nodeOf.push(index);
|
|
328
|
+
offsetOf.push(offset);
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const visit = (node: N, stepNumber: number): void => {
|
|
333
|
+
const value = tree.text(node);
|
|
334
|
+
if (value !== null) {
|
|
335
|
+
append(node, value);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const tag = tree.tag(node);
|
|
339
|
+
if (tag === "br") {
|
|
340
|
+
pendingSpace = text.length > 0;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// A node with no tag (a parsed document's root) is transparent.
|
|
344
|
+
const inline = tag === null || INLINE_TAGS.has(tag);
|
|
345
|
+
if (tag !== null && isSkipped(tree, node, tag)) {
|
|
346
|
+
if (!inline) {
|
|
347
|
+
flush();
|
|
348
|
+
}
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (!inline) {
|
|
352
|
+
flush();
|
|
353
|
+
}
|
|
354
|
+
const cue = tag === null ? null : cueFor(tree, node, tag, cues, stepNumber);
|
|
355
|
+
const cueSlot = pendingCues.length;
|
|
356
|
+
if (cue) {
|
|
357
|
+
pendingCues.push(cue);
|
|
358
|
+
}
|
|
359
|
+
// Steps are numbered among their siblings, the way the CSS counter that
|
|
360
|
+
// draws their markers counts them.
|
|
361
|
+
let steps = 0;
|
|
362
|
+
for (const child of tree.children(node)) {
|
|
363
|
+
if (tree.attr(child, "data-blume-step") !== null) {
|
|
364
|
+
steps += 1;
|
|
365
|
+
}
|
|
366
|
+
visit(child, steps);
|
|
367
|
+
}
|
|
368
|
+
if (!inline) {
|
|
369
|
+
flush();
|
|
370
|
+
}
|
|
371
|
+
// Still pending: nothing inside was read, so drop the cue.
|
|
372
|
+
pendingCues = pendingCues.slice(0, cueSlot);
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
visit(root, 0);
|
|
376
|
+
flush();
|
|
377
|
+
return { blocks, items };
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
const CLAUSE_BREAK = /[,;:–—]\s/gu;
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Split `[start, end)` of `text` into pieces no longer than `max`: after the
|
|
384
|
+
* last clause mark that leaves a piece at least 40% of `max` long, else at
|
|
385
|
+
* the last space, else hard at `max`.
|
|
386
|
+
*/
|
|
387
|
+
const splitLong = (
|
|
388
|
+
text: string,
|
|
389
|
+
start: number,
|
|
390
|
+
end: number,
|
|
391
|
+
max: number
|
|
392
|
+
): [number, number][] => {
|
|
393
|
+
const pieces: [number, number][] = [];
|
|
394
|
+
let from = start;
|
|
395
|
+
while (end - from > max) {
|
|
396
|
+
const window = text.slice(from, from + max);
|
|
397
|
+
const floor = Math.floor(max * 0.4);
|
|
398
|
+
let cut = -1;
|
|
399
|
+
for (const match of window.matchAll(CLAUSE_BREAK)) {
|
|
400
|
+
if (match.index >= floor) {
|
|
401
|
+
cut = match.index + 1;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if (cut === -1) {
|
|
405
|
+
const space = window.lastIndexOf(" ");
|
|
406
|
+
cut = space >= floor ? space : max;
|
|
407
|
+
}
|
|
408
|
+
pieces.push([from, from + cut]);
|
|
409
|
+
from += cut;
|
|
410
|
+
while (from < end && text.charAt(from) === " ") {
|
|
411
|
+
from += 1;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
pieces.push([from, end]);
|
|
415
|
+
return pieces;
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The sentence spans of `text`. ICU can end a sentence at an ASCII `.`, `?`,
|
|
420
|
+
* or `!` with no space after it, which splits inline code like
|
|
421
|
+
* `?install=windows` in two; a break that isn't followed by whitespace is
|
|
422
|
+
* merged back into one sentence.
|
|
423
|
+
* Full-width terminators (`。`, `?`) end a sentence without a space and keep
|
|
424
|
+
* their breaks.
|
|
425
|
+
*/
|
|
426
|
+
const sentenceSpans = (
|
|
427
|
+
segmenter: Intl.Segmenter,
|
|
428
|
+
text: string
|
|
429
|
+
): [number, number][] => {
|
|
430
|
+
const spans: [number, number][] = [];
|
|
431
|
+
let merge = false;
|
|
432
|
+
for (const { index, segment } of segmenter.segment(text)) {
|
|
433
|
+
const end = index + segment.length;
|
|
434
|
+
const last = spans.at(-1);
|
|
435
|
+
if (merge && last) {
|
|
436
|
+
last[1] = end;
|
|
437
|
+
} else {
|
|
438
|
+
spans.push([index, end]);
|
|
439
|
+
}
|
|
440
|
+
merge = end < text.length && /[.?!]$/u.test(segment);
|
|
441
|
+
}
|
|
442
|
+
return spans;
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Split an extract into spoken segments: a cue as it is, and each block into
|
|
447
|
+
* its sentences for `lang` (long ones broken further, see
|
|
448
|
+
* {@link NARRATION_MAX_SEGMENT_CHARS}).
|
|
449
|
+
*/
|
|
450
|
+
export const segmentNarration = <N>(
|
|
451
|
+
extract: NarrationExtract<N>,
|
|
452
|
+
lang: string,
|
|
453
|
+
max: number = NARRATION_MAX_SEGMENT_CHARS
|
|
454
|
+
): NarrationSegment[] => {
|
|
455
|
+
const segmenter = new Intl.Segmenter(lang, { granularity: "sentence" });
|
|
456
|
+
const segments: NarrationSegment[] = [];
|
|
457
|
+
for (const item of extract.items) {
|
|
458
|
+
if (item.kind === "cue") {
|
|
459
|
+
segments.push({ block: null, end: 0, start: 0, text: item.text });
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const text = extract.blocks[item.block]?.text ?? "";
|
|
463
|
+
// A sentence span carries its trailing space (ICU attaches whitespace to
|
|
464
|
+
// the sentence it follows, and a block never starts with one), so only
|
|
465
|
+
// the end needs trimming.
|
|
466
|
+
for (const [start, spanEnd] of sentenceSpans(segmenter, text)) {
|
|
467
|
+
let end = spanEnd;
|
|
468
|
+
while (end > start && text.charAt(end - 1) === " ") {
|
|
469
|
+
end -= 1;
|
|
470
|
+
}
|
|
471
|
+
for (const [from, to] of splitLong(text, start, end, max)) {
|
|
472
|
+
segments.push({
|
|
473
|
+
block: item.block,
|
|
474
|
+
end: to,
|
|
475
|
+
start: from,
|
|
476
|
+
text: text.slice(from, to),
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return segments;
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
/** How many characters the page's blocks will speak, cues excluded. */
|
|
485
|
+
export const narrationLength = <N>(extract: NarrationExtract<N>): number =>
|
|
486
|
+
extract.blocks.reduce((total, block) => total + block.text.length, 0);
|
|
487
|
+
|
|
488
|
+
/** Whether a page has enough prose to offer narration. */
|
|
489
|
+
export const isNarratable = <N>(extract: NarrationExtract<N>): boolean =>
|
|
490
|
+
narrationLength(extract) >= NARRATION_MIN_CHARS;
|
|
491
|
+
|
|
492
|
+
const DENSE_SCRIPT_LANGS = new Set(["ja", "ko", "zh"]);
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Characters read per second at 1x: about 170 words a minute for
|
|
496
|
+
* space-separated scripts, and a slower character rate where a character is a
|
|
497
|
+
* word or more. Estimates only; neither engine reports durations up front.
|
|
498
|
+
*/
|
|
499
|
+
export const narrationCharsPerSecond = (lang: string): number => {
|
|
500
|
+
const [primary = ""] = lang.toLowerCase().replaceAll("_", "-").split("-");
|
|
501
|
+
return DENSE_SCRIPT_LANGS.has(primary) ? 7 : 15;
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
/** The page's estimated listening time at 1x, in whole minutes (at least 1). */
|
|
505
|
+
export const narrationMinutes = (chars: number, lang: string): number =>
|
|
506
|
+
Math.max(1, Math.round(chars / narrationCharsPerSecond(lang) / 60));
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Map each manifest block onto the live page's blocks by exact text, in
|
|
510
|
+
* order, skipping page blocks the build didn't see (a script may have added
|
|
511
|
+
* some). A manifest block with no match maps to `-1`: its sentences still
|
|
512
|
+
* play, without a highlight.
|
|
513
|
+
*/
|
|
514
|
+
export const alignBlocks = (
|
|
515
|
+
manifestBlocks: readonly string[],
|
|
516
|
+
pageBlocks: readonly string[]
|
|
517
|
+
): number[] => {
|
|
518
|
+
const aligned: number[] = [];
|
|
519
|
+
let next = 0;
|
|
520
|
+
for (const text of manifestBlocks) {
|
|
521
|
+
const found = pageBlocks.indexOf(text, next);
|
|
522
|
+
aligned.push(found);
|
|
523
|
+
if (found !== -1) {
|
|
524
|
+
next = found + 1;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return aligned;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const isString = (value: unknown): value is string => typeof value === "string";
|
|
531
|
+
|
|
532
|
+
const isManifestSegment = (
|
|
533
|
+
value: unknown
|
|
534
|
+
): value is NarrationManifestSegment => {
|
|
535
|
+
if (typeof value !== "object" || value === null) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
if (!("audio" in value) || typeof value.audio !== "string") {
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
if ("text" in value && typeof value.text === "string") {
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
return (
|
|
545
|
+
"block" in value &&
|
|
546
|
+
"start" in value &&
|
|
547
|
+
"end" in value &&
|
|
548
|
+
Number.isInteger(value.block) &&
|
|
549
|
+
Number.isInteger(value.start) &&
|
|
550
|
+
Number.isInteger(value.end)
|
|
551
|
+
);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const CUE_KEYS = [
|
|
555
|
+
"danger",
|
|
556
|
+
"info",
|
|
557
|
+
"note",
|
|
558
|
+
"section",
|
|
559
|
+
"step",
|
|
560
|
+
"success",
|
|
561
|
+
"tab",
|
|
562
|
+
"tip",
|
|
563
|
+
"warning",
|
|
564
|
+
] as const;
|
|
565
|
+
|
|
566
|
+
/** Whether a value (the player's `data-cues` JSON) is a full set of cues. */
|
|
567
|
+
export const isNarrationCues = (value: unknown): value is NarrationCues => {
|
|
568
|
+
if (typeof value !== "object" || value === null) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
const fields = new Map<string, unknown>(Object.entries(value));
|
|
572
|
+
return CUE_KEYS.every((key) => isString(fields.get(key)));
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
/** Whether a fetched manifest has the shape the player plays. */
|
|
576
|
+
export const isNarrationManifest = (
|
|
577
|
+
value: unknown
|
|
578
|
+
): value is NarrationManifest => {
|
|
579
|
+
if (typeof value !== "object" || value === null) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
return (
|
|
583
|
+
"version" in value &&
|
|
584
|
+
value.version === 1 &&
|
|
585
|
+
"blocks" in value &&
|
|
586
|
+
Array.isArray(value.blocks) &&
|
|
587
|
+
value.blocks.every(isString) &&
|
|
588
|
+
"segments" in value &&
|
|
589
|
+
Array.isArray(value.segments) &&
|
|
590
|
+
value.segments.every(isManifestSegment)
|
|
591
|
+
);
|
|
592
|
+
};
|
package/src/og/card.ts
CHANGED
|
@@ -63,15 +63,16 @@ export interface OgFontFamilies {
|
|
|
63
63
|
title?: string;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
// Named presets resolve from the theme's own OKLCH table
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
66
|
+
// Named presets resolve from the theme's own OKLCH table, in their light
|
|
67
|
+
// shade since the card is light — Takumi parses the full CSS color grammar,
|
|
68
|
+
// so the card renders exactly the accent the site shows (a separate
|
|
69
|
+
// hand-synced hex palette used to drift: the card's "blue" was Tailwind's,
|
|
70
|
+
// not Blume's). Anything else is handed to Takumi as-is, and a genuinely
|
|
71
|
+
// malformed value fails the build with a parse error naming it.
|
|
71
72
|
// `isAccentPreset` keeps a preset name like "constructor" from resolving up
|
|
72
73
|
// the prototype chain.
|
|
73
74
|
const resolveAccent = (accent: string): string =>
|
|
74
|
-
isAccentPreset(accent) ? ACCENTS[accent] : accent;
|
|
75
|
+
isAccentPreset(accent) ? ACCENTS[accent].light : accent;
|
|
75
76
|
|
|
76
77
|
export interface OgCardPalette {
|
|
77
78
|
accent?: string;
|