blume 2.0.1 → 2.0.2
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 +82 -0
- package/dist/cli/{chunk-vtk4a6dg.js → chunk-00gs3wqs.js} +1 -1
- package/dist/cli/{chunk-fa25z98p.js → chunk-273ygyr4.js} +4 -3
- package/dist/cli/chunk-273ygyr4.js.map +11 -0
- package/dist/cli/{chunk-yw7dm696.js → chunk-3yce002v.js} +21 -8
- package/dist/cli/{chunk-yw7dm696.js.map → chunk-3yce002v.js.map} +3 -3
- package/dist/cli/{chunk-yt5n7ppj.js → chunk-4e9b9ra6.js} +17 -7
- package/dist/cli/chunk-4e9b9ra6.js.map +10 -0
- package/dist/cli/{chunk-fs23ddbb.js → chunk-5r8g91qn.js} +352 -676
- package/dist/cli/chunk-5r8g91qn.js.map +34 -0
- package/dist/cli/{chunk-6vm74dry.js → chunk-6dtt0zfn.js} +8 -8
- package/dist/cli/{chunk-qwsrynx5.js → chunk-6k4ftwze.js} +38 -19
- package/dist/cli/{chunk-qwsrynx5.js.map → chunk-6k4ftwze.js.map} +4 -4
- package/dist/cli/{chunk-qs4q5p4e.js → chunk-7mbqtmgb.js} +16 -7
- package/dist/cli/chunk-7mbqtmgb.js.map +10 -0
- package/dist/cli/{chunk-q5163e60.js → chunk-7vtckvaw.js} +21 -19
- package/dist/cli/chunk-7vtckvaw.js.map +11 -0
- package/dist/cli/{chunk-kdp5q7ke.js → chunk-8g8ytmgx.js} +17 -18
- package/dist/cli/{chunk-kdp5q7ke.js.map → chunk-8g8ytmgx.js.map} +2 -2
- package/dist/cli/{chunk-epjnccmv.js → chunk-91ws1n6j.js} +18 -15
- package/dist/cli/chunk-91ws1n6j.js.map +10 -0
- package/dist/cli/{chunk-fxypxtvm.js → chunk-bbnwccaz.js} +2 -2
- package/dist/cli/{chunk-m3vmjgmq.js → chunk-bfwp9vp6.js} +16 -8
- package/dist/cli/chunk-bfwp9vp6.js.map +10 -0
- package/dist/cli/{chunk-f2z5v128.js → chunk-d1v5rhy0.js} +14 -15
- package/dist/cli/{chunk-f2z5v128.js.map → chunk-d1v5rhy0.js.map} +2 -2
- package/dist/cli/{chunk-6crbhc3x.js → chunk-ddndchfr.js} +21 -6
- package/dist/cli/chunk-ddndchfr.js.map +14 -0
- package/dist/cli/{chunk-ce574jw2.js → chunk-esh98wmb.js} +1 -1
- package/dist/cli/{chunk-zxcczpyx.js → chunk-fsmrqk8a.js} +1 -1
- package/dist/cli/{chunk-hdpx1tax.js → chunk-g698a744.js} +5 -5
- package/dist/cli/{chunk-jts8mvcz.js → chunk-gs7r695n.js} +9 -3
- package/dist/cli/{chunk-jts8mvcz.js.map → chunk-gs7r695n.js.map} +3 -3
- package/dist/cli/{chunk-zxh4d9vy.js → chunk-h2ez8dzb.js} +4 -4
- package/dist/cli/{chunk-5shv93fd.js → chunk-h7k3nq3v.js} +2 -2
- package/dist/cli/{chunk-79jhk4py.js → chunk-hqp2ajnh.js} +251 -122
- package/dist/cli/chunk-hqp2ajnh.js.map +35 -0
- package/dist/cli/{chunk-2hn4b8z7.js → chunk-hr8ne106.js} +109 -40
- package/dist/cli/chunk-hr8ne106.js.map +13 -0
- package/dist/cli/{chunk-6hsn950k.js → chunk-j85scx15.js} +62 -18
- package/dist/cli/chunk-j85scx15.js.map +10 -0
- package/dist/cli/{chunk-s1p84fyh.js → chunk-k7pj68a8.js} +63 -22
- package/dist/cli/chunk-k7pj68a8.js.map +11 -0
- package/dist/cli/{chunk-ah61y8py.js → chunk-mqc662a6.js} +2 -2
- package/dist/cli/{chunk-ch6g3ar0.js → chunk-n1yg3tj3.js} +4 -4
- package/dist/cli/{chunk-mb2919y2.js → chunk-nfcyttvj.js} +17 -6
- package/dist/cli/chunk-nfcyttvj.js.map +10 -0
- package/dist/cli/{chunk-kpf8rrjc.js → chunk-pbg5a4s3.js} +56 -24
- package/dist/cli/chunk-pbg5a4s3.js.map +19 -0
- package/dist/cli/{chunk-jwyddg7y.js → chunk-ppzjqwx2.js} +21 -14
- package/dist/cli/{chunk-jwyddg7y.js.map → chunk-ppzjqwx2.js.map} +4 -4
- package/dist/cli/{chunk-dh8cwk36.js → chunk-qkb5a8sa.js} +24 -9
- package/dist/cli/chunk-qkb5a8sa.js.map +10 -0
- package/dist/cli/{chunk-wm7js3j9.js → chunk-sqw4ekg1.js} +2 -2
- package/dist/cli/{chunk-qkqwkpte.js → chunk-v6ya5kcb.js} +3083 -953
- package/dist/cli/chunk-v6ya5kcb.js.map +189 -0
- package/dist/cli/{chunk-fz5wtpmh.js → chunk-w4bxdvsa.js} +18 -15
- package/dist/cli/chunk-w4bxdvsa.js.map +10 -0
- package/dist/cli/{chunk-27g6wdth.js → chunk-wdrt2k2v.js} +2 -2
- package/dist/cli/{chunk-wgm7m9qk.js → chunk-xh43dwgw.js} +158 -43
- package/dist/cli/chunk-xh43dwgw.js.map +36 -0
- package/dist/cli/{chunk-s6jhgk0q.js → chunk-zp79m0ts.js} +2 -2
- package/dist/cli/index.js +160 -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/relative-links.d.ts +9 -4
- package/dist/types/ai/static-expression.d.ts +28 -0
- package/dist/types/analytics/databuddy.d.ts +43 -0
- package/dist/types/analytics/index.d.ts +2 -0
- package/dist/types/analytics/schema.d.ts +14 -0
- 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 +9 -0
- package/dist/types/core/config-input.d.ts +4 -4
- package/dist/types/core/config.d.ts +2 -2
- package/dist/types/core/graph.d.ts +2 -0
- package/dist/types/core/i18n-ui.d.ts +31 -0
- package/dist/types/core/i18n.d.ts +7 -1
- 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 +5 -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 +52 -13
- package/dist/types/core/sources/github-releases.d.ts +5 -0
- package/dist/types/core/sources/lower.d.ts +14 -2
- package/dist/types/core/sources/normalize.d.ts +10 -1
- 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/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/cloudflare-negotiation.d.ts +3 -2
- package/dist/types/deploy/headers.d.ts +31 -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 +7 -0
- package/dist/types/deploy/platforms/vercel.d.ts +3 -2
- package/dist/types/deploy/redirects.d.ts +7 -2
- package/dist/types/deploy/vercel-negotiation.d.ts +3 -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 +17 -8
- package/docs/08-faq.mdx +9 -2
- package/docs/advanced/changelog.mdx +1 -1
- package/docs/advanced/custom-pages.mdx +4 -2
- package/docs/cli/audit.mdx +2 -2
- package/docs/cli/doctor.mdx +2 -2
- package/docs/cli/evals.mdx +2 -2
- package/docs/cli/index.mdx +4 -1
- package/docs/cli/translate.mdx +1 -1
- package/docs/configuration/analytics.mdx +23 -2
- package/docs/configuration/assistant.mdx +1 -1
- package/docs/configuration/customization.mdx +4 -3
- package/docs/configuration/index.mdx +5 -3
- package/docs/configuration/search.mdx +1 -1
- package/docs/content/components.mdx +1 -1
- package/docs/content/frontmatter.mdx +5 -1
- package/docs/content/i18n.mdx +1 -1
- package/docs/content/index.mdx +4 -2
- package/docs/content/meta.mdx +5 -3
- package/docs/content/navigation.mdx +29 -4
- package/docs/content/sources.mdx +18 -12
- package/docs/content/versioning.mdx +1 -0
- package/docs/discoverability/agent-discovery.mdx +21 -7
- package/docs/discoverability/index.mdx +2 -2
- package/docs/discoverability/llms-txt.mdx +2 -5
- package/docs/discoverability/markdown.mdx +5 -3
- package/docs/discoverability/mcp.mdx +4 -0
- package/docs/discoverability/metadata.mdx +3 -2
- package/docs/discoverability/open-graph.mdx +6 -4
- package/docs/discoverability/rss.mdx +3 -1
- package/docs/references/asyncapi.mdx +1 -1
- package/docs/references/graphql.mdx +2 -2
- package/docs/references/openapi.mdx +3 -3
- 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/mintlify.md +3 -3
- package/skills/blume-migrate/references/monorepo.md +1 -1
- package/skills/blume-migrate/references/nextra.md +1 -1
- package/skills/blume-migrate/references/starlight.md +5 -5
- package/skills/blume-migrate/scripts/mintlify-codemod.mjs +2 -2
- package/skills/blume-update-docs/references/audit-checklist.md +1 -1
- package/src/ai/agent-surface.ts +56 -0
- package/src/ai/api/spec.ts +15 -2
- package/src/ai/api-catalog.ts +7 -1
- package/src/ai/ask-context.ts +14 -2
- package/src/ai/ask-data.ts +26 -12
- package/src/ai/component-markdown.ts +43 -37
- package/src/ai/link-headers.ts +8 -3
- package/src/ai/llms.ts +4 -3
- package/src/ai/markdown.ts +23 -11
- package/src/ai/mcp/server.ts +78 -7
- package/src/ai/relative-links.ts +78 -10
- package/src/ai/static-expression.ts +416 -0
- package/src/ai/visibility.ts +45 -14
- package/src/analytics/databuddy.ts +67 -0
- package/src/analytics/head.ts +4 -0
- package/src/analytics/index.ts +2 -0
- package/src/analytics/posthog.ts +21 -3
- package/src/analytics/schema.ts +2 -0
- package/src/astro/generate.ts +71 -27
- package/src/astro/templates.ts +61 -23
- 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 +14 -0
- 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/report.ts +54 -17
- package/src/audit/run.ts +1 -0
- package/src/audit/types.ts +18 -0
- package/src/audit/url.ts +37 -6
- package/src/cli/build-failure.ts +50 -0
- package/src/cli/commands/audit.ts +7 -0
- package/src/cli/commands/build.ts +19 -5
- package/src/cli/commands/check.ts +2 -0
- package/src/cli/commands/eval.ts +1 -7
- 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 +9 -6
- package/src/cli/commands/upgrade.ts +11 -0
- 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/components/content/AccordionItem.astro +26 -22
- package/src/components/content/Badge.astro +2 -9
- package/src/components/content/Card.astro +2 -2
- package/src/components/content/Component.astro +9 -3
- 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/Tabs.astro +54 -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 +17 -0
- package/src/components/content/base-href.ts +18 -3
- package/src/components/content/inline-markdown.ts +27 -7
- package/src/components/copy-feedback.ts +35 -8
- package/src/components/islands/assistant.tsx +16 -2
- package/src/components/islands/hooks.ts +7 -2
- package/src/components/islands/webmcp.ts +12 -8
- package/src/components/layout/Banner.astro +23 -4
- package/src/components/layout/Header.astro +22 -9
- package/src/components/layout/Logo.astro +5 -0
- package/src/components/layout/NavSelector.astro +6 -1
- package/src/components/layout/NavTabMenu.astro +133 -0
- package/src/components/layout/NavTree.astro +15 -6
- package/src/components/layout/NavTreeCache.astro +5 -2
- package/src/components/layout/NavTreeScript.astro +45 -5
- package/src/components/layout/PageLayout.astro +22 -8
- package/src/components/layout/ReferenceLayout.astro +4 -1
- package/src/components/layout/RootLayout.astro +21 -8
- package/src/components/layout/Search.astro +5 -1
- package/src/components/layout/analytics-client.ts +2 -0
- package/src/components/openapi/GraphqlType.astro +11 -3
- package/src/components/openapi/MessageComposer.astro +1 -1
- package/src/components/openapi/Operation.astro +21 -5
- package/src/components/openapi/PanelTabs.astro +4 -1
- package/src/components/openapi/Playground.astro +8 -2
- package/src/components/openapi/RequestPanel.astro +9 -5
- package/src/components/openapi/SchemaProperty.astro +11 -33
- package/src/components/openapi/SchemaTable.astro +27 -52
- package/src/components/openapi/helpers.ts +90 -12
- package/src/components/openapi/message-composer.ts +8 -0
- package/src/components/openapi/message-model.ts +12 -2
- package/src/components/openapi/message.ts +4 -1
- package/src/components/openapi/operation-model.ts +40 -8
- package/src/components/openapi/panel.ts +29 -5
- package/src/components/openapi/playground-client.ts +37 -7
- package/src/components/openapi/playground-schema.ts +25 -6
- package/src/components/openapi/request.ts +2 -0
- package/src/components/openapi/schema-tree.ts +203 -0
- package/src/components/openapi/snippets.ts +30 -15
- package/src/components/openapi/validate-json.ts +1 -1
- package/src/core/base-path.ts +15 -0
- package/src/core/config-input.ts +4 -4
- package/src/core/config.ts +18 -4
- package/src/core/diagnostics.ts +217 -30
- package/src/core/graph.ts +120 -11
- package/src/core/i18n-ui.ts +39 -2
- package/src/core/i18n.ts +13 -2
- package/src/core/links.ts +7 -4
- package/src/core/load-module.ts +20 -0
- package/src/core/locale-links.ts +17 -17
- package/src/core/manifest.ts +3 -2
- package/src/core/meta.ts +69 -6
- package/src/core/nav-diagnostics.ts +56 -1
- package/src/core/navigation.ts +245 -76
- package/src/core/ordering-prefix.ts +27 -0
- package/src/core/project-graph.ts +23 -1
- package/src/core/safe-href.ts +53 -1
- package/src/core/safe-links.ts +11 -2
- package/src/core/schema.ts +70 -14
- package/src/core/sources/assets.ts +83 -41
- package/src/core/sources/contentful-rich-text.ts +3 -2
- package/src/core/sources/contentful.ts +25 -12
- package/src/core/sources/filesystem.ts +5 -1
- package/src/core/sources/github-releases.ts +106 -5
- package/src/core/sources/lexical.ts +9 -4
- package/src/core/sources/lower.ts +79 -26
- package/src/core/sources/mdx-remote.ts +1 -0
- package/src/core/sources/normalize.ts +132 -30
- package/src/core/sources/notion.ts +26 -10
- package/src/core/sources/obsidian.ts +17 -3
- package/src/core/sources/payload.ts +1 -0
- package/src/core/sources/portable-text.ts +63 -44
- 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 +9 -4
- package/src/core/sources/strapi.ts +1 -0
- package/src/core/sources/types.ts +31 -0
- 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 +17 -2
- 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 +25 -5
- package/src/deploy/cloudflare-negotiation.ts +23 -4
- package/src/deploy/headers.ts +67 -51
- package/src/deploy/node-headers.ts +148 -27
- package/src/deploy/platforms/cloudflare.ts +1 -0
- package/src/deploy/platforms/netlify.ts +82 -5
- package/src/deploy/platforms/node.ts +7 -5
- package/src/deploy/platforms/static.ts +1 -0
- package/src/deploy/platforms/types.ts +7 -0
- package/src/deploy/platforms/vercel.ts +9 -3
- package/src/deploy/redirects.ts +14 -3
- 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 +55 -8
- package/src/markdown/index.ts +6 -3
- package/src/markdown/relative-links.ts +3 -23
- package/src/markdown/route-snapshot.ts +37 -0
- package/src/og/card.ts +20 -4
- package/src/og/derive.ts +145 -4
- package/src/openapi/graphql-build.ts +28 -2
- package/src/openapi/model.ts +77 -13
- package/src/openapi/render-mdx.ts +10 -1
- package/src/registry/eject.ts +119 -22
- package/src/search/documents.ts +22 -1
- package/src/search/sync/algolia.ts +36 -2
- 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 +11 -3
- package/src/translate/run.ts +11 -5
- package/src/translate/validate.ts +10 -1
- package/src/translate/work-list.ts +36 -3
- package/src/upgrade/upgrade.ts +36 -4
- 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-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-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-vtk4a6dg.js.map → chunk-00gs3wqs.js.map} +0 -0
- /package/dist/cli/{chunk-6vm74dry.js.map → chunk-6dtt0zfn.js.map} +0 -0
- /package/dist/cli/{chunk-fxypxtvm.js.map → chunk-bbnwccaz.js.map} +0 -0
- /package/dist/cli/{chunk-ce574jw2.js.map → chunk-esh98wmb.js.map} +0 -0
- /package/dist/cli/{chunk-zxcczpyx.js.map → chunk-fsmrqk8a.js.map} +0 -0
- /package/dist/cli/{chunk-hdpx1tax.js.map → chunk-g698a744.js.map} +0 -0
- /package/dist/cli/{chunk-zxh4d9vy.js.map → chunk-h2ez8dzb.js.map} +0 -0
- /package/dist/cli/{chunk-5shv93fd.js.map → chunk-h7k3nq3v.js.map} +0 -0
- /package/dist/cli/{chunk-ah61y8py.js.map → chunk-mqc662a6.js.map} +0 -0
- /package/dist/cli/{chunk-ch6g3ar0.js.map → chunk-n1yg3tj3.js.map} +0 -0
- /package/dist/cli/{chunk-wm7js3j9.js.map → chunk-sqw4ekg1.js.map} +0 -0
- /package/dist/cli/{chunk-27g6wdth.js.map → chunk-wdrt2k2v.js.map} +0 -0
- /package/dist/cli/{chunk-s6jhgk0q.js.map → chunk-zp79m0ts.js.map} +0 -0
|
@@ -3,12 +3,14 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import GithubSlugger from "github-slugger";
|
|
4
4
|
import { extname } from "pathe";
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { mountBasePath } from "../base-path.ts";
|
|
7
7
|
import { nextFenceState } from "../code-fences.ts";
|
|
8
8
|
import type { FenceState } from "../code-fences.ts";
|
|
9
9
|
import { diagnosticsFromIssues, diagnosticsFromZod } from "../diagnostics.ts";
|
|
10
10
|
import { occupySlug, parseHeadingMarkers } from "../heading-markers.ts";
|
|
11
11
|
import { localePlacement, localizeRoute } from "../i18n.ts";
|
|
12
|
+
import { titleWord } from "../navigation.ts";
|
|
13
|
+
import { stripOrderingPrefix } from "../ordering-prefix.ts";
|
|
12
14
|
import { pageMetaSchema } from "../schema.ts";
|
|
13
15
|
import type {
|
|
14
16
|
FrontmatterExtend,
|
|
@@ -20,13 +22,12 @@ import type { Diagnostic, Heading, PageLink, PageRecord } from "../types.ts";
|
|
|
20
22
|
import { detectVersionRef, versionizeRoute } from "../versions.ts";
|
|
21
23
|
import type { NormalizeContext, SourceEntry } from "./types.ts";
|
|
22
24
|
|
|
23
|
-
const NUMERIC_PREFIX = /^\d+[-_.]/u;
|
|
24
25
|
const GROUP_FOLDER = /^\((?<label>.+)\)$/u;
|
|
25
26
|
const WORD_SPLIT = /[-_]/u;
|
|
26
27
|
|
|
27
28
|
/** Strip a leading numeric ordering prefix (`01-intro` -> `intro`). */
|
|
28
29
|
const stripNumericPrefix = (segment: string): string =>
|
|
29
|
-
segment
|
|
30
|
+
stripOrderingPrefix(segment);
|
|
30
31
|
|
|
31
32
|
/** Detect a group folder `(name)` and return its label, else null. */
|
|
32
33
|
const groupLabel = (segment: string): string | null =>
|
|
@@ -63,13 +64,9 @@ export const slugify = (text: string): string =>
|
|
|
63
64
|
export const slugifyPath = (text: string): string =>
|
|
64
65
|
text.split("/").map(slugify).filter(Boolean).join("/");
|
|
65
66
|
|
|
66
|
-
/** Title-case a slug segment for display. */
|
|
67
|
+
/** Title-case a slug segment for display, acronyms as the sidebar spells them. */
|
|
67
68
|
const titleCase = (value: string): string =>
|
|
68
|
-
value
|
|
69
|
-
.split(WORD_SPLIT)
|
|
70
|
-
.filter(Boolean)
|
|
71
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
72
|
-
.join(" ");
|
|
69
|
+
value.split(WORD_SPLIT).filter(Boolean).map(titleWord).join(" ");
|
|
73
70
|
|
|
74
71
|
/**
|
|
75
72
|
* Strip characters that cannot survive the route → URL → output-file round
|
|
@@ -83,11 +80,15 @@ const titleCase = (value: string): string =>
|
|
|
83
80
|
const sanitizeSegment = (segment: string): string =>
|
|
84
81
|
segment.replaceAll(/[:\p{Cc}]/gu, "");
|
|
85
82
|
|
|
86
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* Fold one raw path part into the accumulating route segments/groups.
|
|
85
|
+
* `ordered` parts are file or folder names, whose ordering prefix is dropped.
|
|
86
|
+
*/
|
|
87
87
|
const addRouteSegment = (
|
|
88
88
|
part: string,
|
|
89
89
|
segments: string[],
|
|
90
|
-
groups: string[]
|
|
90
|
+
groups: string[],
|
|
91
|
+
ordered: boolean
|
|
91
92
|
): void => {
|
|
92
93
|
// A leading/trailing/double slash yields an empty part; keeping it would
|
|
93
94
|
// produce a malformed route (`//foo`, `/foo/`) that nothing can link to.
|
|
@@ -99,7 +100,7 @@ const addRouteSegment = (
|
|
|
99
100
|
groups.push(group);
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
|
-
const clean = stripNumericPrefix(part);
|
|
103
|
+
const clean = ordered ? stripNumericPrefix(part) : part;
|
|
103
104
|
if (clean === "index") {
|
|
104
105
|
return;
|
|
105
106
|
}
|
|
@@ -119,22 +120,31 @@ interface MappedRoute {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
/**
|
|
122
|
-
* Convert a content-root-relative path into URL +
|
|
123
|
-
*
|
|
123
|
+
* Convert a source's route prefix and a content-root-relative path into URL +
|
|
124
|
+
* nav metadata. Only the path's parts lose an ordering prefix, and only when
|
|
125
|
+
* `ordered` says they are file and folder names: the route prefix, a slug,
|
|
126
|
+
* a release tag, or a CMS slug is a route spelled out, kept as written. Not
|
|
127
|
+
* exported: a source that needs to predict a route goes through
|
|
124
128
|
* {@link resolveEntryRoute}, so there is exactly one derivation.
|
|
125
129
|
*/
|
|
126
|
-
const mapRoute = (
|
|
130
|
+
const mapRoute = (
|
|
131
|
+
prefix: string | undefined,
|
|
132
|
+
relativePath: string,
|
|
133
|
+
ordered: boolean
|
|
134
|
+
): MappedRoute => {
|
|
127
135
|
const withoutExt = relativePath.slice(
|
|
128
136
|
0,
|
|
129
137
|
relativePath.length - extname(relativePath).length
|
|
130
138
|
);
|
|
131
|
-
const rawParts = withoutExt.split("/");
|
|
132
139
|
|
|
133
140
|
const segments: string[] = [];
|
|
134
141
|
const groups: string[] = [];
|
|
135
142
|
|
|
136
|
-
for (const part of
|
|
137
|
-
addRouteSegment(part, segments, groups);
|
|
143
|
+
for (const part of prefix ? prefix.split("/") : []) {
|
|
144
|
+
addRouteSegment(part, segments, groups, false);
|
|
145
|
+
}
|
|
146
|
+
for (const part of withoutExt.split("/")) {
|
|
147
|
+
addRouteSegment(part, segments, groups, ordered);
|
|
138
148
|
}
|
|
139
149
|
|
|
140
150
|
const route = segments.length === 0 ? "/" : `/${segments.join("/")}`;
|
|
@@ -241,6 +251,8 @@ interface HeadingScanState {
|
|
|
241
251
|
* end so a tag wrapped over several lines still matches.
|
|
242
252
|
*/
|
|
243
253
|
anchorLines: string[];
|
|
254
|
+
/** The multi-line comment the scan is inside, if any — see `scanCommentLine`. */
|
|
255
|
+
comment: "html" | "jsx" | null;
|
|
244
256
|
curlyMarkers: CurlyMarker[];
|
|
245
257
|
fence: FenceState;
|
|
246
258
|
/** 1-based body line of the line being scanned. */
|
|
@@ -473,6 +485,48 @@ const scanContentLine = (
|
|
|
473
485
|
state.paragraph.push(line.trim());
|
|
474
486
|
};
|
|
475
487
|
|
|
488
|
+
// A comment that opens a line hides everything up to its close from the
|
|
489
|
+
// reader: an HTML comment (`<!--` … `-->`, a CommonMark HTML block, so up to
|
|
490
|
+
// 3 leading spaces) or an MDX JSX one (`{/*` … `*/}`, any indentation — MDX
|
|
491
|
+
// has no indented code).
|
|
492
|
+
const HTML_COMMENT_OPEN = /^ {0,3}<!--/u;
|
|
493
|
+
const JSX_COMMENT_OPEN = /^\s*\{\/\*/u;
|
|
494
|
+
const JSX_COMMENT_CLOSE = /\*\/\s*\}/u;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Advance the multi-line comment state over one line, returning whether the
|
|
498
|
+
* line belongs to a comment. A commented-out `# Old title` renders nothing, so
|
|
499
|
+
* it must not become a heading — the page title, a TOC entry, or an anchor.
|
|
500
|
+
* A comment that closes on its opening line is left to the regular scan,
|
|
501
|
+
* which never reads a line starting with `<` or `{` as a heading. The
|
|
502
|
+
* CommonMark end condition is any `-->` on the line, the opening one
|
|
503
|
+
* included.
|
|
504
|
+
*/
|
|
505
|
+
const scanCommentLine = (line: string, state: HeadingScanState): boolean => {
|
|
506
|
+
if (state.comment === "html") {
|
|
507
|
+
if (line.includes("-->")) {
|
|
508
|
+
state.comment = null;
|
|
509
|
+
}
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
if (state.comment === "jsx") {
|
|
513
|
+
if (JSX_COMMENT_CLOSE.test(line)) {
|
|
514
|
+
state.comment = null;
|
|
515
|
+
}
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
if (HTML_COMMENT_OPEN.test(line) && !line.includes("-->")) {
|
|
519
|
+
state.comment = "html";
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
const jsx = JSX_COMMENT_OPEN.exec(line);
|
|
523
|
+
if (jsx && !JSX_COMMENT_CLOSE.test(line.slice(jsx[0].length))) {
|
|
524
|
+
state.comment = "jsx";
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
return false;
|
|
528
|
+
};
|
|
529
|
+
|
|
476
530
|
/** Scan one line for a heading, advancing the fence/paragraph state. */
|
|
477
531
|
const scanHeadingLine = (
|
|
478
532
|
line: string,
|
|
@@ -481,6 +535,18 @@ const scanHeadingLine = (
|
|
|
481
535
|
headings: Heading[],
|
|
482
536
|
isRefDefined: (label: string) => boolean
|
|
483
537
|
): void => {
|
|
538
|
+
// Comments hide fences too, and fences and prompts hide comments. A comment
|
|
539
|
+
// line still goes to the anchor pass, which strips HTML comments itself.
|
|
540
|
+
if (
|
|
541
|
+
state.fence === null &&
|
|
542
|
+
state.promptDepth === 0 &&
|
|
543
|
+
!state.promptTag &&
|
|
544
|
+
scanCommentLine(line, state)
|
|
545
|
+
) {
|
|
546
|
+
state.anchorLines.push(line.replaceAll(INLINE_CODE, ""));
|
|
547
|
+
state.paragraph = [];
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
484
550
|
const next = nextFenceState(line, state.fence);
|
|
485
551
|
// Skip fence delimiter lines themselves and anything inside a fence. A fence
|
|
486
552
|
// also ends any open paragraph, so no underline can reach across it.
|
|
@@ -548,6 +614,7 @@ export const scanBody = (body: string): BodyScan => {
|
|
|
548
614
|
const slugger = new GithubSlugger();
|
|
549
615
|
const state: HeadingScanState = {
|
|
550
616
|
anchorLines: [],
|
|
617
|
+
comment: null,
|
|
551
618
|
curlyMarkers: [],
|
|
552
619
|
fence: null,
|
|
553
620
|
line: 0,
|
|
@@ -792,6 +859,14 @@ export const strippedLineOffset = (
|
|
|
792
859
|
): number =>
|
|
793
860
|
raw ? Math.max(0, raw.split("\n").length - body.split("\n").length) : 0;
|
|
794
861
|
|
|
862
|
+
/**
|
|
863
|
+
* How many lines of the entry's source file sit above its body: what the
|
|
864
|
+
* source reported (`bodyLineOffset`, when `raw` is a rewrite of the file), or
|
|
865
|
+
* else the height of `raw`'s stripped front matter.
|
|
866
|
+
*/
|
|
867
|
+
const entryLineOffset = (entry: SourceEntry): number =>
|
|
868
|
+
entry.bodyLineOffset ?? strippedLineOffset(entry.raw, entry.body.text);
|
|
869
|
+
|
|
795
870
|
/**
|
|
796
871
|
* Map links extracted from include-expanded text back to the file and raw
|
|
797
872
|
* line each expanded line came from, so a broken link inside a partial is
|
|
@@ -834,10 +909,7 @@ const entryLinks = (entry: SourceEntry): PageLink[] =>
|
|
|
834
909
|
entry.expanded.origins,
|
|
835
910
|
entry.sourcePath
|
|
836
911
|
)
|
|
837
|
-
: extractLinks(
|
|
838
|
-
entry.body.text,
|
|
839
|
-
strippedLineOffset(entry.raw, entry.body.text)
|
|
840
|
-
);
|
|
912
|
+
: extractLinks(entry.body.text, entryLineOffset(entry));
|
|
841
913
|
|
|
842
914
|
/**
|
|
843
915
|
* Diagnostics for `{#id}` heading markers in an `.mdx` page. The MDX parser
|
|
@@ -860,8 +932,7 @@ const curlyMarkerDiagnostics = (
|
|
|
860
932
|
return {
|
|
861
933
|
code: "BLUME_MDX_CURLY_ANCHOR",
|
|
862
934
|
file: origin?.file ?? page,
|
|
863
|
-
line:
|
|
864
|
-
origin?.line ?? line + strippedLineOffset(entry.raw, entry.body.text),
|
|
935
|
+
line: origin?.line ?? line + entryLineOffset(entry),
|
|
865
936
|
message: inPartial
|
|
866
937
|
? `\`{#${id}}\` is a JSX expression once this partial is included in ${page} (.mdx), so that page fails to compile.`
|
|
867
938
|
: `\`{#${id}}\` is a JSX expression in .mdx, so this page fails to compile.`,
|
|
@@ -882,8 +953,17 @@ const deriveTitle = (
|
|
|
882
953
|
if (firstHeading) {
|
|
883
954
|
return firstHeading.text;
|
|
884
955
|
}
|
|
885
|
-
const
|
|
886
|
-
|
|
956
|
+
const parts = id.split("/");
|
|
957
|
+
const base = parts.at(-1) ?? id;
|
|
958
|
+
const stem = stripNumericPrefix(base.replace(extname(base), ""));
|
|
959
|
+
// An index page stands for its folder, so an untitled one takes the label
|
|
960
|
+
// the sidebar gives that folder (`guides/index.md` is "Guides"). The
|
|
961
|
+
// content root's own index has no folder to borrow from.
|
|
962
|
+
const folder = stem === "index" ? parts.at(-2) : undefined;
|
|
963
|
+
if (folder !== undefined) {
|
|
964
|
+
return titleCase(stripNumericPrefix(groupLabel(folder) ?? folder));
|
|
965
|
+
}
|
|
966
|
+
return titleCase(stem);
|
|
887
967
|
};
|
|
888
968
|
|
|
889
969
|
/** Strip habitual leading/trailing slashes (`/getting-started`, `guides/`). */
|
|
@@ -902,6 +982,13 @@ const withPrefix = (prefix: string | undefined, path: string): string => {
|
|
|
902
982
|
|
|
903
983
|
/** What a route resolution needs from the owning source and the config. */
|
|
904
984
|
export type RouteContext = Pick<NormalizeContext, "i18n" | "versions"> & {
|
|
985
|
+
/**
|
|
986
|
+
* Whether the entry's ref is a path of file and folder names whose ordering
|
|
987
|
+
* prefixes (`01-intro`) sort the sidebar and drop from the route: true for
|
|
988
|
+
* filesystem sources. A staged source's ref is a slug, a release tag, or a
|
|
989
|
+
* note name, and keeps its leading numbers.
|
|
990
|
+
*/
|
|
991
|
+
orderingPrefixes?: boolean;
|
|
905
992
|
/** The source's route prefix (`NormalizeContext["source"]["prefix"]`). */
|
|
906
993
|
prefix?: string;
|
|
907
994
|
};
|
|
@@ -970,7 +1057,9 @@ export interface EntryRoute extends Pick<
|
|
|
970
1057
|
* A frontmatter `slug` wins, then the adapter-supplied `entry.slug` (the typed
|
|
971
1058
|
* SPI's "logical route input; defaults to ref if omitted"), then the ref. The
|
|
972
1059
|
* extension is re-appended so `mapRoute`'s extname strip can't eat a dotted
|
|
973
|
-
* slug segment (`v1.2`). A slug that trims to nothing falls back.
|
|
1060
|
+
* slug segment (`v1.2`). A slug that trims to nothing falls back. Only a
|
|
1061
|
+
* filesystem ref (`ctx.orderingPrefixes`) loses its ordering prefixes; a slug
|
|
1062
|
+
* is a route spelled out, so `2024-year-in-review` stays whole. The version
|
|
974
1063
|
* prefixes the mapped route *after* `mapRoute` runs: the mapped route is the
|
|
975
1064
|
* version-agnostic key, the config id is prepended verbatim (never
|
|
976
1065
|
* numeric-prefix-stripped), a frontmatter `slug` gets versionized so snapshots
|
|
@@ -987,8 +1076,18 @@ export const resolveEntryRoute = (
|
|
|
987
1076
|
const { locales, navPath, version } = placeEntryRef(entry.ref, ext, ctx);
|
|
988
1077
|
const slugInput = frontmatterSlug ?? entry.slug;
|
|
989
1078
|
const slug = slugInput ? trimSlashes(slugInput) : "";
|
|
990
|
-
|
|
991
|
-
|
|
1079
|
+
// A frontmatter slug is a route spelled out; an adapter's `entry.slug` is
|
|
1080
|
+
// one too unless the source's names are ordered file names (a vault note's
|
|
1081
|
+
// path), which lose their prefixes like the ref does.
|
|
1082
|
+
const ordered =
|
|
1083
|
+
ctx.orderingPrefixes === true && frontmatterSlug === undefined;
|
|
1084
|
+
const {
|
|
1085
|
+
segments,
|
|
1086
|
+
groups,
|
|
1087
|
+
route: versionKey,
|
|
1088
|
+
} = slug
|
|
1089
|
+
? mapRoute(ctx.prefix, `${slug}${ext}`, ordered)
|
|
1090
|
+
: mapRoute(ctx.prefix, navPath, ctx.orderingPrefixes === true);
|
|
992
1091
|
return {
|
|
993
1092
|
groups,
|
|
994
1093
|
locales,
|
|
@@ -1192,6 +1291,7 @@ export const normalizeEntry = (
|
|
|
1192
1291
|
versionKey,
|
|
1193
1292
|
} = resolveEntryRoute(entry, ext, meta.slug, {
|
|
1194
1293
|
i18n: ctx.i18n,
|
|
1294
|
+
orderingPrefixes: !ctx.source.staged || ctx.source.orderedNames === true,
|
|
1195
1295
|
prefix: ctx.source.prefix,
|
|
1196
1296
|
versions: ctx.versions,
|
|
1197
1297
|
});
|
|
@@ -1237,10 +1337,12 @@ export const normalizeEntry = (
|
|
|
1237
1337
|
// `basePath` is applied outermost — after locale prefixing — so the route
|
|
1238
1338
|
// reads `{basePath}/{locale?}/{prefix?}/…`; `navPath` and `translationKey`
|
|
1239
1339
|
// stay base-less so the nav tree and translation matching are unaffected.
|
|
1340
|
+
// The base is mounted unconditionally: a `docs/` folder under a `/docs`
|
|
1341
|
+
// base is a real `/docs/docs/…` route, not an already-based one.
|
|
1240
1342
|
const pages = locales.map((locale) => ({
|
|
1241
1343
|
...base,
|
|
1242
1344
|
locale,
|
|
1243
|
-
route:
|
|
1345
|
+
route: mountBasePath(
|
|
1244
1346
|
ctx.basePath ?? "",
|
|
1245
1347
|
localizedRoute(logicalRoute, locale, ctx.i18n)
|
|
1246
1348
|
),
|
|
@@ -15,7 +15,13 @@ import {
|
|
|
15
15
|
pollingWatch,
|
|
16
16
|
snapshotCache,
|
|
17
17
|
} from "./cache.ts";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
codeFence,
|
|
20
|
+
guardBlockStart,
|
|
21
|
+
image,
|
|
22
|
+
renderInline,
|
|
23
|
+
renderLink,
|
|
24
|
+
} from "./lower.ts";
|
|
19
25
|
import { slugifyPath } from "./normalize.ts";
|
|
20
26
|
import type {
|
|
21
27
|
ContentSource,
|
|
@@ -217,12 +223,12 @@ const withNotionRetry = async <T>(
|
|
|
217
223
|
if (status !== RATE_LIMITED || attempt === MAX_RETRIES) {
|
|
218
224
|
throw error;
|
|
219
225
|
}
|
|
220
|
-
// SAFETY: same APIResponseError shape — `headers`
|
|
221
|
-
//
|
|
222
|
-
// back to
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
226
|
+
// SAFETY: same APIResponseError shape — `headers` is the failed
|
|
227
|
+
// response's fetch `Headers`, read with `get()`; a missing header (or
|
|
228
|
+
// headers without `get`) yields no positive wait and falls back to
|
|
229
|
+
// exponential backoff.
|
|
230
|
+
const { headers } = error as { headers?: Headers };
|
|
231
|
+
const retryAfter = Number(headers?.get?.("retry-after"));
|
|
226
232
|
const wait =
|
|
227
233
|
retryAfter > 0
|
|
228
234
|
? retryAfter * SECOND_MS
|
|
@@ -266,6 +272,12 @@ const richToPlain = (rich: NotionRichText[] = []): string =>
|
|
|
266
272
|
*/
|
|
267
273
|
const jsxString = (value: string): string => `{${JSON.stringify(value)}}`;
|
|
268
274
|
|
|
275
|
+
// `<Frame>` renders its caption as Markdown after escaping `<` and `>` itself
|
|
276
|
+
// (so a caption can't inject HTML), which turns the lowering's own `\<` into
|
|
277
|
+
// a visible `<`. Its caption drops those backslashes and leaves the
|
|
278
|
+
// escaping to Frame; every other escape still applies.
|
|
279
|
+
const FRAME_ESCAPED = /\\(?=[<>])/gu;
|
|
280
|
+
|
|
269
281
|
const YOUTUBE_HOST = /(?:^|\.)(?:youtube(?:-nocookie)?\.com|youtu\.be)$/u;
|
|
270
282
|
|
|
271
283
|
// `parseYouTubeId` is host-agnostic on purpose (the component accepts bare
|
|
@@ -300,14 +312,14 @@ const renderVideo = (data: NotionBlockPayload): string => {
|
|
|
300
312
|
? `<YouTube${title} url=${JSON.stringify(url)} />`
|
|
301
313
|
: `<video controls src=${JSON.stringify(url)} />`;
|
|
302
314
|
return caption
|
|
303
|
-
? `<Frame caption=${jsxString(caption)}>\n${media}\n</Frame>`
|
|
315
|
+
? `<Frame caption=${jsxString(caption.replaceAll(FRAME_ESCAPED, ""))}>\n${media}\n</Frame>`
|
|
304
316
|
: media;
|
|
305
317
|
};
|
|
306
318
|
|
|
307
319
|
/** Render a leaf (non-container) block to Markdown, or null for containers. */
|
|
308
320
|
const renderLeaf = (block: NotionBlock): string | null => {
|
|
309
321
|
const data = payloadOf(block) ?? {};
|
|
310
|
-
const text = richToMarkdown(blockField(block));
|
|
322
|
+
const text = guardBlockStart(richToMarkdown(blockField(block)));
|
|
311
323
|
switch (block.type) {
|
|
312
324
|
case "paragraph": {
|
|
313
325
|
return text;
|
|
@@ -440,7 +452,10 @@ export const notionSource = (
|
|
|
440
452
|
};
|
|
441
453
|
|
|
442
454
|
if (block.type === "callout") {
|
|
443
|
-
const body = [
|
|
455
|
+
const body = [
|
|
456
|
+
guardBlockStart(richToMarkdown(blockField(block))),
|
|
457
|
+
await children(block),
|
|
458
|
+
]
|
|
444
459
|
.filter(Boolean)
|
|
445
460
|
.join("\n\n");
|
|
446
461
|
return `<Callout>\n${body}\n</Callout>`;
|
|
@@ -680,5 +695,6 @@ export const notionSource = (
|
|
|
680
695
|
() => load()
|
|
681
696
|
)
|
|
682
697
|
: undefined,
|
|
698
|
+
withContext: (next) => notionSource(options, next),
|
|
683
699
|
};
|
|
684
700
|
};
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
placeEntryRef,
|
|
21
21
|
resolveEntryRoute,
|
|
22
22
|
slugifyPath,
|
|
23
|
+
strippedLineOffset,
|
|
23
24
|
} from "./normalize.ts";
|
|
24
25
|
import type {
|
|
25
26
|
ContentSource,
|
|
@@ -84,6 +85,8 @@ interface ParsedNote {
|
|
|
84
85
|
/** The note body, frontmatter stripped and otherwise untouched. */
|
|
85
86
|
content: string;
|
|
86
87
|
data: SourceEntry["data"];
|
|
88
|
+
/** How many lines of the file the frontmatter block (if any) spans. */
|
|
89
|
+
frontmatterLines: number;
|
|
87
90
|
placement: EntryPlacement;
|
|
88
91
|
/** Vault-relative path, e.g. `guides/Getting Started.md`. */
|
|
89
92
|
rel: string;
|
|
@@ -638,7 +641,7 @@ const buildLinkIndex = (
|
|
|
638
641
|
{ ref: note.rel, slug: entrySlugFor(note.placement.navPath) },
|
|
639
642
|
".md",
|
|
640
643
|
isStringValue(note.data.slug) ? note.data.slug : undefined,
|
|
641
|
-
options
|
|
644
|
+
{ ...options, orderingPrefixes: true }
|
|
642
645
|
);
|
|
643
646
|
const built: IndexedNote = {
|
|
644
647
|
anchors: new Map(),
|
|
@@ -756,10 +759,17 @@ const noteToEntry = (
|
|
|
756
759
|
)
|
|
757
760
|
);
|
|
758
761
|
const merged = title === undefined ? data : { ...data, title };
|
|
759
|
-
const
|
|
762
|
+
const body = note.content.trim();
|
|
763
|
+
const text = transformBody(body, index, pair.self, unresolved);
|
|
760
764
|
const raw = matter.stringify(`${text}\n`, merged);
|
|
765
|
+
// `raw` re-serializes the frontmatter without the dropped properties, so
|
|
766
|
+
// its height is not the file's: count the file's own frontmatter and the
|
|
767
|
+
// blank lines the trim dropped, so a link diagnostic names the line the
|
|
768
|
+
// author sees in the note.
|
|
769
|
+
const leading = note.content.slice(0, note.content.indexOf(body));
|
|
761
770
|
return {
|
|
762
771
|
body: { format: "md", text },
|
|
772
|
+
bodyLineOffset: note.frontmatterLines + leading.split("\n").length - 1,
|
|
763
773
|
data: merged,
|
|
764
774
|
hash: hashText(raw),
|
|
765
775
|
raw,
|
|
@@ -782,11 +792,13 @@ const readNote = async (
|
|
|
782
792
|
): Promise<ParsedNote | null> => {
|
|
783
793
|
const absPath = join(vaultDir, rel);
|
|
784
794
|
try {
|
|
785
|
-
const
|
|
795
|
+
const file = await readFile(absPath, "utf-8");
|
|
796
|
+
const { content, data } = matter(file);
|
|
786
797
|
return {
|
|
787
798
|
absPath,
|
|
788
799
|
content,
|
|
789
800
|
data,
|
|
801
|
+
frontmatterLines: strippedLineOffset(file, content),
|
|
790
802
|
// Version outermost, then locale, the way `normalizeEntry` reads the
|
|
791
803
|
// ref — the slug is built from what remains, so the pipeline's own
|
|
792
804
|
// re-prefixing does not stack a second `fr/` or `v1.0/` onto the route.
|
|
@@ -1030,6 +1042,8 @@ export const obsidianSource = (
|
|
|
1030
1042
|
contentRoot: vaultDir,
|
|
1031
1043
|
load,
|
|
1032
1044
|
name: options.name,
|
|
1045
|
+
// Notes are named like files: `01 Intro` sorts first and routes `/intro`.
|
|
1046
|
+
orderedNames: true,
|
|
1033
1047
|
prefix: options.prefix,
|
|
1034
1048
|
read,
|
|
1035
1049
|
staged: true,
|
|
@@ -4,7 +4,14 @@
|
|
|
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 {
|
|
8
|
+
guardBlockStart,
|
|
9
|
+
image,
|
|
10
|
+
renderInline,
|
|
11
|
+
renderLink,
|
|
12
|
+
unsupported,
|
|
13
|
+
writesMdx,
|
|
14
|
+
} from "./lower.ts";
|
|
8
15
|
|
|
9
16
|
/**
|
|
10
17
|
* A field value on a Portable Text node: the arbitrary JSON the CMS query
|
|
@@ -66,47 +73,29 @@ const HEADING_STYLES = new Map([
|
|
|
66
73
|
["h6", "###### "],
|
|
67
74
|
]);
|
|
68
75
|
|
|
69
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* A span as Markdown for its marks, through the shared lowering every CMS
|
|
78
|
+
* uses: prose is escaped, a code span outruns the backticks inside it, edge
|
|
79
|
+
* whitespace stays outside emphasis, and a link def wraps last (so its label
|
|
80
|
+
* keeps emphasis) only when its destination is safe to click.
|
|
81
|
+
*/
|
|
70
82
|
const renderSpan = (
|
|
71
83
|
span: PortableTextSpan,
|
|
72
84
|
defs: Map<string, PortableTextMarkDef>
|
|
73
85
|
): string => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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;
|
|
86
|
+
const marks = span.marks ?? [];
|
|
87
|
+
const link = marks
|
|
88
|
+
.map((mark) => defs.get(mark))
|
|
89
|
+
.findLast((def) => def?._type === "link");
|
|
90
|
+
return renderLink(
|
|
91
|
+
renderInline(span.text ?? "", {
|
|
92
|
+
bold: marks.includes("strong"),
|
|
93
|
+
code: marks.includes("code"),
|
|
94
|
+
italic: marks.includes("em"),
|
|
95
|
+
strike: marks.includes("strike-through"),
|
|
96
|
+
}),
|
|
97
|
+
link?.href
|
|
98
|
+
);
|
|
110
99
|
};
|
|
111
100
|
|
|
112
101
|
/** Render the inline children of a block to a single Markdown string. */
|
|
@@ -114,16 +103,45 @@ const renderChildren = (block: PortableTextBlock): string => {
|
|
|
114
103
|
const defs = new Map(
|
|
115
104
|
(block.markDefs ?? []).map((def) => [def._key, def] as const)
|
|
116
105
|
);
|
|
117
|
-
return (
|
|
106
|
+
return guardBlockStart(
|
|
107
|
+
(block.children ?? []).map((span) => renderSpan(span, defs)).join("")
|
|
108
|
+
);
|
|
118
109
|
};
|
|
119
110
|
|
|
120
111
|
/** Whether an image block's `alt` field is usable alt text (CMS JSON may hold anything). */
|
|
121
112
|
const isAltText = (value: PortableTextValue): value is string =>
|
|
122
113
|
typeof value === "string";
|
|
123
114
|
|
|
115
|
+
/** The marker a list block opens with. */
|
|
116
|
+
const listMarker = (block: PortableTextBlock): string =>
|
|
117
|
+
block.listItem === "number" ? "1." : "-";
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* How far each block is indented. Portable Text lists are flat blocks with a
|
|
121
|
+
* `level`, while Markdown nests an item by indenting it to its parent item's
|
|
122
|
+
* content column: two past a `-` item's indent, three past a `1.` item's. The
|
|
123
|
+
* column comes from the item actually above, so a skipped level nests under
|
|
124
|
+
* the deepest open item instead of indenting into a code block.
|
|
125
|
+
*/
|
|
126
|
+
const listIndents = (blocks: PortableTextBlock[]): number[] => {
|
|
127
|
+
const columns: number[] = [];
|
|
128
|
+
return blocks.map((block) => {
|
|
129
|
+
if (!block.listItem) {
|
|
130
|
+
columns.length = 0;
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
const depth = Math.min(Math.max(0, (block.level ?? 1) - 1), columns.length);
|
|
134
|
+
const indent = columns[depth - 1] ?? 0;
|
|
135
|
+
columns.length = depth;
|
|
136
|
+
columns.push(indent + listMarker(block).length + 1);
|
|
137
|
+
return indent;
|
|
138
|
+
});
|
|
139
|
+
};
|
|
140
|
+
|
|
124
141
|
const renderBlock = (
|
|
125
142
|
block: PortableTextBlock,
|
|
126
|
-
options: PortableTextOptions
|
|
143
|
+
options: PortableTextOptions,
|
|
144
|
+
indent: number
|
|
127
145
|
): string => {
|
|
128
146
|
const custom = options.serializers?.[block._type];
|
|
129
147
|
if (custom) {
|
|
@@ -143,9 +161,7 @@ const renderBlock = (
|
|
|
143
161
|
|
|
144
162
|
const inline = renderChildren(block);
|
|
145
163
|
if (block.listItem) {
|
|
146
|
-
|
|
147
|
-
const marker = block.listItem === "number" ? "1." : "-";
|
|
148
|
-
return `${indent}${marker} ${inline}`;
|
|
164
|
+
return `${" ".repeat(indent)}${listMarker(block)} ${inline}`;
|
|
149
165
|
}
|
|
150
166
|
if (block.style === "blockquote") {
|
|
151
167
|
return `> ${inline}`;
|
|
@@ -158,7 +174,10 @@ export const portableTextToMarkdown = (
|
|
|
158
174
|
blocks: PortableTextBlock[],
|
|
159
175
|
options: PortableTextOptions = {}
|
|
160
176
|
): string => {
|
|
161
|
-
const
|
|
177
|
+
const indents = listIndents(blocks);
|
|
178
|
+
const lines = blocks.map((block, i) =>
|
|
179
|
+
renderBlock(block, options, indents[i] ?? 0)
|
|
180
|
+
);
|
|
162
181
|
// List items are single-newline separated; everything else gets a blank line.
|
|
163
182
|
const out: string[] = [];
|
|
164
183
|
for (let i = 0; i < lines.length; i += 1) {
|