@highjumpdigitalsoftware/blog-kit 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INTEGRATION.md +76 -0
- package/LICENSE +74 -0
- package/README.md +102 -0
- package/astro/AdPreview.astro +64 -0
- package/astro/AdPreviewPair.astro +10 -0
- package/astro/AuditFindings.astro +29 -0
- package/astro/AuditScores.astro +60 -0
- package/astro/AuthorCard.astro +32 -0
- package/astro/BeforeAfter.astro +26 -0
- package/astro/BlogBehaviors.astro +15 -0
- package/astro/CTABanner.astro +28 -0
- package/astro/CalloutBox.astro +28 -0
- package/astro/CaseStudyHero.astro +45 -0
- package/astro/ChannelMixBars.astro +33 -0
- package/astro/Checklist.astro +24 -0
- package/astro/ChecklistItem.astro +15 -0
- package/astro/CodeSnippet.astro +20 -0
- package/astro/ComparisonTable.astro +103 -0
- package/astro/Definition.astro +30 -0
- package/astro/DeliveryComparison.astro +40 -0
- package/astro/FAQList.astro +43 -0
- package/astro/FurtherReading.astro +34 -0
- package/astro/ImageFeature.astro +22 -0
- package/astro/Infographic.astro +12 -0
- package/astro/KeyMetric.astro +40 -0
- package/astro/KeywordTable.astro +69 -0
- package/astro/List.astro +46 -0
- package/astro/MetricHighlight.astro +77 -0
- package/astro/NewsletterCTA.astro +40 -0
- package/astro/NumberedCard.astro +6 -0
- package/astro/ProConBlock.astro +38 -0
- package/astro/ProseList.astro +46 -0
- package/astro/QuoteBlock.astro +72 -0
- package/astro/RegionCallout.astro +24 -0
- package/astro/RelatedPosts.astro +47 -0
- package/astro/ResultsStrip.astro +59 -0
- package/astro/ScoreBar.astro +19 -0
- package/astro/SerpPreview.astro +35 -0
- package/astro/ServicePromoCard.astro +21 -0
- package/astro/StatCard.astro +48 -0
- package/astro/StepBlock.astro +5 -0
- package/astro/TableOfContents.astro +12 -0
- package/astro/TimelineBlock.astro +30 -0
- package/astro/TipBox.astro +14 -0
- package/astro/TrafficChart.astro +61 -0
- package/astro/VerdictCard.astro +48 -0
- package/astro/blogkit/Article.astro +63 -0
- package/astro/blogkit/BlogIndex.astro +144 -0
- package/core/behaviors/code.js +78 -0
- package/core/behaviors/comparison.js +52 -0
- package/core/behaviors/delivery-comparison.js +52 -0
- package/core/behaviors/faq.js +61 -0
- package/core/behaviors/index.d.ts +3 -0
- package/core/behaviors/index.js +35 -0
- package/core/behaviors/keyword-table.js +52 -0
- package/core/behaviors/toc.js +130 -0
- package/core/css/base.css +146 -0
- package/core/css/components.css +2632 -0
- package/core/css/index-listing.css +207 -0
- package/core/css/index.css +13 -0
- package/core/css/tokens.css +127 -0
- package/core/icons.ts +20 -0
- package/core/lib.ts +70 -0
- package/core/manifest/components.json +573 -0
- package/core/manifest/frontmatter.json +19 -0
- package/core/manifest/templates.json +77 -0
- package/dist/core/behaviors/code.js +78 -0
- package/dist/core/behaviors/comparison.js +52 -0
- package/dist/core/behaviors/delivery-comparison.js +52 -0
- package/dist/core/behaviors/faq.js +61 -0
- package/dist/core/behaviors/index.d.ts +3 -0
- package/dist/core/behaviors/index.js +35 -0
- package/dist/core/behaviors/keyword-table.js +52 -0
- package/dist/core/behaviors/toc.js +130 -0
- package/dist/core/css/base.css +146 -0
- package/dist/core/css/components.css +2632 -0
- package/dist/core/css/index-listing.css +207 -0
- package/dist/core/css/index.css +13 -0
- package/dist/core/css/tokens.css +127 -0
- package/dist/core/icons.d.ts +2 -0
- package/dist/core/icons.d.ts.map +1 -0
- package/dist/core/icons.js +20 -0
- package/dist/core/icons.js.map +1 -0
- package/dist/core/lib.d.ts +21 -0
- package/dist/core/lib.d.ts.map +1 -0
- package/dist/core/lib.js +57 -0
- package/dist/core/lib.js.map +1 -0
- package/dist/core/manifest/components.json +573 -0
- package/dist/core/manifest/frontmatter.json +19 -0
- package/dist/core/manifest/templates.json +77 -0
- package/dist/package/adapters/hjd-api.d.ts +14 -0
- package/dist/package/adapters/hjd-api.d.ts.map +1 -0
- package/dist/package/adapters/hjd-api.js +57 -0
- package/dist/package/adapters/hjd-api.js.map +1 -0
- package/dist/package/adapters/index.d.ts +13 -0
- package/dist/package/adapters/index.d.ts.map +1 -0
- package/dist/package/adapters/index.js +16 -0
- package/dist/package/adapters/index.js.map +1 -0
- package/dist/package/adapters/local.d.ts +13 -0
- package/dist/package/adapters/local.d.ts.map +1 -0
- package/dist/package/adapters/local.js +72 -0
- package/dist/package/adapters/local.js.map +1 -0
- package/dist/package/adapters/source.d.ts +39 -0
- package/dist/package/adapters/source.d.ts.map +1 -0
- package/dist/package/adapters/source.js +19 -0
- package/dist/package/adapters/source.js.map +1 -0
- package/dist/package/article.d.ts +17 -0
- package/dist/package/article.d.ts.map +1 -0
- package/dist/package/article.js +37 -0
- package/dist/package/article.js.map +1 -0
- package/dist/package/astro/data.d.ts +45 -0
- package/dist/package/astro/data.d.ts.map +1 -0
- package/dist/package/astro/data.js +81 -0
- package/dist/package/astro/data.js.map +1 -0
- package/dist/package/astro/freshness.d.ts +11 -0
- package/dist/package/astro/freshness.d.ts.map +1 -0
- package/dist/package/astro/freshness.js +48 -0
- package/dist/package/astro/freshness.js.map +1 -0
- package/dist/package/astro/index.d.ts +12 -0
- package/dist/package/astro/index.d.ts.map +1 -0
- package/dist/package/astro/index.js +31 -0
- package/dist/package/astro/index.js.map +1 -0
- package/dist/package/blog-index.d.ts +10 -0
- package/dist/package/blog-index.d.ts.map +1 -0
- package/dist/package/blog-index.js +27 -0
- package/dist/package/blog-index.js.map +1 -0
- package/dist/package/cli/exchange.d.ts +27 -0
- package/dist/package/cli/exchange.d.ts.map +1 -0
- package/dist/package/cli/exchange.js +94 -0
- package/dist/package/cli/exchange.js.map +1 -0
- package/dist/package/cli/index.d.ts +3 -0
- package/dist/package/cli/index.d.ts.map +1 -0
- package/dist/package/cli/index.js +301 -0
- package/dist/package/cli/index.js.map +1 -0
- package/dist/package/config/define.d.ts +13 -0
- package/dist/package/config/define.d.ts.map +1 -0
- package/dist/package/config/define.js +14 -0
- package/dist/package/config/define.js.map +1 -0
- package/dist/package/config/resolve.d.ts +11 -0
- package/dist/package/config/resolve.d.ts.map +1 -0
- package/dist/package/config/resolve.js +43 -0
- package/dist/package/config/resolve.js.map +1 -0
- package/dist/package/config/types.d.ts +74 -0
- package/dist/package/config/types.d.ts.map +1 -0
- package/dist/package/config/types.js +13 -0
- package/dist/package/config/types.js.map +1 -0
- package/dist/package/index-core.d.ts +28 -0
- package/dist/package/index-core.d.ts.map +1 -0
- package/dist/package/index-core.js +102 -0
- package/dist/package/index-core.js.map +1 -0
- package/dist/package/index.d.ts +13 -0
- package/dist/package/index.d.ts.map +1 -0
- package/dist/package/index.js +25 -0
- package/dist/package/index.js.map +1 -0
- package/dist/package/mdx/render-astro.d.ts +18 -0
- package/dist/package/mdx/render-astro.d.ts.map +1 -0
- package/dist/package/mdx/render-astro.js +75 -0
- package/dist/package/mdx/render-astro.js.map +1 -0
- package/dist/package/mdx/render.d.ts +13 -0
- package/dist/package/mdx/render.d.ts.map +1 -0
- package/dist/package/mdx/render.js +37 -0
- package/dist/package/mdx/render.js.map +1 -0
- package/dist/react/AdPreview.d.ts +26 -0
- package/dist/react/AdPreview.d.ts.map +1 -0
- package/dist/react/AdPreview.js +8 -0
- package/dist/react/AdPreview.js.map +1 -0
- package/dist/react/AdPreviewPair.d.ts +7 -0
- package/dist/react/AdPreviewPair.d.ts.map +1 -0
- package/dist/react/AdPreviewPair.js +5 -0
- package/dist/react/AdPreviewPair.js.map +1 -0
- package/dist/react/AuditFindings.d.ts +14 -0
- package/dist/react/AuditFindings.d.ts.map +1 -0
- package/dist/react/AuditFindings.js +5 -0
- package/dist/react/AuditFindings.js.map +1 -0
- package/dist/react/AuditScores.d.ts +12 -0
- package/dist/react/AuditScores.d.ts.map +1 -0
- package/dist/react/AuditScores.js +25 -0
- package/dist/react/AuditScores.js.map +1 -0
- package/dist/react/AuthorCard.d.ts +10 -0
- package/dist/react/AuthorCard.d.ts.map +1 -0
- package/dist/react/AuthorCard.js +6 -0
- package/dist/react/AuthorCard.js.map +1 -0
- package/dist/react/BeforeAfter.d.ts +12 -0
- package/dist/react/BeforeAfter.d.ts.map +1 -0
- package/dist/react/BeforeAfter.js +7 -0
- package/dist/react/BeforeAfter.js.map +1 -0
- package/dist/react/BlogBehaviors.d.ts +10 -0
- package/dist/react/BlogBehaviors.d.ts.map +1 -0
- package/dist/react/BlogBehaviors.js +20 -0
- package/dist/react/BlogBehaviors.js.map +1 -0
- package/dist/react/CTABanner.d.ts +8 -0
- package/dist/react/CTABanner.d.ts.map +1 -0
- package/dist/react/CTABanner.js +9 -0
- package/dist/react/CTABanner.js.map +1 -0
- package/dist/react/CalloutBox.d.ts +13 -0
- package/dist/react/CalloutBox.d.ts.map +1 -0
- package/dist/react/CalloutBox.js +9 -0
- package/dist/react/CalloutBox.js.map +1 -0
- package/dist/react/CaseStudyHero.d.ts +20 -0
- package/dist/react/CaseStudyHero.d.ts.map +1 -0
- package/dist/react/CaseStudyHero.js +7 -0
- package/dist/react/CaseStudyHero.js.map +1 -0
- package/dist/react/ChannelMixBars.d.ts +18 -0
- package/dist/react/ChannelMixBars.d.ts.map +1 -0
- package/dist/react/ChannelMixBars.js +6 -0
- package/dist/react/ChannelMixBars.js.map +1 -0
- package/dist/react/Checklist.d.ts +10 -0
- package/dist/react/Checklist.d.ts.map +1 -0
- package/dist/react/Checklist.js +7 -0
- package/dist/react/Checklist.js.map +1 -0
- package/dist/react/ChecklistItem.d.ts +7 -0
- package/dist/react/ChecklistItem.d.ts.map +1 -0
- package/dist/react/ChecklistItem.js +5 -0
- package/dist/react/ChecklistItem.js.map +1 -0
- package/dist/react/CodeSnippet.d.ts +17 -0
- package/dist/react/CodeSnippet.d.ts.map +1 -0
- package/dist/react/CodeSnippet.js +14 -0
- package/dist/react/CodeSnippet.js.map +1 -0
- package/dist/react/ComparisonTable.d.ts +22 -0
- package/dist/react/ComparisonTable.d.ts.map +1 -0
- package/dist/react/ComparisonTable.js +35 -0
- package/dist/react/ComparisonTable.js.map +1 -0
- package/dist/react/Definition.d.ts +9 -0
- package/dist/react/Definition.d.ts.map +1 -0
- package/dist/react/Definition.js +19 -0
- package/dist/react/Definition.js.map +1 -0
- package/dist/react/DeliveryComparison.d.ts +16 -0
- package/dist/react/DeliveryComparison.d.ts.map +1 -0
- package/dist/react/DeliveryComparison.js +7 -0
- package/dist/react/DeliveryComparison.js.map +1 -0
- package/dist/react/FAQList.d.ts +20 -0
- package/dist/react/FAQList.d.ts.map +1 -0
- package/dist/react/FAQList.js +19 -0
- package/dist/react/FAQList.js.map +1 -0
- package/dist/react/FurtherReading.d.ts +21 -0
- package/dist/react/FurtherReading.d.ts.map +1 -0
- package/dist/react/FurtherReading.js +13 -0
- package/dist/react/FurtherReading.js.map +1 -0
- package/dist/react/ImageFeature.d.ts +9 -0
- package/dist/react/ImageFeature.d.ts.map +1 -0
- package/dist/react/ImageFeature.js +6 -0
- package/dist/react/ImageFeature.js.map +1 -0
- package/dist/react/Infographic.d.ts +6 -0
- package/dist/react/Infographic.d.ts.map +1 -0
- package/dist/react/Infographic.js +7 -0
- package/dist/react/Infographic.js.map +1 -0
- package/dist/react/KeyMetric.d.ts +16 -0
- package/dist/react/KeyMetric.d.ts.map +1 -0
- package/dist/react/KeyMetric.js +15 -0
- package/dist/react/KeyMetric.js.map +1 -0
- package/dist/react/KeywordTable.d.ts +18 -0
- package/dist/react/KeywordTable.d.ts.map +1 -0
- package/dist/react/KeywordTable.js +23 -0
- package/dist/react/KeywordTable.js.map +1 -0
- package/dist/react/List.d.ts +11 -0
- package/dist/react/List.d.ts.map +1 -0
- package/dist/react/List.js +21 -0
- package/dist/react/List.js.map +1 -0
- package/dist/react/MetricHighlight.d.ts +15 -0
- package/dist/react/MetricHighlight.d.ts.map +1 -0
- package/dist/react/MetricHighlight.js +26 -0
- package/dist/react/MetricHighlight.js.map +1 -0
- package/dist/react/NewsletterCTA.d.ts +9 -0
- package/dist/react/NewsletterCTA.d.ts.map +1 -0
- package/dist/react/NewsletterCTA.js +5 -0
- package/dist/react/NewsletterCTA.js.map +1 -0
- package/dist/react/NumberedCard.d.ts +9 -0
- package/dist/react/NumberedCard.d.ts.map +1 -0
- package/dist/react/NumberedCard.js +7 -0
- package/dist/react/NumberedCard.js.map +1 -0
- package/dist/react/ProConBlock.d.ts +6 -0
- package/dist/react/ProConBlock.d.ts.map +1 -0
- package/dist/react/ProConBlock.js +7 -0
- package/dist/react/ProConBlock.js.map +1 -0
- package/dist/react/ProseList.d.ts +17 -0
- package/dist/react/ProseList.d.ts.map +1 -0
- package/dist/react/ProseList.js +26 -0
- package/dist/react/ProseList.js.map +1 -0
- package/dist/react/QuoteBlock.d.ts +17 -0
- package/dist/react/QuoteBlock.d.ts.map +1 -0
- package/dist/react/QuoteBlock.js +26 -0
- package/dist/react/QuoteBlock.js.map +1 -0
- package/dist/react/RegionCallout.d.ts +13 -0
- package/dist/react/RegionCallout.d.ts.map +1 -0
- package/dist/react/RegionCallout.js +5 -0
- package/dist/react/RegionCallout.js.map +1 -0
- package/dist/react/RelatedPosts.d.ts +20 -0
- package/dist/react/RelatedPosts.d.ts.map +1 -0
- package/dist/react/RelatedPosts.js +7 -0
- package/dist/react/RelatedPosts.js.map +1 -0
- package/dist/react/ResultsStrip.d.ts +18 -0
- package/dist/react/ResultsStrip.d.ts.map +1 -0
- package/dist/react/ResultsStrip.js +22 -0
- package/dist/react/ResultsStrip.js.map +1 -0
- package/dist/react/ScoreBar.d.ts +8 -0
- package/dist/react/ScoreBar.d.ts.map +1 -0
- package/dist/react/ScoreBar.js +6 -0
- package/dist/react/ScoreBar.js.map +1 -0
- package/dist/react/SerpPreview.d.ts +18 -0
- package/dist/react/SerpPreview.d.ts.map +1 -0
- package/dist/react/SerpPreview.js +13 -0
- package/dist/react/SerpPreview.js.map +1 -0
- package/dist/react/ServicePromoCard.d.ts +14 -0
- package/dist/react/ServicePromoCard.d.ts.map +1 -0
- package/dist/react/ServicePromoCard.js +12 -0
- package/dist/react/ServicePromoCard.js.map +1 -0
- package/dist/react/StatCard.d.ts +13 -0
- package/dist/react/StatCard.d.ts.map +1 -0
- package/dist/react/StatCard.js +20 -0
- package/dist/react/StatCard.js.map +1 -0
- package/dist/react/StepBlock.d.ts +8 -0
- package/dist/react/StepBlock.d.ts.map +1 -0
- package/dist/react/StepBlock.js +5 -0
- package/dist/react/StepBlock.js.map +1 -0
- package/dist/react/TableOfContents.d.ts +14 -0
- package/dist/react/TableOfContents.d.ts.map +1 -0
- package/dist/react/TableOfContents.js +12 -0
- package/dist/react/TableOfContents.js.map +1 -0
- package/dist/react/TimelineBlock.d.ts +14 -0
- package/dist/react/TimelineBlock.d.ts.map +1 -0
- package/dist/react/TimelineBlock.js +8 -0
- package/dist/react/TimelineBlock.js.map +1 -0
- package/dist/react/TipBox.d.ts +6 -0
- package/dist/react/TipBox.d.ts.map +1 -0
- package/dist/react/TipBox.js +5 -0
- package/dist/react/TipBox.js.map +1 -0
- package/dist/react/TrafficChart.d.ts +16 -0
- package/dist/react/TrafficChart.d.ts.map +1 -0
- package/dist/react/TrafficChart.js +14 -0
- package/dist/react/TrafficChart.js.map +1 -0
- package/dist/react/VerdictCard.d.ts +15 -0
- package/dist/react/VerdictCard.d.ts.map +1 -0
- package/dist/react/VerdictCard.js +5 -0
- package/dist/react/VerdictCard.js.map +1 -0
- package/dist/react/components-map.d.ts +133 -0
- package/dist/react/components-map.d.ts.map +1 -0
- package/dist/react/components-map.js +120 -0
- package/dist/react/components-map.js.map +1 -0
- package/dist/react/index.d.ts +5 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +13 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +116 -0
- package/react/AdPreview.tsx +94 -0
- package/react/AdPreviewPair.tsx +16 -0
- package/react/AuditFindings.tsx +43 -0
- package/react/AuditScores.tsx +73 -0
- package/react/AuthorCard.tsx +35 -0
- package/react/BeforeAfter.tsx +27 -0
- package/react/BlogBehaviors.tsx +21 -0
- package/react/CTABanner.tsx +32 -0
- package/react/CalloutBox.tsx +31 -0
- package/react/CaseStudyHero.tsx +71 -0
- package/react/ChannelMixBars.tsx +50 -0
- package/react/Checklist.tsx +31 -0
- package/react/ChecklistItem.tsx +19 -0
- package/react/CodeSnippet.tsx +36 -0
- package/react/ComparisonTable.tsx +114 -0
- package/react/Definition.tsx +36 -0
- package/react/DeliveryComparison.tsx +62 -0
- package/react/FAQList.tsx +61 -0
- package/react/FurtherReading.tsx +46 -0
- package/react/ImageFeature.tsx +26 -0
- package/react/Infographic.tsx +18 -0
- package/react/KeyMetric.tsx +61 -0
- package/react/KeywordTable.tsx +92 -0
- package/react/List.tsx +58 -0
- package/react/MetricHighlight.tsx +86 -0
- package/react/NewsletterCTA.tsx +48 -0
- package/react/NumberedCard.tsx +7 -0
- package/react/ProConBlock.tsx +42 -0
- package/react/ProseList.tsx +72 -0
- package/react/QuoteBlock.tsx +89 -0
- package/react/RegionCallout.tsx +38 -0
- package/react/RelatedPosts.tsx +58 -0
- package/react/ResultsStrip.tsx +77 -0
- package/react/ScoreBar.tsx +27 -0
- package/react/SerpPreview.tsx +59 -0
- package/react/ServicePromoCard.tsx +43 -0
- package/react/StatCard.tsx +62 -0
- package/react/StepBlock.tsx +5 -0
- package/react/TableOfContents.tsx +27 -0
- package/react/TimelineBlock.tsx +35 -0
- package/react/TipBox.tsx +16 -0
- package/react/TrafficChart.tsx +79 -0
- package/react/VerdictCard.tsx +60 -0
- package/react/components-map.ts +122 -0
- package/react/index.ts +13 -0
- package/templates/blogkit/app/api/blogkit/revalidate/route.ts.tmpl +32 -0
- package/templates/blogkit/app/blog/[slug]/page.tsx.tmpl +41 -0
- package/templates/blogkit/app/blog/page.tsx.tmpl +18 -0
- package/templates/blogkit/blogkit.config.ts.tmpl +23 -0
- package/templates/blogkit-astro/BLOGKIT_ASTRO_SETUP.md.tmpl +49 -0
- package/templates/blogkit-astro/src/blogkit.config.ts.tmpl +29 -0
- package/templates/blogkit-astro/src/pages/api/blogkit/revalidate.ts.tmpl +46 -0
- package/templates/blogkit-astro/src/pages/blog/[slug].astro.tmpl +39 -0
- package/templates/blogkit-astro/src/pages/blog/index.astro.tmpl +29 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
---
|
|
2
|
+
/* ============================================================
|
|
3
|
+
<BlogIndex> — the listing route surface for ASTRO.
|
|
4
|
+
------------------------------------------------------------
|
|
5
|
+
The Astro counterpart of package/blog-index.tsx. Self-contained
|
|
6
|
+
and source-agnostic: a featured hero + category nav + card grid
|
|
7
|
+
+ pagination, all using the kit's `bk-idx-*` classes (shipped in
|
|
8
|
+
core/css/index-listing.css), so a site gets a working /blog with
|
|
9
|
+
no bespoke listing chrome. Byte-identical markup to the Next
|
|
10
|
+
<BlogIndex>.
|
|
11
|
+
|
|
12
|
+
Data comes pre-computed from loadIndex() (in the route's
|
|
13
|
+
frontmatter), so this template just iterates.
|
|
14
|
+
============================================================ */
|
|
15
|
+
import type { IndexData, ResolvedConfig } from "@highjumpdigitalsoftware/blog-kit/astro";
|
|
16
|
+
import {
|
|
17
|
+
articleHref,
|
|
18
|
+
categoryHref,
|
|
19
|
+
listingHref,
|
|
20
|
+
formatDate,
|
|
21
|
+
readingLabel,
|
|
22
|
+
} from "@highjumpdigitalsoftware/blog-kit/astro";
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
data: IndexData;
|
|
26
|
+
config: ResolvedConfig;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const { data, config } = Astro.props;
|
|
30
|
+
const { listing } = config;
|
|
31
|
+
const { featured, items, categories, page, totalPages, categorySlug } = data;
|
|
32
|
+
|
|
33
|
+
const base = (p: number) =>
|
|
34
|
+
categorySlug
|
|
35
|
+
? categoryHref(listing.listingBasePath, categorySlug, p)
|
|
36
|
+
: listingHref(listing.listingBasePath, p);
|
|
37
|
+
|
|
38
|
+
const heading = categorySlug
|
|
39
|
+
? categories.find((c) => c.slug === categorySlug)?.label ?? "Articles"
|
|
40
|
+
: "Latest articles";
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
<div class="blog-root bk-idx">
|
|
44
|
+
<header class="bk-idx-head">
|
|
45
|
+
<p class="bk-idx-eyebrow">{config.brand.name}</p>
|
|
46
|
+
<h1 class="bk-idx-h1">{heading}</h1>
|
|
47
|
+
</header>
|
|
48
|
+
|
|
49
|
+
{listing.showCategoryNav && categories.length > 0 && (
|
|
50
|
+
<nav class="bk-idx-nav" aria-label="Categories">
|
|
51
|
+
<a
|
|
52
|
+
class={`bk-idx-pill${!categorySlug ? " is-active" : ""}`}
|
|
53
|
+
href={listingHref(listing.listingBasePath, 1)}
|
|
54
|
+
>
|
|
55
|
+
All
|
|
56
|
+
</a>
|
|
57
|
+
{categories.map((c) => (
|
|
58
|
+
<a
|
|
59
|
+
class={`bk-idx-pill${c.slug === categorySlug ? " is-active" : ""}`}
|
|
60
|
+
href={categoryHref(listing.listingBasePath, c.slug, 1)}
|
|
61
|
+
>
|
|
62
|
+
{c.label} <span class="bk-idx-pill-count">{c.count}</span>
|
|
63
|
+
</a>
|
|
64
|
+
))}
|
|
65
|
+
</nav>
|
|
66
|
+
)}
|
|
67
|
+
|
|
68
|
+
{featured && (
|
|
69
|
+
<section class="bk-idx-featured">
|
|
70
|
+
<a
|
|
71
|
+
class="bk-idx-card bk-idx-card--featured"
|
|
72
|
+
href={articleHref(listing.articleBasePath, featured.slug)}
|
|
73
|
+
>
|
|
74
|
+
<div
|
|
75
|
+
class={`bk-idx-thumb${featured.featuredImage ? "" : " bk-idx-thumb--ph"}`}
|
|
76
|
+
aria-hidden={featured.featuredImage ? undefined : true}
|
|
77
|
+
>
|
|
78
|
+
{featured.featuredImage && (
|
|
79
|
+
<img
|
|
80
|
+
class="bk-idx-img"
|
|
81
|
+
src={featured.featuredImage}
|
|
82
|
+
alt={featured.featuredImageAlt ?? featured.title}
|
|
83
|
+
loading="lazy"
|
|
84
|
+
/>
|
|
85
|
+
)}
|
|
86
|
+
</div>
|
|
87
|
+
<div class="bk-idx-body">
|
|
88
|
+
{featured.category && <span class="bk-idx-cat">{featured.category}</span>}
|
|
89
|
+
<h3 class="bk-idx-title">{featured.title}</h3>
|
|
90
|
+
<p class="bk-idx-desc">{featured.description}</p>
|
|
91
|
+
<p class="bk-idx-meta">
|
|
92
|
+
{formatDate(featured.date, listing.dateLocale)}
|
|
93
|
+
{featured.readingTime ? ` · ${readingLabel(featured.readingTime)}` : ""}
|
|
94
|
+
</p>
|
|
95
|
+
</div>
|
|
96
|
+
</a>
|
|
97
|
+
</section>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{items.length > 0 ? (
|
|
101
|
+
<section class="bk-idx-grid">
|
|
102
|
+
{items.map((post) => (
|
|
103
|
+
<a class="bk-idx-card" href={articleHref(listing.articleBasePath, post.slug)}>
|
|
104
|
+
<div
|
|
105
|
+
class={`bk-idx-thumb${post.featuredImage ? "" : " bk-idx-thumb--ph"}`}
|
|
106
|
+
aria-hidden={post.featuredImage ? undefined : true}
|
|
107
|
+
>
|
|
108
|
+
{post.featuredImage && (
|
|
109
|
+
<img
|
|
110
|
+
class="bk-idx-img"
|
|
111
|
+
src={post.featuredImage}
|
|
112
|
+
alt={post.featuredImageAlt ?? post.title}
|
|
113
|
+
loading="lazy"
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
<div class="bk-idx-body">
|
|
118
|
+
{post.category && <span class="bk-idx-cat">{post.category}</span>}
|
|
119
|
+
<h3 class="bk-idx-title">{post.title}</h3>
|
|
120
|
+
<p class="bk-idx-desc">{post.description}</p>
|
|
121
|
+
<p class="bk-idx-meta">
|
|
122
|
+
{formatDate(post.date, listing.dateLocale)}
|
|
123
|
+
{post.readingTime ? ` · ${readingLabel(post.readingTime)}` : ""}
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
</a>
|
|
127
|
+
))}
|
|
128
|
+
</section>
|
|
129
|
+
) : featured ? null : (
|
|
130
|
+
<p class="bk-idx-empty">No articles yet.</p>
|
|
131
|
+
)}
|
|
132
|
+
|
|
133
|
+
{totalPages > 1 && (
|
|
134
|
+
<nav class="bk-idx-pager" aria-label="Pagination">
|
|
135
|
+
{page > 1 && (
|
|
136
|
+
<a class="bk-idx-pagelink" href={base(page - 1)}>← Newer</a>
|
|
137
|
+
)}
|
|
138
|
+
<span class="bk-idx-pageinfo">Page {page} of {totalPages}</span>
|
|
139
|
+
{page < totalPages && (
|
|
140
|
+
<a class="bk-idx-pagelink" href={base(page + 1)}>Older →</a>
|
|
141
|
+
)}
|
|
142
|
+
</nav>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — CodeSnippet behaviour
|
|
3
|
+
Copy-to-clipboard for code blocks. Each block's copy button
|
|
4
|
+
reads the exact rendered text from .bk-code__code, writes it to
|
|
5
|
+
the clipboard, and flips its label to "Copied" for 1.5s. Falls
|
|
6
|
+
back to a hidden textarea + execCommand where the async Clipboard
|
|
7
|
+
API is unavailable.
|
|
8
|
+
|
|
9
|
+
Framework-neutral ESM: exports initCode() (run it after each
|
|
10
|
+
client navigation) and also self-runs once on load for plain
|
|
11
|
+
<script> usage. Idempotent: a block is wired at most once.
|
|
12
|
+
============================================================ */
|
|
13
|
+
var RESET_MS = 1500;
|
|
14
|
+
|
|
15
|
+
function writeClipboard(text) {
|
|
16
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
17
|
+
return navigator.clipboard.writeText(text);
|
|
18
|
+
}
|
|
19
|
+
return new Promise(function (resolve, reject) {
|
|
20
|
+
try {
|
|
21
|
+
var ta = document.createElement("textarea");
|
|
22
|
+
ta.value = text;
|
|
23
|
+
ta.setAttribute("readonly", "");
|
|
24
|
+
ta.style.position = "absolute";
|
|
25
|
+
ta.style.left = "-9999px";
|
|
26
|
+
document.body.appendChild(ta);
|
|
27
|
+
ta.select();
|
|
28
|
+
document.execCommand("copy");
|
|
29
|
+
document.body.removeChild(ta);
|
|
30
|
+
resolve();
|
|
31
|
+
} catch (err) {
|
|
32
|
+
reject(err);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function enhance(root) {
|
|
38
|
+
if (root.getAttribute("data-bk-code") === "ready") return;
|
|
39
|
+
root.setAttribute("data-bk-code", "ready");
|
|
40
|
+
|
|
41
|
+
var btn = root.querySelector("[data-bk-code-copy]");
|
|
42
|
+
var code = root.querySelector(".bk-code__code");
|
|
43
|
+
if (!btn || !code) return;
|
|
44
|
+
|
|
45
|
+
var labelEl = btn.querySelector(".bk-code__copy-label");
|
|
46
|
+
var timer = null;
|
|
47
|
+
|
|
48
|
+
btn.addEventListener("click", function () {
|
|
49
|
+
writeClipboard(code.textContent || "").then(
|
|
50
|
+
function () {
|
|
51
|
+
if (labelEl) labelEl.textContent = "Copied";
|
|
52
|
+
btn.setAttribute("data-copied", "true");
|
|
53
|
+
if (timer) clearTimeout(timer);
|
|
54
|
+
timer = setTimeout(function () {
|
|
55
|
+
if (labelEl) labelEl.textContent = "Copy";
|
|
56
|
+
btn.removeAttribute("data-copied");
|
|
57
|
+
}, RESET_MS);
|
|
58
|
+
},
|
|
59
|
+
function () {
|
|
60
|
+
/* clipboard unavailable — silently ignore */
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function initCode() {
|
|
67
|
+
if (typeof document === "undefined") return;
|
|
68
|
+
var blocks = document.querySelectorAll("[data-bk-code]");
|
|
69
|
+
for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof document !== "undefined") {
|
|
73
|
+
if (document.readyState === "loading") {
|
|
74
|
+
document.addEventListener("DOMContentLoaded", initCode);
|
|
75
|
+
} else {
|
|
76
|
+
initCode();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — ComparisonTable behaviour
|
|
3
|
+
The desktop comparison table can overflow its container on
|
|
4
|
+
narrow viewports. Native overflow-x scrolls fine but gives no
|
|
5
|
+
visual cue that more columns exist (the hidden-horizontal-
|
|
6
|
+
scroll trap). This script toggles edge-fade hint classes on the
|
|
7
|
+
wrap depending on whether the inner scroller can scroll left or
|
|
8
|
+
right, updating on scroll and resize.
|
|
9
|
+
|
|
10
|
+
Framework-neutral ESM: exports initComparison() (run it after
|
|
11
|
+
each client navigation) and also self-runs once on load for
|
|
12
|
+
plain <script> usage. Idempotent: a block is wired at most once.
|
|
13
|
+
============================================================ */
|
|
14
|
+
function update(wrap, scroller) {
|
|
15
|
+
var max = scroller.scrollWidth - scroller.clientWidth;
|
|
16
|
+
var x = scroller.scrollLeft;
|
|
17
|
+
var canScroll = max > 1;
|
|
18
|
+
wrap.classList.toggle("bk-comparison__table-wrap--hint-right", canScroll && x < max - 1);
|
|
19
|
+
wrap.classList.toggle("bk-comparison__table-wrap--hint-left", canScroll && x > 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function enhance(wrap) {
|
|
23
|
+
if (wrap.getAttribute("data-bk-comparison") === "ready") return;
|
|
24
|
+
wrap.setAttribute("data-bk-comparison", "ready");
|
|
25
|
+
var scroller = wrap.querySelector(".bk-comparison__scroll");
|
|
26
|
+
if (!scroller) return;
|
|
27
|
+
var sync = function () {
|
|
28
|
+
update(wrap, scroller);
|
|
29
|
+
};
|
|
30
|
+
scroller.addEventListener("scroll", sync, { passive: true });
|
|
31
|
+
if (typeof window.ResizeObserver === "function") {
|
|
32
|
+
var ro = new ResizeObserver(sync);
|
|
33
|
+
ro.observe(scroller);
|
|
34
|
+
} else {
|
|
35
|
+
window.addEventListener("resize", sync);
|
|
36
|
+
}
|
|
37
|
+
sync();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function initComparison() {
|
|
41
|
+
if (typeof document === "undefined") return;
|
|
42
|
+
var blocks = document.querySelectorAll("[data-bk-comparison]");
|
|
43
|
+
for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof document !== "undefined") {
|
|
47
|
+
if (document.readyState === "loading") {
|
|
48
|
+
document.addEventListener("DOMContentLoaded", initComparison);
|
|
49
|
+
} else {
|
|
50
|
+
initComparison();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — DeliveryComparison behaviour
|
|
3
|
+
The N-column comparison matrix can overflow its container on
|
|
4
|
+
narrow viewports. Native overflow-x scrolls fine but gives no
|
|
5
|
+
visual cue that more columns exist (the hidden-horizontal-scroll
|
|
6
|
+
trap). This script toggles edge-fade hint classes on the wrap
|
|
7
|
+
depending on whether the inner scroller can scroll left or right,
|
|
8
|
+
updating on scroll and resize.
|
|
9
|
+
|
|
10
|
+
Framework-neutral ESM: exports initDeliveryComparison() (run it
|
|
11
|
+
after each client navigation) and also self-runs once on load for
|
|
12
|
+
plain <script> usage. Idempotent: a block is wired at most once.
|
|
13
|
+
============================================================ */
|
|
14
|
+
function update(wrap, scroller) {
|
|
15
|
+
var max = scroller.scrollWidth - scroller.clientWidth;
|
|
16
|
+
var x = scroller.scrollLeft;
|
|
17
|
+
var canScroll = max > 1;
|
|
18
|
+
wrap.classList.toggle("bk-delivery-comparison--hint-right", canScroll && x < max - 1);
|
|
19
|
+
wrap.classList.toggle("bk-delivery-comparison--hint-left", canScroll && x > 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function enhance(wrap) {
|
|
23
|
+
if (wrap.getAttribute("data-bk-delivery-comparison") === "ready") return;
|
|
24
|
+
wrap.setAttribute("data-bk-delivery-comparison", "ready");
|
|
25
|
+
var scroller = wrap.querySelector(".bk-delivery-comparison__scroll");
|
|
26
|
+
if (!scroller) return;
|
|
27
|
+
var sync = function () {
|
|
28
|
+
update(wrap, scroller);
|
|
29
|
+
};
|
|
30
|
+
scroller.addEventListener("scroll", sync, { passive: true });
|
|
31
|
+
if (typeof window.ResizeObserver === "function") {
|
|
32
|
+
var ro = new ResizeObserver(sync);
|
|
33
|
+
ro.observe(scroller);
|
|
34
|
+
} else {
|
|
35
|
+
window.addEventListener("resize", sync);
|
|
36
|
+
}
|
|
37
|
+
sync();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function initDeliveryComparison() {
|
|
41
|
+
if (typeof document === "undefined") return;
|
|
42
|
+
var blocks = document.querySelectorAll("[data-bk-delivery-comparison]");
|
|
43
|
+
for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof document !== "undefined") {
|
|
47
|
+
if (document.readyState === "loading") {
|
|
48
|
+
document.addEventListener("DOMContentLoaded", initDeliveryComparison);
|
|
49
|
+
} else {
|
|
50
|
+
initDeliveryComparison();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — FAQList behaviour
|
|
3
|
+
Native <details> handles open/close on its own. This script's
|
|
4
|
+
only job is SEO: lift every rendered Q&A pair into FAQPage
|
|
5
|
+
JSON-LD so search engines can surface it.
|
|
6
|
+
|
|
7
|
+
Framework-neutral ESM: exports initFaq() (run it after each
|
|
8
|
+
client navigation) and also self-runs once on load for plain
|
|
9
|
+
<script> usage. Idempotent: a block is processed at most once.
|
|
10
|
+
============================================================ */
|
|
11
|
+
function clean(s) {
|
|
12
|
+
return String(s || "").replace(/\s+/g, " ").trim();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function buildSchema(root) {
|
|
16
|
+
var items = root.querySelectorAll(".bk-faq__item");
|
|
17
|
+
var entities = [];
|
|
18
|
+
for (var i = 0; i < items.length; i++) {
|
|
19
|
+
var q = items[i].querySelector(".bk-faq__question");
|
|
20
|
+
var a = items[i].querySelector(".bk-faq__answer");
|
|
21
|
+
var name = clean(q && q.textContent);
|
|
22
|
+
var text = clean(a && a.textContent);
|
|
23
|
+
if (!name) continue;
|
|
24
|
+
entities.push({
|
|
25
|
+
"@type": "Question",
|
|
26
|
+
name: name,
|
|
27
|
+
acceptedAnswer: { "@type": "Answer", text: text },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (!entities.length) return null;
|
|
31
|
+
return {
|
|
32
|
+
"@context": "https://schema.org",
|
|
33
|
+
"@type": "FAQPage",
|
|
34
|
+
mainEntity: entities,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function enhance(root) {
|
|
39
|
+
if (root.getAttribute("data-bk-faq") === "ready") return;
|
|
40
|
+
root.setAttribute("data-bk-faq", "ready");
|
|
41
|
+
var schema = buildSchema(root);
|
|
42
|
+
if (!schema) return;
|
|
43
|
+
var tag = document.createElement("script");
|
|
44
|
+
tag.type = "application/ld+json";
|
|
45
|
+
tag.textContent = JSON.stringify(schema);
|
|
46
|
+
root.appendChild(tag);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function initFaq() {
|
|
50
|
+
if (typeof document === "undefined") return;
|
|
51
|
+
var blocks = document.querySelectorAll("[data-bk-faq]");
|
|
52
|
+
for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (typeof document !== "undefined") {
|
|
56
|
+
if (document.readyState === "loading") {
|
|
57
|
+
document.addEventListener("DOMContentLoaded", initFaq);
|
|
58
|
+
} else {
|
|
59
|
+
initFaq();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — behaviours barrel
|
|
3
|
+
One entry point that wires every interactive component. Importing
|
|
4
|
+
this module also self-runs each behaviour once on load (see each
|
|
5
|
+
file's guarded auto-run), so a plain `<script type="module">` that
|
|
6
|
+
imports it is enough for a static (Astro) site. For SPA frameworks
|
|
7
|
+
call initBlogBehaviors() after each client-side navigation so newly
|
|
8
|
+
mounted markup gets enhanced — each enhance step is idempotent, so
|
|
9
|
+
re-running is safe and only touches not-yet-wired elements.
|
|
10
|
+
============================================================ */
|
|
11
|
+
import { initToc } from "./toc.js";
|
|
12
|
+
import { initFaq } from "./faq.js";
|
|
13
|
+
import { initComparison } from "./comparison.js";
|
|
14
|
+
import { initCode } from "./code.js";
|
|
15
|
+
import { initKeywordTable } from "./keyword-table.js";
|
|
16
|
+
import { initDeliveryComparison } from "./delivery-comparison.js";
|
|
17
|
+
|
|
18
|
+
export {
|
|
19
|
+
initToc,
|
|
20
|
+
initFaq,
|
|
21
|
+
initComparison,
|
|
22
|
+
initCode,
|
|
23
|
+
initKeywordTable,
|
|
24
|
+
initDeliveryComparison,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Run every blog-kit behaviour. Idempotent; safe to call on each navigation. */
|
|
28
|
+
export function initBlogBehaviors() {
|
|
29
|
+
initToc();
|
|
30
|
+
initFaq();
|
|
31
|
+
initComparison();
|
|
32
|
+
initCode();
|
|
33
|
+
initKeywordTable();
|
|
34
|
+
initDeliveryComparison();
|
|
35
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — KeywordTable behaviour
|
|
3
|
+
A six-column SEO keyword-ranking table overflows its container
|
|
4
|
+
on narrow viewports. Native overflow-x scrolls fine but gives no
|
|
5
|
+
visual cue that more columns exist (the hidden-horizontal-scroll
|
|
6
|
+
trap). This script toggles edge-fade hint classes on the wrap
|
|
7
|
+
depending on whether the inner scroller can scroll left or right,
|
|
8
|
+
updating on scroll and resize.
|
|
9
|
+
|
|
10
|
+
Framework-neutral ESM: exports initKeywordTable() (run it after
|
|
11
|
+
each client navigation) and also self-runs once on load for
|
|
12
|
+
plain <script> usage. Idempotent: a block is wired at most once.
|
|
13
|
+
============================================================ */
|
|
14
|
+
function update(wrap, scroller) {
|
|
15
|
+
var max = scroller.scrollWidth - scroller.clientWidth;
|
|
16
|
+
var x = scroller.scrollLeft;
|
|
17
|
+
var canScroll = max > 1;
|
|
18
|
+
wrap.classList.toggle("bk-keyword-table--hint-right", canScroll && x < max - 1);
|
|
19
|
+
wrap.classList.toggle("bk-keyword-table--hint-left", canScroll && x > 1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function enhance(wrap) {
|
|
23
|
+
if (wrap.getAttribute("data-bk-keyword-table") === "ready") return;
|
|
24
|
+
wrap.setAttribute("data-bk-keyword-table", "ready");
|
|
25
|
+
var scroller = wrap.querySelector(".bk-keyword-table__scroll");
|
|
26
|
+
if (!scroller) return;
|
|
27
|
+
var sync = function () {
|
|
28
|
+
update(wrap, scroller);
|
|
29
|
+
};
|
|
30
|
+
scroller.addEventListener("scroll", sync, { passive: true });
|
|
31
|
+
if (typeof window.ResizeObserver === "function") {
|
|
32
|
+
var ro = new ResizeObserver(sync);
|
|
33
|
+
ro.observe(scroller);
|
|
34
|
+
} else {
|
|
35
|
+
window.addEventListener("resize", sync);
|
|
36
|
+
}
|
|
37
|
+
sync();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function initKeywordTable() {
|
|
41
|
+
if (typeof document === "undefined") return;
|
|
42
|
+
var blocks = document.querySelectorAll("[data-bk-keyword-table]");
|
|
43
|
+
for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof document !== "undefined") {
|
|
47
|
+
if (document.readyState === "loading") {
|
|
48
|
+
document.addEventListener("DOMContentLoaded", initKeywordTable);
|
|
49
|
+
} else {
|
|
50
|
+
initKeywordTable();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
blog-kit — TableOfContents behaviour
|
|
3
|
+
The TOC is built from the live document, not authored markup:
|
|
4
|
+
it scans the article for <h2> headings (sub-headings inside MDX
|
|
5
|
+
components are not document structure and are skipped), renders
|
|
6
|
+
one numbered entry per heading, smooth-scrolls on click, and
|
|
7
|
+
runs a scrollspy (IntersectionObserver) that flags the heading
|
|
8
|
+
currently in view with the --active modifier. If the scope has
|
|
9
|
+
no <h2>s the container hides itself.
|
|
10
|
+
|
|
11
|
+
Scope resolution (centralised so no site has to wrap its body in
|
|
12
|
+
a particular tag): an explicit data-bk-toc-scope selector wins if
|
|
13
|
+
it matches; otherwise the TOC scans the prose region that
|
|
14
|
+
contains it (.bk-prose, then .blog-root), falling back to body.
|
|
15
|
+
|
|
16
|
+
Framework-neutral ESM: exports initToc() (run it after each
|
|
17
|
+
client navigation) and also self-runs once on load for plain
|
|
18
|
+
<script> usage. Idempotent: a block is wired at most once.
|
|
19
|
+
============================================================ */
|
|
20
|
+
function pad2(n) {
|
|
21
|
+
return n < 10 ? "0" + n : String(n);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function resolveScope(root) {
|
|
25
|
+
var sel = root.getAttribute("data-bk-toc-scope");
|
|
26
|
+
if (sel) {
|
|
27
|
+
var bySel = document.querySelector(sel);
|
|
28
|
+
if (bySel) return bySel;
|
|
29
|
+
}
|
|
30
|
+
return (
|
|
31
|
+
root.closest(".bk-prose") ||
|
|
32
|
+
root.closest(".blog-root") ||
|
|
33
|
+
document.body
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function build(root) {
|
|
38
|
+
var scope = resolveScope(root);
|
|
39
|
+
if (!scope) {
|
|
40
|
+
root.hidden = true;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// h2 only — sub-headings inside MDX components (h3/h4 from cards,
|
|
45
|
+
// CTAs, etc.) are not document structure and shouldn't be entries.
|
|
46
|
+
var headings = scope.querySelectorAll("h2");
|
|
47
|
+
if (!headings.length) {
|
|
48
|
+
root.hidden = true;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
root.hidden = false;
|
|
52
|
+
|
|
53
|
+
var list = root.querySelector(".bk-toc__list");
|
|
54
|
+
if (!list) return;
|
|
55
|
+
list.textContent = "";
|
|
56
|
+
|
|
57
|
+
var links = [];
|
|
58
|
+
var byId = {};
|
|
59
|
+
|
|
60
|
+
Array.prototype.forEach.call(headings, function (el, i) {
|
|
61
|
+
var id = el.id;
|
|
62
|
+
var text = el.textContent || "";
|
|
63
|
+
|
|
64
|
+
var li = document.createElement("li");
|
|
65
|
+
li.className = "bk-toc__item";
|
|
66
|
+
|
|
67
|
+
var num = document.createElement("span");
|
|
68
|
+
num.className = "bk-toc__num";
|
|
69
|
+
num.textContent = pad2(i + 1);
|
|
70
|
+
|
|
71
|
+
var a = document.createElement("a");
|
|
72
|
+
a.className = "bk-toc__link";
|
|
73
|
+
a.href = id ? "#" + id : "#";
|
|
74
|
+
a.textContent = text;
|
|
75
|
+
a.addEventListener("click", function (e) {
|
|
76
|
+
e.preventDefault();
|
|
77
|
+
var target = id ? document.getElementById(id) : null;
|
|
78
|
+
if (target) target.scrollIntoView({ behavior: "smooth" });
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
li.appendChild(num);
|
|
82
|
+
li.appendChild(a);
|
|
83
|
+
list.appendChild(li);
|
|
84
|
+
|
|
85
|
+
links.push(a);
|
|
86
|
+
if (id) byId[id] = a;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
function setActive(id) {
|
|
90
|
+
for (var i = 0; i < links.length; i++) {
|
|
91
|
+
links[i].classList.remove("bk-toc__link--active");
|
|
92
|
+
}
|
|
93
|
+
var hit = byId[id];
|
|
94
|
+
if (hit) hit.classList.add("bk-toc__link--active");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (typeof window.IntersectionObserver === "function") {
|
|
98
|
+
var observer = new IntersectionObserver(
|
|
99
|
+
function (entries) {
|
|
100
|
+
for (var i = 0; i < entries.length; i++) {
|
|
101
|
+
if (entries[i].isIntersecting) setActive(entries[i].target.id);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
{ rootMargin: "-80px 0px -60% 0px" }
|
|
105
|
+
);
|
|
106
|
+
Array.prototype.forEach.call(headings, function (el) {
|
|
107
|
+
observer.observe(el);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function enhance(root) {
|
|
113
|
+
if (root.getAttribute("data-bk-toc") === "ready") return;
|
|
114
|
+
root.setAttribute("data-bk-toc", "ready");
|
|
115
|
+
build(root);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function initToc() {
|
|
119
|
+
if (typeof document === "undefined") return;
|
|
120
|
+
var blocks = document.querySelectorAll("[data-bk-toc]");
|
|
121
|
+
for (var i = 0; i < blocks.length; i++) enhance(blocks[i]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (typeof document !== "undefined") {
|
|
125
|
+
if (document.readyState === "loading") {
|
|
126
|
+
document.addEventListener("DOMContentLoaded", initToc);
|
|
127
|
+
} else {
|
|
128
|
+
initToc();
|
|
129
|
+
}
|
|
130
|
+
}
|