@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,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Content-type templates surfaced by the MCP's list_templates. Templates are suggestions — any components can be mixed freely. Phase 1 reconciles this against the MCP's current TEMPLATE_SUMMARIES; seeded here as the single source of truth.",
|
|
3
|
+
"templates": [
|
|
4
|
+
{
|
|
5
|
+
"name": "informational",
|
|
6
|
+
"description": "Explainer / thought-leadership piece that teaches a concept.",
|
|
7
|
+
"structure": "Intro → TableOfContents → sections → CalloutBox/TipBox asides → FAQList → conclusion CTA",
|
|
8
|
+
"suggestedComponents": [
|
|
9
|
+
"TableOfContents",
|
|
10
|
+
"CalloutBox",
|
|
11
|
+
"TipBox",
|
|
12
|
+
"Definition",
|
|
13
|
+
"Infographic",
|
|
14
|
+
"FAQList",
|
|
15
|
+
"CTABanner"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "comparison",
|
|
20
|
+
"description": "Head-to-head of two or more options.",
|
|
21
|
+
"structure": "Intro → ComparisonTable → ProConBlock per option → verdict → FAQList",
|
|
22
|
+
"suggestedComponents": [
|
|
23
|
+
"ComparisonTable",
|
|
24
|
+
"ProConBlock",
|
|
25
|
+
"QuoteBlock",
|
|
26
|
+
"CalloutBox",
|
|
27
|
+
"FAQList",
|
|
28
|
+
"CTABanner"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "how-to",
|
|
33
|
+
"description": "Step-by-step guide to accomplish a task.",
|
|
34
|
+
"structure": "Intro → what you'll need → StepBlock sequence → TipBox → FAQList",
|
|
35
|
+
"suggestedComponents": [
|
|
36
|
+
"StepBlock",
|
|
37
|
+
"TipBox",
|
|
38
|
+
"CalloutBox",
|
|
39
|
+
"CodeSnippet",
|
|
40
|
+
"Checklist",
|
|
41
|
+
"FAQList"
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "listicle",
|
|
46
|
+
"description": "Numbered/curated list of items, tools, or tactics.",
|
|
47
|
+
"structure": "Intro → numbered items → per-item detail → summary → FAQList",
|
|
48
|
+
"suggestedComponents": [
|
|
49
|
+
"NumberedCard",
|
|
50
|
+
"List",
|
|
51
|
+
"CalloutBox",
|
|
52
|
+
"ImageFeature",
|
|
53
|
+
"FAQList",
|
|
54
|
+
"CTABanner"
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "case-study",
|
|
59
|
+
"description": "Client/result story with metrics.",
|
|
60
|
+
"structure": "Hero → results strip → narrative → metrics → quote → CTA",
|
|
61
|
+
"suggestedComponents": [
|
|
62
|
+
"MetricHighlight",
|
|
63
|
+
"StatCard",
|
|
64
|
+
"BeforeAfter",
|
|
65
|
+
"QuoteBlock",
|
|
66
|
+
"ProConBlock",
|
|
67
|
+
"CTABanner"
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "freestyle",
|
|
72
|
+
"description": "No fixed structure — mix any components as the topic demands.",
|
|
73
|
+
"structure": "Author's choice.",
|
|
74
|
+
"suggestedComponents": []
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { HjdApiSourceConfig } from "../config/types";
|
|
2
|
+
import type { ContentSource, PostMeta, PostWithBody } from "./source";
|
|
3
|
+
export declare class HjdApiSource implements ContentSource {
|
|
4
|
+
readonly mode: "hjd-api";
|
|
5
|
+
private readonly base;
|
|
6
|
+
private readonly token;
|
|
7
|
+
private readonly revalidate;
|
|
8
|
+
constructor(cfg: HjdApiSourceConfig);
|
|
9
|
+
private req;
|
|
10
|
+
listPosts(): Promise<PostMeta[]>;
|
|
11
|
+
listSlugs(): Promise<string[]>;
|
|
12
|
+
getPost(slug: string): Promise<PostWithBody | null>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=hjd-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hjd-api.d.ts","sourceRoot":"","sources":["../../../package/adapters/hjd-api.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAStE,qBAAa,YAAa,YAAW,aAAa;IAChD,QAAQ,CAAC,IAAI,EAAG,SAAS,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,GAAG,EAAE,kBAAkB;YAcrB,GAAG;IA2BX,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAQhC,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAK9B,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;CAM1D"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export class HjdApiSource {
|
|
2
|
+
constructor(cfg) {
|
|
3
|
+
this.mode = "hjd-api";
|
|
4
|
+
this.base = (cfg.contentUrl ?? "").replace(/\/+$/, "");
|
|
5
|
+
this.token = cfg.siteToken ?? "";
|
|
6
|
+
this.revalidate = cfg.fetchRevalidate ?? 60;
|
|
7
|
+
if (!this.base) {
|
|
8
|
+
// Fail loud at construction in dev; in prod the route renders an
|
|
9
|
+
// empty list rather than crashing the whole site.
|
|
10
|
+
console.error("[blog-kit] hjd-api source has no contentUrl. " +
|
|
11
|
+
"Set BLOGKIT_CONTENT_URL (and BLOGKIT_SITE_TOKEN) in the host env.");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async req(pathname) {
|
|
15
|
+
if (!this.base)
|
|
16
|
+
return null;
|
|
17
|
+
const url = `${this.base}${pathname}`;
|
|
18
|
+
try {
|
|
19
|
+
const res = await fetch(url, {
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${this.token}`,
|
|
22
|
+
Accept: "application/json",
|
|
23
|
+
},
|
|
24
|
+
// Next.js ISR: cache + background-revalidate. This is what makes
|
|
25
|
+
// the render static HTML refreshed on a timer (no redeploy).
|
|
26
|
+
// `next` is a Next.js extension to RequestInit (typed there, not
|
|
27
|
+
// in the lib's standalone build) — cast to keep it.
|
|
28
|
+
next: { revalidate: this.revalidate, tags: ["blogkit"] },
|
|
29
|
+
});
|
|
30
|
+
if (res.status === 404)
|
|
31
|
+
return null;
|
|
32
|
+
if (!res.ok) {
|
|
33
|
+
console.error(`[blog-kit] hjd-api ${res.status} for ${pathname}`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return (await res.json());
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
console.error(`[blog-kit] hjd-api fetch failed for ${pathname}:`, err);
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async listPosts() {
|
|
44
|
+
const data = await this.req("/posts");
|
|
45
|
+
const posts = data?.posts ?? [];
|
|
46
|
+
return [...posts].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
47
|
+
}
|
|
48
|
+
async listSlugs() {
|
|
49
|
+
const posts = await this.listPosts();
|
|
50
|
+
return posts.map((p) => p.slug);
|
|
51
|
+
}
|
|
52
|
+
async getPost(slug) {
|
|
53
|
+
const data = await this.req(`/posts/${encodeURIComponent(slug)}`);
|
|
54
|
+
return data?.post ?? null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=hjd-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hjd-api.js","sourceRoot":"","sources":["../../../package/adapters/hjd-api.ts"],"names":[],"mappings":"AA2BA,MAAM,OAAO,YAAY;IAMvB,YAAY,GAAuB;QAL1B,SAAI,GAAG,SAAkB,CAAC;QAMjC,IAAI,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACf,iEAAiE;YACjE,kDAAkD;YAClD,OAAO,CAAC,KAAK,CACX,+CAA+C;gBAC7C,mEAAmE,CACtE,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,GAAG,CAAI,QAAgB;QACnC,IAAI,CAAC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QAC5B,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,GAAG,QAAQ,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC3B,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;oBACrC,MAAM,EAAE,kBAAkB;iBAC3B;gBACD,iEAAiE;gBACjE,6DAA6D;gBAC7D,iEAAiE;gBACjE,oDAAoD;gBACpD,IAAI,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,SAAS,CAAC,EAAE;aACS,CAAC,CAAC;YACrE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAAE,OAAO,IAAI,CAAC;YACpC,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;gBACZ,OAAO,CAAC,KAAK,CAAC,sBAAsB,GAAG,CAAC,MAAM,QAAQ,QAAQ,EAAE,CAAC,CAAC;gBAClE,OAAO,IAAI,CAAC;YACd,CAAC;YACD,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;QACjC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,uCAAuC,QAAQ,GAAG,EAAE,GAAG,CAAC,CAAC;YACvE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CAAkB,QAAQ,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CACpB,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAClE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,GAAG,CACzB,UAAU,kBAAkB,CAAC,IAAI,CAAC,EAAE,CACrC,CAAC;QACF,OAAO,IAAI,EAAE,IAAI,IAAI,IAAI,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "../config/types";
|
|
2
|
+
import type { ContentSource } from "./source";
|
|
3
|
+
export type { ContentSource, PostMeta, PostWithBody, PostFrontmatter } from "./source";
|
|
4
|
+
export { LocalSource } from "./local";
|
|
5
|
+
export { HjdApiSource } from "./hjd-api";
|
|
6
|
+
/**
|
|
7
|
+
* Build the configured content source. THE swap point: the whole
|
|
8
|
+
* difference between "pull from our API" and "ejected, local files"
|
|
9
|
+
* is which branch this returns. Eject flips `source.mode` and dumps
|
|
10
|
+
* the MDX to disk; nothing else in the kit changes.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createSource(cfg: ResolvedConfig): ContentSource;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../package/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAI9C,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,GAAG,aAAa,CAG/D"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { LocalSource } from "./local.js";
|
|
2
|
+
import { HjdApiSource } from "./hjd-api.js";
|
|
3
|
+
export { LocalSource } from "./local.js";
|
|
4
|
+
export { HjdApiSource } from "./hjd-api.js";
|
|
5
|
+
/**
|
|
6
|
+
* Build the configured content source. THE swap point: the whole
|
|
7
|
+
* difference between "pull from our API" and "ejected, local files"
|
|
8
|
+
* is which branch this returns. Eject flips `source.mode` and dumps
|
|
9
|
+
* the MDX to disk; nothing else in the kit changes.
|
|
10
|
+
*/
|
|
11
|
+
export function createSource(cfg) {
|
|
12
|
+
if (cfg.source.mode === "local")
|
|
13
|
+
return new LocalSource(cfg.source);
|
|
14
|
+
return new HjdApiSource(cfg.source);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../package/adapters/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAGzC,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,GAAmB;IAC9C,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpE,OAAO,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LocalSourceConfig } from "../config/types";
|
|
2
|
+
import type { ContentSource, PostMeta, PostWithBody } from "./source";
|
|
3
|
+
export declare class LocalSource implements ContentSource {
|
|
4
|
+
readonly mode: "local";
|
|
5
|
+
private readonly dir;
|
|
6
|
+
constructor(cfg: LocalSourceConfig);
|
|
7
|
+
private slugs;
|
|
8
|
+
private read;
|
|
9
|
+
listSlugs(): Promise<string[]>;
|
|
10
|
+
listPosts(): Promise<PostMeta[]>;
|
|
11
|
+
getPost(slug: string): Promise<PostWithBody | null>;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../../package/adapters/local.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEtE,qBAAa,WAAY,YAAW,aAAa;IAC/C,QAAQ,CAAC,IAAI,EAAG,OAAO,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,EAAE,iBAAiB;IAIlC,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,IAAI;IAoBN,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAI9B,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;IAiBhC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;CAM1D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
LocalSource — reads MDX-with-components from local files.
|
|
3
|
+
Mirrors hjd-website's src/lib/blog.js exactly (the proven
|
|
4
|
+
path), generalised over a configurable posts directory.
|
|
5
|
+
This is also the EJECTED state's source.
|
|
6
|
+
============================================================ */
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import matter from "gray-matter";
|
|
10
|
+
import readingTime from "reading-time";
|
|
11
|
+
export class LocalSource {
|
|
12
|
+
constructor(cfg) {
|
|
13
|
+
this.mode = "local";
|
|
14
|
+
this.dir = path.resolve(process.cwd(), cfg.postsDir ?? "content/posts");
|
|
15
|
+
}
|
|
16
|
+
slugs() {
|
|
17
|
+
if (!fs.existsSync(this.dir))
|
|
18
|
+
return [];
|
|
19
|
+
return fs
|
|
20
|
+
.readdirSync(this.dir)
|
|
21
|
+
.filter((f) => f.endsWith(".mdx"))
|
|
22
|
+
.map((f) => f.replace(/\.mdx$/, ""));
|
|
23
|
+
}
|
|
24
|
+
read(slug) {
|
|
25
|
+
const file = path.resolve(this.dir, `${slug}.mdx`);
|
|
26
|
+
// Containment: a route slug is request-derived, so refuse anything that
|
|
27
|
+
// resolves outside the posts dir (path traversal via `../` / encoded
|
|
28
|
+
// segments). Without this, a crafted slug could read other `*.mdx` files
|
|
29
|
+
// on the host in the ejected/local mode.
|
|
30
|
+
const root = this.dir.endsWith(path.sep) ? this.dir : this.dir + path.sep;
|
|
31
|
+
if (file !== this.dir && !file.startsWith(root))
|
|
32
|
+
return null;
|
|
33
|
+
if (!fs.existsSync(file))
|
|
34
|
+
return null;
|
|
35
|
+
const raw = fs.readFileSync(file, "utf-8");
|
|
36
|
+
const { data, content } = matter(raw);
|
|
37
|
+
const rt = readingTime(content);
|
|
38
|
+
return {
|
|
39
|
+
...data,
|
|
40
|
+
slug,
|
|
41
|
+
readingTime: Math.ceil(rt.minutes),
|
|
42
|
+
body: content,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async listSlugs() {
|
|
46
|
+
return this.slugs();
|
|
47
|
+
}
|
|
48
|
+
async listPosts() {
|
|
49
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
50
|
+
return this.slugs()
|
|
51
|
+
.map((slug) => {
|
|
52
|
+
const post = this.read(slug);
|
|
53
|
+
if (!post)
|
|
54
|
+
return null;
|
|
55
|
+
// strip the heavy body for the listing shape
|
|
56
|
+
const { body: _body, ...meta } = post;
|
|
57
|
+
return meta;
|
|
58
|
+
})
|
|
59
|
+
.filter((p) => !!p)
|
|
60
|
+
.filter((p) => !(isProd && p.draft))
|
|
61
|
+
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
|
|
62
|
+
}
|
|
63
|
+
async getPost(slug) {
|
|
64
|
+
const post = this.read(slug);
|
|
65
|
+
if (!post)
|
|
66
|
+
return null;
|
|
67
|
+
if (process.env.NODE_ENV === "production" && post.draft)
|
|
68
|
+
return null;
|
|
69
|
+
return post;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=local.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.js","sourceRoot":"","sources":["../../../package/adapters/local.ts"],"names":[],"mappings":"AAAA;;;;;kEAKkE;AAClE,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,WAAW,MAAM,cAAc,CAAC;AAIvC,MAAM,OAAO,WAAW;IAItB,YAAY,GAAsB;QAHzB,SAAI,GAAG,OAAgB,CAAC;QAI/B,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,GAAG,CAAC,QAAQ,IAAI,eAAe,CAAC,CAAC;IAC1E,CAAC;IAEO,KAAK;QACX,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,EAAE,CAAC;QACxC,OAAO,EAAE;aACN,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC;aACrB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;IACzC,CAAC;IAEO,IAAI,CAAC,IAAY;QACvB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;QACnD,wEAAwE;QACxE,qEAAqE;QACrE,yEAAyE;QACzE,yCAAyC;QACzC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QAC1E,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7D,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACtC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QACtC,MAAM,EAAE,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAChC,OAAO;YACL,GAAI,IAAiB;YACrB,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;YAClC,IAAI,EAAE,OAAO;SACd,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,SAAS;QACb,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;QACrD,OAAO,IAAI,CAAC,KAAK,EAAE;aAChB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACZ,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC7B,IAAI,CAAC,IAAI;gBAAE,OAAO,IAAI,CAAC;YACvB,6CAA6C;YAC7C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;YACtC,OAAO,IAAgB,CAAC;QAC1B,CAAC,CAAC;aACD,MAAM,CAAC,CAAC,CAAC,EAAiB,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;aACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC;aACnC,IAAI,CACH,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAClE,CAAC;IACN,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** The frontmatter we read off a post (superset across both consumer sites). */
|
|
2
|
+
export interface PostFrontmatter {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
date: string;
|
|
6
|
+
author: string;
|
|
7
|
+
category: string;
|
|
8
|
+
tags?: string[];
|
|
9
|
+
featuredImage?: string;
|
|
10
|
+
featuredImageAlt?: string;
|
|
11
|
+
draft?: boolean;
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
}
|
|
14
|
+
/** Listing-shape metadata (no body) — what the index needs. */
|
|
15
|
+
export interface PostMeta extends PostFrontmatter {
|
|
16
|
+
slug: string;
|
|
17
|
+
readingTime: number;
|
|
18
|
+
}
|
|
19
|
+
/** A full post: metadata + the raw MDX body to render. */
|
|
20
|
+
export interface PostWithBody extends PostMeta {
|
|
21
|
+
/** Raw MDX-with-components source (frontmatter already stripped). */
|
|
22
|
+
body: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The adapter contract. Both modes implement exactly this.
|
|
26
|
+
*
|
|
27
|
+
* All methods are async so the remote source is natural; the local
|
|
28
|
+
* source just resolves immediately. Routes always `await`.
|
|
29
|
+
*/
|
|
30
|
+
export interface ContentSource {
|
|
31
|
+
readonly mode: "local" | "hjd-api";
|
|
32
|
+
/** All published posts as listing metadata, newest-first, drafts excluded in prod. */
|
|
33
|
+
listPosts(): Promise<PostMeta[]>;
|
|
34
|
+
/** One post (meta + body) by slug, or null if absent/unpublished. */
|
|
35
|
+
getPost(slug: string): Promise<PostWithBody | null>;
|
|
36
|
+
/** All slugs (for generateStaticParams). */
|
|
37
|
+
listSlugs(): Promise<string[]>;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../../package/adapters/source.ts"],"names":[],"mappings":"AAkBA,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;IAGhB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,+DAA+D;AAC/D,MAAM,WAAW,QAAS,SAAQ,eAAe;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,0DAA0D;AAC1D,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IACnC,sFAAsF;IACtF,SAAS,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IACjC,qEAAqE;IACrE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACpD,4CAA4C;IAC5C,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CAChC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
@highjumpdigitalsoftware/blog-kit — content source adapter (the swap point)
|
|
3
|
+
------------------------------------------------------------
|
|
4
|
+
A ContentSource yields portable MDX-with-components + rich
|
|
5
|
+
frontmatter. Two implementations ship:
|
|
6
|
+
|
|
7
|
+
- LocalSource reads .mdx from the repo filesystem
|
|
8
|
+
- HjdApiSource fetches MDX from our per-tenant content API
|
|
9
|
+
|
|
10
|
+
They satisfy the SAME interface, so flipping
|
|
11
|
+
`source.mode` between them changes nothing else — that is
|
|
12
|
+
the entire eject story (hjd-api -> local + a content dump).
|
|
13
|
+
|
|
14
|
+
CONTRACT: a source returns raw MDX text + parsed frontmatter.
|
|
15
|
+
It never renders. The routes render. This keeps the source
|
|
16
|
+
testable and the render path identical across modes.
|
|
17
|
+
============================================================ */
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../../package/adapters/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;kEAgBkE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ResolvedConfig } from "./config/types";
|
|
3
|
+
import type { PostWithBody } from "./adapters";
|
|
4
|
+
export interface ArticleProps {
|
|
5
|
+
config: ResolvedConfig;
|
|
6
|
+
slug: string;
|
|
7
|
+
/** Extra/override MDX components merged over the kit map. */
|
|
8
|
+
components?: Record<string, unknown>;
|
|
9
|
+
/** Optional custom hero rendered above the body (per-site frame). */
|
|
10
|
+
hero?: (post: PostWithBody) => ReactNode;
|
|
11
|
+
/** Set false to omit the AuthorCard/Newsletter/Related tail. Default true. */
|
|
12
|
+
showTail?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Load a post for the route. Returns null when absent (route calls notFound()). */
|
|
15
|
+
export declare function getArticle(config: ResolvedConfig, slug: string): Promise<PostWithBody | null>;
|
|
16
|
+
export declare function Article({ config, slug, components, hero, showTail, }: ArticleProps): Promise<import("react").JSX.Element | null>;
|
|
17
|
+
//# sourceMappingURL=article.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../package/article.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAO/C,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,cAAc,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,qEAAqE;IACrE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,CAAC;IACzC,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,oFAAoF;AACpF,wBAAsB,UAAU,CAC9B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAG9B;AAoBD,wBAAsB,OAAO,CAAC,EAC5B,MAAM,EACN,IAAI,EACJ,UAAU,EACV,IAAI,EACJ,QAAe,GAChB,EAAE,YAAY,+CA+Bd"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createSource } from "./adapters/index.js";
|
|
3
|
+
import { RenderArticleBody } from "./mdx/render.js";
|
|
4
|
+
import { BlogBehaviors } from "../react/index.js";
|
|
5
|
+
import { AuthorCard } from "../react/AuthorCard.js";
|
|
6
|
+
import { NewsletterCTA } from "../react/NewsletterCTA.js";
|
|
7
|
+
import { RelatedPosts } from "../react/RelatedPosts.js";
|
|
8
|
+
/** Load a post for the route. Returns null when absent (route calls notFound()). */
|
|
9
|
+
export async function getArticle(config, slug) {
|
|
10
|
+
const source = createSource(config);
|
|
11
|
+
return source.getPost(slug);
|
|
12
|
+
}
|
|
13
|
+
/** Build the related-posts strip (same category, newest 3). */
|
|
14
|
+
async function relatedFor(config, post) {
|
|
15
|
+
const source = createSource(config);
|
|
16
|
+
const all = await source.listPosts();
|
|
17
|
+
return all
|
|
18
|
+
.filter((p) => p.slug !== post.slug && p.category === post.category)
|
|
19
|
+
.slice(0, 3)
|
|
20
|
+
.map((p) => ({
|
|
21
|
+
slug: p.slug,
|
|
22
|
+
title: p.title,
|
|
23
|
+
excerpt: p.description,
|
|
24
|
+
date: p.date,
|
|
25
|
+
category: p.category,
|
|
26
|
+
featuredImage: p.featuredImage,
|
|
27
|
+
featuredImageAlt: p.featuredImageAlt,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
export async function Article({ config, slug, components, hero, showTail = true, }) {
|
|
31
|
+
const post = await getArticle(config, slug);
|
|
32
|
+
if (!post)
|
|
33
|
+
return null;
|
|
34
|
+
const related = showTail ? await relatedFor(config, post) : [];
|
|
35
|
+
return (_jsxs("div", { className: "blog-root", children: [hero ? hero(post) : null, _jsx(BlogBehaviors, {}), _jsx("div", { className: "bk-prose", children: _jsx(RenderArticleBody, { source: post.body, components: components }) }), showTail && (_jsxs("div", { style: { marginTop: 48 }, children: [_jsx(AuthorCard, { name: String(post.author ?? config.brand.name), role: String(post.authorRole ?? config.brand.name), bio: post.authorBio }), _jsx(NewsletterCTA, {}), related.length > 0 && (_jsx(RelatedPosts, { posts: related, hrefBase: `/${config.listing.articleBasePath.replace(/^\/+|\/+$/g, "")}/`.replace("//", "/") }))] }))] }));
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=article.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../package/article.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ResolvedConfig } from "../config/types";
|
|
2
|
+
import type { PostMeta, PostWithBody } from "../adapters";
|
|
3
|
+
import { type CategoryRef } from "../index-core";
|
|
4
|
+
/** All published posts (listing shape), cached per the freshness window. */
|
|
5
|
+
export declare function loadPosts(config: ResolvedConfig): Promise<PostMeta[]>;
|
|
6
|
+
/** Every slug (for getStaticPaths in hybrid mode, or sitemaps). */
|
|
7
|
+
export declare function loadSlugs(config: ResolvedConfig): Promise<string[]>;
|
|
8
|
+
/** One post (meta + raw body), cached by slug. null when absent/unpublished. */
|
|
9
|
+
export declare function loadPost(config: ResolvedConfig, slug: string): Promise<PostWithBody | null>;
|
|
10
|
+
export interface ArticleData {
|
|
11
|
+
post: PostWithBody;
|
|
12
|
+
/** Ready-to-inject HTML for the `.bk-prose` region (set:html). */
|
|
13
|
+
bodyHtml: string;
|
|
14
|
+
/** Same-category, newest 3, excluding self — for the RelatedPosts strip. */
|
|
15
|
+
related: Array<{
|
|
16
|
+
slug: string;
|
|
17
|
+
title: string;
|
|
18
|
+
excerpt?: string;
|
|
19
|
+
date?: string;
|
|
20
|
+
category?: string;
|
|
21
|
+
featuredImage?: string;
|
|
22
|
+
featuredImageAlt?: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Load + render an article for the Astro route. Returns null when the post
|
|
27
|
+
* is absent (the route then renders a 404). The MDX body is compiled to HTML
|
|
28
|
+
* here (runtime), with optional per-site custom components.
|
|
29
|
+
*/
|
|
30
|
+
export declare function loadArticle(config: ResolvedConfig, slug: string, components?: Record<string, unknown>): Promise<ArticleData | null>;
|
|
31
|
+
export interface IndexData {
|
|
32
|
+
featured: PostMeta | null;
|
|
33
|
+
items: PostMeta[];
|
|
34
|
+
categories: CategoryRef[];
|
|
35
|
+
page: number;
|
|
36
|
+
totalPages: number;
|
|
37
|
+
categorySlug?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Compute the listing-page data (featured hero + paged grid + category nav)
|
|
41
|
+
* for the Astro index route. Mirrors the Next <BlogIndex> selection logic
|
|
42
|
+
* exactly, but returns plain data the .astro template iterates.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadIndex(config: ResolvedConfig, page?: number, categorySlug?: string): Promise<IndexData>;
|
|
45
|
+
//# sourceMappingURL=data.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.d.ts","sourceRoot":"","sources":["../../../package/astro/data.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEtD,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG1D,OAAO,EAKL,KAAK,WAAW,EAEjB,MAAM,eAAe,CAAC;AAWvB,4EAA4E;AAC5E,wBAAsB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAK3E;AAED,mEAAmE;AACnE,wBAAsB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAGzE;AAED,gFAAgF;AAChF,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAK9B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,4EAA4E;IAC5E,OAAO,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;KAC3B,CAAC,CAAC;CACJ;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,cAAc,EACtB,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAqB7B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,cAAc,EACtB,IAAI,SAAI,EACR,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,SAAS,CAAC,CAyBpB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createSource } from "../adapters/index.js";
|
|
2
|
+
import { renderMdxToHtml } from "../mdx/render-astro.js";
|
|
3
|
+
import { cached } from "./freshness.js";
|
|
4
|
+
import { getCategories, getFeatured, paginate, postsInCategory, } from "../index-core.js";
|
|
5
|
+
function ttl(config) {
|
|
6
|
+
// hjd-api source carries fetchRevalidate; fall back to freshness.revalidate.
|
|
7
|
+
const src = config.source;
|
|
8
|
+
if (src.mode === "hjd-api" && typeof src.fetchRevalidate === "number") {
|
|
9
|
+
return src.fetchRevalidate;
|
|
10
|
+
}
|
|
11
|
+
return config.freshness.revalidate;
|
|
12
|
+
}
|
|
13
|
+
/** All published posts (listing shape), cached per the freshness window. */
|
|
14
|
+
export async function loadPosts(config) {
|
|
15
|
+
return cached("posts", ttl(config), async () => {
|
|
16
|
+
const source = createSource(config);
|
|
17
|
+
return source.listPosts();
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/** Every slug (for getStaticPaths in hybrid mode, or sitemaps). */
|
|
21
|
+
export async function loadSlugs(config) {
|
|
22
|
+
const posts = await loadPosts(config);
|
|
23
|
+
return posts.map((p) => p.slug);
|
|
24
|
+
}
|
|
25
|
+
/** One post (meta + raw body), cached by slug. null when absent/unpublished. */
|
|
26
|
+
export async function loadPost(config, slug) {
|
|
27
|
+
return cached(`post:${slug}`, ttl(config), async () => {
|
|
28
|
+
const source = createSource(config);
|
|
29
|
+
return source.getPost(slug);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load + render an article for the Astro route. Returns null when the post
|
|
34
|
+
* is absent (the route then renders a 404). The MDX body is compiled to HTML
|
|
35
|
+
* here (runtime), with optional per-site custom components.
|
|
36
|
+
*/
|
|
37
|
+
export async function loadArticle(config, slug, components) {
|
|
38
|
+
const post = await loadPost(config, slug);
|
|
39
|
+
if (!post)
|
|
40
|
+
return null;
|
|
41
|
+
const bodyHtml = await renderMdxToHtml(post.body, { components });
|
|
42
|
+
const all = await loadPosts(config);
|
|
43
|
+
const related = all
|
|
44
|
+
.filter((p) => p.slug !== post.slug && p.category === post.category)
|
|
45
|
+
.slice(0, 3)
|
|
46
|
+
.map((p) => ({
|
|
47
|
+
slug: p.slug,
|
|
48
|
+
title: p.title,
|
|
49
|
+
excerpt: p.description,
|
|
50
|
+
date: p.date,
|
|
51
|
+
category: p.category,
|
|
52
|
+
featuredImage: p.featuredImage,
|
|
53
|
+
featuredImageAlt: p.featuredImageAlt,
|
|
54
|
+
}));
|
|
55
|
+
return { post, bodyHtml, related };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Compute the listing-page data (featured hero + paged grid + category nav)
|
|
59
|
+
* for the Astro index route. Mirrors the Next <BlogIndex> selection logic
|
|
60
|
+
* exactly, but returns plain data the .astro template iterates.
|
|
61
|
+
*/
|
|
62
|
+
export async function loadIndex(config, page = 1, categorySlug) {
|
|
63
|
+
const { listing } = config;
|
|
64
|
+
const all = await loadPosts(config);
|
|
65
|
+
const scoped = categorySlug ? postsInCategory(all, categorySlug) : all;
|
|
66
|
+
const featured = !categorySlug && listing.showFeaturedHero && page <= 1
|
|
67
|
+
? getFeatured(scoped, listing.pinnedSlug)
|
|
68
|
+
: null;
|
|
69
|
+
const pool = featured ? scoped.filter((p) => p.slug !== featured.slug) : scoped;
|
|
70
|
+
const paged = paginate(pool, page, listing.pageSize);
|
|
71
|
+
const categories = getCategories(all, listing.categoryLabels, listing.categoryOrder);
|
|
72
|
+
return {
|
|
73
|
+
featured,
|
|
74
|
+
items: paged.items,
|
|
75
|
+
categories,
|
|
76
|
+
page: paged.page,
|
|
77
|
+
totalPages: paged.totalPages,
|
|
78
|
+
categorySlug,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=data.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data.js","sourceRoot":"","sources":["../../../package/astro/data.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Drop every cached read. Called by /api/blogkit/revalidate on publish. */
|
|
2
|
+
export declare function clearBlogkitCache(): void;
|
|
3
|
+
/** Drop a single keyed read (e.g. one slug). */
|
|
4
|
+
export declare function clearBlogkitCacheKey(key: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* Memoise an async read for `ttlSeconds`. `ttlSeconds <= 0` disables
|
|
7
|
+
* caching (pure SSR, always fresh). A rejected promise is never cached,
|
|
8
|
+
* so a transient API error doesn't pin an empty result.
|
|
9
|
+
*/
|
|
10
|
+
export declare function cached<T>(key: string, ttlSeconds: number, loader: () => Promise<T>): Promise<T>;
|
|
11
|
+
//# sourceMappingURL=freshness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.d.ts","sourceRoot":"","sources":["../../../package/astro/freshness.ts"],"names":[],"mappings":"AA+BA,4EAA4E;AAC5E,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,gDAAgD;AAChD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEtD;AAED;;;;GAIG;AACH,wBAAsB,MAAM,CAAC,CAAC,EAC5B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAQZ"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Astro freshness — the ISR-equivalent for Astro.
|
|
3
|
+
------------------------------------------------------------
|
|
4
|
+
Astro has NO ISR. The managed promise ("new posts appear with
|
|
5
|
+
no redeploy") is met on Astro by running the site in SSR mode
|
|
6
|
+
(`output: 'server'`/hybrid): every request can re-fetch from our
|
|
7
|
+
content API, so the blog is ALWAYS fresh with zero host
|
|
8
|
+
cooperation — works on Replit out of the box.
|
|
9
|
+
|
|
10
|
+
Hitting our API on literally every request is wasteful, so this
|
|
11
|
+
module adds a tiny in-process TTL cache around the source reads.
|
|
12
|
+
That is the Astro analogue of Next's `revalidate = N`: serve
|
|
13
|
+
cached for N seconds, refresh in the background of the next
|
|
14
|
+
request after the window. Plus a `clearBlogkitCache()` the
|
|
15
|
+
on-demand revalidate endpoint calls so a publish is reflected
|
|
16
|
+
INSTANTLY (the analogue of Next's revalidatePath).
|
|
17
|
+
|
|
18
|
+
The cache is per server process and best-effort: a cold/scaled
|
|
19
|
+
instance simply fetches fresh. Never a correctness risk — at
|
|
20
|
+
worst a post appears up to `ttlSeconds` later than publish on a
|
|
21
|
+
warm instance that hasn't been revalidate-pinged.
|
|
22
|
+
============================================================ */
|
|
23
|
+
const store = new Map();
|
|
24
|
+
/** Drop every cached read. Called by /api/blogkit/revalidate on publish. */
|
|
25
|
+
export function clearBlogkitCache() {
|
|
26
|
+
store.clear();
|
|
27
|
+
}
|
|
28
|
+
/** Drop a single keyed read (e.g. one slug). */
|
|
29
|
+
export function clearBlogkitCacheKey(key) {
|
|
30
|
+
store.delete(key);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Memoise an async read for `ttlSeconds`. `ttlSeconds <= 0` disables
|
|
34
|
+
* caching (pure SSR, always fresh). A rejected promise is never cached,
|
|
35
|
+
* so a transient API error doesn't pin an empty result.
|
|
36
|
+
*/
|
|
37
|
+
export async function cached(key, ttlSeconds, loader) {
|
|
38
|
+
if (ttlSeconds <= 0)
|
|
39
|
+
return loader();
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const hit = store.get(key);
|
|
42
|
+
if (hit && hit.expires > now)
|
|
43
|
+
return hit.value;
|
|
44
|
+
const value = await loader();
|
|
45
|
+
store.set(key, { value, expires: now + ttlSeconds * 1000 });
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=freshness.js.map
|