blume 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +147 -0
- package/dist/cli/{chunk-qwsrynx5.js → chunk-1d7ve1dm.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-1d7ve1dm.js.map} +4 -4
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-2eytanqx.js} +2 -2
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-2hsdwb9n.js} +18 -19
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-2hsdwb9n.js.map} +2 -2
- package/dist/cli/{chunk-hdpx1tax.js → chunk-2z928egk.js} +5 -5
- package/dist/cli/{chunk-jwyddg7y.js → chunk-35d4wj9f.js} +30 -24
- package/dist/cli/chunk-35d4wj9f.js.map +15 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-364znk6q.js} +2 -2
- package/dist/cli/{chunk-jts8mvcz.js → chunk-3em5wd2y.js} +25 -6
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-3em5wd2y.js.map} +3 -3
- package/dist/cli/{chunk-s1p84fyh.js → chunk-5f86nr5m.js} +63 -22
- package/dist/cli/chunk-5f86nr5m.js.map +11 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-5m5nmvyq.js} +170 -76
- package/dist/cli/chunk-5m5nmvyq.js.map +13 -0
- package/dist/cli/{chunk-epjnccmv.js → chunk-5xvm6tfj.js} +18 -15
- package/dist/cli/chunk-5xvm6tfj.js.map +10 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-6dsbexzp.js} +18 -15
- package/dist/cli/chunk-6dsbexzp.js.map +10 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-8ktnccpt.js} +1 -1
- package/dist/cli/{chunk-6hsn950k.js → chunk-9t7a85s3.js} +178 -48
- package/dist/cli/chunk-9t7a85s3.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-a58773jm.js} +2 -2
- package/dist/cli/{chunk-mb2919y2.js → chunk-acanzt5p.js} +17 -6
- package/dist/cli/chunk-acanzt5p.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-akbpwfxc.js} +90 -26
- package/dist/cli/chunk-akbpwfxc.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-b07cmahc.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-c8chx29p.js} +179 -51
- package/dist/cli/chunk-c8chx29p.js.map +36 -0
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-crgn1q09.js} +32 -14
- package/dist/cli/chunk-crgn1q09.js.map +10 -0
- package/dist/cli/chunk-e04dxsz1.js +39 -0
- package/dist/cli/chunk-e04dxsz1.js.map +10 -0
- package/dist/cli/{chunk-yt5n7ppj.js → chunk-ey84smr6.js} +17 -7
- package/dist/cli/chunk-ey84smr6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-g4hq16wv.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-g4hq16wv.js.map} +2 -2
- package/dist/cli/{chunk-fa25z98p.js → chunk-ga0pf4aj.js} +9 -8
- package/dist/cli/chunk-ga0pf4aj.js.map +11 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-hm3vjy5s.js} +109 -58
- package/dist/cli/chunk-hm3vjy5s.js.map +19 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-j85vccga.js} +625 -750
- package/dist/cli/chunk-j85vccga.js.map +36 -0
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-p3v96n38.js} +6 -6
- package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-p3v96n38.js.map} +3 -3
- package/dist/cli/{chunk-q5163e60.js → chunk-p73c0m7w.js} +21 -19
- package/dist/cli/chunk-p73c0m7w.js.map +11 -0
- package/dist/cli/{chunk-dh8cwk36.js → chunk-pehfxfta.js} +24 -9
- package/dist/cli/chunk-pehfxfta.js.map +10 -0
- package/dist/cli/{chunk-qkqwkpte.js → chunk-pv29h0wf.js} +3688 -1313
- package/dist/cli/chunk-pv29h0wf.js.map +190 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-q58y5e6a.js} +10 -10
- package/dist/cli/{chunk-6vm74dry.js.map → chunk-q58y5e6a.js.map} +3 -3
- package/dist/cli/{chunk-zxcczpyx.js → chunk-r20tn01b.js} +1 -1
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-r9rcc4w7.js} +16 -8
- package/dist/cli/chunk-r9rcc4w7.js.map +10 -0
- package/dist/cli/{chunk-5shv93fd.js → chunk-tkacnehg.js} +2 -2
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-tzmab476.js} +4 -4
- package/dist/cli/{chunk-yw7dm696.js → chunk-vg9r4eb9.js} +24 -11
- package/dist/cli/chunk-vg9r4eb9.js.map +14 -0
- package/dist/cli/{chunk-79jhk4py.js → chunk-wrr3j9w9.js} +260 -132
- package/dist/cli/chunk-wrr3j9w9.js.map +35 -0
- package/dist/cli/{chunk-6crbhc3x.js → chunk-yfyb25rh.js} +54 -27
- package/dist/cli/chunk-yfyb25rh.js.map +14 -0
- package/dist/cli/index.js +162 -35
- package/dist/cli/index.js.map +4 -4
- package/dist/types/ai/agent-surface.d.ts +32 -0
- package/dist/types/ai/api-catalog.d.ts +7 -1
- package/dist/types/ai/ask-context.d.ts +7 -0
- package/dist/types/ai/component-markdown.d.ts +4 -4
- package/dist/types/ai/link-headers.d.ts +8 -1
- package/dist/types/ai/openapi-components.d.ts +5 -2
- package/dist/types/ai/relative-links.d.ts +11 -4
- package/dist/types/ai/skills.d.ts +4 -1
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/ai/tar.d.ts +1 -3
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +4 -0
- package/dist/types/analytics/one-dollar-stats.d.ts +48 -0
- package/dist/types/analytics/schema.d.ts +28 -0
- package/dist/types/astro/integration.d.ts +3 -2
- package/dist/types/cli/env.d.ts +5 -0
- package/dist/types/cli/init/scaffold.d.ts +19 -3
- package/dist/types/cli/init/starter-spec.d.ts +11 -0
- package/dist/types/core/base-path.d.ts +24 -1
- package/dist/types/core/config-input.d.ts +13 -11
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/directive-diagnostics.d.ts +12 -0
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/heading-markers.d.ts +5 -7
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +9 -1
- package/dist/types/core/last-modified.d.ts +10 -0
- package/dist/types/core/links.d.ts +3 -1
- package/dist/types/core/load-module.d.ts +10 -0
- package/dist/types/core/locale-links.d.ts +12 -2
- package/dist/types/core/meta.d.ts +13 -1
- package/dist/types/core/nav-diagnostics.d.ts +10 -0
- package/dist/types/core/navigation.d.ts +38 -0
- package/dist/types/core/ordering-prefix.d.ts +4 -0
- package/dist/types/core/safe-links.d.ts +3 -1
- package/dist/types/core/schema.d.ts +59 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +38 -13
- package/dist/types/core/sources/normalize.d.ts +23 -2
- package/dist/types/core/sources/remote.d.ts +11 -1
- package/dist/types/core/sources/resolve.d.ts +12 -0
- package/dist/types/core/sources/types.d.ts +31 -0
- package/dist/types/core/sources/watch.d.ts +12 -5
- package/dist/types/core/standard-schema.d.ts +5 -0
- package/dist/types/core/types.d.ts +9 -0
- package/dist/types/deploy/adapters/node.d.ts +5 -2
- package/dist/types/deploy/adapters/types.d.ts +7 -0
- package/dist/types/deploy/artifacts.d.ts +6 -4
- package/dist/types/deploy/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +36 -7
- package/dist/types/deploy/node-headers.d.ts +43 -8
- package/dist/types/deploy/platforms/netlify.d.ts +27 -2
- package/dist/types/deploy/platforms/node.d.ts +6 -5
- package/dist/types/deploy/platforms/types.d.ts +15 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +31 -15
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -2
- package/dist/types/markdown/directives.d.ts +62 -0
- package/dist/types/markdown/features.d.ts +21 -0
- package/dist/types/markdown/mdast.d.ts +63 -0
- package/dist/types/openapi/asyncapi.d.ts +4 -2
- package/dist/types/openapi/model.d.ts +20 -6
- package/dist/types/search/sync/algolia.d.ts +3 -1
- package/docs/01-quickstart.mdx +3 -2
- package/docs/02-deployment.mdx +20 -9
- package/docs/08-faq.mdx +10 -3
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +7 -5
- package/docs/cli/audit.mdx +19 -3
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +4 -4
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +4 -4
- package/docs/cli/version.mdx +1 -1
- package/docs/configuration/analytics.mdx +42 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +6 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +2 -2
- package/docs/content/components.mdx +2 -2
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +3 -1
- package/docs/content/index.mdx +8 -4
- package/docs/content/islands.mdx +1 -1
- package/docs/content/meta.mdx +7 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -16
- package/docs/content/syntax.mdx +14 -0
- package/docs/content/versioning.mdx +2 -1
- package/docs/discoverability/agent-discovery.mdx +22 -8
- package/docs/discoverability/index.mdx +4 -3
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +6 -4
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +8 -4
- package/docs/discoverability/rss.mdx +4 -2
- package/docs/references/asyncapi.mdx +2 -2
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +6 -4
- package/package.json +1 -1
- package/skills/blume-migrate/SKILL.md +6 -6
- package/skills/blume-migrate/references/docusaurus.md +6 -6
- package/skills/blume-migrate/references/fumadocs.md +1 -1
- package/skills/blume-migrate/references/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +2 -2
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +9 -4
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-readability.ts +2 -2
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/ai-catalog.ts +6 -2
- package/src/ai/api/handlers.ts +2 -2
- package/src/ai/api/spec.ts +17 -4
- package/src/ai/api-catalog.ts +13 -3
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/changelog-markdown.ts +2 -2
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +22 -8
- package/src/ai/llms.ts +22 -10
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/discovery.ts +2 -2
- package/src/ai/mcp/query.ts +2 -2
- package/src/ai/mcp/server.ts +80 -9
- package/src/ai/openapi-components.ts +22 -6
- package/src/ai/relative-links.ts +99 -15
- package/src/ai/serializers.ts +2 -1
- package/src/ai/skills.ts +14 -1
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/tar.ts +139 -12
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +8 -0
- package/src/analytics/index.ts +7 -0
- package/src/analytics/one-dollar-stats.ts +86 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +4 -0
- package/src/astro/generate.ts +72 -28
- package/src/astro/include-hmr.ts +31 -12
- package/src/astro/include-refresh.ts +19 -8
- package/src/astro/integration.ts +108 -14
- package/src/astro/runtime-modules.ts +28 -13
- package/src/astro/templates.ts +139 -57
- package/src/audit/catalog.ts +2 -2
- package/src/audit/checks/assets.ts +25 -5
- package/src/audit/checks/content.ts +20 -2
- package/src/audit/checks/i18n.ts +46 -8
- package/src/audit/checks/indexability.ts +39 -19
- package/src/audit/checks/links.ts +22 -1
- package/src/audit/checks/llms.ts +6 -3
- package/src/audit/checks/network.ts +3 -1
- package/src/audit/checks/og-image.ts +10 -0
- package/src/audit/checks/robots.ts +6 -1
- package/src/audit/checks/sitemap.ts +56 -31
- package/src/audit/checks/social.ts +21 -2
- package/src/audit/crawl.ts +88 -15
- package/src/audit/graph.ts +3 -1
- package/src/audit/report.ts +54 -17
- package/src/audit/run.ts +13 -9
- package/src/audit/snapshot.ts +5 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/args.ts +32 -0
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +11 -1
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/doctor.ts +3 -1
- package/src/cli/commands/eval.ts +20 -18
- package/src/cli/commands/init.ts +15 -5
- package/src/cli/commands/preview.ts +15 -0
- package/src/cli/commands/sync.ts +2 -0
- package/src/cli/commands/translate.ts +18 -18
- package/src/cli/commands/upgrade.ts +11 -0
- package/src/cli/commands/validate.ts +3 -1
- package/src/cli/dev-lock.ts +157 -37
- package/src/cli/eject-scripts.ts +32 -7
- package/src/cli/env.ts +12 -1
- package/src/cli/init/scaffold.ts +117 -11
- package/src/cli/init/starter-spec.ts +235 -0
- package/src/cli/report-format.ts +11 -6
- package/src/components/colors.ts +19 -0
- package/src/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +7 -12
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +11 -5
- package/src/components/content/Expandable.astro +5 -1
- package/src/components/content/Frame.astro +2 -7
- package/src/components/content/GithubInfo.astro +12 -2
- package/src/components/content/Prompt.astro +2 -7
- package/src/components/content/Tab.astro +0 -1
- package/src/components/content/Tabs.astro +58 -5
- package/src/components/content/Tile.astro +1 -1
- package/src/components/content/Tooltip.astro +69 -7
- package/src/components/content/Tree.astro +7 -2
- package/src/components/content/TypeTable.astro +10 -5
- package/src/components/content/Update.astro +8 -2
- package/src/components/content/auto-type-table.ts +4 -1
- package/src/components/content/badge-color.ts +20 -0
- package/src/components/content/base-href.ts +14 -22
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +37 -6
- package/src/components/islands/base-path.ts +47 -10
- package/src/components/islands/hooks.ts +42 -21
- package/src/components/islands/webmcp.ts +16 -10
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Breadcrumbs.astro +5 -2
- package/src/components/layout/DiscoveryLinks.astro +9 -5
- package/src/components/layout/Header.astro +23 -10
- package/src/components/layout/LanguageSwitcher.astro +2 -2
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +8 -3
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +31 -12
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageActions.astro +4 -3
- package/src/components/layout/PageLayout.astro +28 -13
- package/src/components/layout/Pagination.astro +3 -3
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +25 -12
- package/src/components/layout/Search.astro +38 -14
- package/src/components/layout/VersionBanner.astro +2 -2
- package/src/components/layout/analytics-client.ts +14 -0
- package/src/components/layout/toc-active.ts +41 -0
- package/src/components/layout/toc-element.ts +8 -14
- package/src/components/openapi/ApiTagOperations.astro +2 -2
- package/src/components/openapi/AsyncApiOperation.astro +7 -4
- package/src/components/openapi/GraphqlChip.astro +2 -2
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +23 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +13 -6
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +34 -52
- package/src/components/openapi/async.ts +38 -6
- package/src/components/openapi/helpers.ts +100 -12
- package/src/components/openapi/message-composer.ts +14 -1
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +21 -3
- package/src/components/openapi/operation-model.ts +119 -17
- package/src/components/openapi/panel.ts +31 -5
- package/src/components/openapi/param-style.ts +181 -0
- package/src/components/openapi/playground-client.ts +110 -23
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +192 -26
- package/src/components/openapi/schema-tree.ts +209 -0
- package/src/components/openapi/snippets.ts +113 -19
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/components/openapi/ws-client.ts +18 -2
- package/src/core/base-path.ts +69 -8
- package/src/core/config-input.ts +13 -11
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +219 -30
- package/src/core/directive-diagnostics.ts +99 -0
- package/src/core/frontmatter.ts +21 -18
- package/src/core/graph.ts +120 -11
- package/src/core/heading-markers.ts +5 -18
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +44 -5
- package/src/core/last-modified.ts +25 -3
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +22 -18
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +89 -12
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +272 -83
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +50 -6
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +94 -18
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +28 -20
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +25 -3
- package/src/core/sources/github-releases.ts +131 -11
- package/src/core/sources/lexical.ts +30 -20
- package/src/core/sources/lower.ts +276 -56
- package/src/core/sources/mdx-remote.ts +52 -16
- package/src/core/sources/normalize.ts +456 -119
- package/src/core/sources/notion.ts +77 -34
- package/src/core/sources/obsidian.ts +40 -9
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +85 -49
- package/src/core/sources/remote.ts +18 -2
- package/src/core/sources/resolve.ts +52 -33
- package/src/core/sources/sanity.ts +1 -0
- package/src/core/sources/strapi-blocks.ts +28 -19
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- package/src/core/sources/watch.ts +20 -7
- package/src/core/standard-schema.ts +10 -6
- package/src/core/types.ts +9 -0
- package/src/core/ui-packs/ar.ts +13 -0
- package/src/core/ui-packs/bg.ts +13 -0
- package/src/core/ui-packs/bn.ts +13 -0
- package/src/core/ui-packs/ca.ts +13 -0
- package/src/core/ui-packs/cs.ts +13 -0
- package/src/core/ui-packs/da.ts +13 -0
- package/src/core/ui-packs/de.ts +13 -0
- package/src/core/ui-packs/el.ts +13 -0
- package/src/core/ui-packs/es.ts +13 -0
- package/src/core/ui-packs/fa.ts +13 -0
- package/src/core/ui-packs/fi.ts +13 -0
- package/src/core/ui-packs/fr.ts +13 -0
- package/src/core/ui-packs/he.ts +13 -0
- package/src/core/ui-packs/hi.ts +13 -0
- package/src/core/ui-packs/hr.ts +13 -0
- package/src/core/ui-packs/hu.ts +13 -0
- package/src/core/ui-packs/id.ts +13 -0
- package/src/core/ui-packs/it.ts +13 -0
- package/src/core/ui-packs/ja.ts +13 -0
- package/src/core/ui-packs/ko.ts +13 -0
- package/src/core/ui-packs/nl.ts +13 -0
- package/src/core/ui-packs/no.ts +13 -0
- package/src/core/ui-packs/pl.ts +13 -0
- package/src/core/ui-packs/pt-br.ts +13 -0
- package/src/core/ui-packs/pt.ts +13 -0
- package/src/core/ui-packs/ro.ts +13 -0
- package/src/core/ui-packs/ru.ts +13 -0
- package/src/core/ui-packs/sk.ts +13 -0
- package/src/core/ui-packs/sr.ts +13 -0
- package/src/core/ui-packs/sv.ts +13 -0
- package/src/core/ui-packs/th.ts +13 -0
- package/src/core/ui-packs/tr.ts +13 -0
- package/src/core/ui-packs/uk.ts +13 -0
- package/src/core/ui-packs/vi.ts +13 -0
- package/src/core/ui-packs/zh-tw.ts +13 -0
- package/src/core/ui-packs/zh.ts +13 -0
- package/src/core/version-cut.ts +69 -17
- package/src/core/versions.ts +4 -1
- package/src/deploy/adapters/node.ts +5 -2
- package/src/deploy/adapters/registry.ts +2 -1
- package/src/deploy/adapters/types.ts +13 -1
- package/src/deploy/artifacts.ts +52 -11
- package/src/deploy/cloudflare-negotiation.ts +27 -16
- package/src/deploy/headers.ts +75 -55
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +13 -3
- package/src/deploy/platforms/netlify.ts +83 -5
- package/src/deploy/platforms/node.ts +8 -5
- package/src/deploy/platforms/static.ts +2 -0
- package/src/deploy/platforms/types.ts +15 -0
- package/src/deploy/platforms/vercel.ts +10 -3
- package/src/deploy/redirects.ts +68 -21
- package/src/deploy/robots.ts +2 -2
- package/src/deploy/rss.ts +4 -3
- package/src/deploy/sitemap.ts +7 -5
- package/src/deploy/vercel-negotiation.ts +35 -3
- package/src/eval/agents.ts +10 -2
- package/src/eval/run.ts +25 -0
- package/src/markdown/base-links.ts +74 -29
- package/src/markdown/directives.ts +242 -36
- package/src/markdown/features.ts +17 -0
- package/src/markdown/index.ts +13 -9
- package/src/markdown/mdast.ts +5 -2
- package/src/markdown/relative-links.ts +15 -27
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +149 -9
- package/src/og/derive.ts +156 -5
- package/src/og/index.ts +1 -0
- package/src/openapi/asyncapi.ts +4 -2
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +99 -27
- package/src/openapi/proxy.ts +63 -10
- package/src/openapi/render-mdx.ts +51 -3
- package/src/registry/eject.ts +297 -43
- package/src/search/adapters/version-scope.ts +30 -0
- package/src/search/documents.ts +89 -27
- package/src/search/popular.ts +2 -1
- package/src/search/sync/algolia.ts +36 -2
- package/src/seo/jsonld.ts +7 -3
- package/src/sources/registry.ts +5 -0
- package/src/theme/entry.ts +11 -4
- package/src/translate/agents.ts +6 -1
- package/src/translate/ledger.ts +26 -3
- package/src/translate/meta.ts +76 -24
- package/src/translate/run.ts +14 -8
- package/src/translate/validate.ts +14 -2
- package/src/translate/work-list.ts +91 -19
- package/src/upgrade/upgrade.ts +36 -4
- package/dist/cli/chunk-27g6wdth.js.map +0 -10
- package/dist/cli/chunk-2hn4b8z7.js.map +0 -12
- package/dist/cli/chunk-6crbhc3x.js.map +0 -14
- package/dist/cli/chunk-6hsn950k.js.map +0 -10
- package/dist/cli/chunk-79jhk4py.js.map +0 -35
- package/dist/cli/chunk-82bbrxdn.js +0 -51
- package/dist/cli/chunk-82bbrxdn.js.map +0 -10
- package/dist/cli/chunk-abh8yjkn.js +0 -31
- package/dist/cli/chunk-abh8yjkn.js.map +0 -10
- package/dist/cli/chunk-ce574jw2.js +0 -23
- package/dist/cli/chunk-ce574jw2.js.map +0 -10
- package/dist/cli/chunk-dh8cwk36.js.map +0 -10
- package/dist/cli/chunk-epjnccmv.js.map +0 -10
- package/dist/cli/chunk-fa25z98p.js.map +0 -11
- package/dist/cli/chunk-fs23ddbb.js.map +0 -35
- package/dist/cli/chunk-fz5wtpmh.js.map +0 -10
- package/dist/cli/chunk-jwyddg7y.js.map +0 -15
- package/dist/cli/chunk-kpf8rrjc.js.map +0 -19
- package/dist/cli/chunk-m3vmjgmq.js.map +0 -10
- package/dist/cli/chunk-mb2919y2.js.map +0 -10
- package/dist/cli/chunk-q5163e60.js.map +0 -11
- package/dist/cli/chunk-qkqwkpte.js.map +0 -182
- package/dist/cli/chunk-qs4q5p4e.js.map +0 -10
- package/dist/cli/chunk-s1p84fyh.js.map +0 -10
- package/dist/cli/chunk-wgm7m9qk.js.map +0 -36
- package/dist/cli/chunk-yt5n7ppj.js.map +0 -10
- package/dist/cli/chunk-yw7dm696.js.map +0 -14
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-2eytanqx.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-2z928egk.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-364znk6q.js.map} +0 -0
- /package/dist/cli/{chunk-vtk4a6dg.js.map → chunk-8ktnccpt.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-a58773jm.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-b07cmahc.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-r20tn01b.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-tkacnehg.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-tzmab476.js.map} +0 -0
|
@@ -15,8 +15,15 @@ import {
|
|
|
15
15
|
pollingWatch,
|
|
16
16
|
snapshotCache,
|
|
17
17
|
} from "./cache.ts";
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import type { InlineRun } from "./lower.ts";
|
|
19
|
+
import {
|
|
20
|
+
codeFence,
|
|
21
|
+
guardBlockStart,
|
|
22
|
+
image,
|
|
23
|
+
linkParts,
|
|
24
|
+
renderInline,
|
|
25
|
+
} from "./lower.ts";
|
|
26
|
+
import { slugify, slugifyPath } from "./normalize.ts";
|
|
20
27
|
import type {
|
|
21
28
|
ContentSource,
|
|
22
29
|
SourceContext,
|
|
@@ -154,26 +161,43 @@ interface NotionFrontmatter {
|
|
|
154
161
|
[key: string]: boolean | string | { order: number } | undefined;
|
|
155
162
|
}
|
|
156
163
|
|
|
164
|
+
/** Neighboring rich-text runs that link to one `href` (or none), and it. */
|
|
165
|
+
interface LinkedRuns {
|
|
166
|
+
href?: string;
|
|
167
|
+
runs: InlineRun[];
|
|
168
|
+
}
|
|
169
|
+
|
|
157
170
|
/**
|
|
158
|
-
* Rich text as Markdown for an MDX body.
|
|
171
|
+
* Rich text as Markdown for an MDX body. The runs go through the shared
|
|
159
172
|
* lowering, so a literal `{`, `<`, `*`, or `[` typed in Notion renders as
|
|
160
|
-
* itself instead of opening a JSX expression, a tag, or emphasis,
|
|
161
|
-
*
|
|
173
|
+
* itself instead of opening a JSX expression, a tag, or emphasis, a code run
|
|
174
|
+
* keeps its text verbatim inside a long-enough code span, neighbors share
|
|
175
|
+
* their marks' delimiters, and neighbors that link to one `href` share one
|
|
176
|
+
* link.
|
|
162
177
|
*/
|
|
163
|
-
const richToMarkdown = (rich: NotionRichText[] = []): string =>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
176
|
-
.
|
|
178
|
+
const richToMarkdown = (rich: NotionRichText[] = []): string => {
|
|
179
|
+
const linked: LinkedRuns[] = [];
|
|
180
|
+
for (const { annotations = {}, href, plain_text: text } of rich) {
|
|
181
|
+
const run: InlineRun = {
|
|
182
|
+
marks: {
|
|
183
|
+
bold: annotations.bold,
|
|
184
|
+
code: annotations.code,
|
|
185
|
+
italic: annotations.italic,
|
|
186
|
+
strike: annotations.strikethrough,
|
|
187
|
+
},
|
|
188
|
+
text,
|
|
189
|
+
};
|
|
190
|
+
const last = linked.at(-1);
|
|
191
|
+
if (last && last.href === (href ?? undefined)) {
|
|
192
|
+
last.runs.push(run);
|
|
193
|
+
} else {
|
|
194
|
+
linked.push({ href: href ?? undefined, runs: [run] });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return renderInline(
|
|
198
|
+
linked.flatMap(({ href, runs }) => linkParts(runs, href))
|
|
199
|
+
);
|
|
200
|
+
};
|
|
177
201
|
|
|
178
202
|
const isBlockPayload = (
|
|
179
203
|
value: NotionBlockPayload | boolean | string | undefined
|
|
@@ -217,12 +241,12 @@ const withNotionRetry = async <T>(
|
|
|
217
241
|
if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
|
|
218
242
|
throw error;
|
|
219
243
|
}
|
|
220
|
-
// SAFETY: same APIResponseError shape — `headers`
|
|
221
|
-
//
|
|
222
|
-
// back to
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
244
|
+
// SAFETY: same APIResponseError shape — `headers` is the failed
|
|
245
|
+
// response's fetch `Headers`, read with `get()`; a missing header (or
|
|
246
|
+
// headers without `get`) yields no positive wait and falls back to
|
|
247
|
+
// exponential backoff.
|
|
248
|
+
const { headers } = error as { headers?: Headers };
|
|
249
|
+
const retryAfter = Number(headers?.get?.("retry-after"));
|
|
226
250
|
const wait =
|
|
227
251
|
retryAfter > 0
|
|
228
252
|
? retryAfter * SECOND_MS
|
|
@@ -266,6 +290,12 @@ const richToPlain = (rich: NotionRichText[] = []): string =>
|
|
|
266
290
|
*/
|
|
267
291
|
const jsxString = (value: string): string => `{${JSON.stringify(value)}}`;
|
|
268
292
|
|
|
293
|
+
// `<Frame>` renders its caption as Markdown after escaping `<` and `>` itself
|
|
294
|
+
// (so a caption can't inject HTML), which turns the lowering's own `\<` into
|
|
295
|
+
// a visible `<`. Its caption drops those backslashes and leaves the
|
|
296
|
+
// escaping to Frame; every other escape still applies.
|
|
297
|
+
const FRAME_ESCAPED = /\\(?=[<>])/gu;
|
|
298
|
+
|
|
269
299
|
const YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
|
|
270
300
|
|
|
271
301
|
// `parseYouTubeId` is host-agnostic on purpose (the component accepts bare
|
|
@@ -295,19 +325,23 @@ const renderVideo = (data: NotionBlockPayload): string => {
|
|
|
295
325
|
const title = caption ? ` title=${jsxString(richToPlain(data.caption))}` : "";
|
|
296
326
|
// Everything else (a Notion upload, or a direct link to a media file) is a
|
|
297
327
|
// real video file: `materializeAssets` rewrites the `src`, which matters most
|
|
298
|
-
// for uploads, whose Notion URLs are signed and expire.
|
|
328
|
+
// for uploads, whose Notion URLs are signed and expire. It finds the file by
|
|
329
|
+
// that quoted `src`, where MDX decodes no backslash escapes — a JSON
|
|
330
|
+
// string's `\\` and `\"` would corrupt the URL or end the attribute — so the
|
|
331
|
+
// URL goes in as written, with a double quote as the `%22` it means anyway.
|
|
332
|
+
// The embed's `url` takes the expression form, like every string prop here.
|
|
299
333
|
const media = isYouTubeUrl(url)
|
|
300
|
-
? `<YouTube${title} url=${
|
|
301
|
-
: `<video controls src
|
|
334
|
+
? `<YouTube${title} url=${jsxString(url)} />`
|
|
335
|
+
: `<video controls src="${url.replaceAll('"', "%22")}" />`;
|
|
302
336
|
return caption
|
|
303
|
-
? `<Frame caption=${jsxString(caption)}>\n${media}\n</Frame>`
|
|
337
|
+
? `<Frame caption=${jsxString(caption.replaceAll(FRAME_ESCAPED, ""))}>\n${media}\n</Frame>`
|
|
304
338
|
: media;
|
|
305
339
|
};
|
|
306
340
|
|
|
307
341
|
/** Render a leaf (non-container) block to Markdown, or null for containers. */
|
|
308
342
|
const renderLeaf = (block: NotionBlock): string | null => {
|
|
309
343
|
const data = payloadOf(block) ?? {};
|
|
310
|
-
const text = richToMarkdown(blockField(block));
|
|
344
|
+
const text = guardBlockStart(richToMarkdown(blockField(block)));
|
|
311
345
|
switch (block.type) {
|
|
312
346
|
case "paragraph": {
|
|
313
347
|
return text;
|
|
@@ -440,7 +474,10 @@ export const notionSource = (
|
|
|
440
474
|
};
|
|
441
475
|
|
|
442
476
|
if (block.type === "callout") {
|
|
443
|
-
const body = [
|
|
477
|
+
const body = [
|
|
478
|
+
guardBlockStart(richToMarkdown(blockField(block))),
|
|
479
|
+
await children(block),
|
|
480
|
+
]
|
|
444
481
|
.filter(Boolean)
|
|
445
482
|
.join("\n\n");
|
|
446
483
|
return `<Callout>\n${body}\n</Callout>`;
|
|
@@ -562,9 +599,14 @@ export const notionSource = (
|
|
|
562
599
|
const slugProp = richToPlain(
|
|
563
600
|
page.properties[props.slug ?? "Slug"]?.rich_text
|
|
564
601
|
);
|
|
565
|
-
//
|
|
566
|
-
// instead of mashing into `guidessetup`.
|
|
567
|
-
|
|
602
|
+
// A Slug property is path-aware: `guides/setup` keeps its `/` (per-segment
|
|
603
|
+
// slugging) instead of mashing into `guidessetup`. A title is one name, so
|
|
604
|
+
// its slashes become hyphens: "CI/CD Setup" routes to `ci-cd-setup`, not
|
|
605
|
+
// to `cd-setup` inside an invented `ci` group.
|
|
606
|
+
const slug =
|
|
607
|
+
(slugProp
|
|
608
|
+
? slugifyPath(slugProp)
|
|
609
|
+
: slugify(title.replaceAll("/", "-"))) || page.id;
|
|
568
610
|
return { data, slug };
|
|
569
611
|
};
|
|
570
612
|
|
|
@@ -680,5 +722,6 @@ export const notionSource = (
|
|
|
680
722
|
() => load()
|
|
681
723
|
)
|
|
682
724
|
: undefined,
|
|
725
|
+
withContext: (next) => notionSource(options, next),
|
|
683
726
|
};
|
|
684
727
|
};
|
|
@@ -20,6 +20,8 @@ import {
|
|
|
20
20
|
placeEntryRef,
|
|
21
21
|
resolveEntryRoute,
|
|
22
22
|
slugifyPath,
|
|
23
|
+
strippedLineOffset,
|
|
24
|
+
unloadablePathDiagnostic,
|
|
23
25
|
} from "./normalize.ts";
|
|
24
26
|
import type {
|
|
25
27
|
ContentSource,
|
|
@@ -84,6 +86,8 @@ interface ParsedNote {
|
|
|
84
86
|
/** The note body, frontmatter stripped and otherwise untouched. */
|
|
85
87
|
content: string;
|
|
86
88
|
data: SourceEntry["data"];
|
|
89
|
+
/** How many lines of the file the frontmatter block (if any) spans. */
|
|
90
|
+
frontmatterLines: number;
|
|
87
91
|
placement: EntryPlacement;
|
|
88
92
|
/** Vault-relative path, e.g. `guides/Getting Started.md`. */
|
|
89
93
|
rel: string;
|
|
@@ -584,10 +588,11 @@ const transformBody = (
|
|
|
584
588
|
* `[#custom-id]` pin and the `setup-1` a later collision gets are honored
|
|
585
589
|
* rather than re-slugged. When no heading holds anything the rewrite changes,
|
|
586
590
|
* the raw body's headings are the staged body's; otherwise the body is lowered
|
|
587
|
-
* once more so the headings are scanned exactly as they ship. A link
|
|
588
|
-
* heading is rewritten here before
|
|
589
|
-
*
|
|
590
|
-
*
|
|
591
|
+
* once more so the headings are scanned exactly as they ship. A heading link
|
|
592
|
+
* inside a heading is rewritten here before every note's anchors are known,
|
|
593
|
+
* so its href may still lack the anchor it ships with. That moves no id: a
|
|
594
|
+
* heading is slugged by its rendered text, where the link is its label alone,
|
|
595
|
+
* so the heading stays as addressable as any other.
|
|
591
596
|
*/
|
|
592
597
|
const fillAnchors = (pair: IndexedPair, index: LinkIndex): void => {
|
|
593
598
|
const raw = extractHeadings(pair.note.content);
|
|
@@ -638,7 +643,7 @@ const buildLinkIndex = (
|
|
|
638
643
|
{ ref: note.rel, slug: entrySlugFor(note.placement.navPath) },
|
|
639
644
|
".md",
|
|
640
645
|
isStringValue(note.data.slug) ? note.data.slug : undefined,
|
|
641
|
-
options
|
|
646
|
+
{ ...options, orderingPrefixes: true }
|
|
642
647
|
);
|
|
643
648
|
const built: IndexedNote = {
|
|
644
649
|
anchors: new Map(),
|
|
@@ -756,10 +761,17 @@ const noteToEntry = (
|
|
|
756
761
|
)
|
|
757
762
|
);
|
|
758
763
|
const merged = title === undefined ? data : { ...data, title };
|
|
759
|
-
const
|
|
764
|
+
const body = note.content.trim();
|
|
765
|
+
const text = transformBody(body, index, pair.self, unresolved);
|
|
760
766
|
const raw = matter.stringify(`${text}\n`, merged);
|
|
767
|
+
// `raw` re-serializes the frontmatter without the dropped properties, so
|
|
768
|
+
// its height is not the file's: count the file's own frontmatter and the
|
|
769
|
+
// blank lines the trim dropped, so a link diagnostic names the line the
|
|
770
|
+
// author sees in the note.
|
|
771
|
+
const leading = note.content.slice(0, note.content.indexOf(body));
|
|
761
772
|
return {
|
|
762
773
|
body: { format: "md", text },
|
|
774
|
+
bodyLineOffset: note.frontmatterLines + leading.split("\n").length - 1,
|
|
763
775
|
data: merged,
|
|
764
776
|
hash: hashText(raw),
|
|
765
777
|
raw,
|
|
@@ -782,11 +794,13 @@ const readNote = async (
|
|
|
782
794
|
): Promise<ParsedNote | null> => {
|
|
783
795
|
const absPath = join(vaultDir, rel);
|
|
784
796
|
try {
|
|
785
|
-
const
|
|
797
|
+
const file = await readFile(absPath, "utf-8");
|
|
798
|
+
const { content, data } = matter(file);
|
|
786
799
|
return {
|
|
787
800
|
absPath,
|
|
788
801
|
content,
|
|
789
802
|
data,
|
|
803
|
+
frontmatterLines: strippedLineOffset(file, content),
|
|
790
804
|
// Version outermost, then locale, the way `normalizeEntry` reads the
|
|
791
805
|
// ref — the slug is built from what remains, so the pipeline's own
|
|
792
806
|
// re-prefixing does not stack a second `fr/` or `v1.0/` onto the route.
|
|
@@ -946,7 +960,19 @@ export const obsidianSource = (
|
|
|
946
960
|
const exclude = new Set([...BLUME_IGNORE_DIRS, ...(options.exclude ?? [])]);
|
|
947
961
|
|
|
948
962
|
const load = async (): Promise<SourceLoadResult> => {
|
|
949
|
-
|
|
963
|
+
// A note is staged under its vault path, where Astro's content loader
|
|
964
|
+
// can't read a name holding `#` or `?`; such a note is reported and left
|
|
965
|
+
// out, and links to it degrade like links to any missing note.
|
|
966
|
+
const skipped: Diagnostic[] = [];
|
|
967
|
+
const files: string[] = [];
|
|
968
|
+
for (const rel of await walkVault(vaultDir, vaultDir, exclude)) {
|
|
969
|
+
const unloadable = unloadablePathDiagnostic(rel, join(vaultDir, rel));
|
|
970
|
+
if (unloadable) {
|
|
971
|
+
skipped.push(unloadable);
|
|
972
|
+
} else {
|
|
973
|
+
files.push(rel);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
950
976
|
const read = await Promise.all(
|
|
951
977
|
files.map((rel) => readNote(vaultDir, rel, options))
|
|
952
978
|
);
|
|
@@ -959,7 +985,10 @@ export const obsidianSource = (
|
|
|
959
985
|
noteToEntry(pair, index, options, unresolved)
|
|
960
986
|
);
|
|
961
987
|
return {
|
|
962
|
-
diagnostics:
|
|
988
|
+
diagnostics: [
|
|
989
|
+
...skipped,
|
|
990
|
+
...unresolvedDiagnostics(options.name, unresolved),
|
|
991
|
+
],
|
|
963
992
|
entries,
|
|
964
993
|
};
|
|
965
994
|
};
|
|
@@ -1030,6 +1059,8 @@ export const obsidianSource = (
|
|
|
1030
1059
|
contentRoot: vaultDir,
|
|
1031
1060
|
load,
|
|
1032
1061
|
name: options.name,
|
|
1062
|
+
// Notes are named like files: `01 Intro` sorts first and routes `/intro`.
|
|
1063
|
+
orderedNames: true,
|
|
1033
1064
|
prefix: options.prefix,
|
|
1034
1065
|
read,
|
|
1035
1066
|
staged: true,
|
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
* user-supplied serializer or are skipped with a noted comment. Output is
|
|
5
5
|
* Markdown text that flows through Blume's normal pipeline.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import type { InlineRun } from "./lower.ts";
|
|
8
|
+
import {
|
|
9
|
+
guardBlockStart,
|
|
10
|
+
image,
|
|
11
|
+
linkParts,
|
|
12
|
+
renderInline,
|
|
13
|
+
unsupported,
|
|
14
|
+
writesMdx,
|
|
15
|
+
} from "./lower.ts";
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
18
|
* A field value on a Portable Text node: the arbitrary JSON the CMS query
|
|
@@ -66,64 +74,91 @@ const HEADING_STYLES = new Map([
|
|
|
66
74
|
["h6", "###### "],
|
|
67
75
|
]);
|
|
68
76
|
|
|
69
|
-
/**
|
|
70
|
-
const
|
|
71
|
-
span
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
// Decorators wrap inline; a link def wraps last so its label keeps emphasis.
|
|
82
|
-
let link: PortableTextMarkDef | undefined;
|
|
83
|
-
for (const mark of span.marks) {
|
|
84
|
-
switch (mark) {
|
|
85
|
-
case "strong": {
|
|
86
|
-
text = `**${text}**`;
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
|
-
case "em": {
|
|
90
|
-
text = `*${text}*`;
|
|
91
|
-
break;
|
|
92
|
-
}
|
|
93
|
-
case "code": {
|
|
94
|
-
text = `\`${text}\``;
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
case "strike-through": {
|
|
98
|
-
text = `~~${text}~~`;
|
|
99
|
-
break;
|
|
100
|
-
}
|
|
101
|
-
default: {
|
|
102
|
-
const def = defs.get(mark);
|
|
103
|
-
if (def?._type === "link") {
|
|
104
|
-
link = def;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return link?.href ? `[${text}](${link.href})` : text;
|
|
77
|
+
/** A span's text and the marks Markdown has syntax for. */
|
|
78
|
+
const spanRun = (span: PortableTextSpan): InlineRun => {
|
|
79
|
+
const marks = span.marks ?? [];
|
|
80
|
+
return {
|
|
81
|
+
marks: {
|
|
82
|
+
bold: marks.includes("strong"),
|
|
83
|
+
code: marks.includes("code"),
|
|
84
|
+
italic: marks.includes("em"),
|
|
85
|
+
strike: marks.includes("strike-through"),
|
|
86
|
+
},
|
|
87
|
+
text: span.text ?? "",
|
|
88
|
+
};
|
|
110
89
|
};
|
|
111
90
|
|
|
112
|
-
/**
|
|
91
|
+
/** Neighboring spans under one link def (or none), and that def. */
|
|
92
|
+
interface LinkedSpans {
|
|
93
|
+
link?: PortableTextMarkDef;
|
|
94
|
+
runs: InlineRun[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Render the inline children of a block to a single Markdown string, through
|
|
99
|
+
* the shared lowering every CMS uses: prose is escaped, a code span outruns
|
|
100
|
+
* the backticks inside it, edge whitespace stays outside emphasis, neighbors
|
|
101
|
+
* share their marks' delimiters, and neighbors under one link def share one
|
|
102
|
+
* link — wrapped last (so its label keeps emphasis), and only when its
|
|
103
|
+
* destination is safe to click.
|
|
104
|
+
*/
|
|
113
105
|
const renderChildren = (block: PortableTextBlock): string => {
|
|
114
106
|
const defs = new Map(
|
|
115
107
|
(block.markDefs ?? []).map((def) => [def._key, def] as const)
|
|
116
108
|
);
|
|
117
|
-
|
|
109
|
+
const linked: LinkedSpans[] = [];
|
|
110
|
+
for (const span of block.children ?? []) {
|
|
111
|
+
const link = (span.marks ?? [])
|
|
112
|
+
.map((mark) => defs.get(mark))
|
|
113
|
+
.findLast((def) => def?._type === "link");
|
|
114
|
+
const last = linked.at(-1);
|
|
115
|
+
if (last && last.link === link) {
|
|
116
|
+
last.runs.push(spanRun(span));
|
|
117
|
+
} else {
|
|
118
|
+
linked.push({ link, runs: [spanRun(span)] });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return guardBlockStart(
|
|
122
|
+
renderInline(
|
|
123
|
+
linked.flatMap(({ link, runs }) => linkParts(runs, link?.href))
|
|
124
|
+
)
|
|
125
|
+
);
|
|
118
126
|
};
|
|
119
127
|
|
|
120
128
|
/** Whether an image block's `alt` field is usable alt text (CMS JSON may hold anything). */
|
|
121
129
|
const isAltText = (value: PortableTextValue): value is string =>
|
|
122
130
|
typeof value === "string";
|
|
123
131
|
|
|
132
|
+
/** The marker a list block opens with. */
|
|
133
|
+
const listMarker = (block: PortableTextBlock): string =>
|
|
134
|
+
block.listItem === "number" ? "1." : "-";
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* How far each block is indented. Portable Text lists are flat blocks with a
|
|
138
|
+
* `level`, while Markdown nests an item by indenting it to its parent item's
|
|
139
|
+
* content column: two past a `-` item's indent, three past a `1.` item's. The
|
|
140
|
+
* column comes from the item actually above, so a skipped level nests under
|
|
141
|
+
* the deepest open item instead of indenting into a code block.
|
|
142
|
+
*/
|
|
143
|
+
const listIndents = (blocks: PortableTextBlock[]): number[] => {
|
|
144
|
+
const columns: number[] = [];
|
|
145
|
+
return blocks.map((block) => {
|
|
146
|
+
if (!block.listItem) {
|
|
147
|
+
columns.length = 0;
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
const depth = Math.min(Math.max(0, (block.level ?? 1) - 1), columns.length);
|
|
151
|
+
const indent = columns[depth - 1] ?? 0;
|
|
152
|
+
columns.length = depth;
|
|
153
|
+
columns.push(indent + listMarker(block).length + 1);
|
|
154
|
+
return indent;
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
124
158
|
const renderBlock = (
|
|
125
159
|
block: PortableTextBlock,
|
|
126
|
-
options: PortableTextOptions
|
|
160
|
+
options: PortableTextOptions,
|
|
161
|
+
indent: number
|
|
127
162
|
): string => {
|
|
128
163
|
const custom = options.serializers?.[block._type];
|
|
129
164
|
if (custom) {
|
|
@@ -143,9 +178,7 @@ const renderBlock = (
|
|
|
143
178
|
|
|
144
179
|
const inline = renderChildren(block);
|
|
145
180
|
if (block.listItem) {
|
|
146
|
-
|
|
147
|
-
const marker = block.listItem === "number" ? "1." : "-";
|
|
148
|
-
return `${indent}${marker} ${inline}`;
|
|
181
|
+
return `${" ".repeat(indent)}${listMarker(block)} ${inline}`;
|
|
149
182
|
}
|
|
150
183
|
if (block.style === "blockquote") {
|
|
151
184
|
return `> ${inline}`;
|
|
@@ -158,7 +191,10 @@ export const portableTextToMarkdown = (
|
|
|
158
191
|
blocks: PortableTextBlock[],
|
|
159
192
|
options: PortableTextOptions = {}
|
|
160
193
|
): string => {
|
|
161
|
-
const
|
|
194
|
+
const indents = listIndents(blocks);
|
|
195
|
+
const lines = blocks.map((block, i) =>
|
|
196
|
+
renderBlock(block, options, indents[i] ?? 0)
|
|
197
|
+
);
|
|
162
198
|
// List items are single-newline separated; everything else gets a blank line.
|
|
163
199
|
const out: string[] = [];
|
|
164
200
|
for (let i = 0; i < lines.length; i += 1) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from "pathe";
|
|
2
2
|
|
|
3
3
|
import matter from "../frontmatter.ts";
|
|
4
|
+
import { neutralizeUnsafeLinks } from "../safe-links.ts";
|
|
4
5
|
import {
|
|
5
6
|
hashText,
|
|
6
7
|
loadWithCache,
|
|
@@ -19,12 +20,21 @@ import type {
|
|
|
19
20
|
|
|
20
21
|
/** What every REST-backed CMS source needs to be a staged, cached source. */
|
|
21
22
|
export interface RemoteSourceOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Throw a `BlumeError` when the source cannot load as configured. Runs
|
|
25
|
+
* before the cache is consulted: thrown from `fetchEntries` instead, a
|
|
26
|
+
* misconfiguration would be served over by a stale snapshot with only an
|
|
27
|
+
* offline warning.
|
|
28
|
+
*/
|
|
29
|
+
assertConfigured?: () => void;
|
|
22
30
|
/** Pull every entry from the API; called on refresh and on each poll. */
|
|
23
31
|
fetchEntries: () => Promise<SourceEntry[]>;
|
|
24
32
|
name: string;
|
|
25
33
|
/** Opt-in dev polling interval (seconds); omit to freeze for the session. */
|
|
26
34
|
pollInterval?: number;
|
|
27
35
|
prefix?: string;
|
|
36
|
+
/** The adapter rebuilt on another context (`ContentSource.withContext`). */
|
|
37
|
+
withContext?: (ctx: SourceContext) => ContentSource;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
/**
|
|
@@ -45,6 +55,7 @@ export const remoteSource = (
|
|
|
45
55
|
const load = async (
|
|
46
56
|
refresh = ctx?.refresh ?? true
|
|
47
57
|
): Promise<SourceLoadResult> => {
|
|
58
|
+
options.assertConfigured?.();
|
|
48
59
|
const result = await loadWithCache(
|
|
49
60
|
options.name,
|
|
50
61
|
cache,
|
|
@@ -77,6 +88,7 @@ export const remoteSource = (
|
|
|
77
88
|
() => load()
|
|
78
89
|
)
|
|
79
90
|
: undefined,
|
|
91
|
+
withContext: options.withContext,
|
|
80
92
|
};
|
|
81
93
|
};
|
|
82
94
|
|
|
@@ -182,7 +194,8 @@ export interface RemoteFieldMap {
|
|
|
182
194
|
* when the slug field is missing or slugifies to nothing (pure punctuation),
|
|
183
195
|
* so distinct documents never collapse onto one `untitled.md`; a slashed
|
|
184
196
|
* slug keeps its segments. A string body is Markdown and passes through as
|
|
185
|
-
* `.md
|
|
197
|
+
* `.md`, its unsafe links reduced to their labels; any other shape goes to
|
|
198
|
+
* the CMS's lowerer, and is written as `.mdx`
|
|
186
199
|
* when `lowersToMdx` (the source has serializers, see `writesMdx`).
|
|
187
200
|
*/
|
|
188
201
|
export const documentEntry = (
|
|
@@ -211,7 +224,10 @@ export const documentEntry = (
|
|
|
211
224
|
let markdown = "";
|
|
212
225
|
let format: "md" | "mdx" = "md";
|
|
213
226
|
if (isStringValue(body)) {
|
|
214
|
-
|
|
227
|
+
// The CMS's Markdown is its editors', not the site author's: a link whose
|
|
228
|
+
// destination isn't a web, mail, or relative address (`javascript:`)
|
|
229
|
+
// keeps only its label, as release notes do (see `safe-links.ts`).
|
|
230
|
+
markdown = neutralizeUnsafeLinks(`${body.trimEnd()}\n`);
|
|
215
231
|
} else if (body !== undefined) {
|
|
216
232
|
markdown = lower(body);
|
|
217
233
|
format = lowersToMdx ? "mdx" : "md";
|