blume 2.1.3 → 2.2.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 -1
- package/CHANGELOG.md +126 -0
- package/README.md +4 -4
- package/dist/cli/{chunk-74wfhv79.js → chunk-05f21r8h.js} +1 -1
- package/dist/cli/{chunk-9qzgg6mj.js → chunk-1cm0h819.js} +3 -3
- package/dist/cli/{chunk-nbyvgnnm.js → chunk-1rkxzg3n.js} +104 -47
- package/dist/cli/chunk-1rkxzg3n.js.map +36 -0
- package/dist/cli/{chunk-desvm72z.js → chunk-2nr29y95.js} +22 -26
- package/dist/cli/chunk-2nr29y95.js.map +13 -0
- package/dist/cli/{chunk-32khwcb6.js → chunk-49r370mb.js} +42 -69
- package/dist/cli/{chunk-32khwcb6.js.map → chunk-49r370mb.js.map} +9 -10
- package/dist/cli/{chunk-ms9z66nz.js → chunk-4rftmttv.js} +2 -2
- package/dist/cli/{chunk-s0bdnc7v.js → chunk-79bzzkx4.js} +5 -4
- package/dist/cli/chunk-79bzzkx4.js.map +10 -0
- package/dist/cli/{chunk-hvcxng5f.js → chunk-7tekknev.js} +6 -6
- package/dist/cli/{chunk-dv2z0rzf.js → chunk-8b489v1b.js} +38 -13
- package/dist/cli/{chunk-dv2z0rzf.js.map → chunk-8b489v1b.js.map} +5 -4
- package/dist/cli/{chunk-0pmb4tr4.js → chunk-8f8xw4x2.js} +1195 -222
- package/dist/cli/chunk-8f8xw4x2.js.map +44 -0
- package/dist/cli/chunk-96snx8yy.js +329 -0
- package/dist/cli/chunk-96snx8yy.js.map +13 -0
- package/dist/cli/{chunk-ckrcm08h.js → chunk-a3ydrqny.js} +48 -62
- package/dist/cli/{chunk-ckrcm08h.js.map → chunk-a3ydrqny.js.map} +4 -5
- package/dist/cli/{chunk-bxwhtvw8.js → chunk-ahc5gc9h.js} +5 -5
- package/dist/cli/{chunk-dpy8qdjf.js → chunk-as09pafp.js} +73 -182
- package/dist/cli/chunk-as09pafp.js.map +18 -0
- package/dist/cli/{chunk-tfycddbt.js → chunk-bjk964fp.js} +2 -2
- package/dist/cli/{chunk-gfc5fqvf.js → chunk-br0n8n3e.js} +18 -18
- package/dist/cli/{chunk-gfc5fqvf.js.map → chunk-br0n8n3e.js.map} +1 -1
- package/dist/cli/{chunk-297ajdcv.js → chunk-c69tw29j.js} +6 -6
- package/dist/cli/{chunk-297ajdcv.js.map → chunk-c69tw29j.js.map} +3 -3
- package/dist/cli/chunk-cn3dy3hz.js +146 -0
- package/dist/cli/chunk-cn3dy3hz.js.map +11 -0
- package/dist/cli/{chunk-124fvmzq.js → chunk-dm4mg9bj.js} +11064 -8742
- package/dist/cli/{chunk-124fvmzq.js.map → chunk-dm4mg9bj.js.map} +78 -64
- package/dist/cli/{chunk-dqhg54mr.js → chunk-dmx6cezx.js} +1 -1
- package/dist/cli/{chunk-60hz265r.js → chunk-dpcahcrb.js} +24 -11
- package/dist/cli/chunk-dpcahcrb.js.map +10 -0
- package/dist/cli/{chunk-a57yh0h9.js → chunk-dyk3r7jw.js} +7 -7
- package/dist/cli/{chunk-a57yh0h9.js.map → chunk-dyk3r7jw.js.map} +1 -1
- package/dist/cli/{chunk-twy7psdz.js → chunk-enk76qd5.js} +14 -10
- package/dist/cli/chunk-enk76qd5.js.map +10 -0
- package/dist/cli/{chunk-k3gr1nm9.js → chunk-fqy4n3q2.js} +13 -16
- package/dist/cli/{chunk-k3gr1nm9.js.map → chunk-fqy4n3q2.js.map} +3 -3
- package/dist/cli/{chunk-sc93apfs.js → chunk-frk2wdq1.js} +17 -17
- package/dist/cli/{chunk-sc93apfs.js.map → chunk-frk2wdq1.js.map} +2 -2
- package/dist/cli/{chunk-kdpzw38a.js → chunk-gprwej7h.js} +95 -7
- package/dist/cli/chunk-gprwej7h.js.map +11 -0
- package/dist/cli/chunk-kkrteb15.js +157 -0
- package/dist/cli/chunk-kkrteb15.js.map +10 -0
- package/dist/cli/{chunk-ave1wcbr.js → chunk-m9bp2hc1.js} +1 -1
- package/dist/cli/{chunk-58bt3hag.js → chunk-mrvh4c88.js} +4 -4
- package/dist/cli/{chunk-5cmts6dv.js → chunk-p8zg3a2h.js} +7 -7
- package/dist/cli/{chunk-5cmts6dv.js.map → chunk-p8zg3a2h.js.map} +1 -1
- package/dist/cli/{chunk-0w6hs479.js → chunk-rkqae7kh.js} +30 -18
- package/dist/cli/chunk-rkqae7kh.js.map +10 -0
- package/dist/cli/{chunk-bqxaqn5p.js → chunk-t5xx74rd.js} +8 -8
- package/dist/cli/{chunk-bqxaqn5p.js.map → chunk-t5xx74rd.js.map} +1 -1
- package/dist/cli/{chunk-1f882900.js → chunk-th0pgaj5.js} +2 -2
- package/dist/cli/{chunk-dhwtqnnd.js → chunk-vnbvhgmd.js} +2 -2
- package/dist/cli/{chunk-gxtg6ed2.js → chunk-vzy9rh6y.js} +21 -21
- package/dist/cli/{chunk-gxtg6ed2.js.map → chunk-vzy9rh6y.js.map} +3 -3
- package/dist/cli/{chunk-j1x92mx9.js → chunk-w6e455pz.js} +1 -1
- package/dist/cli/chunk-wehv26ry.js +155 -0
- package/dist/cli/chunk-wehv26ry.js.map +11 -0
- package/dist/cli/{chunk-y4msrwm9.js → chunk-xgq4eytj.js} +15 -15
- package/dist/cli/{chunk-36bxs9c7.js → chunk-xt0qvz1t.js} +27 -23
- package/dist/cli/chunk-xt0qvz1t.js.map +10 -0
- package/dist/cli/index.js +78 -42
- package/dist/cli/index.js.map +7 -6
- package/dist/types/ai/changelog-components.d.ts +18 -0
- package/dist/types/ai/changelog-markdown.d.ts +12 -0
- package/dist/types/ai/openapi-components.d.ts +7 -7
- package/dist/types/ai/operation-details.d.ts +39 -0
- package/dist/types/ai/relative-links.d.ts +3 -1
- package/dist/types/ai/serializers.d.ts +6 -4
- package/dist/types/analytics/head.d.ts +4 -2
- package/dist/types/analytics/script.d.ts +8 -2
- package/dist/types/components/content/youtube.d.ts +20 -0
- package/dist/types/components/layout/nav-utils.d.ts +8 -0
- package/dist/types/components/openapi/example-panels.d.ts +33 -0
- package/dist/types/components/openapi/helpers.d.ts +53 -11
- package/dist/types/components/openapi/snippets.d.ts +7 -1
- package/dist/types/core/alert-diagnostics.d.ts +10 -0
- package/dist/types/core/builtin-tags.d.ts +16 -0
- package/dist/types/core/changelog-list.d.ts +48 -0
- package/dist/types/core/closest-match.d.ts +6 -0
- package/dist/types/core/code-fence-diagnostics.d.ts +18 -0
- package/dist/types/core/code-fences.d.ts +8 -1
- package/dist/types/core/config-input.d.ts +1 -1
- package/dist/types/core/content-assets.d.ts +16 -18
- package/dist/types/core/diagnostics.d.ts +14 -0
- package/dist/types/core/directive-diagnostics.d.ts +22 -8
- package/dist/types/core/former-routes.d.ts +14 -0
- package/dist/types/core/github.d.ts +5 -4
- package/dist/types/core/heading-markers.d.ts +65 -2
- package/dist/types/core/hidden-pages.d.ts +3 -0
- package/dist/types/core/includes.d.ts +23 -0
- package/dist/types/core/last-modified.d.ts +18 -6
- package/dist/types/core/links.d.ts +42 -2
- package/dist/types/core/load-module.d.ts +19 -4
- package/dist/types/core/manifest.d.ts +14 -1
- package/dist/types/core/markdown-mirrors.d.ts +22 -0
- package/dist/types/core/mdx-elements.d.ts +32 -0
- package/dist/types/core/mdx-syntax.d.ts +100 -0
- package/dist/types/core/meta-diagnostics.d.ts +7 -0
- package/dist/types/core/meta.d.ts +32 -5
- package/dist/types/core/nav-diagnostics.d.ts +9 -2
- package/dist/types/core/navigation.d.ts +13 -0
- package/dist/types/core/ordering-prefix.d.ts +6 -0
- package/dist/types/core/pinned-ids.d.ts +23 -0
- package/dist/types/core/project-graph.d.ts +9 -1
- package/dist/types/core/redirect-patterns.d.ts +18 -0
- package/dist/types/core/relative-files.d.ts +31 -0
- package/dist/types/core/schema.d.ts +267 -7
- package/dist/types/core/sources/normalize.d.ts +101 -9
- package/dist/types/core/sources/types.d.ts +16 -0
- package/dist/types/core/static-files.d.ts +10 -0
- package/dist/types/core/static-host.d.ts +42 -0
- package/dist/types/core/svg-images.d.ts +26 -0
- package/dist/types/core/syntax-diagnostics.d.ts +14 -0
- package/dist/types/core/types.d.ts +42 -1
- package/dist/types/deploy/generated-files.d.ts +41 -0
- package/dist/types/deploy/redirects.d.ts +21 -7
- package/dist/types/markdown/code-title.d.ts +9 -5
- package/dist/types/markdown/directives.d.ts +8 -0
- package/dist/types/markdown/fence-language.d.ts +51 -0
- package/dist/types/markdown/fence-meta.d.ts +5 -0
- package/dist/types/markdown/github-alerts.d.ts +35 -0
- package/dist/types/openapi/asyncapi.d.ts +6 -5
- package/dist/types/openapi/checks.d.ts +10 -5
- package/dist/types/openapi/model.d.ts +55 -8
- package/dist/types/openapi/parse.d.ts +4 -1
- package/dist/types/sources/filesystem.d.ts +14 -4
- package/dist/types/sources/mdx-remote.d.ts +6 -3
- package/dist/types/sources/registry.d.ts +3 -3
- package/dist/types/theme/color.d.ts +33 -0
- package/dist/types/theme/palette.d.ts +122 -0
- package/docs/01-quickstart.mdx +1 -1
- package/docs/02-deployment.mdx +23 -8
- package/docs/04-migrating.mdx +16 -4
- package/docs/advanced/changelog.mdx +27 -1
- package/docs/advanced/skills.mdx +1 -1
- package/docs/cli/audit.mdx +1 -1
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +1 -1
- package/docs/cli/index.mdx +4 -2
- package/docs/cli/validate.mdx +10 -7
- package/docs/configuration/analytics.mdx +2 -0
- package/docs/configuration/customization.mdx +3 -1
- package/docs/configuration/index.mdx +2 -2
- package/docs/configuration/theming.mdx +5 -1
- package/docs/content/components.mdx +26 -6
- package/docs/content/frontmatter.mdx +1 -1
- package/docs/content/includes.mdx +6 -2
- package/docs/content/index.mdx +11 -1
- package/docs/content/islands.mdx +2 -0
- package/docs/content/meta.mdx +9 -3
- package/docs/content/navigation.mdx +14 -6
- package/docs/content/sources/obsidian.mdx +1 -1
- package/docs/content/sources/remote-mdx.mdx +3 -1
- package/docs/content/syntax.mdx +76 -8
- package/docs/content/variables.mdx +3 -1
- package/docs/discoverability/agent-discovery.mdx +1 -1
- package/docs/discoverability/json-api.mdx +1 -1
- package/docs/discoverability/llms-txt.mdx +1 -1
- package/docs/discoverability/markdown.mdx +2 -2
- package/docs/discoverability/open-graph.mdx +1 -1
- package/docs/discoverability/sitemap-and-robots.mdx +1 -1
- package/docs/references/graphql.mdx +2 -0
- package/docs/references/openapi.mdx +45 -7
- package/package.json +4 -4
- package/skills/blume/SKILL.md +2 -2
- package/skills/blume-migrate/SKILL.md +49 -27
- package/skills/blume-migrate/references/docsify.md +262 -0
- package/skills/blume-migrate/references/docus.md +298 -0
- package/skills/blume-migrate/references/docusaurus.md +3 -3
- package/skills/blume-migrate/references/fern.md +327 -0
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/gitbook.md +267 -0
- package/skills/blume-migrate/references/github-wiki.md +259 -0
- package/skills/blume-migrate/references/jekyll.md +253 -0
- package/skills/blume-migrate/references/mdbook.md +256 -0
- package/skills/blume-migrate/references/mintlify.md +5 -5
- package/skills/blume-migrate/references/mkdocs.md +236 -0
- package/skills/blume-migrate/references/monorepo.md +20 -8
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/readme.md +257 -0
- package/skills/blume-migrate/references/redocly.md +198 -0
- package/skills/blume-migrate/references/starlight.md +3 -3
- package/skills/blume-migrate/references/vitepress.md +204 -0
- package/skills/blume-migrate/references/vuepress.md +271 -0
- package/skills/blume-migrate/scripts/docsify-codemod.mjs +2387 -0
- package/skills/blume-migrate/scripts/docus-codemod.mjs +2542 -0
- package/skills/blume-migrate/scripts/fern-codemod.mjs +2633 -0
- package/skills/blume-migrate/scripts/github-wiki-codemod.mjs +2808 -0
- package/skills/blume-migrate/scripts/include-excerpts.mjs +649 -0
- package/skills/blume-migrate/scripts/jekyll-codemod.mjs +3649 -0
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +17 -13
- package/skills/blume-migrate/scripts/mkdocs-codemod.mjs +2526 -0
- package/skills/blume-migrate/scripts/operation-routes.mjs +508 -0
- package/skills/blume-migrate/scripts/pin-heading-ids.mjs +1049 -0
- package/skills/blume-migrate/scripts/readme-codemod.mjs +4139 -0
- package/skills/blume-migrate/scripts/vitepress-codemod.mjs +1511 -0
- package/src/ai/changelog-components.ts +24 -0
- package/src/ai/changelog-markdown.ts +45 -41
- package/src/ai/component-markdown.ts +22 -9
- package/src/ai/llms.ts +36 -5
- package/src/ai/markdown.ts +2 -2
- package/src/ai/openapi-components.ts +25 -9
- package/src/ai/operation-details.ts +208 -0
- package/src/ai/relative-links.ts +48 -8
- package/src/ai/serializers.ts +7 -6
- package/src/ai/site-skill.ts +6 -2
- package/src/analytics/head.ts +11 -5
- package/src/analytics/script.ts +13 -3
- package/src/astro/generate.ts +32 -6
- package/src/astro/index.ts +1 -0
- package/src/astro/integration.ts +27 -1
- package/src/astro/mdx-source-errors.ts +46 -0
- package/src/astro/templates.ts +89 -185
- package/src/audit/checks/redirects.ts +5 -1
- package/src/audit/redirects.ts +36 -19
- package/src/audit/report.ts +22 -9
- package/src/audit/run.ts +12 -28
- package/src/audit/terms.ts +23 -0
- package/src/audit/url.ts +2 -2
- package/src/cli/build-failure.ts +256 -7
- package/src/cli/command-meta.ts +1 -1
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +12 -3
- package/src/cli/commands/check.ts +13 -2
- package/src/cli/commands/dev.ts +1 -1
- package/src/cli/commands/doctor.ts +4 -2
- package/src/cli/commands/init.ts +3 -3
- package/src/cli/commands/preview.ts +17 -1
- package/src/cli/commands/validate.ts +22 -10
- package/src/cli/prepare.ts +1 -1
- package/src/cli/preview-pages.ts +161 -0
- package/src/cli/unknown-flags.ts +1 -42
- package/src/components/code-copy.ts +51 -0
- package/src/components/code-language.ts +75 -0
- package/src/components/content/AccordionItem.astro +16 -1
- package/src/components/content/Callout.astro +11 -1
- package/src/components/content/Changelog.astro +189 -0
- package/src/components/content/CodeBlock.astro +8 -11
- package/src/components/content/Tabs.astro +16 -2
- package/src/components/content/YouTube.astro +6 -4
- package/src/components/content/code-block.ts +33 -0
- package/src/components/content/content-strings.ts +5 -1
- package/src/components/content/inline-markdown.ts +35 -8
- package/src/components/content/youtube.ts +49 -7
- package/src/components/layout/Analytics.astro +5 -1
- package/src/components/layout/NavTree.astro +8 -3
- package/src/components/layout/RootLayout.astro +18 -31
- package/src/components/layout/nav-utils.ts +14 -0
- package/src/components/layout/search/pagefind.ts +2 -1
- package/src/components/openapi/Operation.astro +4 -0
- package/src/components/openapi/PanelTabs.astro +9 -3
- package/src/components/openapi/RequestBody.astro +42 -8
- package/src/components/openapi/RequestPanel.astro +34 -53
- package/src/components/openapi/example-panels.ts +110 -0
- package/src/components/openapi/helpers.ts +195 -20
- package/src/components/openapi/operation-model.ts +63 -26
- package/src/components/openapi/request.ts +13 -0
- package/src/components/openapi/snippets.ts +14 -2
- package/src/core/alert-diagnostics.ts +89 -0
- package/src/core/builtin-tags.ts +76 -0
- package/src/core/changelog-list.ts +87 -0
- package/src/core/closest-match.ts +41 -0
- package/src/core/code-fence-diagnostics.ts +267 -0
- package/src/core/code-fences.ts +43 -7
- package/src/core/component-diagnostics.ts +65 -12
- package/src/core/config-input.ts +1 -1
- package/src/core/config.ts +46 -1
- package/src/core/content-assets.ts +64 -144
- package/src/core/diagnostics.ts +36 -3
- package/src/core/directive-diagnostics.ts +265 -47
- package/src/core/former-routes.ts +46 -0
- package/src/core/git-ignored.ts +53 -0
- package/src/core/github.ts +5 -4
- package/src/core/heading-markers.ts +156 -2
- package/src/core/hidden-pages.ts +64 -0
- package/src/core/includes.ts +181 -41
- package/src/core/last-modified.ts +47 -10
- package/src/core/links.ts +438 -83
- package/src/core/load-module.ts +98 -9
- package/src/core/manifest.ts +12 -9
- package/src/core/markdown-mirrors.ts +52 -0
- package/src/core/mdx-elements.ts +153 -0
- package/src/core/mdx-syntax.ts +377 -0
- package/src/core/meta-diagnostics.ts +134 -0
- package/src/core/meta.ts +154 -55
- package/src/core/nav-diagnostics.ts +16 -4
- package/src/core/navigation.ts +70 -10
- package/src/core/ordering-prefix.ts +22 -2
- package/src/core/pinned-ids.ts +141 -0
- package/src/core/project-graph.ts +221 -29
- package/src/core/redirect-patterns.ts +41 -0
- package/src/core/relative-files.ts +145 -0
- package/src/core/schema.ts +17 -3
- package/src/core/sources/mdx-remote.ts +75 -14
- package/src/core/sources/normalize.ts +885 -179
- package/src/core/sources/notion.ts +5 -4
- package/src/core/sources/obsidian.ts +2 -5
- package/src/core/sources/types.ts +17 -0
- package/src/core/static-files.ts +40 -0
- package/src/core/static-host.ts +125 -0
- package/src/core/svg-images.ts +98 -0
- package/src/core/syntax-diagnostics.ts +491 -0
- package/src/core/types.ts +43 -1
- package/src/deploy/artifacts.ts +2 -28
- package/src/deploy/generated-files.ts +128 -0
- package/src/deploy/platforms/vercel.ts +25 -1
- package/src/deploy/redirects.ts +43 -16
- package/src/deploy/sitemap.ts +2 -1
- package/src/markdown/base-links.ts +87 -6
- package/src/markdown/code-title.ts +29 -13
- package/src/markdown/content-assets.ts +210 -0
- package/src/markdown/directives.ts +69 -5
- package/src/markdown/fence-language.ts +106 -0
- package/src/markdown/fence-meta.ts +42 -0
- package/src/markdown/github-alerts.ts +172 -0
- package/src/markdown/heading-anchors.ts +265 -56
- package/src/markdown/include.ts +30 -3
- package/src/markdown/index.ts +145 -40
- package/src/markdown/prompt-text.ts +95 -0
- package/src/markdown/relative-links.ts +37 -2
- package/src/markdown/route-snapshot.ts +22 -12
- package/src/markdown/svg-fallback.ts +51 -0
- package/src/markdown/title-heading.ts +134 -0
- package/src/migrate/migrate.ts +127 -2
- package/src/og/card.ts +14 -4
- package/src/openapi/asyncapi.ts +14 -6
- package/src/openapi/checks.ts +160 -15
- package/src/openapi/model.ts +172 -33
- package/src/openapi/parse.ts +13 -4
- package/src/openapi/render-mdx.ts +55 -9
- package/src/openapi/source.ts +87 -6
- package/src/registry/eject.ts +0 -1
- package/src/search/documents.ts +10 -2
- package/src/sources/filesystem.ts +28 -3
- package/src/sources/mdx-remote.ts +7 -15
- package/src/theme/color.ts +14 -0
- package/src/theme/entry.ts +22 -1
- package/src/theme/palette.ts +74 -1
- package/dist/cli/chunk-0pmb4tr4.js.map +0 -35
- package/dist/cli/chunk-0w6hs479.js.map +0 -10
- package/dist/cli/chunk-150jk7km.js +0 -52
- package/dist/cli/chunk-150jk7km.js.map +0 -10
- package/dist/cli/chunk-36bxs9c7.js.map +0 -10
- package/dist/cli/chunk-53jqw8z0.js +0 -89
- package/dist/cli/chunk-53jqw8z0.js.map +0 -10
- package/dist/cli/chunk-60hz265r.js.map +0 -10
- package/dist/cli/chunk-desvm72z.js.map +0 -14
- package/dist/cli/chunk-dpy8qdjf.js.map +0 -17
- package/dist/cli/chunk-es8h0v3j.js +0 -441
- package/dist/cli/chunk-es8h0v3j.js.map +0 -11
- package/dist/cli/chunk-kdpzw38a.js.map +0 -11
- package/dist/cli/chunk-nbyvgnnm.js.map +0 -36
- package/dist/cli/chunk-s0bdnc7v.js.map +0 -10
- package/dist/cli/chunk-twy7psdz.js.map +0 -10
- package/dist/cli/chunk-ydp3065p.js +0 -64
- package/dist/cli/chunk-ydp3065p.js.map +0 -10
- /package/dist/cli/{chunk-74wfhv79.js.map → chunk-05f21r8h.js.map} +0 -0
- /package/dist/cli/{chunk-9qzgg6mj.js.map → chunk-1cm0h819.js.map} +0 -0
- /package/dist/cli/{chunk-ms9z66nz.js.map → chunk-4rftmttv.js.map} +0 -0
- /package/dist/cli/{chunk-hvcxng5f.js.map → chunk-7tekknev.js.map} +0 -0
- /package/dist/cli/{chunk-bxwhtvw8.js.map → chunk-ahc5gc9h.js.map} +0 -0
- /package/dist/cli/{chunk-tfycddbt.js.map → chunk-bjk964fp.js.map} +0 -0
- /package/dist/cli/{chunk-dqhg54mr.js.map → chunk-dmx6cezx.js.map} +0 -0
- /package/dist/cli/{chunk-ave1wcbr.js.map → chunk-m9bp2hc1.js.map} +0 -0
- /package/dist/cli/{chunk-58bt3hag.js.map → chunk-mrvh4c88.js.map} +0 -0
- /package/dist/cli/{chunk-1f882900.js.map → chunk-th0pgaj5.js.map} +0 -0
- /package/dist/cli/{chunk-dhwtqnnd.js.map → chunk-vnbvhgmd.js.map} +0 -0
- /package/dist/cli/{chunk-j1x92mx9.js.map → chunk-w6e455pz.js.map} +0 -0
- /package/dist/cli/{chunk-y4msrwm9.js.map → chunk-xgq4eytj.js.map} +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The changelog index's release list: every visible `type: changelog` entry,
|
|
3
|
+
// newest first, grouped by year, each row linking to its own page.
|
|
4
|
+
//
|
|
5
|
+
// The generated `/changelog` page renders this, and so can a hand-written page
|
|
6
|
+
// that wants prose above the list — an introduction, a feed link — which the
|
|
7
|
+
// generated page has no slot for. Only the list lives here: the heading and the
|
|
8
|
+
// description belong to whoever owns the page, so an authored page writes its
|
|
9
|
+
// own and the generated page keeps the translatable `changelog` strings.
|
|
10
|
+
import { getCollection } from "astro:content";
|
|
11
|
+
import data from "blume:data";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
compareChangelogDates,
|
|
15
|
+
groupChangelogYears,
|
|
16
|
+
parseChangelogDate,
|
|
17
|
+
uniqueAnchorIds,
|
|
18
|
+
} from "../../core/changelog-list.ts";
|
|
19
|
+
import { resolveDateFormatOptions } from "../../core/date-format.ts";
|
|
20
|
+
import { withMountedBase } from "../islands/base-path.ts";
|
|
21
|
+
import { contentStrings } from "./content-strings.ts";
|
|
22
|
+
|
|
23
|
+
const i18n = data.config.i18n;
|
|
24
|
+
// The list is always the default locale's entries, whatever locale the page
|
|
25
|
+
// sits in. Every other surface that publishes this list already works that way
|
|
26
|
+
// — the generated page, the `/changelog.md` mirror, llms-full.txt, MCP
|
|
27
|
+
// `get_page`, search — so a translated page listing its own locale's entries
|
|
28
|
+
// would be the one surface disagreeing with the rest about which releases
|
|
29
|
+
// exist. Releases are also commonly written once and left untranslated, in
|
|
30
|
+
// which case a per-locale list is the same list with fallback copies anyway.
|
|
31
|
+
// (The chrome around it still follows the reader: `strings` and the dates.)
|
|
32
|
+
const locale = i18n ? i18n.defaultLocale : null;
|
|
33
|
+
const { changelog: strings } = contentStrings(data, Astro.currentLocale);
|
|
34
|
+
// The page's own language, for the row dates and year headings. On the
|
|
35
|
+
// unprefixed generated `/changelog` Astro resolves the default locale.
|
|
36
|
+
const readerLocale = i18n ? (Astro.currentLocale ?? i18n.defaultLocale) : "en";
|
|
37
|
+
|
|
38
|
+
const dateFormatOptions = resolveDateFormatOptions(data.config.dateFormat);
|
|
39
|
+
// The year heads each group, so a row shows the rest of the date: a preset
|
|
40
|
+
// style keeps its month wording, a component format simply drops the year.
|
|
41
|
+
const { dateStyle, year: _year, ...dateComponents } = dateFormatOptions;
|
|
42
|
+
const rowDateFormat: Intl.DateTimeFormatOptions = dateStyle
|
|
43
|
+
? {
|
|
44
|
+
...dateComponents,
|
|
45
|
+
day: "numeric",
|
|
46
|
+
// `medium` abbreviates the month; every other preset spells it out, so a
|
|
47
|
+
// `long` site keeps reading "October 5" rather than dropping to "Oct 5".
|
|
48
|
+
month: dateStyle === "medium" ? "short" : "long",
|
|
49
|
+
}
|
|
50
|
+
: dateComponents;
|
|
51
|
+
const formatRowDate = (date: Date) =>
|
|
52
|
+
new Intl.DateTimeFormat(readerLocale, rowDateFormat).format(date);
|
|
53
|
+
const formatYear = (date: Date) =>
|
|
54
|
+
new Intl.DateTimeFormat(readerLocale, {
|
|
55
|
+
timeZone: dateFormatOptions.timeZone,
|
|
56
|
+
year: "numeric",
|
|
57
|
+
}).format(date);
|
|
58
|
+
|
|
59
|
+
const slugify = (text: string) =>
|
|
60
|
+
text
|
|
61
|
+
.toLowerCase()
|
|
62
|
+
.replaceAll(/[^a-z0-9]+/gu, "-")
|
|
63
|
+
.replaceAll(/^-+|-+$/gu, "") || "update";
|
|
64
|
+
|
|
65
|
+
// Map each entry to its own page. The collection entry id matches the route
|
|
66
|
+
// manifest's `entryId`, and the manifest carries a route per locale and per
|
|
67
|
+
// archived version, all sharing that id — so a map over every route would keep
|
|
68
|
+
// whichever came last: a Portuguese permalink on an unlocalized index, or a
|
|
69
|
+
// row pointing into last year's archived docs. Keep the current docs of the
|
|
70
|
+
// listed locale, never a fallback copy (which renders another locale's content
|
|
71
|
+
// at this URL), and list an entry only when it has such a route, so a
|
|
72
|
+
// translated changelog file does not add a second row for the same release.
|
|
73
|
+
// These are the conditions `ai/changelog-markdown.ts` filters pages by, so the
|
|
74
|
+
// rendered list and the Markdown mirror select the same releases.
|
|
75
|
+
//
|
|
76
|
+
// Keyed by collection and id together, the way the catch-all looks an entry up:
|
|
77
|
+
// both collections id an entry by its file path, so a `releases/v1-0-0.md` on
|
|
78
|
+
// disk and a staged release from a source named `releases` share an id while
|
|
79
|
+
// rendering at different routes.
|
|
80
|
+
const entryKey = (collection: string, id: string) => `${collection}:${id}`;
|
|
81
|
+
const routeByEntry = new Map<string, string>();
|
|
82
|
+
for (const route of data.routes) {
|
|
83
|
+
if (
|
|
84
|
+
route.version === "" &&
|
|
85
|
+
!route.fallback &&
|
|
86
|
+
(locale === null || route.locale === locale)
|
|
87
|
+
) {
|
|
88
|
+
routeByEntry.set(entryKey(route.collection, route.entryId), route.path);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Non-filesystem changelog sources (GitHub Releases) render through a parallel
|
|
93
|
+
// `staged` collection, which exists only when such a source is configured — the
|
|
94
|
+
// route manifest says whether to look, so the index folds those releases in
|
|
95
|
+
// without a generator flag, and a page that reads only `docs` can't quietly
|
|
96
|
+
// drop them.
|
|
97
|
+
const hasStaged = data.routes.some((route) => route.collection === "staged");
|
|
98
|
+
const entries = [
|
|
99
|
+
...(await getCollection("docs")),
|
|
100
|
+
...(hasStaged ? await getCollection("staged" as "docs") : []),
|
|
101
|
+
].filter(
|
|
102
|
+
(entry) =>
|
|
103
|
+
entry.data.type === "changelog" &&
|
|
104
|
+
!entry.data.draft &&
|
|
105
|
+
!entry.data.sidebar?.hidden &&
|
|
106
|
+
// A route is required on every site, not only localized ones: without
|
|
107
|
+
// i18n the map still drops archived versions, and an entry with no
|
|
108
|
+
// current-docs route would otherwise be listed with a dead self-anchor
|
|
109
|
+
// while the Markdown mirror left it out.
|
|
110
|
+
routeByEntry.has(entryKey(entry.collection, entry.id))
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const rows = entries
|
|
114
|
+
.map((entry) => {
|
|
115
|
+
const date = parseChangelogDate(entry.data.date ?? entry.data.changelog?.date);
|
|
116
|
+
const label =
|
|
117
|
+
entry.data.title ??
|
|
118
|
+
(entry.data.changelog?.version
|
|
119
|
+
? `v${entry.data.changelog.version}`
|
|
120
|
+
: "Update");
|
|
121
|
+
const route = routeByEntry.get(entryKey(entry.collection, entry.id));
|
|
122
|
+
return {
|
|
123
|
+
date,
|
|
124
|
+
href: route ? withMountedBase(route) : null,
|
|
125
|
+
label,
|
|
126
|
+
tag: entry.data.changelog?.category ?? null,
|
|
127
|
+
};
|
|
128
|
+
})
|
|
129
|
+
.toSorted((a, b) => compareChangelogDates(a.date, b.date));
|
|
130
|
+
|
|
131
|
+
const ids = uniqueAnchorIds(rows.map((row) => slugify(row.label)));
|
|
132
|
+
const items = rows.map((row, index) => ({
|
|
133
|
+
date: row.date ? formatRowDate(row.date) : null,
|
|
134
|
+
dateTime: row.date ? row.date.toISOString().slice(0, 10) : null,
|
|
135
|
+
href: row.href,
|
|
136
|
+
id: ids[index] as string,
|
|
137
|
+
label: row.label,
|
|
138
|
+
tag: row.tag,
|
|
139
|
+
year: row.date ? formatYear(row.date) : null,
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
const groups = groupChangelogYears(items, (item) => item.year);
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
{
|
|
146
|
+
items.length === 0 ? (
|
|
147
|
+
<p>{strings.empty}</p>
|
|
148
|
+
) : (
|
|
149
|
+
<div class="not-prose mt-10 divide-y divide-border border-border border-y">
|
|
150
|
+
{groups.map((group) => (
|
|
151
|
+
<section
|
|
152
|
+
aria-label={group.year ?? undefined}
|
|
153
|
+
class="grid md:grid-cols-[6rem_minmax(0,1fr)] md:gap-x-8"
|
|
154
|
+
>
|
|
155
|
+
<h2 class="mt-0! pt-3! font-medium! text-muted-foreground text-sm! leading-5! tabular-nums max-md:pb-1">
|
|
156
|
+
{group.year}
|
|
157
|
+
</h2>
|
|
158
|
+
<ul class="m-0! list-none divide-y divide-border p-0!">
|
|
159
|
+
{group.items.map((item) => (
|
|
160
|
+
<li class="m-0! p-0!" id={item.id}>
|
|
161
|
+
<a
|
|
162
|
+
class="group/release flex items-baseline gap-4 py-3 no-underline! hover:no-underline!"
|
|
163
|
+
href={item.href ?? `#${item.id}`}
|
|
164
|
+
>
|
|
165
|
+
<span class="min-w-0 flex-1 font-medium text-foreground text-sm transition-colors group-hover/release:text-accent">
|
|
166
|
+
{item.label}
|
|
167
|
+
</span>
|
|
168
|
+
{item.tag && (
|
|
169
|
+
<span class="shrink-0 rounded-full bg-muted px-2 py-0.5 font-medium text-[0.65rem] text-muted-foreground">
|
|
170
|
+
{item.tag}
|
|
171
|
+
</span>
|
|
172
|
+
)}
|
|
173
|
+
{item.date && (
|
|
174
|
+
<time
|
|
175
|
+
class="shrink-0 font-mono text-muted-foreground text-xs tabular-nums"
|
|
176
|
+
datetime={item.dateTime}
|
|
177
|
+
>
|
|
178
|
+
{item.date}
|
|
179
|
+
</time>
|
|
180
|
+
)}
|
|
181
|
+
</a>
|
|
182
|
+
</li>
|
|
183
|
+
))}
|
|
184
|
+
</ul>
|
|
185
|
+
</section>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
188
|
+
)
|
|
189
|
+
}
|
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
// The theme's code styles are scoped to `.prose`, so the output is wrapped in a
|
|
10
10
|
// `.prose` container (`max-w-none` so it fills its own column rather than the
|
|
11
11
|
// prose reading measure); that's what makes the light/dark swap work on a bare
|
|
12
|
-
// page without the manual `[data-theme]` CSS it would otherwise need.
|
|
12
|
+
// page without the manual `[data-theme]` CSS it would otherwise need. Without
|
|
13
|
+
// `code`, the children render as written: a fenced block wrapped in
|
|
14
|
+
// `<CodeBlock>` (Fern's and Mintlify's spelling) is already highlighted.
|
|
13
15
|
import data from "blume:data";
|
|
14
16
|
|
|
15
|
-
import {
|
|
17
|
+
import { codeBlockHtml } from "./code-block.ts";
|
|
16
18
|
|
|
17
19
|
interface Props {
|
|
18
|
-
/** The source to highlight. */
|
|
19
|
-
code
|
|
20
|
+
/** The source to highlight; without it, the children render instead. */
|
|
21
|
+
code?: string;
|
|
20
22
|
/** Shiki language id (default `txt`); an unknown one renders plain. */
|
|
21
23
|
lang?: string;
|
|
22
24
|
/** Show the brand language icon in the header (default on, like fences). */
|
|
@@ -25,12 +27,7 @@ interface Props {
|
|
|
25
27
|
title?: string;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const
|
|
29
|
-
const html = await highlightCode(code.replace(/\n+$/u, ""), lang, {
|
|
30
|
-
icons,
|
|
31
|
-
themes: data.config.codeThemes,
|
|
32
|
-
title,
|
|
33
|
-
});
|
|
30
|
+
const html = await codeBlockHtml(Astro.props, data.config.codeThemes);
|
|
34
31
|
---
|
|
35
32
|
|
|
36
|
-
<div class="prose max-w-none"><Fragment set:html={html} /></div>
|
|
33
|
+
{html === null ? <slot /> : <div class="prose max-w-none"><Fragment set:html={html} /></div>}
|
|
@@ -110,6 +110,8 @@ const { content: strings } = contentStrings(data, Astro.currentLocale);
|
|
|
110
110
|
</blume-tabs>
|
|
111
111
|
|
|
112
112
|
<script>
|
|
113
|
+
import { languageLabel } from "../code-language.ts";
|
|
114
|
+
|
|
113
115
|
const TRIGGER_CLASS =
|
|
114
116
|
"-mb-px inline-flex shrink-0 cursor-pointer items-center gap-1.5 border-transparent border-b-2 bg-transparent px-1 py-2.5 font-medium text-muted-foreground text-xs transition-colors hover:text-foreground aria-[selected=true]:border-accent aria-[selected=true]:text-foreground";
|
|
115
117
|
const SELECT_CLASS =
|
|
@@ -120,9 +122,20 @@ const { content: strings } = contentStrings(data, Astro.currentLocale);
|
|
|
120
122
|
// in several <Tabs> instances on one page.
|
|
121
123
|
let instanceCount = 0;
|
|
122
124
|
|
|
123
|
-
// An untitled
|
|
125
|
+
// An untitled code block (a CodeGroup fence) is named by its language, read
|
|
126
|
+
// from the fence's own id: the docs layout moves that to
|
|
127
|
+
// `data-blume-language` when it relabels `data-language` for the header.
|
|
128
|
+
const codeLanguage = (panel: HTMLElement) => {
|
|
129
|
+
const language = panel.matches("pre")
|
|
130
|
+
? (panel.dataset.blumeLanguage ?? panel.dataset.language)
|
|
131
|
+
: undefined;
|
|
132
|
+
return language ? languageLabel(language) : undefined;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Any other untitled panel is numbered with its group's localized `Tab {n}`.
|
|
124
136
|
const panelTitle = (panel: HTMLElement, index: number) =>
|
|
125
137
|
panel.dataset.title ??
|
|
138
|
+
codeLanguage(panel) ??
|
|
126
139
|
(
|
|
127
140
|
panel.closest<HTMLElement>("blume-tabs")?.dataset.i18nTab ?? "Tab {n}"
|
|
128
141
|
).replace("{n}", String(index + 1));
|
|
@@ -160,7 +173,8 @@ const { content: strings } = contentStrings(data, Astro.currentLocale);
|
|
|
160
173
|
).filter((panel) => panel.closest("blume-tabs") === this);
|
|
161
174
|
// CodeGroup passes raw code fences instead of <Tab> elements, so adopt
|
|
162
175
|
// the content wrapper's direct code blocks as panels. The tab label comes
|
|
163
|
-
// from each block's title (the text after the language in the fence)
|
|
176
|
+
// from each block's title (the text after the language in the fence),
|
|
177
|
+
// or from its language when it has none.
|
|
164
178
|
// A ts2js fence arrives as its own TypeScript/JavaScript group; it is
|
|
165
179
|
// one panel too, labeled by the fence title both of its blocks carry.
|
|
166
180
|
if (panels.length === 0) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
import {
|
|
2
|
+
import { youtubeEmbedSrc } from "./youtube.ts";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
/** A YouTube video id (e.g. `dQw4w9WgXcQ`). */
|
|
@@ -8,14 +8,16 @@ interface Props {
|
|
|
8
8
|
start?: number;
|
|
9
9
|
/** Accessible title for the embedded player. */
|
|
10
10
|
title?: string;
|
|
11
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* A full YouTube URL to extract the id from, as an alternative to `id`; a
|
|
13
|
+
* playlist URL embeds the playlist.
|
|
14
|
+
*/
|
|
12
15
|
url?: string;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
const { id, start, title = "YouTube video player", url } = Astro.props;
|
|
16
19
|
|
|
17
|
-
const
|
|
18
|
-
const src = videoId ? youtubeEmbedUrl(videoId, { start }) : null;
|
|
20
|
+
const src = youtubeEmbedSrc(id ?? url ?? "", { start });
|
|
19
21
|
---
|
|
20
22
|
|
|
21
23
|
{
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The highlighting half of the `<CodeBlock>` content component, kept in a
|
|
3
|
+
* sibling `.ts` (like `badge-color.ts`) so it is unit-testable.
|
|
4
|
+
*/
|
|
5
|
+
import { highlightCode } from "../../markdown/index.ts";
|
|
6
|
+
import type { CodeThemes } from "../../markdown/index.ts";
|
|
7
|
+
|
|
8
|
+
/** The props `<CodeBlock>` highlights from. */
|
|
9
|
+
export interface CodeBlockSource {
|
|
10
|
+
code?: string;
|
|
11
|
+
icons?: boolean;
|
|
12
|
+
lang?: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The highlighted HTML for a `<CodeBlock>`, or null when it has no `code`.
|
|
18
|
+
* Fern and Mintlify wrap a fenced block in `<CodeBlock>…</CodeBlock>`; the
|
|
19
|
+
* fence inside is already highlighted by the Markdown pipeline, so a
|
|
20
|
+
* `CodeBlock` without `code` renders its children as written rather than
|
|
21
|
+
* failing the page on the missing string.
|
|
22
|
+
*/
|
|
23
|
+
export const codeBlockHtml = async (
|
|
24
|
+
{ code, icons, lang = "txt", title }: CodeBlockSource,
|
|
25
|
+
themes?: CodeThemes
|
|
26
|
+
): Promise<string | null> =>
|
|
27
|
+
code === undefined
|
|
28
|
+
? null
|
|
29
|
+
: await highlightCode(code.replace(/\n+$/u, ""), lang, {
|
|
30
|
+
icons,
|
|
31
|
+
themes,
|
|
32
|
+
title,
|
|
33
|
+
});
|
|
@@ -9,7 +9,10 @@ export interface ContentStringsData {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/** The UI string groups a content component's chrome reads. */
|
|
12
|
-
export type ContentStrings = Pick<
|
|
12
|
+
export type ContentStrings = Pick<
|
|
13
|
+
UIStrings,
|
|
14
|
+
"actions" | "changelog" | "content"
|
|
15
|
+
>;
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
18
|
* Labels for a component rendered inside page content (MDX), which gets no
|
|
@@ -29,6 +32,7 @@ export const contentStrings = (
|
|
|
29
32
|
: data.ui;
|
|
30
33
|
return {
|
|
31
34
|
actions: { ...EN_UI.actions, ...dictionary.actions },
|
|
35
|
+
changelog: { ...EN_UI.changelog, ...dictionary.changelog },
|
|
32
36
|
content: { ...EN_UI.content, ...dictionary.content },
|
|
33
37
|
};
|
|
34
38
|
};
|
|
@@ -29,20 +29,47 @@ export const rawHtmlAsTextPlugin = defineMdastPlugin({
|
|
|
29
29
|
* `<p>a</p>\n<p>b</p>` would "unwrap" to `a</p>\n<p>b` — unbalanced HTML
|
|
30
30
|
* injected via `set:html`.
|
|
31
31
|
*/
|
|
32
|
+
const SINGLE_PARAGRAPH = /^<p>(?<content>(?:(?!<\/p>)[\s\S])*)<\/p>$/u;
|
|
33
|
+
|
|
32
34
|
export const unwrapParagraph = (html: string): string => {
|
|
33
35
|
const trimmed = html.trim();
|
|
34
|
-
|
|
35
|
-
return match?.groups?.content ?? trimmed;
|
|
36
|
+
return SINGLE_PARAGRAPH.exec(trimmed)?.groups?.content ?? trimmed;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
* text (see {@link rawHtmlAsTextPlugin}) and a single paragraph unwrapped.
|
|
41
|
-
*/
|
|
42
|
-
export const renderInlineMarkdown = async (value: string): Promise<string> => {
|
|
39
|
+
/** Render Markdown to HTML, its raw HTML shown as text. */
|
|
40
|
+
const renderMarkdown = async (value: string): Promise<string> => {
|
|
43
41
|
const processor = await createSatteriMarkdownProcessor({
|
|
44
42
|
mdastPlugins: [rawHtmlAsTextPlugin],
|
|
45
43
|
});
|
|
46
44
|
const rendered = await processor.render(value);
|
|
47
|
-
return
|
|
45
|
+
return rendered.code;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Render a short Markdown text prop to inline HTML, its raw HTML shown as
|
|
50
|
+
* text (see {@link rawHtmlAsTextPlugin}) and a single paragraph unwrapped.
|
|
51
|
+
*/
|
|
52
|
+
export const renderInlineMarkdown = async (value: string): Promise<string> =>
|
|
53
|
+
unwrapParagraph(await renderMarkdown(value));
|
|
54
|
+
|
|
55
|
+
// What inline Markdown a title can hold starts with: a backslash escape, a
|
|
56
|
+
// code span, emphasis or strikethrough, or a link.
|
|
57
|
+
const INLINE_SYNTAX = /[\\`*_~[]/u;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* A title prop (a callout's, an accordion item's) as inline HTML, with its
|
|
61
|
+
* Markdown rendered — `` `code` ``, `**bold**`, `[links](/x)` — and its raw
|
|
62
|
+
* HTML shown as text. Undefined when the title renders as written instead:
|
|
63
|
+
* when it holds none of that syntax, so a plain title keeps the exact text it
|
|
64
|
+
* always rendered, and when it doesn't render as one paragraph (`1. Install`
|
|
65
|
+
* is no list).
|
|
66
|
+
*/
|
|
67
|
+
export const renderInlineTitle = async (
|
|
68
|
+
value: string
|
|
69
|
+
): Promise<string | undefined> => {
|
|
70
|
+
if (!INLINE_SYNTAX.test(value)) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
const html = await renderMarkdown(value);
|
|
74
|
+
return SINGLE_PARAGRAPH.exec(html.trim())?.groups?.content;
|
|
48
75
|
};
|
|
@@ -8,9 +8,15 @@
|
|
|
8
8
|
const BARE_ID = /^[\w-]{11}$/u;
|
|
9
9
|
|
|
10
10
|
// Pull the id out of any common YouTube URL: youtu.be/<id>, watch?v=<id>,
|
|
11
|
-
// /embed/<id>, /shorts/<id>, /live/<id>.
|
|
11
|
+
// /embed/<id>, /shorts/<id>, /live/<id>. A playlist embed's
|
|
12
|
+
// `/embed/videoseries` is 11 characters too, but it names no video.
|
|
12
13
|
const URL_ID =
|
|
13
|
-
/(?:youtu\.be\/|\/embed
|
|
14
|
+
/(?:youtu\.be\/|\/embed\/(?!videoseries\b)|\/shorts\/|\/live\/|[?&]v=)(?<id>[\w-]{11})/u;
|
|
15
|
+
|
|
16
|
+
// A playlist URL: the embed form, `/embed/videoseries?list=<id>`, or the
|
|
17
|
+
// playlist page, `/playlist?list=<id>`.
|
|
18
|
+
const PLAYLIST =
|
|
19
|
+
/\/(?:embed\/videoseries|playlist)\?(?:[^#]*&)?list=(?<list>[\w-]+)/u;
|
|
14
20
|
|
|
15
21
|
/**
|
|
16
22
|
* Resolve a YouTube video id from either a bare id or a full URL. Returns `null`
|
|
@@ -28,6 +34,18 @@ export const parseYouTubeId = (input: string): string | null => {
|
|
|
28
34
|
return URL_ID.exec(value)?.groups?.id ?? null;
|
|
29
35
|
};
|
|
30
36
|
|
|
37
|
+
/**
|
|
38
|
+
* The playlist id of a YouTube playlist URL (`/embed/videoseries?list=…` or
|
|
39
|
+
* `/playlist?list=…`), or `null` for anything else — a video URL that also
|
|
40
|
+
* carries a `list` (`watch?v=…&list=…`) is that video.
|
|
41
|
+
*/
|
|
42
|
+
export const parseYouTubePlaylist = (input: string): string | null =>
|
|
43
|
+
PLAYLIST.exec(input.trim())?.groups?.list ?? null;
|
|
44
|
+
|
|
45
|
+
/** The query a `start` time adds to an embed URL, or nothing. */
|
|
46
|
+
const startParam = (start: number | undefined): Record<string, string> =>
|
|
47
|
+
start && start > 0 ? { start: String(Math.floor(start)) } : {};
|
|
48
|
+
|
|
31
49
|
/**
|
|
32
50
|
* Build a privacy-enhanced (`youtube-nocookie.com`) embed URL, optionally
|
|
33
51
|
* starting at `start` seconds.
|
|
@@ -37,10 +55,34 @@ export const youtubeEmbedUrl = (
|
|
|
37
55
|
options: { start?: number } = {}
|
|
38
56
|
): string => {
|
|
39
57
|
const base = `https://www.youtube-nocookie.com/embed/${id}`;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
const params = new URLSearchParams(startParam(options.start));
|
|
59
|
+
return params.size > 0 ? `${base}?${params.toString()}` : base;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The privacy-enhanced embed URL for a playlist, optionally starting its
|
|
64
|
+
* first video at `start` seconds.
|
|
65
|
+
*/
|
|
66
|
+
export const youtubePlaylistEmbedUrl = (
|
|
67
|
+
list: string,
|
|
68
|
+
options: { start?: number } = {}
|
|
69
|
+
): string => {
|
|
70
|
+
const params = new URLSearchParams({ list, ...startParam(options.start) });
|
|
71
|
+
return `https://www.youtube-nocookie.com/embed/videoseries?${params.toString()}`;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The embed URL for a `<YouTube>`'s `id` or `url`: a video, else a playlist,
|
|
76
|
+
* else `null` when the input names neither.
|
|
77
|
+
*/
|
|
78
|
+
export const youtubeEmbedSrc = (
|
|
79
|
+
input: string,
|
|
80
|
+
options: { start?: number } = {}
|
|
81
|
+
): string | null => {
|
|
82
|
+
const id = parseYouTubeId(input);
|
|
83
|
+
if (id) {
|
|
84
|
+
return youtubeEmbedUrl(id, options);
|
|
44
85
|
}
|
|
45
|
-
|
|
86
|
+
const list = parseYouTubePlaylist(input);
|
|
87
|
+
return list ? youtubePlaylistEmbedUrl(list, options) : null;
|
|
46
88
|
};
|
|
@@ -21,7 +21,11 @@ interface Props {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
const { analytics } = Astro.props;
|
|
24
|
-
|
|
24
|
+
// A `script()` src in `public/` is served under the deployment base.
|
|
25
|
+
const nodes = analyticsHead(
|
|
26
|
+
import.meta.env.PROD ? (analytics ?? []) : [],
|
|
27
|
+
import.meta.env.BASE_URL
|
|
28
|
+
);
|
|
25
29
|
const held = data.config.consent
|
|
26
30
|
? { "data-blume-consent": "analytics", type: "text/plain" }
|
|
27
31
|
: {};
|
|
@@ -8,7 +8,7 @@ import { withBase, withMountedBase } from "../islands/base-path.ts";
|
|
|
8
8
|
import Self from "./NavTree.astro";
|
|
9
9
|
import NavTreeCache from "./NavTreeCache.astro";
|
|
10
10
|
import NavTreeScript from "./NavTreeScript.astro";
|
|
11
|
-
import { isGroupRowCurrent } from "./nav-utils.ts";
|
|
11
|
+
import { groupStartsOpen, isGroupRowCurrent } from "./nav-utils.ts";
|
|
12
12
|
|
|
13
13
|
interface PagePanel {
|
|
14
14
|
id: string;
|
|
@@ -388,8 +388,13 @@ const initialId =
|
|
|
388
388
|
if (display === "group") {
|
|
389
389
|
const active = hasActivePage(item);
|
|
390
390
|
// Default every group closed; open only those on the active path (so
|
|
391
|
-
// the current page's ancestors stay expanded)
|
|
392
|
-
|
|
391
|
+
// the current page's ancestors stay expanded), forced open via meta,
|
|
392
|
+
// or alone at the top of the sidebar.
|
|
393
|
+
const open = groupStartsOpen(
|
|
394
|
+
item,
|
|
395
|
+
active,
|
|
396
|
+
depth === 0 && items.length === 1
|
|
397
|
+
);
|
|
393
398
|
return (
|
|
394
399
|
<li class={spacing}>
|
|
395
400
|
<details open={open}>
|
|
@@ -910,14 +910,16 @@ const localeRedirect = localeTargets
|
|
|
910
910
|
<slot />
|
|
911
911
|
</article>
|
|
912
912
|
{
|
|
913
|
-
|
|
913
|
+
showChrome && (
|
|
914
914
|
<div
|
|
915
915
|
class:list={[
|
|
916
916
|
// Aligned with the content column, not centered under the
|
|
917
|
-
// prose measure:
|
|
918
|
-
// page's wider
|
|
919
|
-
|
|
920
|
-
|
|
917
|
+
// prose measure: an API page's (beside its example or
|
|
918
|
+
// request column), a wide page's, or a center page's wider
|
|
919
|
+
// one.
|
|
920
|
+
isApiLayout && "xl:pe-[30.5rem]",
|
|
921
|
+
(isApiLayout || modeLayout.width === "full") &&
|
|
922
|
+
"[&>*]:max-w-none",
|
|
921
923
|
modeLayout.width === "center" && "[&>*]:max-w-[54rem]",
|
|
922
924
|
]}
|
|
923
925
|
data-blume-page-end
|
|
@@ -1005,6 +1007,8 @@ const localeRedirect = localeTargets
|
|
|
1005
1007
|
></button>
|
|
1006
1008
|
<script>
|
|
1007
1009
|
import type { Zoom } from "medium-zoom";
|
|
1010
|
+
import { copyableCode } from "../code-copy.ts";
|
|
1011
|
+
import { languageLabel } from "../code-language.ts";
|
|
1008
1012
|
import { copyText, createCopyFlash } from "../copy-feedback.ts";
|
|
1009
1013
|
import { syncDrawerInert } from "./drawer-inert.ts";
|
|
1010
1014
|
import { chromeIcons as icons } from "../../theme/chrome-icons.ts";
|
|
@@ -1081,27 +1085,6 @@ const localeRedirect = localeTargets
|
|
|
1081
1085
|
return panel.matches("pre") ? panel : panel.querySelector("pre");
|
|
1082
1086
|
};
|
|
1083
1087
|
|
|
1084
|
-
const languageLabels: Record<string, string> = {
|
|
1085
|
-
astro: "Astro",
|
|
1086
|
-
bash: "Bash",
|
|
1087
|
-
css: "CSS",
|
|
1088
|
-
html: "HTML",
|
|
1089
|
-
js: "JavaScript",
|
|
1090
|
-
json: "JSON",
|
|
1091
|
-
jsx: "JSX",
|
|
1092
|
-
md: "Markdown",
|
|
1093
|
-
mdx: "MDX",
|
|
1094
|
-
plaintext: "Text",
|
|
1095
|
-
sh: "Shell",
|
|
1096
|
-
shell: "Shell",
|
|
1097
|
-
ts: "TypeScript",
|
|
1098
|
-
tsx: "TSX",
|
|
1099
|
-
txt: "Text",
|
|
1100
|
-
yaml: "YAML",
|
|
1101
|
-
yml: "YAML",
|
|
1102
|
-
zsh: "Zsh",
|
|
1103
|
-
};
|
|
1104
|
-
|
|
1105
1088
|
// Per-page setup, run on the initial load and again after every
|
|
1106
1089
|
// client-router swap (which replaces the body with server-rendered
|
|
1107
1090
|
// markup that has none of this applied). Everything in here binds to
|
|
@@ -1176,7 +1159,14 @@ const localeRedirect = localeTargets
|
|
|
1176
1159
|
};
|
|
1177
1160
|
const flash = createCopyFlash(setChecked, copiedLabel);
|
|
1178
1161
|
button.addEventListener("click", async () => {
|
|
1179
|
-
|
|
1162
|
+
// A terminal session copies its commands alone (see
|
|
1163
|
+
// code-copy.ts), keyed off the fence's own language id.
|
|
1164
|
+
const pre = readPre();
|
|
1165
|
+
const language =
|
|
1166
|
+
pre instanceof HTMLElement
|
|
1167
|
+
? (pre.dataset.blumeLanguage ?? pre.dataset.language)
|
|
1168
|
+
: undefined;
|
|
1169
|
+
if (await copyText(copyableCode(codeText(pre), language))) {
|
|
1180
1170
|
flash();
|
|
1181
1171
|
}
|
|
1182
1172
|
});
|
|
@@ -1277,10 +1267,7 @@ const localeRedirect = localeTargets
|
|
|
1277
1267
|
if (pre instanceof HTMLElement) {
|
|
1278
1268
|
pre.dataset.blumeLanguage = language;
|
|
1279
1269
|
}
|
|
1280
|
-
pre.setAttribute(
|
|
1281
|
-
"data-language",
|
|
1282
|
-
languageLabels[language.toLowerCase()] ?? language
|
|
1283
|
-
);
|
|
1270
|
+
pre.setAttribute("data-language", languageLabel(language));
|
|
1284
1271
|
}
|
|
1285
1272
|
pre.classList.add("group", "relative");
|
|
1286
1273
|
// The code element is the scroll container (see the theme entry), but
|
|
@@ -273,6 +273,20 @@ export const getPagination = (flat: FlatPage[], route: string) => {
|
|
|
273
273
|
/** A group node in the sidebar tree. */
|
|
274
274
|
export type NavGroup = Extract<NavNode, { kind: "group" }>;
|
|
275
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Whether a `group`-mode disclosure renders open: it holds the current page,
|
|
278
|
+
* its meta forces it open (`collapsed: false`), or it is the only row at the
|
|
279
|
+
* top of the sidebar, where starting closed would leave a sidebar of one row
|
|
280
|
+
* to click before any link shows (a wrapper folder around every page). An
|
|
281
|
+
* explicit `collapsed: true` keeps that lone group closed.
|
|
282
|
+
*/
|
|
283
|
+
export const groupStartsOpen = (
|
|
284
|
+
group: NavGroup,
|
|
285
|
+
active: boolean,
|
|
286
|
+
lone: boolean
|
|
287
|
+
): boolean =>
|
|
288
|
+
active || group.collapsed === false || (lone && group.collapsed !== true);
|
|
289
|
+
|
|
276
290
|
/**
|
|
277
291
|
* The group whose own page `route` is, when that page lists the group's
|
|
278
292
|
* pages: its `directory` (inherited when the group sets none) is `card` or
|
|
@@ -32,7 +32,8 @@ interface PagefindEntry {
|
|
|
32
32
|
|
|
33
33
|
// Pagefind names each page after its built file (`quickstart/index.html`), so
|
|
34
34
|
// its URLs end in a slash. Blume serves pages without one (`trailingSlash:
|
|
35
|
-
// "never"`):
|
|
35
|
+
// "never"`): Vercel, `blume dev`, and `blume preview` redirect the slashed
|
|
36
|
+
// URL, so linking it costs a hop.
|
|
36
37
|
const TRAILING_SLASH = /(?<=.)\/(?=[#?]|$)/u;
|
|
37
38
|
|
|
38
39
|
/**
|