blume 2.0.2 → 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 +113 -0
- package/README.md +1 -1
- package/dist/cli/{chunk-6k4ftwze.js → chunk-0gz36akr.js} +18 -2
- package/dist/cli/{chunk-6k4ftwze.js.map → chunk-0gz36akr.js.map} +3 -3
- package/dist/cli/{chunk-zp79m0ts.js → chunk-1n228xqb.js} +2 -2
- package/dist/cli/{chunk-ddndchfr.js → chunk-2f3ndkpw.js} +57 -29
- package/dist/cli/chunk-2f3ndkpw.js.map +14 -0
- package/dist/cli/{chunk-fsmrqk8a.js → chunk-2f7n4qzt.js} +8 -2
- package/dist/cli/{chunk-fsmrqk8a.js.map → chunk-2f7n4qzt.js.map} +3 -3
- package/dist/cli/{chunk-ppzjqwx2.js → chunk-2n4ps1b3.js} +17 -18
- package/dist/cli/{chunk-ppzjqwx2.js.map → chunk-2n4ps1b3.js.map} +3 -3
- package/dist/cli/{chunk-5r8g91qn.js → chunk-43zcf4gz.js} +398 -159
- package/dist/cli/chunk-43zcf4gz.js.map +36 -0
- package/dist/cli/chunk-4ejxbzee.js +39 -0
- package/dist/cli/chunk-4ejxbzee.js.map +10 -0
- package/dist/cli/{chunk-hr8ne106.js → chunk-4h8z9vv5.js} +296 -77
- package/dist/cli/chunk-4h8z9vv5.js.map +16 -0
- package/dist/cli/{chunk-k7pj68a8.js → chunk-5cg2rzgf.js} +18 -16
- package/dist/cli/{chunk-k7pj68a8.js.map → chunk-5cg2rzgf.js.map} +2 -2
- package/dist/cli/{chunk-h2ez8dzb.js → chunk-5ffywsmb.js} +4 -4
- package/dist/cli/{chunk-g698a744.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-3yce002v.js → chunk-713mngdq.js} +15 -8
- package/dist/cli/chunk-713mngdq.js.map +14 -0
- package/dist/cli/{chunk-91ws1n6j.js → chunk-9geymqns.js} +17 -15
- package/dist/cli/{chunk-91ws1n6j.js.map → chunk-9geymqns.js.map} +2 -2
- package/dist/cli/{chunk-6dtt0zfn.js → chunk-9xr4dgee.js} +27 -18
- package/dist/cli/chunk-9xr4dgee.js.map +10 -0
- package/dist/cli/{chunk-sqw4ekg1.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-wdrt2k2v.js → chunk-cmtcnh9f.js} +90 -26
- package/dist/cli/chunk-cmtcnh9f.js.map +10 -0
- package/dist/cli/chunk-cy5bm0j0.js +104 -0
- package/dist/cli/chunk-cy5bm0j0.js.map +11 -0
- package/dist/cli/{chunk-7mbqtmgb.js → chunk-e7vvk5ya.js} +21 -12
- package/dist/cli/chunk-e7vvk5ya.js.map +10 -0
- package/dist/cli/{chunk-qkb5a8sa.js → chunk-g2z9x01v.js} +6 -98
- package/dist/cli/chunk-g2z9x01v.js.map +10 -0
- package/dist/cli/{chunk-273ygyr4.js → chunk-ga0pf4aj.js} +7 -7
- package/dist/cli/{chunk-273ygyr4.js.map → chunk-ga0pf4aj.js.map} +3 -3
- package/dist/cli/{chunk-h7k3nq3v.js → chunk-gp84za0h.js} +2 -2
- package/dist/cli/{chunk-hqp2ajnh.js → chunk-h2a48yhd.js} +195 -73
- package/dist/cli/chunk-h2a48yhd.js.map +36 -0
- package/dist/cli/{chunk-pbg5a4s3.js → chunk-jnwj5kjj.js} +71 -47
- package/dist/cli/chunk-jnwj5kjj.js.map +19 -0
- package/dist/cli/chunk-kwrg63tm.js +31 -0
- package/dist/cli/chunk-kwrg63tm.js.map +10 -0
- package/dist/cli/{chunk-8g8ytmgx.js → chunk-nd1821sk.js} +21 -19
- package/dist/cli/{chunk-8g8ytmgx.js.map → chunk-nd1821sk.js.map} +2 -2
- package/dist/cli/{chunk-w4bxdvsa.js → chunk-ntv4qrt7.js} +17 -15
- package/dist/cli/{chunk-w4bxdvsa.js.map → chunk-ntv4qrt7.js.map} +2 -2
- package/dist/cli/{chunk-bfwp9vp6.js → chunk-p804f3pe.js} +7 -7
- package/dist/cli/{chunk-4e9b9ra6.js → chunk-py62fewh.js} +6 -6
- package/dist/cli/{chunk-4e9b9ra6.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-j85scx15.js → chunk-rekqz4qg.js} +142 -46
- package/dist/cli/chunk-rekqz4qg.js.map +10 -0
- package/dist/cli/{chunk-00gs3wqs.js → chunk-ta27feev.js} +1 -1
- package/dist/cli/{chunk-v6ya5kcb.js → chunk-xkz5xbtv.js} +4031 -1132
- package/dist/cli/chunk-xkz5xbtv.js.map +213 -0
- package/dist/cli/{chunk-bbnwccaz.js → chunk-y4ykbark.js} +2 -2
- package/dist/cli/{chunk-n1yg3tj3.js → chunk-y5h4r2y0.js} +6 -6
- package/dist/cli/{chunk-n1yg3tj3.js.map → chunk-y5h4r2y0.js.map} +3 -3
- package/dist/cli/{chunk-nfcyttvj.js → chunk-yj17vs39.js} +9 -9
- package/dist/cli/{chunk-nfcyttvj.js.map → chunk-yj17vs39.js.map} +1 -1
- package/dist/cli/{chunk-7vtckvaw.js → chunk-yxmk4pec.js} +16 -14
- package/dist/cli/{chunk-7vtckvaw.js.map → chunk-yxmk4pec.js.map} +2 -2
- package/dist/cli/{chunk-gs7r695n.js → chunk-zccbjcj0.js} +22 -8
- package/dist/cli/{chunk-gs7r695n.js.map → chunk-zccbjcj0.js.map} +3 -3
- package/dist/cli/{chunk-xh43dwgw.js → chunk-zf748avv.js} +415 -225
- package/dist/cli/chunk-zf748avv.js.map +37 -0
- package/dist/cli/{chunk-d1v5rhy0.js → chunk-zhvd6adm.js} +16 -14
- package/dist/cli/{chunk-d1v5rhy0.js.map → chunk-zhvd6adm.js.map} +2 -2
- package/dist/cli/index.js +28 -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/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +9 -7
- package/dist/types/ai/site-skill.d.ts +26 -0
- package/dist/types/ai/skills.d.ts +20 -1
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- package/dist/types/astro/integration.d.ts +11 -2
- 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/base-path.d.ts +21 -7
- package/dist/types/core/config-input.d.ts +196 -27
- package/dist/types/core/data.d.ts +49 -4
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/footer.d.ts +8 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +86 -0
- package/dist/types/core/i18n.d.ts +2 -0
- package/dist/types/core/includes.d.ts +8 -2
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/localizable.d.ts +7 -0
- package/dist/types/core/meta.d.ts +8 -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 +558 -56
- package/dist/types/core/sources/lower.d.ts +29 -16
- package/dist/types/core/sources/normalize.d.ts +13 -1
- package/dist/types/core/sources/read.d.ts +5 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +1 -1
- package/dist/types/core/variables.d.ts +42 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/headers.d.ts +5 -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/types.d.ts +8 -0
- package/dist/types/deploy/platforms/vercel.d.ts +6 -0
- package/dist/types/deploy/redirects.d.ts +61 -22
- package/dist/types/deploy/vercel-negotiation.d.ts +9 -0
- package/dist/types/markdown/code-title.d.ts +17 -0
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +24 -2
- package/dist/types/markdown/mdast.d.ts +63 -0
- 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/asyncapi.d.ts +4 -2
- 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 +44 -6
- package/docs/03-upgrading.mdx +4 -4
- package/docs/04-migrating.mdx +1 -1
- package/docs/08-faq.mdx +1 -1
- package/docs/advanced/changelog.mdx +2 -2
- package/docs/advanced/custom-pages.mdx +3 -3
- package/docs/advanced/skills.mdx +14 -0
- package/docs/cli/audit.mdx +38 -2
- package/docs/cli/evals.mdx +3 -3
- package/docs/cli/index.mdx +1 -0
- package/docs/cli/translate.mdx +3 -3
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +22 -1
- package/docs/configuration/assistant.mdx +188 -36
- package/docs/configuration/consent.mdx +100 -0
- package/docs/configuration/customization.mdx +6 -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 +30 -2
- package/docs/configuration/theming.mdx +3 -1
- package/docs/content/components.mdx +107 -1
- package/docs/content/frontmatter.mdx +77 -1
- package/docs/content/i18n.mdx +40 -3
- package/docs/content/includes.mdx +16 -0
- package/docs/content/index.mdx +4 -2
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +4 -1
- 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 +81 -2
- package/docs/content/variables.mdx +41 -0
- package/docs/content/versioning.mdx +1 -1
- package/docs/discoverability/agent-discovery.mdx +30 -3
- package/docs/discoverability/index.mdx +3 -2
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/metadata.mdx +17 -0
- package/docs/discoverability/open-graph.mdx +5 -3
- package/docs/discoverability/rss.mdx +1 -1
- package/docs/references/api-pages.mdx +74 -0
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +3 -3
- package/docs/references/meta.ts +1 -1
- package/docs/references/openapi.mdx +106 -5
- 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/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +37 -19
- package/skills/blume-migrate/references/nextra.md +3 -3
- package/skills/blume-migrate/references/starlight.md +2 -1
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +67 -16
- package/skills/blume-write-skill/SKILL.md +82 -0
- package/src/ai/agent-readability.ts +4 -3
- package/src/ai/agent-surface.ts +9 -7
- package/src/ai/ai-catalog.ts +12 -5
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +2 -2
- package/src/ai/api-catalog.ts +6 -2
- package/src/ai/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/changelog-markdown.ts +2 -2
- 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/link-headers.ts +14 -5
- package/src/ai/llms.ts +24 -9
- package/src/ai/markdown.ts +2 -1
- package/src/ai/mcp/data.ts +6 -0
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +2 -2
- package/src/ai/openapi-components.ts +31 -9
- package/src/ai/relative-links.ts +42 -26
- package/src/ai/serializers.ts +2 -1
- package/src/ai/site-skill.ts +274 -0
- package/src/ai/skills.ts +49 -3
- package/src/ai/tar.ts +139 -12
- package/src/ai/write-skill.ts +97 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +5 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +133 -19
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +143 -14
- package/src/astro/module-types.ts +6 -0
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +364 -67
- 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 +13 -13
- package/src/audit/checks/sitemap.ts +2 -3
- package/src/audit/graph.ts +3 -1
- package/src/audit/redirects.ts +46 -15
- package/src/audit/run.ts +14 -9
- package/src/audit/snapshot.ts +5 -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/args.ts +32 -0
- package/src/cli/command-meta.ts +5 -0
- package/src/cli/commands/audit.ts +4 -1
- package/src/cli/commands/doctor.ts +25 -17
- package/src/cli/commands/eval.ts +19 -11
- package/src/cli/commands/skill.ts +96 -0
- package/src/cli/commands/translate.ts +9 -12
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/index.ts +5 -0
- package/src/cli/report-format.ts +11 -6
- package/src/cli/required-secrets.ts +71 -29
- package/src/components/BlumePage.astro +2 -0
- package/src/components/colors.ts +19 -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/Badge.astro +5 -3
- package/src/components/content/Color.astro +1 -0
- package/src/components/content/Component.astro +2 -2
- 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/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +4 -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/badge-color.ts +5 -2
- package/src/components/content/base-href.ts +13 -36
- 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 +125 -18
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/code-question.ts +62 -0
- package/src/components/islands/hooks.ts +145 -40
- package/src/components/islands/support-link.ts +68 -0
- package/src/components/islands/webmcp.ts +4 -2
- package/src/components/layout/Analytics.astro +11 -2
- package/src/components/layout/Banner.astro +14 -10
- package/src/components/layout/Breadcrumbs.astro +11 -4
- 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/DiscoveryLinks.astro +9 -5
- package/src/components/layout/DrawerSwitchers.astro +51 -0
- package/src/components/layout/Empty.astro +2 -2
- package/src/components/layout/Header.astro +124 -82
- package/src/components/layout/LanguageSwitcher.astro +50 -17
- package/src/components/layout/NarrationPlayer.astro +1085 -0
- package/src/components/layout/NavSelector.astro +35 -9
- package/src/components/layout/NavTabMenu.astro +3 -3
- package/src/components/layout/NavTree.astro +16 -6
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageFeedback.astro +54 -49
- package/src/components/layout/PageLayout.astro +78 -9
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +17 -6
- package/src/components/layout/RelatedPages.astro +42 -0
- package/src/components/layout/RootLayout.astro +313 -50
- package/src/components/layout/Search.astro +64 -30
- package/src/components/layout/SiteFooter.astro +124 -0
- package/src/components/layout/SiteMetatags.astro +16 -0
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +12 -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/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/Operation.astro +163 -13
- package/src/components/openapi/Playground.astro +4 -4
- package/src/components/openapi/RequestBody.astro +6 -4
- package/src/components/openapi/RequestPanel.astro +63 -8
- package/src/components/openapi/Responses.astro +6 -4
- package/src/components/openapi/SchemaTable.astro +7 -0
- package/src/components/openapi/async-snippets.ts +4 -4
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/code-samples.ts +97 -0
- package/src/components/openapi/helpers.ts +19 -9
- package/src/components/openapi/message-composer.ts +6 -1
- package/src/components/openapi/message.ts +17 -2
- package/src/components/openapi/operation-model.ts +81 -11
- package/src/components/openapi/panel.ts +4 -2
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +73 -16
- package/src/components/openapi/request.ts +190 -26
- package/src/components/openapi/schema-tree.ts +30 -24
- package/src/components/openapi/security.ts +6 -3
- package/src/components/openapi/snippets.ts +953 -13
- package/src/components/openapi/ws-client.ts +18 -2
- 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/base-path.ts +62 -16
- package/src/core/builtin-tags.ts +5 -0
- package/src/core/client-address.ts +19 -0
- package/src/core/config-input.ts +203 -27
- package/src/core/data.ts +44 -4
- package/src/core/diagnostics.ts +5 -2
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/footer.ts +26 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +39 -36
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +82 -2
- package/src/core/i18n.ts +31 -3
- package/src/core/includes.ts +20 -3
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +16 -2
- package/src/core/locale-links.ts +5 -1
- package/src/core/localizable.ts +26 -0
- package/src/core/meta.ts +33 -19
- package/src/core/navigation.ts +71 -20
- package/src/core/page-modes.ts +56 -0
- package/src/core/project-graph.ts +138 -10
- package/src/core/redirect-patterns.ts +491 -0
- package/src/core/related.ts +131 -0
- package/src/core/schema.ts +342 -68
- package/src/core/sources/contentful-rich-text.ts +27 -20
- package/src/core/sources/filesystem.ts +20 -2
- package/src/core/sources/github-releases.ts +25 -6
- package/src/core/sources/lexical.ts +23 -18
- package/src/core/sources/lower.ts +201 -34
- package/src/core/sources/mdx-remote.ts +51 -16
- package/src/core/sources/normalize.ts +327 -91
- package/src/core/sources/notion.ts +52 -25
- package/src/core/sources/obsidian.ts +23 -6
- package/src/core/sources/portable-text.ts +38 -21
- package/src/core/sources/read.ts +8 -3
- package/src/core/sources/strapi-blocks.ts +20 -16
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/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/core/version-cut.ts +52 -15
- package/src/deploy/artifacts.ts +104 -14
- package/src/deploy/cloudflare-negotiation.ts +39 -17
- package/src/deploy/headers.ts +10 -5
- package/src/deploy/node-headers.ts +64 -10
- package/src/deploy/platforms/cloudflare.ts +44 -3
- package/src/deploy/platforms/netlify.ts +34 -1
- package/src/deploy/platforms/node.ts +1 -0
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +8 -0
- package/src/deploy/platforms/vercel.ts +38 -2
- package/src/deploy/redirects.ts +139 -36
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +36 -0
- package/src/markdown/api-rail.ts +259 -0
- package/src/markdown/base-links.ts +34 -36
- package/src/markdown/code-title.ts +35 -9
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +20 -2
- package/src/markdown/fence-meta.ts +2 -0
- package/src/markdown/include.ts +11 -1
- package/src/markdown/index.ts +35 -11
- package/src/markdown/math.ts +4 -4
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +12 -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 +136 -11
- package/src/og/derive.ts +41 -31
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/model.ts +77 -36
- package/src/openapi/overlay.ts +256 -0
- package/src/openapi/parse.ts +55 -2
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/references.ts +21 -5
- package/src/openapi/render-mdx.ts +46 -3
- 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 +201 -28
- package/src/registry/registry.ts +7 -0
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +93 -28
- package/src/search/orama-index.ts +28 -3
- package/src/search/pagefind-ranking.ts +39 -0
- package/src/search/popular.ts +2 -1
- 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/jsonld.ts +7 -3
- 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/meta.ts +68 -24
- package/src/translate/prompts.ts +1 -0
- package/src/translate/run.ts +3 -3
- package/src/translate/validate.ts +12 -3
- package/src/translate/work-list.ts +56 -17
- package/src/upgrade/upgrade.ts +4 -171
- package/dist/cli/chunk-3yce002v.js.map +0 -14
- package/dist/cli/chunk-5r8g91qn.js.map +0 -34
- package/dist/cli/chunk-6dtt0zfn.js.map +0 -10
- package/dist/cli/chunk-7mbqtmgb.js.map +0 -10
- package/dist/cli/chunk-ddndchfr.js.map +0 -14
- package/dist/cli/chunk-esh98wmb.js +0 -23
- package/dist/cli/chunk-esh98wmb.js.map +0 -10
- package/dist/cli/chunk-hqp2ajnh.js.map +0 -35
- package/dist/cli/chunk-hr8ne106.js.map +0 -13
- package/dist/cli/chunk-j85scx15.js.map +0 -10
- package/dist/cli/chunk-mqc662a6.js +0 -75
- package/dist/cli/chunk-mqc662a6.js.map +0 -11
- package/dist/cli/chunk-pbg5a4s3.js.map +0 -19
- package/dist/cli/chunk-qkb5a8sa.js.map +0 -10
- package/dist/cli/chunk-v6ya5kcb.js.map +0 -189
- package/dist/cli/chunk-wdrt2k2v.js.map +0 -10
- package/dist/cli/chunk-xh43dwgw.js.map +0 -36
- package/docs/content/sources.mdx +0 -338
- /package/dist/cli/{chunk-zp79m0ts.js.map → chunk-1n228xqb.js.map} +0 -0
- /package/dist/cli/{chunk-h2ez8dzb.js.map → chunk-5ffywsmb.js.map} +0 -0
- /package/dist/cli/{chunk-g698a744.js.map → chunk-5wj2qwn2.js.map} +0 -0
- /package/dist/cli/{chunk-sqw4ekg1.js.map → chunk-amrb6hgh.js.map} +0 -0
- /package/dist/cli/{chunk-h7k3nq3v.js.map → chunk-gp84za0h.js.map} +0 -0
- /package/dist/cli/{chunk-bfwp9vp6.js.map → chunk-p804f3pe.js.map} +0 -0
- /package/dist/cli/{chunk-00gs3wqs.js.map → chunk-ta27feev.js.map} +0 -0
- /package/dist/cli/{chunk-bbnwccaz.js.map → chunk-y4ykbark.js.map} +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
+
import data from "blume:data";
|
|
3
|
+
|
|
2
4
|
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
3
5
|
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
4
6
|
import type { Navigation } from "../../core/types.ts";
|
|
7
|
+
import { scopesVersions } from "../../search/adapters/version-scope.ts";
|
|
5
8
|
import { resolvePopularIconMarkup } from "../../search/popular-icon.ts";
|
|
6
9
|
import Icon from "../Icon.astro";
|
|
10
|
+
import { withBase, withMountedBase } from "../islands/base-path.ts";
|
|
7
11
|
import { flattenPages } from "./nav-utils.ts";
|
|
8
12
|
|
|
9
13
|
// The provider-specific loader is generated per project into `blume:search-client`
|
|
@@ -21,7 +25,8 @@ interface Props {
|
|
|
21
25
|
locale?: string;
|
|
22
26
|
/**
|
|
23
27
|
* Docs version to filter results to (`""` = the current docs — a meaningful
|
|
24
|
-
* value, so `null`/omitted is what disables version filtering).
|
|
28
|
+
* value, so `null`/omitted is what disables version filtering). Ignored when
|
|
29
|
+
* the configured provider can't scope by version.
|
|
25
30
|
*/
|
|
26
31
|
version?: string | null;
|
|
27
32
|
}
|
|
@@ -32,27 +37,41 @@ const {
|
|
|
32
37
|
popularPages,
|
|
33
38
|
strings,
|
|
34
39
|
locale,
|
|
35
|
-
version = null,
|
|
40
|
+
version: viewedVersion = null,
|
|
36
41
|
} = Astro.props;
|
|
42
|
+
// Pagefind, Orama Cloud, and Mixedbread search every version whatever the
|
|
43
|
+
// dialog asks, so on those an "All versions" toggle would change nothing:
|
|
44
|
+
// treat the site as unversioned for search instead.
|
|
45
|
+
const version = scopesVersions(data.config.search.provider)
|
|
46
|
+
? viewedVersion
|
|
47
|
+
: null;
|
|
37
48
|
// Merge over the English baseline per key (rather than `strings ?? …`) so a
|
|
38
49
|
// partial — or empty `{}` — strings object still resolves every label to a
|
|
39
50
|
// default, matching the pattern PageActions uses for its own dictionary.
|
|
40
51
|
const s = { ...EN_UI.search, ...strings };
|
|
41
52
|
|
|
42
53
|
// Resolve popular icons to markup here (icon set is server-only). Unknown
|
|
43
|
-
// names fall through to the island's file glyph.
|
|
54
|
+
// names fall through to the island's file glyph. Each entry's served href is
|
|
55
|
+
// resolved here too: a configured entry is authored, so a base written by hand
|
|
56
|
+
// is kept (`withBase`); the sidebar fallback links generated routes, which
|
|
57
|
+
// always go under the base (`withMountedBase`).
|
|
44
58
|
const popular =
|
|
45
59
|
popularPages && popularPages.length > 0
|
|
46
60
|
? popularPages.map((page) => ({
|
|
61
|
+
href: withBase(page.route),
|
|
47
62
|
icon: resolvePopularIconMarkup(
|
|
48
63
|
page.icon,
|
|
49
64
|
import.meta.env.BASE_URL ?? "/"
|
|
50
65
|
),
|
|
51
66
|
label: page.label,
|
|
52
|
-
route: page.route,
|
|
53
67
|
}))
|
|
54
68
|
: navigation
|
|
55
|
-
? flattenPages(navigation.sidebar)
|
|
69
|
+
? flattenPages(navigation.sidebar)
|
|
70
|
+
.slice(0, 6)
|
|
71
|
+
.map((page) => ({
|
|
72
|
+
href: withMountedBase(page.route),
|
|
73
|
+
label: page.label,
|
|
74
|
+
}))
|
|
56
75
|
: [];
|
|
57
76
|
|
|
58
77
|
const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
@@ -74,23 +93,19 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
74
93
|
data-version={version ?? undefined}
|
|
75
94
|
data-versioned={version === null ? undefined : ""}
|
|
76
95
|
>
|
|
77
|
-
{/*
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
{/* An icon button like the theme toggle beside it, named with its shortcut
|
|
97
|
+
by the header's tooltip. The chord reads left to right in every locale
|
|
98
|
+
("⌘K", never "K⌘"), and the script swaps in Ctrl off Apple devices. */}
|
|
80
99
|
<button
|
|
100
|
+
aria-keyshortcuts="Meta+K Control+K"
|
|
81
101
|
aria-label={s.button}
|
|
82
|
-
class="inline-flex
|
|
102
|
+
class="inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
|
|
83
103
|
data-blume-search-open
|
|
104
|
+
data-blume-tooltip={`${s.button} (\u2066⌘K\u2069)`}
|
|
105
|
+
data-label={s.button}
|
|
84
106
|
type="button"
|
|
85
107
|
>
|
|
86
|
-
<Icon name="search" size={
|
|
87
|
-
<span class="flex-1 text-start max-lg:hidden">{s.button}</span>
|
|
88
|
-
{/* A key chord reads left to right in every locale ("⌘K", never "K⌘"). */}
|
|
89
|
-
<kbd
|
|
90
|
-
class="font-mono text-[0.7rem] max-lg:hidden"
|
|
91
|
-
data-blume-search-kbd
|
|
92
|
-
dir="ltr">⌘K</kbd
|
|
93
|
-
>
|
|
108
|
+
<Icon name="search" size={18} />
|
|
94
109
|
</button>
|
|
95
110
|
|
|
96
111
|
<dialog
|
|
@@ -220,9 +235,11 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
220
235
|
<script>
|
|
221
236
|
import { navigate } from "astro:transitions/client";
|
|
222
237
|
import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
|
|
223
|
-
import {
|
|
238
|
+
import { mountBase } from "../islands/base-path.ts";
|
|
224
239
|
import { escape as escapeHtml } from "html-escaper";
|
|
225
240
|
|
|
241
|
+
import { track } from "./analytics-client.ts";
|
|
242
|
+
import { createSearchTracker } from "./search/analytics.ts";
|
|
226
243
|
import { highlight, matchSnippet } from "./search/types.ts";
|
|
227
244
|
import type { SearchFn, SearchHit } from "./search/types.ts";
|
|
228
245
|
|
|
@@ -234,10 +251,11 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
234
251
|
}
|
|
235
252
|
|
|
236
253
|
interface PopularPage {
|
|
254
|
+
/** The served URL, base included (resolved server-side). */
|
|
255
|
+
href: string;
|
|
237
256
|
/** Pre-resolved inline SVG from the server; absent falls back to `file`. */
|
|
238
257
|
icon?: string;
|
|
239
258
|
label: string;
|
|
240
|
-
route: string;
|
|
241
259
|
}
|
|
242
260
|
|
|
243
261
|
const GRID_COLS = "md:grid-cols-[22rem_minmax(0,1fr)]";
|
|
@@ -306,6 +324,8 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
306
324
|
message!: HTMLElement;
|
|
307
325
|
preview!: HTMLElement;
|
|
308
326
|
searchFn: SearchFn | null = null;
|
|
327
|
+
/** Records settled queries and picked results (search/analytics.ts). */
|
|
328
|
+
tracker = createSearchTracker(track);
|
|
309
329
|
loaded = false;
|
|
310
330
|
loadFailed = false;
|
|
311
331
|
/** First-open client/index load still in flight. */
|
|
@@ -450,11 +470,13 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
450
470
|
}
|
|
451
471
|
|
|
452
472
|
// The handlers accept both ⌘ and Ctrl chords; show the right modifier
|
|
453
|
-
// per platform
|
|
473
|
+
// per platform in the button's tooltip and the footer's preview hint.
|
|
454
474
|
const isApple = /mac|iphone|ipad|ipod/iu.test(navigator.platform);
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
475
|
+
const trigger = this.querySelector<HTMLElement>(
|
|
476
|
+
"[data-blume-search-open]"
|
|
477
|
+
);
|
|
478
|
+
if (trigger && !isApple) {
|
|
479
|
+
trigger.dataset.blumeTooltip = `${trigger.dataset.label ?? ""} (\u2066Ctrl K\u2069)`;
|
|
458
480
|
}
|
|
459
481
|
const previewHint = this.querySelector(
|
|
460
482
|
"[data-blume-search-preview-kbd]"
|
|
@@ -474,7 +496,10 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
474
496
|
this.dialog.addEventListener("keydown", (event) =>
|
|
475
497
|
this.onKeydown(event)
|
|
476
498
|
);
|
|
477
|
-
this.dialog.addEventListener("close", () =>
|
|
499
|
+
this.dialog.addEventListener("close", () => {
|
|
500
|
+
this.unlockPageScroll();
|
|
501
|
+
this.tracker.closed();
|
|
502
|
+
});
|
|
478
503
|
this.dialog.addEventListener("click", (event) => {
|
|
479
504
|
if (event.target === this.dialog) {
|
|
480
505
|
this.dialog.close();
|
|
@@ -658,6 +683,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
658
683
|
this.setMessage(this.noResultsMsg);
|
|
659
684
|
}
|
|
660
685
|
|
|
686
|
+
this.tracker.settled(query, result.hits.length);
|
|
661
687
|
this.finishRender();
|
|
662
688
|
}
|
|
663
689
|
|
|
@@ -671,7 +697,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
671
697
|
if (this.popular.length > 0) {
|
|
672
698
|
const group = this.addGroup(this.popularMsg);
|
|
673
699
|
for (const page of this.popular) {
|
|
674
|
-
const item = this.createLinkRow(page.
|
|
700
|
+
const item = this.createLinkRow(page.href, page.label, page.icon);
|
|
675
701
|
group.appendChild(item.el);
|
|
676
702
|
this.selectables.push(item);
|
|
677
703
|
}
|
|
@@ -755,9 +781,10 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
755
781
|
|
|
756
782
|
createHitRow(hit: SearchHit, query: string): Selectable {
|
|
757
783
|
const el = document.createElement("a");
|
|
758
|
-
// Index URLs are base-less logical routes;
|
|
759
|
-
// clicking a result lands on the page's real served
|
|
760
|
-
|
|
784
|
+
// Index URLs are base-less logical routes; mount them under the
|
|
785
|
+
// deployment base so clicking a result lands on the page's real served
|
|
786
|
+
// URL, even when the route starts with the base's own name.
|
|
787
|
+
const href = mountBase(import.meta.env.BASE_URL, hit.url);
|
|
761
788
|
el.href = href;
|
|
762
789
|
el.className = `${ROW_CLASS} ${MARK}`;
|
|
763
790
|
// `line-clamp-2` already sets `display`, so no `block` here (it would
|
|
@@ -782,9 +809,8 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
782
809
|
return item;
|
|
783
810
|
}
|
|
784
811
|
|
|
785
|
-
createLinkRow(
|
|
812
|
+
createLinkRow(href: string, label: string, icon?: string): Selectable {
|
|
786
813
|
const el = document.createElement("a");
|
|
787
|
-
const href = prefixBase(import.meta.env.BASE_URL, url);
|
|
788
814
|
el.href = href;
|
|
789
815
|
el.className = ROW_CLASS;
|
|
790
816
|
// `icon` is server-resolved markup (built-in Lucide, `<img>`, or
|
|
@@ -881,6 +907,14 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
|
|
|
881
907
|
new CustomEvent("blume:open-assistant", { detail: { query } })
|
|
882
908
|
);
|
|
883
909
|
} else if (item.url) {
|
|
910
|
+
if (item.hit) {
|
|
911
|
+
const hits = this.selectables.filter((row) => row.hit);
|
|
912
|
+
this.tracker.selected(
|
|
913
|
+
this.input.value.trim(),
|
|
914
|
+
hits.indexOf(item) + 1,
|
|
915
|
+
item.url
|
|
916
|
+
);
|
|
917
|
+
}
|
|
884
918
|
// Close first so the dialog isn't left open over the transition;
|
|
885
919
|
// navigate() rides the client router (and falls back to a normal
|
|
886
920
|
// full load on pages without it).
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The site footer, below the content on every page: one row, as tall as the
|
|
3
|
+
// header, with `footer.links` on one side and the social profile icons on the
|
|
4
|
+
// other. The site's repository (`github`, unless `navigation.repo` hides it)
|
|
5
|
+
// is the first icon; the footer is the only place it's linked. With `consent` configured the links end with the
|
|
6
|
+
// Cookie settings link, which reopens the reader's consent choices. Renders
|
|
7
|
+
// nothing when there's none of these; a `components.ts` `Footer` replaces it.
|
|
8
|
+
import data from "blume:data";
|
|
9
|
+
|
|
10
|
+
import type { BlumeDataConfig } from "../../core/data.ts";
|
|
11
|
+
import type { Navigation } from "../../core/types.ts";
|
|
12
|
+
import { EN_UI } from "../../core/i18n-ui.ts";
|
|
13
|
+
import type { UIStrings } from "../../core/i18n-ui.ts";
|
|
14
|
+
import { routeSetFor } from "../../core/locale-links.ts";
|
|
15
|
+
import { newTabAttrs } from "../../core/new-tab.ts";
|
|
16
|
+
import Icon from "../Icon.astro";
|
|
17
|
+
import { withBase } from "../islands/base-path.ts";
|
|
18
|
+
import { footerLinks } from "./footer-links.ts";
|
|
19
|
+
import { footerSocialLinks } from "./footer-socials.ts";
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
footer?: BlumeDataConfig["footer"];
|
|
23
|
+
/**
|
|
24
|
+
* Active locale of the page: labels written as per-locale maps show its
|
|
25
|
+
* entry, and internal links move into it (see `footer-links.ts`).
|
|
26
|
+
*/
|
|
27
|
+
locale?: string;
|
|
28
|
+
/** The page's navigation: its `repoUrl` is the repository link. */
|
|
29
|
+
navigation?: Navigation;
|
|
30
|
+
/** Localized UI strings; the Cookie settings label comes from `consent`. */
|
|
31
|
+
ui?: UIStrings;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { footer, locale, navigation, ui } = Astro.props;
|
|
35
|
+
// `github`, as `navigation.repo` leaves it: hidden, or pointed elsewhere.
|
|
36
|
+
const repoUrl = navigation?.repoUrl;
|
|
37
|
+
const socials = footerSocialLinks(
|
|
38
|
+
footer?.socials ?? {},
|
|
39
|
+
repoUrl
|
|
40
|
+
? { href: repoUrl, label: { ...EN_UI.nav, ...ui?.nav }.githubRepository }
|
|
41
|
+
: null
|
|
42
|
+
);
|
|
43
|
+
const links = footerLinks(footer?.links ?? [], {
|
|
44
|
+
basePath: data.config.basePath,
|
|
45
|
+
i18n: data.config.i18n,
|
|
46
|
+
locale,
|
|
47
|
+
routes: routeSetFor(data.routes),
|
|
48
|
+
});
|
|
49
|
+
const consentSettings = data.config.consent
|
|
50
|
+
? { ...EN_UI.consent, ...ui?.consent }.settings
|
|
51
|
+
: null;
|
|
52
|
+
const linkClass =
|
|
53
|
+
"text-muted-foreground transition-colors hover:text-foreground focus-visible:text-foreground";
|
|
54
|
+
// Round icon buttons, like the header's.
|
|
55
|
+
const iconButton =
|
|
56
|
+
"inline-flex size-9 shrink-0 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:text-foreground";
|
|
57
|
+
const hasLinks = links.length > 0 || consentSettings !== null;
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
(hasLinks || socials.length > 0) && (
|
|
62
|
+
<footer class="border-border border-t px-4 md:px-6" data-blume-footer>
|
|
63
|
+
<div class="flex min-h-16 flex-wrap items-center justify-between gap-x-6 gap-y-3 py-3">
|
|
64
|
+
{hasLinks && (
|
|
65
|
+
<ul class="flex flex-wrap items-center gap-x-5 gap-y-2 text-sm">
|
|
66
|
+
{links.map((link) => (
|
|
67
|
+
<li>
|
|
68
|
+
<a
|
|
69
|
+
class={linkClass}
|
|
70
|
+
href={withBase(link.href)}
|
|
71
|
+
{...newTabAttrs(link.href)}
|
|
72
|
+
>
|
|
73
|
+
{link.label}
|
|
74
|
+
</a>
|
|
75
|
+
</li>
|
|
76
|
+
))}
|
|
77
|
+
{consentSettings && (
|
|
78
|
+
<li>
|
|
79
|
+
<button
|
|
80
|
+
class:list={[linkClass, "cursor-pointer"]}
|
|
81
|
+
data-blume-consent-open
|
|
82
|
+
type="button"
|
|
83
|
+
>
|
|
84
|
+
{consentSettings}
|
|
85
|
+
</button>
|
|
86
|
+
</li>
|
|
87
|
+
)}
|
|
88
|
+
</ul>
|
|
89
|
+
)}
|
|
90
|
+
{/* Pulled out by the buttons' padding, so the glyphs line up with the
|
|
91
|
+
footer's edge, and pushed to the far side even with no links. */}
|
|
92
|
+
{socials.length > 0 && (
|
|
93
|
+
<ul class="-me-[9px] -my-[9px] ms-auto flex flex-wrap items-center gap-1">
|
|
94
|
+
{socials.map((social) => (
|
|
95
|
+
<li>
|
|
96
|
+
<a
|
|
97
|
+
aria-label={social.label}
|
|
98
|
+
class={iconButton}
|
|
99
|
+
href={withBase(social.href)}
|
|
100
|
+
title={social.label}
|
|
101
|
+
{...newTabAttrs(social.href)}
|
|
102
|
+
>
|
|
103
|
+
{social.path ? (
|
|
104
|
+
<svg
|
|
105
|
+
aria-hidden="true"
|
|
106
|
+
fill="currentColor"
|
|
107
|
+
height="18"
|
|
108
|
+
viewBox="0 0 24 24"
|
|
109
|
+
width="18"
|
|
110
|
+
>
|
|
111
|
+
<path d={social.path} />
|
|
112
|
+
</svg>
|
|
113
|
+
) : (
|
|
114
|
+
<Icon name={social.icon} size={18} />
|
|
115
|
+
)}
|
|
116
|
+
</a>
|
|
117
|
+
</li>
|
|
118
|
+
))}
|
|
119
|
+
</ul>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</footer>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { metatagAttributes } from "../../seo/metatags.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The site's own `seo.metatags` in a page's head, after the tags Blume
|
|
6
|
+
* writes (see seo/metatags.ts). Every layout renders it, so a verification
|
|
7
|
+
* token or `theme-color` reaches every page.
|
|
8
|
+
*/
|
|
9
|
+
interface Props {
|
|
10
|
+
tags: Record<string, string> | null | undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { tags } = Astro.props;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
{metatagAttributes(tags).map((attributes) => <meta {...attributes} />)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
3
3
|
import Icon from "../Icon.astro";
|
|
4
4
|
|
|
5
5
|
// The "you're viewing an old version" notice, shared by RootLayout and
|
|
@@ -29,7 +29,7 @@ const { notice } = Astro.props;
|
|
|
29
29
|
<span>{notice.message}</span>
|
|
30
30
|
<a
|
|
31
31
|
class="inline-flex items-center gap-1 font-medium underline underline-offset-2"
|
|
32
|
-
href={
|
|
32
|
+
href={withMountedBase(notice.latestHref)}
|
|
33
33
|
>
|
|
34
34
|
{notice.latestLabel}
|
|
35
35
|
<Icon name="arrow-right" size={14} />
|
|
@@ -40,6 +40,9 @@ interface AnalyticsWindow {
|
|
|
40
40
|
pirsch?: (event: string, options?: { meta?: TrackProps }) => void;
|
|
41
41
|
plausible?: (event: string, options?: { props?: TrackProps }) => void;
|
|
42
42
|
posthog?: { capture?: (event: string, props?: TrackProps) => void };
|
|
43
|
+
stonks?: {
|
|
44
|
+
event?: (event: string, props?: Record<string, string>) => void;
|
|
45
|
+
};
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
/** Run one provider call; its failure must not reach the others or the caller. */
|
|
@@ -85,6 +88,15 @@ const trackGlobals = (
|
|
|
85
88
|
attempt(() => w.plausible?.(event, { props }));
|
|
86
89
|
attempt(() => w.databuddy?.track?.(event, props));
|
|
87
90
|
attempt(() => w.fathom?.trackEvent?.(event));
|
|
91
|
+
// OneDollarStats only takes string property values.
|
|
92
|
+
attempt(() =>
|
|
93
|
+
w.stonks?.event?.(
|
|
94
|
+
event,
|
|
95
|
+
Object.fromEntries(
|
|
96
|
+
Object.entries(props).map(([key, value]) => [key, String(value)])
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
);
|
|
88
100
|
// Pirsch stringifies `meta` values in place, so it gets its own copy and
|
|
89
101
|
// the `blume:track` listeners still see the original types.
|
|
90
102
|
attempt(() => w.pirsch?.(event, { meta: { ...props } }));
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `native()`'s browser module: Blume's own banner. The generated
|
|
3
|
+
* `blume:consent-client` brings it in when `native()` is the configured
|
|
4
|
+
* consent adapter (see `consent/clients.ts`). It reports the reader's stored
|
|
5
|
+
* answer, shows the banner (`ConsentBanner.astro`) until they pick one, stores
|
|
6
|
+
* the pick, and makes the footer's Cookie settings link bring the banner back.
|
|
7
|
+
*/
|
|
8
|
+
import type { NativeOptions } from "../../../consent/native.ts";
|
|
9
|
+
import type { BlumeConsent, ConsentClientStart } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
/** Where `native()` keeps the reader's answer. */
|
|
12
|
+
export const CONSENT_STORAGE_KEY = "blume-consent";
|
|
13
|
+
|
|
14
|
+
const BANNER = "[data-blume-consent-banner]";
|
|
15
|
+
const CHOICE = "[data-blume-consent-choice]";
|
|
16
|
+
|
|
17
|
+
/** The stored answer, or `null` before one (or without storage). */
|
|
18
|
+
export const storedChoice = (): boolean | null => {
|
|
19
|
+
try {
|
|
20
|
+
const value = localStorage.getItem(CONSENT_STORAGE_KEY);
|
|
21
|
+
if (value === "granted" || value === "denied") {
|
|
22
|
+
return value === "granted";
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// Storage blocked: ask again on every page rather than fail.
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const storeChoice = (granted: boolean): void => {
|
|
31
|
+
try {
|
|
32
|
+
localStorage.setItem(CONSENT_STORAGE_KEY, granted ? "granted" : "denied");
|
|
33
|
+
} catch {
|
|
34
|
+
// Storage blocked: the answer holds for this page load only.
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const banner = (): HTMLElement | null =>
|
|
39
|
+
document.querySelector<HTMLElement>(BANNER);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Wire this page's banner: show it while the reader hasn't answered, and
|
|
43
|
+
* store and report an answer. Runs again after every client-router swap,
|
|
44
|
+
* which brings a fresh, hidden banner.
|
|
45
|
+
*/
|
|
46
|
+
const syncBanner = (consent: BlumeConsent): void => {
|
|
47
|
+
const element = banner();
|
|
48
|
+
if (!element) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
element.hidden = storedChoice() !== null;
|
|
52
|
+
for (const button of element.querySelectorAll<HTMLElement>(CHOICE)) {
|
|
53
|
+
button.addEventListener("click", () => {
|
|
54
|
+
const granted = button.dataset.blumeConsentChoice === "accept";
|
|
55
|
+
storeChoice(granted);
|
|
56
|
+
element.hidden = true;
|
|
57
|
+
consent.set({ analytics: granted });
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Report the stored answer, and wire the banner on this page and every page
|
|
64
|
+
* the client router swaps in. The options (the banner's privacy link) are
|
|
65
|
+
* rendered into the banner itself, so there's nothing to read here.
|
|
66
|
+
*/
|
|
67
|
+
export const start: ConsentClientStart<NativeOptions> = (consent) => {
|
|
68
|
+
consent.open = () => {
|
|
69
|
+
const element = banner();
|
|
70
|
+
if (element) {
|
|
71
|
+
element.hidden = false;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
syncBanner(consent);
|
|
75
|
+
consent.set({ analytics: storedChoice() === true });
|
|
76
|
+
document.addEventListener("astro:after-swap", () => {
|
|
77
|
+
syncBanner(consent);
|
|
78
|
+
});
|
|
79
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consent contract every adapter talks to, shared by the consent runtime
|
|
3
|
+
* (`consent/client.ts`) and each adapter's browser module (like `native.ts`
|
|
4
|
+
* here). `window.blumeConsent` is created by the inline init script
|
|
5
|
+
* (`consent/init.ts`) before any adapter loads.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** What a consent adapter reports: whether the reader allows analytics. */
|
|
9
|
+
export interface ConsentState {
|
|
10
|
+
analytics: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** `window.blumeConsent`, created by the inline init script. */
|
|
14
|
+
export interface BlumeConsent {
|
|
15
|
+
/** `null` until an adapter reports, then the reader's choice. */
|
|
16
|
+
analytics: boolean | null;
|
|
17
|
+
/** The configured adapter's kind. */
|
|
18
|
+
kind: string;
|
|
19
|
+
/** Reopen the reader's consent choices; set by the adapter. */
|
|
20
|
+
open?: () => void;
|
|
21
|
+
/** Report the reader's choice; fires `blume:consent` when it changes. */
|
|
22
|
+
set: (state: ConsentState) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A consent adapter's browser module entry point, which the generated
|
|
27
|
+
* `blume:consent-client` calls once per real page load, after the runtime is
|
|
28
|
+
* listening. It gets the consent state and the adapter's options from
|
|
29
|
+
* `blume.config.ts`, and must report the reader's answer with
|
|
30
|
+
* `consent.set({ analytics })` (on load, and again whenever it changes) and
|
|
31
|
+
* set `consent.open` to reopen the adapter's preferences. Anything it does per
|
|
32
|
+
* page, it redoes on `astro:after-swap`.
|
|
33
|
+
*/
|
|
34
|
+
export type ConsentClientStart<Options> = (
|
|
35
|
+
consent: BlumeConsent,
|
|
36
|
+
options: Options
|
|
37
|
+
) => void;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { LocaleRouting } from "../../core/i18n.ts";
|
|
2
|
+
import { localizeHref } from "../../core/locale-links.ts";
|
|
3
|
+
import type { RouteSet } from "../../core/locale-links.ts";
|
|
4
|
+
import { resolveLocalizable } from "../../core/localizable.ts";
|
|
5
|
+
import type { ResolvedConfig } from "../../core/schema.ts";
|
|
6
|
+
|
|
7
|
+
/** The configured `footer.links`. */
|
|
8
|
+
type FooterLinks = NonNullable<ResolvedConfig["footer"]>["links"];
|
|
9
|
+
|
|
10
|
+
/** A footer link, ready to render. */
|
|
11
|
+
export interface FooterLinkItem {
|
|
12
|
+
href: string;
|
|
13
|
+
label: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** What resolving needs from the site and the page. */
|
|
17
|
+
export interface FooterContext {
|
|
18
|
+
basePath: string;
|
|
19
|
+
/** The site's locales, when it has more than one. */
|
|
20
|
+
i18n: LocaleRouting | null;
|
|
21
|
+
/** The locale of the page being rendered. */
|
|
22
|
+
locale?: string;
|
|
23
|
+
routes: RouteSet;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The footer's links in the page's locale: a label written as a per-locale
|
|
28
|
+
* map shows that locale's entry (see `core/localizable.ts`), and an internal
|
|
29
|
+
* link moves into the locale when it serves that page, the way content and
|
|
30
|
+
* header links do, so a reader on `/fr/…` stays in French.
|
|
31
|
+
*/
|
|
32
|
+
export const footerLinks = (
|
|
33
|
+
links: FooterLinks,
|
|
34
|
+
context: FooterContext
|
|
35
|
+
): FooterLinkItem[] => {
|
|
36
|
+
const { i18n, locale } = context;
|
|
37
|
+
const defaultLocale = i18n?.defaultLocale;
|
|
38
|
+
const localize = (href: string): string =>
|
|
39
|
+
i18n && locale
|
|
40
|
+
? localizeHref(href, {
|
|
41
|
+
basePath: context.basePath,
|
|
42
|
+
// The layout mounts the deployment base afterwards.
|
|
43
|
+
deployBase: "",
|
|
44
|
+
i18n,
|
|
45
|
+
locale,
|
|
46
|
+
routes: context.routes,
|
|
47
|
+
})
|
|
48
|
+
: href;
|
|
49
|
+
return links.map((link) => ({
|
|
50
|
+
href: localize(link.href),
|
|
51
|
+
label: resolveLocalizable(link.label, locale, defaultLocale),
|
|
52
|
+
}));
|
|
53
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
siBluesky,
|
|
3
|
+
siDiscord,
|
|
4
|
+
siFacebook,
|
|
5
|
+
siGithub,
|
|
6
|
+
siInstagram,
|
|
7
|
+
siMedium,
|
|
8
|
+
siReddit,
|
|
9
|
+
siTelegram,
|
|
10
|
+
siThreads,
|
|
11
|
+
siX,
|
|
12
|
+
siYcombinator,
|
|
13
|
+
siYoutube,
|
|
14
|
+
} from "simple-icons";
|
|
15
|
+
|
|
16
|
+
import type { FooterSocial } from "../../core/footer.ts";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How a `footer.socials` platform is shown: its name, for the link's label,
|
|
20
|
+
* and either a filled brand mark or a Lucide icon.
|
|
21
|
+
*/
|
|
22
|
+
export interface SocialIcon {
|
|
23
|
+
label: string;
|
|
24
|
+
/** A Lucide icon name, where simple-icons carries no mark for the brand. */
|
|
25
|
+
icon?: string;
|
|
26
|
+
/** A simple-icons path on a 24×24 viewBox, drawn filled. */
|
|
27
|
+
path?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Each platform's icon. Brand marks come from simple-icons; LinkedIn and
|
|
32
|
+
* Slack had theirs removed there at the brands' request, so they take
|
|
33
|
+
* Lucide's glyphs, as do the generic website and podcast links.
|
|
34
|
+
*/
|
|
35
|
+
export const SOCIAL_ICONS: Record<FooterSocial, SocialIcon> = {
|
|
36
|
+
bluesky: { label: "Bluesky", path: siBluesky.path },
|
|
37
|
+
discord: { label: "Discord", path: siDiscord.path },
|
|
38
|
+
facebook: { label: "Facebook", path: siFacebook.path },
|
|
39
|
+
github: { label: "GitHub", path: siGithub.path },
|
|
40
|
+
"hacker-news": { label: "Hacker News", path: siYcombinator.path },
|
|
41
|
+
instagram: { label: "Instagram", path: siInstagram.path },
|
|
42
|
+
linkedin: { icon: "linkedin", label: "LinkedIn" },
|
|
43
|
+
medium: { label: "Medium", path: siMedium.path },
|
|
44
|
+
podcast: { icon: "podcast", label: "Podcast" },
|
|
45
|
+
reddit: { label: "Reddit", path: siReddit.path },
|
|
46
|
+
slack: { icon: "slack", label: "Slack" },
|
|
47
|
+
telegram: { label: "Telegram", path: siTelegram.path },
|
|
48
|
+
threads: { label: "Threads", path: siThreads.path },
|
|
49
|
+
website: { icon: "globe", label: "Website" },
|
|
50
|
+
x: { label: "X", path: siX.path },
|
|
51
|
+
youtube: { label: "YouTube", path: siYoutube.path },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** A configured social link, with the icon it's shown by. */
|
|
55
|
+
export interface FooterSocialLink extends SocialIcon {
|
|
56
|
+
href: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isFooterSocial = (key: string): key is FooterSocial =>
|
|
60
|
+
Object.hasOwn(SOCIAL_ICONS, key);
|
|
61
|
+
|
|
62
|
+
/** The site's repository, as the footer links it. */
|
|
63
|
+
export interface FooterRepoLink {
|
|
64
|
+
href: string;
|
|
65
|
+
/** Its accessible name, in the page's language ("GitHub repository"). */
|
|
66
|
+
label: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The footer's social links, in the order written, each with its icon. The
|
|
71
|
+
* footer is the only place the site links its repository, so `repo` (from
|
|
72
|
+
* `github` in the config) leads as the GitHub link, unless `socials.github`
|
|
73
|
+
* names one itself.
|
|
74
|
+
*/
|
|
75
|
+
export const footerSocialLinks = (
|
|
76
|
+
socials: Partial<Record<FooterSocial, string>>,
|
|
77
|
+
repo?: FooterRepoLink | null
|
|
78
|
+
): FooterSocialLink[] => {
|
|
79
|
+
const links = Object.entries(socials).flatMap(([platform, href]) =>
|
|
80
|
+
isFooterSocial(platform) && href
|
|
81
|
+
? [{ ...SOCIAL_ICONS[platform], href }]
|
|
82
|
+
: []
|
|
83
|
+
);
|
|
84
|
+
return repo && !socials.github
|
|
85
|
+
? [{ ...SOCIAL_ICONS.github, ...repo }, ...links]
|
|
86
|
+
: links;
|
|
87
|
+
};
|