@dsaplatform/content-sdk 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Full article response from Content Engine
3
+ */
4
+ interface Article {
5
+ id: string;
6
+ title: string;
7
+ slug: string;
8
+ excerpt: string | null;
9
+ content_html: string;
10
+ content_json: any | null;
11
+ h1: string | null;
12
+ meta_title: string | null;
13
+ meta_description: string | null;
14
+ canonical_url?: string | null;
15
+ target_keyword: string | null;
16
+ secondary_keywords: string[];
17
+ headings: ArticleHeading[];
18
+ faq: FaqItem[];
19
+ internal_links: InternalLink[];
20
+ schema_json: any | null;
21
+ featured_image_url: string | null;
22
+ featured_image_alt: string | null;
23
+ publish_status: string;
24
+ published_at: string | null;
25
+ updated_at?: string | null;
26
+ word_count: number | null;
27
+ reading_time_minutes: number | null;
28
+ seo_score: number | null;
29
+ pillar_name?: string;
30
+ cluster_name?: string;
31
+ content_type?: string;
32
+ }
33
+ /**
34
+ * Article heading (from H1-H6 tags)
35
+ */
36
+ interface ArticleHeading {
37
+ level: number;
38
+ text: string;
39
+ id: string;
40
+ }
41
+ /**
42
+ * FAQ item
43
+ */
44
+ interface FaqItem {
45
+ question: string;
46
+ answer: string;
47
+ }
48
+ /**
49
+ * Internal link reference
50
+ */
51
+ interface InternalLink {
52
+ slug: string;
53
+ anchor_text: string;
54
+ }
55
+
56
+ /**
57
+ * Headless utilities — data transformers with zero UI / zero inline styles.
58
+ * Use these to build your own components with shadcn/Tailwind/custom CSS.
59
+ *
60
+ * ```ts
61
+ * import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';
62
+ * ```
63
+ */
64
+
65
+ /** TOC entry with indentation level for rendering */
66
+ interface TocEntry {
67
+ id: string;
68
+ text: string;
69
+ level: number;
70
+ /** Depth relative to minimum heading level (0-based) */
71
+ depth: number;
72
+ }
73
+ /**
74
+ * Build TOC data from article headings.
75
+ * Normalizes depth so the shallowest heading is depth=0.
76
+ */
77
+ declare function buildTocData(headings: ArticleHeading[]): TocEntry[];
78
+ /**
79
+ * Build Schema.org FAQPage JSON-LD object from FAQ items.
80
+ * Returns the object (not stringified) — you inject it into <script type="application/ld+json">.
81
+ */
82
+ declare function buildFaqSchema(items: FaqItem[]): Record<string, any> | null;
83
+ /**
84
+ * Build Schema.org Article JSON-LD object.
85
+ */
86
+ declare function buildArticleSchema(article: Article, siteUrl?: string): Record<string, any>;
87
+ /**
88
+ * Normalize an article response — ensures all array fields are arrays, never null.
89
+ */
90
+ declare function normalizeArticle(article: Article): Article;
91
+ /**
92
+ * Extract plain text from content_html (strip tags).
93
+ * Useful for excerpts, search indexing, reading time calculation.
94
+ */
95
+ declare function htmlToPlainText(html: string): string;
96
+ /**
97
+ * Calculate reading time from HTML content.
98
+ */
99
+ declare function calculateReadingTime(html: string, wordsPerMinute?: number): number;
100
+
101
+ export { type Article, type ArticleHeading, type FaqItem, type TocEntry, buildArticleSchema, buildFaqSchema, buildTocData, calculateReadingTime, htmlToPlainText, normalizeArticle };
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Full article response from Content Engine
3
+ */
4
+ interface Article {
5
+ id: string;
6
+ title: string;
7
+ slug: string;
8
+ excerpt: string | null;
9
+ content_html: string;
10
+ content_json: any | null;
11
+ h1: string | null;
12
+ meta_title: string | null;
13
+ meta_description: string | null;
14
+ canonical_url?: string | null;
15
+ target_keyword: string | null;
16
+ secondary_keywords: string[];
17
+ headings: ArticleHeading[];
18
+ faq: FaqItem[];
19
+ internal_links: InternalLink[];
20
+ schema_json: any | null;
21
+ featured_image_url: string | null;
22
+ featured_image_alt: string | null;
23
+ publish_status: string;
24
+ published_at: string | null;
25
+ updated_at?: string | null;
26
+ word_count: number | null;
27
+ reading_time_minutes: number | null;
28
+ seo_score: number | null;
29
+ pillar_name?: string;
30
+ cluster_name?: string;
31
+ content_type?: string;
32
+ }
33
+ /**
34
+ * Article heading (from H1-H6 tags)
35
+ */
36
+ interface ArticleHeading {
37
+ level: number;
38
+ text: string;
39
+ id: string;
40
+ }
41
+ /**
42
+ * FAQ item
43
+ */
44
+ interface FaqItem {
45
+ question: string;
46
+ answer: string;
47
+ }
48
+ /**
49
+ * Internal link reference
50
+ */
51
+ interface InternalLink {
52
+ slug: string;
53
+ anchor_text: string;
54
+ }
55
+
56
+ /**
57
+ * Headless utilities — data transformers with zero UI / zero inline styles.
58
+ * Use these to build your own components with shadcn/Tailwind/custom CSS.
59
+ *
60
+ * ```ts
61
+ * import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';
62
+ * ```
63
+ */
64
+
65
+ /** TOC entry with indentation level for rendering */
66
+ interface TocEntry {
67
+ id: string;
68
+ text: string;
69
+ level: number;
70
+ /** Depth relative to minimum heading level (0-based) */
71
+ depth: number;
72
+ }
73
+ /**
74
+ * Build TOC data from article headings.
75
+ * Normalizes depth so the shallowest heading is depth=0.
76
+ */
77
+ declare function buildTocData(headings: ArticleHeading[]): TocEntry[];
78
+ /**
79
+ * Build Schema.org FAQPage JSON-LD object from FAQ items.
80
+ * Returns the object (not stringified) — you inject it into <script type="application/ld+json">.
81
+ */
82
+ declare function buildFaqSchema(items: FaqItem[]): Record<string, any> | null;
83
+ /**
84
+ * Build Schema.org Article JSON-LD object.
85
+ */
86
+ declare function buildArticleSchema(article: Article, siteUrl?: string): Record<string, any>;
87
+ /**
88
+ * Normalize an article response — ensures all array fields are arrays, never null.
89
+ */
90
+ declare function normalizeArticle(article: Article): Article;
91
+ /**
92
+ * Extract plain text from content_html (strip tags).
93
+ * Useful for excerpts, search indexing, reading time calculation.
94
+ */
95
+ declare function htmlToPlainText(html: string): string;
96
+ /**
97
+ * Calculate reading time from HTML content.
98
+ */
99
+ declare function calculateReadingTime(html: string, wordsPerMinute?: number): number;
100
+
101
+ export { type Article, type ArticleHeading, type FaqItem, type TocEntry, buildArticleSchema, buildFaqSchema, buildTocData, calculateReadingTime, htmlToPlainText, normalizeArticle };
@@ -0,0 +1,2 @@
1
+ "use strict";var i=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var u=(e,t)=>{for(var n in t)i(e,n,{get:t[n],enumerable:!0})},c=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of a(t))!l.call(e,r)&&r!==n&&i(e,r,{get:()=>t[r],enumerable:!(o=d(t,r))||o.enumerable});return e};var p=e=>c(i({},"__esModule",{value:!0}),e);var f={};u(f,{buildArticleSchema:()=>_,buildFaqSchema:()=>g,buildTocData:()=>m,calculateReadingTime:()=>h,htmlToPlainText:()=>s,normalizeArticle:()=>y});module.exports=p(f);function m(e){if(!e||e.length===0)return[];let t=Math.min(...e.map(n=>n.level));return e.map(n=>({id:n.id,text:n.text,level:n.level,depth:n.level-t}))}function g(e){return!e||e.length===0?null:{"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(t=>({"@type":"Question",name:t.question,acceptedAnswer:{"@type":"Answer",text:t.answer}}))}}function _(e,t){if(e.schema_json&&Object.keys(e.schema_json).length>0)return e.schema_json;let n=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...n?{url:n}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}}}function y(e){return{...e,headings:e.headings??[],faq:e.faq??[],internal_links:e.internal_links??[],secondary_keywords:e.secondary_keywords??[],schema_json:e.schema_json??null,content_json:e.content_json??null}}function s(e){return e.replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim()}function h(e,t=200){let o=s(e).split(/\s+/).filter(Boolean).length;return Math.max(1,Math.ceil(o/t))}0&&(module.exports={buildArticleSchema,buildFaqSchema,buildTocData,calculateReadingTime,htmlToPlainText,normalizeArticle});
2
+ //# sourceMappingURL=headless.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/headless.ts"],"sourcesContent":["/**\n * Headless utilities — data transformers with zero UI / zero inline styles.\n * Use these to build your own components with shadcn/Tailwind/custom CSS.\n *\n * ```ts\n * import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';\n * ```\n */\nimport type { Article, ArticleHeading, FaqItem } from './types';\n\nexport type { Article, ArticleHeading, FaqItem };\n\n/** TOC entry with indentation level for rendering */\nexport interface TocEntry {\n id: string;\n text: string;\n level: number;\n /** Depth relative to minimum heading level (0-based) */\n depth: number;\n}\n\n/**\n * Build TOC data from article headings.\n * Normalizes depth so the shallowest heading is depth=0.\n */\nexport function buildTocData(headings: ArticleHeading[]): TocEntry[] {\n if (!headings || headings.length === 0) return [];\n const minLevel = Math.min(...headings.map((h) => h.level));\n return headings.map((h) => ({\n id: h.id,\n text: h.text,\n level: h.level,\n depth: h.level - minLevel,\n }));\n}\n\n/**\n * Build Schema.org FAQPage JSON-LD object from FAQ items.\n * Returns the object (not stringified) — you inject it into <script type=\"application/ld+json\">.\n */\nexport function buildFaqSchema(items: FaqItem[]): Record<string, any> | null {\n if (!items || items.length === 0) return null;\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n}\n\n/**\n * Build Schema.org Article JSON-LD object.\n */\nexport function buildArticleSchema(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n if (article.schema_json && Object.keys(article.schema_json).length > 0) {\n return article.schema_json;\n }\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(url ? { url } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n}\n\n/**\n * Normalize an article response — ensures all array fields are arrays, never null.\n */\nexport function normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n}\n\n/**\n * Extract plain text from content_html (strip tags).\n * Useful for excerpts, search indexing, reading time calculation.\n */\nexport function htmlToPlainText(html: string): string {\n return html.replace(/<[^>]+>/g, '').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Calculate reading time from HTML content.\n */\nexport function calculateReadingTime(html: string, wordsPerMinute = 200): number {\n const text = htmlToPlainText(html);\n const words = text.split(/\\s+/).filter(Boolean).length;\n return Math.max(1, Math.ceil(words / wordsPerMinute));\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,EAAA,mBAAAC,EAAA,iBAAAC,EAAA,yBAAAC,EAAA,oBAAAC,EAAA,qBAAAC,IAAA,eAAAC,EAAAR,GAyBO,SAASI,EAAaK,EAAwC,CACnE,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,CAAC,EAChD,IAAMC,EAAW,KAAK,IAAI,GAAGD,EAAS,IAAKE,GAAMA,EAAE,KAAK,CAAC,EACzD,OAAOF,EAAS,IAAKE,IAAO,CAC1B,GAAIA,EAAE,GACN,KAAMA,EAAE,KACR,MAAOA,EAAE,MACT,MAAOA,EAAE,MAAQD,CACnB,EAAE,CACJ,CAMO,SAASP,EAAeS,EAA8C,CAC3E,MAAI,CAACA,GAASA,EAAM,SAAW,EAAU,KAClC,CACL,WAAY,qBACZ,QAAS,UACT,WAAYA,EAAM,IAAKC,IAAU,CAC/B,QAAS,WACT,KAAMA,EAAK,SACX,eAAgB,CAAE,QAAS,SAAU,KAAMA,EAAK,MAAO,CACzD,EAAE,CACJ,CACF,CAKO,SAASX,EACdY,EACAC,EACqB,CACrB,GAAID,EAAQ,aAAe,OAAO,KAAKA,EAAQ,WAAW,EAAE,OAAS,EACnE,OAAOA,EAAQ,YAEjB,IAAME,EAAMD,EACR,GAAGA,EAAQ,QAAQ,OAAQ,EAAE,CAAC,SAASD,EAAQ,IAAI,GACnD,OACJ,MAAO,CACL,WAAY,qBACZ,QAAS,UACT,SAAUA,EAAQ,YAAcA,EAAQ,MACxC,YAAaA,EAAQ,kBAAoBA,EAAQ,SAAW,GAC5D,cAAeA,EAAQ,cAAgB,OACvC,aAAcA,EAAQ,YAAcA,EAAQ,cAAgB,OAC5D,GAAIA,EAAQ,mBAAqB,CAAE,MAAOA,EAAQ,kBAAmB,EAAI,CAAC,EAC1E,GAAIE,EAAM,CAAE,IAAAA,CAAI,EAAI,CAAC,EACrB,GAAIF,EAAQ,eACR,CAAE,SAAU,CAACA,EAAQ,eAAgB,GAAIA,EAAQ,oBAAsB,CAAC,CAAE,EAAE,KAAK,IAAI,CAAE,EACvF,CAAC,CACP,CACF,CAKO,SAASP,EAAiBO,EAA2B,CAC1D,MAAO,CACL,GAAGA,EACH,SAAUA,EAAQ,UAAY,CAAC,EAC/B,IAAKA,EAAQ,KAAO,CAAC,EACrB,eAAgBA,EAAQ,gBAAkB,CAAC,EAC3C,mBAAoBA,EAAQ,oBAAsB,CAAC,EACnD,YAAaA,EAAQ,aAAe,KACpC,aAAcA,EAAQ,cAAgB,IACxC,CACF,CAMO,SAASR,EAAgBW,EAAsB,CACpD,OAAOA,EAAK,QAAQ,WAAY,EAAE,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,CAChE,CAKO,SAASZ,EAAqBY,EAAcC,EAAiB,IAAa,CAE/E,IAAMC,EADOb,EAAgBW,CAAI,EACd,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,OAChD,OAAO,KAAK,IAAI,EAAG,KAAK,KAAKE,EAAQD,CAAc,CAAC,CACtD","names":["headless_exports","__export","buildArticleSchema","buildFaqSchema","buildTocData","calculateReadingTime","htmlToPlainText","normalizeArticle","__toCommonJS","headings","minLevel","h","items","item","article","siteUrl","url","html","wordsPerMinute","words"]}
@@ -0,0 +1,2 @@
1
+ function i(e){if(!e||e.length===0)return[];let n=Math.min(...e.map(t=>t.level));return e.map(t=>({id:t.id,text:t.text,level:t.level,depth:t.level-n}))}function s(e){return!e||e.length===0?null:{"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(n=>({"@type":"Question",name:n.question,acceptedAnswer:{"@type":"Answer",text:n.answer}}))}}function d(e,n){if(e.schema_json&&Object.keys(e.schema_json).length>0)return e.schema_json;let t=n?`${n.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:t}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}}}function a(e){return{...e,headings:e.headings??[],faq:e.faq??[],internal_links:e.internal_links??[],secondary_keywords:e.secondary_keywords??[],schema_json:e.schema_json??null,content_json:e.content_json??null}}function o(e){return e.replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim()}function l(e,n=200){let r=o(e).split(/\s+/).filter(Boolean).length;return Math.max(1,Math.ceil(r/n))}export{d as buildArticleSchema,s as buildFaqSchema,i as buildTocData,l as calculateReadingTime,o as htmlToPlainText,a as normalizeArticle};
2
+ //# sourceMappingURL=headless.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/headless.ts"],"sourcesContent":["/**\n * Headless utilities — data transformers with zero UI / zero inline styles.\n * Use these to build your own components with shadcn/Tailwind/custom CSS.\n *\n * ```ts\n * import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';\n * ```\n */\nimport type { Article, ArticleHeading, FaqItem } from './types';\n\nexport type { Article, ArticleHeading, FaqItem };\n\n/** TOC entry with indentation level for rendering */\nexport interface TocEntry {\n id: string;\n text: string;\n level: number;\n /** Depth relative to minimum heading level (0-based) */\n depth: number;\n}\n\n/**\n * Build TOC data from article headings.\n * Normalizes depth so the shallowest heading is depth=0.\n */\nexport function buildTocData(headings: ArticleHeading[]): TocEntry[] {\n if (!headings || headings.length === 0) return [];\n const minLevel = Math.min(...headings.map((h) => h.level));\n return headings.map((h) => ({\n id: h.id,\n text: h.text,\n level: h.level,\n depth: h.level - minLevel,\n }));\n}\n\n/**\n * Build Schema.org FAQPage JSON-LD object from FAQ items.\n * Returns the object (not stringified) — you inject it into <script type=\"application/ld+json\">.\n */\nexport function buildFaqSchema(items: FaqItem[]): Record<string, any> | null {\n if (!items || items.length === 0) return null;\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n}\n\n/**\n * Build Schema.org Article JSON-LD object.\n */\nexport function buildArticleSchema(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n if (article.schema_json && Object.keys(article.schema_json).length > 0) {\n return article.schema_json;\n }\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(url ? { url } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n}\n\n/**\n * Normalize an article response — ensures all array fields are arrays, never null.\n */\nexport function normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n}\n\n/**\n * Extract plain text from content_html (strip tags).\n * Useful for excerpts, search indexing, reading time calculation.\n */\nexport function htmlToPlainText(html: string): string {\n return html.replace(/<[^>]+>/g, '').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Calculate reading time from HTML content.\n */\nexport function calculateReadingTime(html: string, wordsPerMinute = 200): number {\n const text = htmlToPlainText(html);\n const words = text.split(/\\s+/).filter(Boolean).length;\n return Math.max(1, Math.ceil(words / wordsPerMinute));\n}\n"],"mappings":"AAyBO,SAASA,EAAaC,EAAwC,CACnE,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,CAAC,EAChD,IAAMC,EAAW,KAAK,IAAI,GAAGD,EAAS,IAAKE,GAAMA,EAAE,KAAK,CAAC,EACzD,OAAOF,EAAS,IAAKE,IAAO,CAC1B,GAAIA,EAAE,GACN,KAAMA,EAAE,KACR,MAAOA,EAAE,MACT,MAAOA,EAAE,MAAQD,CACnB,EAAE,CACJ,CAMO,SAASE,EAAeC,EAA8C,CAC3E,MAAI,CAACA,GAASA,EAAM,SAAW,EAAU,KAClC,CACL,WAAY,qBACZ,QAAS,UACT,WAAYA,EAAM,IAAKC,IAAU,CAC/B,QAAS,WACT,KAAMA,EAAK,SACX,eAAgB,CAAE,QAAS,SAAU,KAAMA,EAAK,MAAO,CACzD,EAAE,CACJ,CACF,CAKO,SAASC,EACdC,EACAC,EACqB,CACrB,GAAID,EAAQ,aAAe,OAAO,KAAKA,EAAQ,WAAW,EAAE,OAAS,EACnE,OAAOA,EAAQ,YAEjB,IAAME,EAAMD,EACR,GAAGA,EAAQ,QAAQ,OAAQ,EAAE,CAAC,SAASD,EAAQ,IAAI,GACnD,OACJ,MAAO,CACL,WAAY,qBACZ,QAAS,UACT,SAAUA,EAAQ,YAAcA,EAAQ,MACxC,YAAaA,EAAQ,kBAAoBA,EAAQ,SAAW,GAC5D,cAAeA,EAAQ,cAAgB,OACvC,aAAcA,EAAQ,YAAcA,EAAQ,cAAgB,OAC5D,GAAIA,EAAQ,mBAAqB,CAAE,MAAOA,EAAQ,kBAAmB,EAAI,CAAC,EAC1E,GAAIE,EAAM,CAAE,IAAAA,CAAI,EAAI,CAAC,EACrB,GAAIF,EAAQ,eACR,CAAE,SAAU,CAACA,EAAQ,eAAgB,GAAIA,EAAQ,oBAAsB,CAAC,CAAE,EAAE,KAAK,IAAI,CAAE,EACvF,CAAC,CACP,CACF,CAKO,SAASG,EAAiBH,EAA2B,CAC1D,MAAO,CACL,GAAGA,EACH,SAAUA,EAAQ,UAAY,CAAC,EAC/B,IAAKA,EAAQ,KAAO,CAAC,EACrB,eAAgBA,EAAQ,gBAAkB,CAAC,EAC3C,mBAAoBA,EAAQ,oBAAsB,CAAC,EACnD,YAAaA,EAAQ,aAAe,KACpC,aAAcA,EAAQ,cAAgB,IACxC,CACF,CAMO,SAASI,EAAgBC,EAAsB,CACpD,OAAOA,EAAK,QAAQ,WAAY,EAAE,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,CAChE,CAKO,SAASC,EAAqBD,EAAcE,EAAiB,IAAa,CAE/E,IAAMC,EADOJ,EAAgBC,CAAI,EACd,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,OAChD,OAAO,KAAK,IAAI,EAAG,KAAK,KAAKG,EAAQD,CAAc,CAAC,CACtD","names":["buildTocData","headings","minLevel","h","buildFaqSchema","items","item","buildArticleSchema","article","siteUrl","url","normalizeArticle","htmlToPlainText","html","calculateReadingTime","wordsPerMinute","words"]}
package/dist/index.d.mts CHANGED
@@ -272,16 +272,20 @@ interface ArticleFeedProps {
272
272
  showMeta?: boolean;
273
273
  onArticleClick?: (slug: string) => void;
274
274
  className?: string;
275
+ /** "light" | "dark" | "inherit" — sets CSS variable defaults. Use "inherit" to control via your own CSS vars. */
276
+ theme?: 'light' | 'dark' | 'inherit';
275
277
  renderArticle?: (article: ArticleListItem) => React.ReactNode;
276
278
  }
277
279
  /**
278
280
  * Renders a grid or list of article cards.
281
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
279
282
  *
280
- * ```tsx
281
- * <ArticleFeed articles={articles} layout="grid" columns={3} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />
282
- * ```
283
+ * CSS variables (override in your own CSS for full control):
284
+ * --dsa-text, --dsa-text-muted, --dsa-text-faint,
285
+ * --dsa-card-bg, --dsa-card-border,
286
+ * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow
283
287
  */
284
- declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
288
+ declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, theme, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
285
289
 
286
290
  interface ArticlePageProps {
287
291
  article: Article;
@@ -292,6 +296,8 @@ interface ArticlePageProps {
292
296
  relatedArticles?: ArticleListItem[];
293
297
  onRelatedClick?: (slug: string) => void;
294
298
  className?: string;
299
+ /** "light" | "dark" | "inherit" — sets CSS variable defaults */
300
+ theme?: 'light' | 'dark' | 'inherit';
295
301
  components?: {
296
302
  H1?: React.ComponentType<{
297
303
  children: React.ReactNode;
@@ -305,13 +311,13 @@ interface ArticlePageProps {
305
311
  };
306
312
  }
307
313
  /**
308
- * Renders a full article page with optional TOC, FAQ, and related articles.
314
+ * Full article page with optional TOC, FAQ, related articles, and JSON-LD.
315
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
309
316
  *
310
- * ```tsx
311
- * <ArticlePage article={article} showTableOfContents showFaq />
312
- * ```
317
+ * CSS variables: --dsa-text, --dsa-text-muted, --dsa-toc-bg, --dsa-card-border,
318
+ * --dsa-badge-bg, --dsa-badge-text, --dsa-content-text, --dsa-divider
313
319
  */
314
- declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
320
+ declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, theme, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
315
321
 
316
322
  interface FaqBlockProps {
317
323
  items: FaqItem[];
@@ -319,15 +325,14 @@ interface FaqBlockProps {
319
325
  defaultOpen?: boolean;
320
326
  className?: string;
321
327
  title?: string;
328
+ /** "light" | "dark" | "inherit" */
329
+ theme?: 'light' | 'dark' | 'inherit';
322
330
  }
323
331
  /**
324
- * FAQ block with optional collapse behavior and Schema.org FAQPage markup.
325
- *
326
- * ```tsx
327
- * <FaqBlock items={article.faq} collapsible />
328
- * ```
332
+ * FAQ block with Schema.org FAQPage markup.
333
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
329
334
  */
330
- declare function FaqBlock({ items, collapsible, defaultOpen, className, title, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
335
+ declare function FaqBlock({ items, collapsible, defaultOpen, className, title, theme, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
331
336
 
332
337
  interface RelatedArticlesProps {
333
338
  articles: ArticleListItem[];
@@ -335,15 +340,14 @@ interface RelatedArticlesProps {
335
340
  limit?: number;
336
341
  onArticleClick?: (slug: string) => void;
337
342
  className?: string;
343
+ /** "light" | "dark" | "inherit" */
344
+ theme?: 'light' | 'dark' | 'inherit';
338
345
  }
339
346
  /**
340
347
  * Related articles widget.
341
- *
342
- * ```tsx
343
- * <RelatedArticles articles={related} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />
344
- * ```
348
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
345
349
  */
346
- declare function RelatedArticles({ articles, title, limit, onArticleClick, className, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
350
+ declare function RelatedArticles({ articles, title, limit, onArticleClick, className, theme, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
347
351
 
348
352
  /**
349
353
  * Generate Next.js App Router Metadata object from an Article.
package/dist/index.d.ts CHANGED
@@ -272,16 +272,20 @@ interface ArticleFeedProps {
272
272
  showMeta?: boolean;
273
273
  onArticleClick?: (slug: string) => void;
274
274
  className?: string;
275
+ /** "light" | "dark" | "inherit" — sets CSS variable defaults. Use "inherit" to control via your own CSS vars. */
276
+ theme?: 'light' | 'dark' | 'inherit';
275
277
  renderArticle?: (article: ArticleListItem) => React.ReactNode;
276
278
  }
277
279
  /**
278
280
  * Renders a grid or list of article cards.
281
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
279
282
  *
280
- * ```tsx
281
- * <ArticleFeed articles={articles} layout="grid" columns={3} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />
282
- * ```
283
+ * CSS variables (override in your own CSS for full control):
284
+ * --dsa-text, --dsa-text-muted, --dsa-text-faint,
285
+ * --dsa-card-bg, --dsa-card-border,
286
+ * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow
283
287
  */
284
- declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
288
+ declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, theme, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
285
289
 
286
290
  interface ArticlePageProps {
287
291
  article: Article;
@@ -292,6 +296,8 @@ interface ArticlePageProps {
292
296
  relatedArticles?: ArticleListItem[];
293
297
  onRelatedClick?: (slug: string) => void;
294
298
  className?: string;
299
+ /** "light" | "dark" | "inherit" — sets CSS variable defaults */
300
+ theme?: 'light' | 'dark' | 'inherit';
295
301
  components?: {
296
302
  H1?: React.ComponentType<{
297
303
  children: React.ReactNode;
@@ -305,13 +311,13 @@ interface ArticlePageProps {
305
311
  };
306
312
  }
307
313
  /**
308
- * Renders a full article page with optional TOC, FAQ, and related articles.
314
+ * Full article page with optional TOC, FAQ, related articles, and JSON-LD.
315
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
309
316
  *
310
- * ```tsx
311
- * <ArticlePage article={article} showTableOfContents showFaq />
312
- * ```
317
+ * CSS variables: --dsa-text, --dsa-text-muted, --dsa-toc-bg, --dsa-card-border,
318
+ * --dsa-badge-bg, --dsa-badge-text, --dsa-content-text, --dsa-divider
313
319
  */
314
- declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
320
+ declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, theme, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
315
321
 
316
322
  interface FaqBlockProps {
317
323
  items: FaqItem[];
@@ -319,15 +325,14 @@ interface FaqBlockProps {
319
325
  defaultOpen?: boolean;
320
326
  className?: string;
321
327
  title?: string;
328
+ /** "light" | "dark" | "inherit" */
329
+ theme?: 'light' | 'dark' | 'inherit';
322
330
  }
323
331
  /**
324
- * FAQ block with optional collapse behavior and Schema.org FAQPage markup.
325
- *
326
- * ```tsx
327
- * <FaqBlock items={article.faq} collapsible />
328
- * ```
332
+ * FAQ block with Schema.org FAQPage markup.
333
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
329
334
  */
330
- declare function FaqBlock({ items, collapsible, defaultOpen, className, title, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
335
+ declare function FaqBlock({ items, collapsible, defaultOpen, className, title, theme, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
331
336
 
332
337
  interface RelatedArticlesProps {
333
338
  articles: ArticleListItem[];
@@ -335,15 +340,14 @@ interface RelatedArticlesProps {
335
340
  limit?: number;
336
341
  onArticleClick?: (slug: string) => void;
337
342
  className?: string;
343
+ /** "light" | "dark" | "inherit" */
344
+ theme?: 'light' | 'dark' | 'inherit';
338
345
  }
339
346
  /**
340
347
  * Related articles widget.
341
- *
342
- * ```tsx
343
- * <RelatedArticles articles={related} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />
344
- * ```
348
+ * Supports theme="light" | "dark" | "inherit" via CSS variables.
345
349
  */
346
- declare function RelatedArticles({ articles, title, limit, onArticleClick, className, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
350
+ declare function RelatedArticles({ articles, title, limit, onArticleClick, className, theme, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
347
351
 
348
352
  /**
349
353
  * Generate Next.js App Router Metadata object from an Article.
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- "use strict";var j=Object.create;var P=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var O=Object.getOwnPropertyNames;var K=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var J=(e,t)=>{for(var r in t)P(e,r,{get:t[r],enumerable:!0})},I=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of O(t))!G.call(e,i)&&i!==r&&P(e,i,{get:()=>t[i],enumerable:!(o=W(t,i))||o.enumerable});return e};var Q=(e,t,r)=>(r=e!=null?j(K(e)):{},I(t||!e||!e.__esModule?P(r,"default",{value:e,enumerable:!0}):r,e)),X=e=>I(P({},"__esModule",{value:!0}),e);var ee={};J(ee,{ArticleFeed:()=>v,ArticlePage:()=>k,ContentClient:()=>b,DsaContentProvider:()=>L,FaqBlock:()=>x,RelatedArticles:()=>R,SeoMetaBridge:()=>q,generateArticleMetadata:()=>F,useArticle:()=>U,useArticles:()=>E,useCategories:()=>B,useDsaContent:()=>_,useRelatedArticles:()=>z});module.exports=X(ee);var b=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,r){let o=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([n,p])=>{p!=null&&p!==""&&o.searchParams.set(n,String(p))}),o.searchParams.set("site_key",this.apiKey);let i={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(i.next={revalidate:this.revalidateSeconds});let a=await fetch(o.toString(),i);if(!a.ok){let n=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${n||a.statusText}`)}return a.json()}normalizeArticle(t){return{...t,headings:t.headings??[],faq:t.faq??[],internal_links:t.internal_links??[],secondary_keywords:t.secondary_keywords??[],schema_json:t.schema_json??null,content_json:t.content_json??null}}async getArticles(t){let r=await this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search});return{items:r.items??r.data??[],total:r.total??0,page:r.page??1,per_page:r.per_page??20,total_pages:r.total_pages??r.pages??1}}async getArticleBySlug(t){let r=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(r)}async getRelatedArticles(t,r=3){let o=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r});return o.items??(Array.isArray(o)?o:[])}async getCategories(){let t=await this.request("/api/public/categories");return t.items??(Array.isArray(t)?t:[])}async getSitemap(){let t=await this.request("/api/public/sitemap");return t.items??(Array.isArray(t)?t:[])}};var C=require("react");var D=require("react/jsx-runtime"),T=(0,C.createContext)(null);function L({config:e,children:t}){let r=(0,C.useMemo)(()=>new b(e),[e.apiUrl,e.apiKey]);return(0,D.jsx)(T.Provider,{value:r,children:t})}function _(){let e=(0,C.useContext)(T);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}var l=require("react");function E(e){let t=_(),[r,o]=(0,l.useState)({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),i=(0,l.useCallback)(()=>{o(a=>({...a,loading:!0,error:null})),t.getArticles(e).then(a=>o({articles:a.items,loading:!1,error:null,pagination:{page:a.page,per_page:a.per_page,total:a.total,total_pages:a.total_pages}})).catch(a=>o(n=>({...n,loading:!1,error:a instanceof Error?a:new Error(String(a))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return(0,l.useEffect)(()=>{i()},[i]),{...r,refetch:i}}function U(e){let t=_(),[r,o]=(0,l.useState)({article:null,loading:!0,error:null}),i=(0,l.useCallback)(()=>{if(!e){o({article:null,loading:!1,error:null});return}o(a=>({...a,loading:!0,error:null})),t.getArticleBySlug(e).then(a=>o({article:a,loading:!1,error:null})).catch(a=>o({article:null,loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[t,e]);return(0,l.useEffect)(()=>{i()},[i]),{...r,refetch:i}}function z(e,t=3){let r=_(),[o,i]=(0,l.useState)({articles:[],loading:!0,error:null}),a=(0,l.useCallback)(()=>{if(!e){i({articles:[],loading:!1,error:null});return}i(n=>({...n,loading:!0,error:null})),r.getRelatedArticles(e,t).then(n=>i({articles:n,loading:!1,error:null})).catch(n=>i({articles:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[r,e,t]);return(0,l.useEffect)(()=>{a()},[a]),{...o,refetch:a}}function B(){let e=_(),[t,r]=(0,l.useState)({categories:[],loading:!0,error:null}),o=(0,l.useCallback)(()=>{r(i=>({...i,loading:!0,error:null})),e.getCategories().then(i=>r({categories:i,loading:!1,error:null})).catch(i=>r({categories:[],loading:!1,error:i instanceof Error?i:new Error(String(i))}))},[e]);return(0,l.useEffect)(()=>{o()},[o]),{...t,refetch:o}}var w=Q(require("react")),c=require("react/jsx-runtime"),g={grid:{display:"grid",gap:"1.5rem"},card:{border:"1px solid #e5e7eb",borderRadius:"0.75rem",overflow:"hidden",background:"#fff",cursor:"pointer",transition:"box-shadow 0.2s"},cardHover:{boxShadow:"0 4px 12px rgba(0,0,0,0.08)"},image:{width:"100%",height:"200px",objectFit:"cover",display:"block"},body:{padding:"1.25rem"},title:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"#111827"},excerpt:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"#6b7280",lineHeight:1.5},meta:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"#9ca3af",flexWrap:"wrap"},badge:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"#f3f4f6",fontSize:"0.75rem",color:"#4b5563"},listCard:{display:"flex",border:"1px solid #e5e7eb",borderRadius:"0.75rem",overflow:"hidden",background:"#fff",cursor:"pointer",transition:"box-shadow 0.2s"},listImage:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},listBody:{padding:"1.25rem",flex:1}};function V({article:e,layout:t,showExcerpt:r,showImage:o,showMeta:i,onClick:a}){let n=t==="grid",[p,f]=w.default.useState(!1);return(0,c.jsxs)("article",{style:{...n?g.card:g.listCard,...p?g.cardHover:{}},onMouseEnter:()=>f(!0),onMouseLeave:()=>f(!1),onClick:a,role:"link",tabIndex:0,onKeyDown:A=>A.key==="Enter"&&a?.(),children:[o&&e.featured_image_url&&(0,c.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:n?g.image:g.listImage,loading:"lazy"}),(0,c.jsxs)("div",{style:n?g.body:g.listBody,children:[(0,c.jsx)("h3",{style:g.title,children:e.title}),r&&e.excerpt&&(0,c.jsx)("p",{style:g.excerpt,children:e.excerpt}),i&&(0,c.jsxs)("div",{style:g.meta,children:[e.pillar_name&&(0,c.jsx)("span",{style:g.badge,children:e.pillar_name}),e.content_type&&(0,c.jsx)("span",{style:g.badge,children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,c.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,c.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function v({articles:e,layout:t="grid",columns:r=3,showExcerpt:o=!0,showImage:i=!0,showMeta:a=!0,onArticleClick:n,className:p,renderArticle:f}){let A=t==="grid"?`repeat(${r}, 1fr)`:"1fr";return(0,c.jsx)("div",{className:p,style:{...g.grid,gridTemplateColumns:A},children:e.map(S=>f?(0,c.jsx)(w.default.Fragment,{children:f(S)},S.id):(0,c.jsx)(V,{article:S,layout:t,showExcerpt:o,showImage:i,showMeta:a,onClick:()=>n?.(S.slug)},S.id))})}var H=require("react"),d=require("react/jsx-runtime"),y={wrapper:{marginTop:"1rem"},title:{fontSize:"1.5rem",fontWeight:700,color:"#111827",margin:"0 0 1rem"},item:{borderBottom:"1px solid #e5e7eb",padding:"0.75rem 0"},question:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"#1f2937",fontFamily:"inherit"},questionStatic:{fontSize:"1rem",fontWeight:600,color:"#1f2937",margin:"0 0 0.5rem"},chevron:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"#9ca3af"},answer:{fontSize:"0.9375rem",color:"#4b5563",lineHeight:1.6,paddingTop:"0.5rem"}};function Y({item:e,collapsible:t,defaultOpen:r}){let[o,i]=(0,H.useState)(r);return t?(0,d.jsxs)("div",{style:y.item,children:[(0,d.jsxs)("button",{style:y.question,onClick:()=>i(!o),"aria-expanded":o,children:[(0,d.jsx)("span",{children:e.question}),(0,d.jsx)("span",{style:{...y.chevron,transform:o?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),o&&(0,d.jsx)("div",{style:y.answer,children:e.answer})]}):(0,d.jsxs)("div",{style:y.item,children:[(0,d.jsx)("p",{style:y.questionStatic,children:e.question}),(0,d.jsx)("div",{style:y.answer,children:e.answer})]})}function x({items:e,collapsible:t=!0,defaultOpen:r=!1,className:o,title:i="Frequently Asked Questions"}){if(!e||e.length===0)return null;let a={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(n=>({"@type":"Question",name:n.question,acceptedAnswer:{"@type":"Answer",text:n.answer}}))};return(0,d.jsxs)("section",{className:o,style:y.wrapper,children:[(0,d.jsx)("h2",{style:y.title,children:i}),e.map((n,p)=>(0,d.jsx)(Y,{item:n,collapsible:t,defaultOpen:r},p)),(0,d.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(a)}})]})}var u=require("react/jsx-runtime"),h={wrapper:{marginTop:"1rem"},title:{fontSize:"1.25rem",fontWeight:700,color:"#111827",margin:"0 0 1rem"},grid:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},card:{border:"1px solid #e5e7eb",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"#fff"},image:{width:"100%",height:"140px",objectFit:"cover",display:"block"},body:{padding:"1rem"},cardTitle:{fontSize:"0.9375rem",fontWeight:600,color:"#111827",margin:0,lineHeight:1.3},excerpt:{fontSize:"0.8125rem",color:"#6b7280",marginTop:"0.5rem",lineHeight:1.4}};function R({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:o,className:i}){let a=e.slice(0,r);return a.length===0?null:(0,u.jsxs)("section",{className:i,style:h.wrapper,children:[(0,u.jsx)("h3",{style:h.title,children:t}),(0,u.jsx)("div",{style:h.grid,children:a.map(n=>(0,u.jsxs)("div",{style:h.card,onClick:()=>o?.(n.slug),role:"link",tabIndex:0,onKeyDown:p=>p.key==="Enter"&&o?.(n.slug),children:[n.featured_image_url&&(0,u.jsx)("img",{src:n.featured_image_url,alt:n.featured_image_alt||n.title,style:h.image,loading:"lazy"}),(0,u.jsxs)("div",{style:h.body,children:[(0,u.jsx)("h4",{style:h.cardTitle,children:n.title}),n.excerpt&&(0,u.jsx)("p",{style:h.excerpt,children:n.excerpt})]})]},n.id))})]})}var s=require("react/jsx-runtime"),m={wrapper:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif"},meta:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"#6b7280",marginBottom:"1.5rem"},badge:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"#eff6ff",color:"#2563eb",fontSize:"0.75rem"},h1:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"#111827",margin:"0 0 1rem"},image:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"},toc:{background:"#f9fafb",border:"1px solid #e5e7eb",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},tocTitle:{fontSize:"0.875rem",fontWeight:600,color:"#374151",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},tocList:{listStyle:"none",padding:0,margin:0},tocItem:{padding:"0.25rem 0"},tocLink:{color:"#4b5563",textDecoration:"none",fontSize:"0.875rem"},content:{lineHeight:1.75,color:"#374151",fontSize:"1.0625rem"},divider:{border:"none",borderTop:"1px solid #e5e7eb",margin:"2.5rem 0"}};function Z({headings:e}){return!e||e.length===0?null:(0,s.jsxs)("nav",{style:m.toc,children:[(0,s.jsx)("p",{style:m.tocTitle,children:"Table of Contents"}),(0,s.jsx)("ul",{style:m.tocList,children:e.map((t,r)=>(0,s.jsx)("li",{style:{...m.tocItem,paddingLeft:`${(t.level-2)*1}rem`},children:(0,s.jsx)("a",{href:`#${t.id}`,style:m.tocLink,children:t.text})},r))})]})}function k({article:e,showFaq:t=!0,showTableOfContents:r=!0,showMeta:o=!0,showRelated:i=!1,relatedArticles:a,onRelatedClick:n,className:p,components:f}){let A=f?.H1||(({children:N})=>(0,s.jsx)("h1",{style:m.h1,children:N})),S=f?.Toc||Z,$=f?.Faq||x;return(0,s.jsxs)("article",{className:p,style:m.wrapper,children:[o&&(0,s.jsxs)("div",{style:m.meta,children:[e.pillar_name&&(0,s.jsx)("span",{style:m.badge,children:e.pillar_name}),e.content_type&&(0,s.jsx)("span",{style:{...m.badge,background:"#f0fdf4",color:"#16a34a"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,s.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,s.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]}),(0,s.jsx)(A,{children:e.h1||e.title}),e.featured_image_url&&(0,s.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:m.image}),r&&e.headings?.length>0&&(0,s.jsx)(S,{headings:e.headings}),(0,s.jsx)("div",{style:m.content,dangerouslySetInnerHTML:{__html:e.content_html}}),t&&e.faq?.length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:m.divider}),(0,s.jsx)($,{items:e.faq})]}),e.schema_json&&(0,s.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),i&&a&&a.length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:m.divider}),(0,s.jsx)(R,{articles:a,onArticleClick:n})]})]})}var M=require("react/jsx-runtime");function F(e,t){let r=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...r?{url:r}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:r?{alternates:{canonical:r}}:{}}}function q({article:e,siteUrl:t}){let r=e.schema_json||{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:`${t.replace(/\/+$/,"")}/blog/${e.slug}`}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}};return(0,M.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})}0&&(module.exports={ArticleFeed,ArticlePage,ContentClient,DsaContentProvider,FaqBlock,RelatedArticles,SeoMetaBridge,generateArticleMetadata,useArticle,useArticles,useCategories,useDsaContent,useRelatedArticles});
2
+ "use strict";var $=Object.create;var A=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty;var K=(e,t)=>{for(var r in t)A(e,r,{get:t[r],enumerable:!0})},F=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of j(t))!O.call(e,n)&&n!==r&&A(e,n,{get:()=>t[n],enumerable:!(o=N(t,n))||o.enumerable});return e};var V=(e,t,r)=>(r=e!=null?$(W(e)):{},F(t||!e||!e.__esModule?A(r,"default",{value:e,enumerable:!0}):r,e)),G=e=>F(A({},"__esModule",{value:!0}),e);var re={};K(re,{ArticleFeed:()=>w,ArticlePage:()=>k,ContentClient:()=>y,DsaContentProvider:()=>I,FaqBlock:()=>v,RelatedArticles:()=>_,SeoMetaBridge:()=>P,generateArticleMetadata:()=>R,useArticle:()=>D,useArticles:()=>L,useCategories:()=>E,useDsaContent:()=>h,useRelatedArticles:()=>z});module.exports=G(re);var y=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,r){let o=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([i,p])=>{p!=null&&p!==""&&o.searchParams.set(i,String(p))}),o.searchParams.set("site_key",this.apiKey);let n={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(n.next={revalidate:this.revalidateSeconds});let a=await fetch(o.toString(),n);if(!a.ok){let i=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${i||a.statusText}`)}return a.json()}normalizeArticle(t){return{...t,headings:t.headings??[],faq:t.faq??[],internal_links:t.internal_links??[],secondary_keywords:t.secondary_keywords??[],schema_json:t.schema_json??null,content_json:t.content_json??null}}async getArticles(t){let r=await this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search});return{items:r.items??r.data??[],total:r.total??0,page:r.page??1,per_page:r.per_page??20,total_pages:r.total_pages??r.pages??1}}async getArticleBySlug(t){let r=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(r)}async getRelatedArticles(t,r=3){let o=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r});return o.items??(Array.isArray(o)?o:[])}async getCategories(){let t=await this.request("/api/public/categories");return t.items??(Array.isArray(t)?t:[])}async getSitemap(){let t=await this.request("/api/public/sitemap");return t.items??(Array.isArray(t)?t:[])}};var b=require("react");var T=require("react/jsx-runtime"),q=(0,b.createContext)(null);function I({config:e,children:t}){let r=(0,b.useMemo)(()=>new y(e),[e.apiUrl,e.apiKey]);return(0,T.jsx)(q.Provider,{value:r,children:t})}function h(){let e=(0,b.useContext)(q);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}var l=require("react");function L(e){let t=h(),[r,o]=(0,l.useState)({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),n=(0,l.useCallback)(()=>{o(a=>({...a,loading:!0,error:null})),t.getArticles(e).then(a=>o({articles:a.items,loading:!1,error:null,pagination:{page:a.page,per_page:a.per_page,total:a.total,total_pages:a.total_pages}})).catch(a=>o(i=>({...i,loading:!1,error:a instanceof Error?a:new Error(String(a))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return(0,l.useEffect)(()=>{n()},[n]),{...r,refetch:n}}function D(e){let t=h(),[r,o]=(0,l.useState)({article:null,loading:!0,error:null}),n=(0,l.useCallback)(()=>{if(!e){o({article:null,loading:!1,error:null});return}o(a=>({...a,loading:!0,error:null})),t.getArticleBySlug(e).then(a=>o({article:a,loading:!1,error:null})).catch(a=>o({article:null,loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[t,e]);return(0,l.useEffect)(()=>{n()},[n]),{...r,refetch:n}}function z(e,t=3){let r=h(),[o,n]=(0,l.useState)({articles:[],loading:!0,error:null}),a=(0,l.useCallback)(()=>{if(!e){n({articles:[],loading:!1,error:null});return}n(i=>({...i,loading:!0,error:null})),r.getRelatedArticles(e,t).then(i=>n({articles:i,loading:!1,error:null})).catch(i=>n({articles:[],loading:!1,error:i instanceof Error?i:new Error(String(i))}))},[r,e,t]);return(0,l.useEffect)(()=>{a()},[a]),{...o,refetch:a}}function E(){let e=h(),[t,r]=(0,l.useState)({categories:[],loading:!0,error:null}),o=(0,l.useCallback)(()=>{r(n=>({...n,loading:!0,error:null})),e.getCategories().then(n=>r({categories:n,loading:!1,error:null})).catch(n=>r({categories:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[e]);return(0,l.useEffect)(()=>{o()},[o]),{...t,refetch:o}}var C=V(require("react")),c=require("react/jsx-runtime"),J={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-badge-bg":"#f3f4f6","--dsa-badge-text":"#4b5563","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.08)"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-badge-bg":"#374151","--dsa-badge-text":"#d1d5db","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.3)"}};function Q({article:e,layout:t,showExcerpt:r,showImage:o,showMeta:n,onClick:a}){let i=t==="grid",[p,d]=C.default.useState(!1),m={...i?{border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"}:{display:"flex",border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"},...p?{boxShadow:"var(--dsa-hover-shadow)"}:{}};return(0,c.jsxs)("article",{style:m,onMouseEnter:()=>d(!0),onMouseLeave:()=>d(!1),onClick:a,role:"link",tabIndex:0,onKeyDown:x=>x.key==="Enter"&&a?.(),children:[o&&e.featured_image_url&&(0,c.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:i?{width:"100%",height:"200px",objectFit:"cover",display:"block"}:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},loading:"lazy"}),(0,c.jsxs)("div",{style:i?{padding:"1.25rem"}:{padding:"1.25rem",flex:1},children:[(0,c.jsx)("h3",{style:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"var(--dsa-text)"},children:e.title}),r&&e.excerpt&&(0,c.jsx)("p",{style:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"var(--dsa-text-muted)",lineHeight:1.5},children:e.excerpt}),n&&(0,c.jsxs)("div",{style:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"var(--dsa-text-faint)",flexWrap:"wrap"},children:[e.pillar_name&&(0,c.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.pillar_name}),e.content_type&&(0,c.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,c.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,c.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function w({articles:e,layout:t="grid",columns:r=3,showExcerpt:o=!0,showImage:n=!0,showMeta:a=!0,onArticleClick:i,className:p,theme:d="light",renderArticle:m}){let x=t==="grid"?`repeat(${r}, 1fr)`:"1fr",S=d!=="inherit"?J[d]:{};return(0,c.jsx)("div",{className:p,style:{display:"grid",gap:"1.5rem",gridTemplateColumns:x,...S},children:(e??[]).map(f=>m?(0,c.jsx)(C.default.Fragment,{children:m(f)},f.id):(0,c.jsx)(Q,{article:f,layout:t,showExcerpt:o,showImage:n,showMeta:a,onClick:()=>i?.(f.slug)},f.id))})}var U=require("react"),g=require("react/jsx-runtime"),X={light:{"--dsa-text":"#111827","--dsa-text-muted":"#4b5563","--dsa-text-faint":"#9ca3af","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#d1d5db","--dsa-text-faint":"#6b7280","--dsa-divider":"#374151"}};function Y({item:e,collapsible:t,defaultOpen:r}){let[o,n]=(0,U.useState)(r);return t?(0,g.jsxs)("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[(0,g.jsxs)("button",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",fontFamily:"inherit"},onClick:()=>n(!o),"aria-expanded":o,children:[(0,g.jsx)("span",{children:e.question}),(0,g.jsx)("span",{style:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"var(--dsa-text-faint)",transform:o?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),o&&(0,g.jsx)("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6,paddingTop:"0.5rem"},children:e.answer})]}):(0,g.jsxs)("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[(0,g.jsx)("p",{style:{fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",margin:"0 0 0.5rem"},children:e.question}),(0,g.jsx)("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6},children:e.answer})]})}function v({items:e,collapsible:t=!0,defaultOpen:r=!1,className:o,title:n="Frequently Asked Questions",theme:a="light"}){if(!e||e.length===0)return null;let i=a!=="inherit"?X[a]:{},p={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(d=>({"@type":"Question",name:d.question,acceptedAnswer:{"@type":"Answer",text:d.answer}}))};return(0,g.jsxs)("section",{className:o,style:{marginTop:"1rem",...i},children:[(0,g.jsx)("h2",{style:{fontSize:"1.5rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:n}),e.map((d,m)=>(0,g.jsx)(Y,{item:d,collapsible:t,defaultOpen:r},m)),(0,g.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(p)}})]})}var u=require("react/jsx-runtime"),Z={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151"}};function _({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:o,className:n,theme:a="light"}){let i=(e??[]).slice(0,r);if(i.length===0)return null;let p=a!=="inherit"?Z[a]:{};return(0,u.jsxs)("section",{className:n,style:{marginTop:"1rem",...p},children:[(0,u.jsx)("h3",{style:{fontSize:"1.25rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:t}),(0,u.jsx)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},children:i.map(d=>(0,u.jsxs)("div",{style:{border:"1px solid var(--dsa-card-border)",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"var(--dsa-card-bg)"},onClick:()=>o?.(d.slug),role:"link",tabIndex:0,onKeyDown:m=>m.key==="Enter"&&o?.(d.slug),children:[d.featured_image_url&&(0,u.jsx)("img",{src:d.featured_image_url,alt:d.featured_image_alt||d.title,style:{width:"100%",height:"140px",objectFit:"cover",display:"block"},loading:"lazy"}),(0,u.jsxs)("div",{style:{padding:"1rem"},children:[(0,u.jsx)("h4",{style:{fontSize:"0.9375rem",fontWeight:600,color:"var(--dsa-text)",margin:0,lineHeight:1.3},children:d.title}),d.excerpt&&(0,u.jsx)("p",{style:{fontSize:"0.8125rem",color:"var(--dsa-text-muted)",marginTop:"0.5rem",lineHeight:1.4},children:d.excerpt})]})]},d.id))})]})}var s=require("react/jsx-runtime"),ee={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-toc-bg":"#f9fafb","--dsa-badge-bg":"#eff6ff","--dsa-badge-text":"#2563eb","--dsa-badge-alt-bg":"#f0fdf4","--dsa-badge-alt-text":"#16a34a","--dsa-content-text":"#374151","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-toc-bg":"#111827","--dsa-badge-bg":"#1e3a5f","--dsa-badge-text":"#93c5fd","--dsa-badge-alt-bg":"#14532d","--dsa-badge-alt-text":"#86efac","--dsa-content-text":"#d1d5db","--dsa-divider":"#374151"}};function te({headings:e}){return!e||e.length===0?null:(0,s.jsxs)("nav",{style:{background:"var(--dsa-toc-bg, #f9fafb)",border:"1px solid var(--dsa-card-border, #e5e7eb)",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},children:[(0,s.jsx)("p",{style:{fontSize:"0.875rem",fontWeight:600,color:"var(--dsa-text-muted, #374151)",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Table of Contents"}),(0,s.jsx)("ul",{style:{listStyle:"none",padding:0,margin:0},children:e.map((t,r)=>(0,s.jsx)("li",{style:{padding:"0.25rem 0",paddingLeft:`${(t.level-2)*1}rem`},children:(0,s.jsx)("a",{href:`#${t.id}`,style:{color:"var(--dsa-text-muted, #4b5563)",textDecoration:"none",fontSize:"0.875rem"},children:t.text})},r))})]})}function k({article:e,showFaq:t=!0,showTableOfContents:r=!0,showMeta:o=!0,showRelated:n=!1,relatedArticles:a,onRelatedClick:i,className:p,theme:d="light",components:m}){let x=m?.H1||(({children:M})=>(0,s.jsx)("h1",{style:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"var(--dsa-text)",margin:"0 0 1rem"},children:M})),S=m?.Toc||te,f=m?.Faq||v,H=d!=="inherit"?ee[d]:{};return(0,s.jsxs)("article",{className:p,style:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif",...H},children:[o&&(0,s.jsxs)("div",{style:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"var(--dsa-text-muted)",marginBottom:"1.5rem"},children:[e.pillar_name&&(0,s.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",color:"var(--dsa-badge-text)",fontSize:"0.75rem"},children:e.pillar_name}),e.content_type&&(0,s.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-alt-bg, var(--dsa-badge-bg))",color:"var(--dsa-badge-alt-text, var(--dsa-badge-text))",fontSize:"0.75rem"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,s.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,s.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]}),(0,s.jsx)(x,{children:e.h1||e.title}),e.featured_image_url&&(0,s.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"}}),r&&(e.headings??[]).length>0&&(0,s.jsx)(S,{headings:e.headings}),(0,s.jsx)("div",{style:{lineHeight:1.75,color:"var(--dsa-content-text)",fontSize:"1.0625rem"},dangerouslySetInnerHTML:{__html:e.content_html}}),t&&(e.faq??[]).length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),(0,s.jsx)(f,{items:e.faq})]}),e.schema_json&&(0,s.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),n&&a&&a.length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),(0,s.jsx)(_,{articles:a,onArticleClick:i,theme:d})]})]})}var B=require("react/jsx-runtime");function R(e,t){let r=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...r?{url:r}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:r?{alternates:{canonical:r}}:{}}}function P({article:e,siteUrl:t}){let r=e.schema_json||{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:`${t.replace(/\/+$/,"")}/blog/${e.slug}`}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}};return(0,B.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})}0&&(module.exports={ArticleFeed,ArticlePage,ContentClient,DsaContentProvider,FaqBlock,RelatedArticles,SeoMetaBridge,generateArticleMetadata,useArticle,useArticles,useCategories,useDsaContent,useRelatedArticles});
3
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.tsx","../src/components/ArticlePage.tsx","../src/components/SeoMetaBridge.tsx"],"sourcesContent":["// Types\nexport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleHeading,\n FaqItem,\n InternalLink,\n Category,\n ClusterInfo,\n PaginatedResponse,\n ArticleFilters,\n SitemapEntry,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n// Client\nexport { ContentClient } from './client';\n\n// Provider + context hook\nexport { DsaContentProvider, useDsaContent } from './provider';\nexport type { DsaContentProviderProps } from './provider';\n\n// Data-fetching hooks\nexport { useArticles, useArticle, useRelatedArticles, useCategories } from './hooks';\n\n// UI Components\nexport {\n ArticleFeed,\n ArticlePage,\n FaqBlock,\n RelatedArticles,\n SeoMetaBridge,\n generateArticleMetadata,\n} from './components';\n\nexport type {\n ArticleFeedProps,\n ArticlePageProps,\n FaqBlockProps,\n RelatedArticlesProps,\n} from './components';\n","import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst baseStyles = {\n grid: { display: 'grid', gap: '1.5rem' } as React.CSSProperties,\n card: {\n border: '1px solid #e5e7eb',\n borderRadius: '0.75rem',\n overflow: 'hidden',\n background: '#fff',\n cursor: 'pointer',\n transition: 'box-shadow 0.2s',\n } as React.CSSProperties,\n cardHover: { boxShadow: '0 4px 12px rgba(0,0,0,0.08)' },\n image: { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' },\n body: { padding: '1.25rem' } as React.CSSProperties,\n title: { margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: '#111827' } as React.CSSProperties,\n excerpt: { margin: '0 0 0.75rem', fontSize: '0.875rem', color: '#6b7280', lineHeight: 1.5 } as React.CSSProperties,\n meta: { display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: '#9ca3af', flexWrap: 'wrap' as const } as React.CSSProperties,\n badge: {\n display: 'inline-block',\n padding: '0.125rem 0.5rem',\n borderRadius: '9999px',\n background: '#f3f4f6',\n fontSize: '0.75rem',\n color: '#4b5563',\n } as React.CSSProperties,\n listCard: {\n display: 'flex',\n border: '1px solid #e5e7eb',\n borderRadius: '0.75rem',\n overflow: 'hidden',\n background: '#fff',\n cursor: 'pointer',\n transition: 'box-shadow 0.2s',\n } as React.CSSProperties,\n listImage: { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 },\n listBody: { padding: '1.25rem', flex: 1 } as React.CSSProperties,\n};\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n return (\n <article\n style={{\n ...(isGrid ? baseStyles.card : baseStyles.listCard),\n ...(hovered ? baseStyles.cardHover : {}),\n }}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid ? baseStyles.image : baseStyles.listImage}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? baseStyles.body : baseStyles.listBody}>\n <h3 style={baseStyles.title}>{article.title}</h3>\n {showExcerpt && article.excerpt && (\n <p style={baseStyles.excerpt}>{article.excerpt}</p>\n )}\n {showMeta && (\n <div style={baseStyles.meta}>\n {article.pillar_name && <span style={baseStyles.badge}>{article.pillar_name}</span>}\n {article.content_type && (\n <span style={baseStyles.badge}>{article.content_type.replace(/_/g, ' ')}</span>\n )}\n {article.reading_time_minutes && (\n <span>{article.reading_time_minutes} min read</span>\n )}\n {article.published_at && (\n <span>{new Date(article.published_at).toLocaleDateString()}</span>\n )}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n *\n * ```tsx\n * <ArticleFeed articles={articles} layout=\"grid\" columns={3} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />\n * ```\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns =\n layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n\n return (\n <div\n className={className}\n style={{ ...baseStyles.grid, gridTemplateColumns }}\n >\n {articles.map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n}\n\nconst styles = {\n wrapper: { marginTop: '1rem' } as React.CSSProperties,\n title: { fontSize: '1.5rem', fontWeight: 700, color: '#111827', margin: '0 0 1rem' } as React.CSSProperties,\n item: { borderBottom: '1px solid #e5e7eb', padding: '0.75rem 0' } as React.CSSProperties,\n question: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n cursor: 'pointer',\n background: 'none',\n border: 'none',\n width: '100%',\n textAlign: 'left' as const,\n padding: '0.5rem 0',\n fontSize: '1rem',\n fontWeight: 600,\n color: '#1f2937',\n fontFamily: 'inherit',\n } as React.CSSProperties,\n questionStatic: {\n fontSize: '1rem',\n fontWeight: 600,\n color: '#1f2937',\n margin: '0 0 0.5rem',\n } as React.CSSProperties,\n chevron: { flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: '#9ca3af' } as React.CSSProperties,\n answer: { fontSize: '0.9375rem', color: '#4b5563', lineHeight: 1.6, paddingTop: '0.5rem' } as React.CSSProperties,\n};\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={styles.item}>\n <p style={styles.questionStatic}>{item.question}</p>\n <div style={styles.answer}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={styles.item}>\n <button\n style={styles.question}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ ...styles.chevron, transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n &#9660;\n </span>\n </button>\n {open && <div style={styles.answer}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with optional collapse behavior and Schema.org FAQPage markup.\n *\n * ```tsx\n * <FaqBlock items={article.faq} collapsible />\n * ```\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: item.answer,\n },\n })),\n };\n\n return (\n <section className={className} style={styles.wrapper}>\n <h2 style={styles.title}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n}\n\nconst styles = {\n wrapper: { marginTop: '1rem' } as React.CSSProperties,\n title: { fontSize: '1.25rem', fontWeight: 700, color: '#111827', margin: '0 0 1rem' } as React.CSSProperties,\n grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' } as React.CSSProperties,\n card: {\n border: '1px solid #e5e7eb',\n borderRadius: '0.5rem',\n overflow: 'hidden',\n cursor: 'pointer',\n transition: 'box-shadow 0.2s',\n background: '#fff',\n } as React.CSSProperties,\n image: { width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' } as React.CSSProperties,\n body: { padding: '1rem' } as React.CSSProperties,\n cardTitle: { fontSize: '0.9375rem', fontWeight: 600, color: '#111827', margin: 0, lineHeight: 1.3 } as React.CSSProperties,\n excerpt: { fontSize: '0.8125rem', color: '#6b7280', marginTop: '0.5rem', lineHeight: 1.4 } as React.CSSProperties,\n};\n\n/**\n * Related articles widget.\n *\n * ```tsx\n * <RelatedArticles articles={related} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />\n * ```\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n}: RelatedArticlesProps) {\n const displayed = articles.slice(0, limit);\n if (displayed.length === 0) return null;\n\n return (\n <section className={className} style={styles.wrapper}>\n <h3 style={styles.title}>{title}</h3>\n <div style={styles.grid}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={styles.card}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={styles.image}\n loading=\"lazy\"\n />\n )}\n <div style={styles.body}>\n <h4 style={styles.cardTitle}>{a.title}</h4>\n {a.excerpt && <p style={styles.excerpt}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\nexport interface ArticlePageProps {\n article: Article;\n showFaq?: boolean;\n showTableOfContents?: boolean;\n showMeta?: boolean;\n showRelated?: boolean;\n relatedArticles?: ArticleListItem[];\n onRelatedClick?: (slug: string) => void;\n className?: string;\n components?: {\n H1?: React.ComponentType<{ children: React.ReactNode }>;\n Toc?: React.ComponentType<{ headings: Article['headings'] }>;\n Faq?: React.ComponentType<{ items: FaqItem[] }>;\n };\n}\n\nconst styles = {\n wrapper: { maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif' } as React.CSSProperties,\n meta: { display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: '#6b7280', marginBottom: '1.5rem' } as React.CSSProperties,\n badge: { display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: '#eff6ff', color: '#2563eb', fontSize: '0.75rem' } as React.CSSProperties,\n h1: { fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: '#111827', margin: '0 0 1rem' } as React.CSSProperties,\n image: { width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' } as React.CSSProperties,\n toc: { background: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' } as React.CSSProperties,\n tocTitle: { fontSize: '0.875rem', fontWeight: 600, color: '#374151', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' } as React.CSSProperties,\n tocList: { listStyle: 'none', padding: 0, margin: 0 } as React.CSSProperties,\n tocItem: { padding: '0.25rem 0' } as React.CSSProperties,\n tocLink: { color: '#4b5563', textDecoration: 'none', fontSize: '0.875rem' } as React.CSSProperties,\n content: { lineHeight: 1.75, color: '#374151', fontSize: '1.0625rem' } as React.CSSProperties,\n divider: { border: 'none', borderTop: '1px solid #e5e7eb', margin: '2.5rem 0' } as React.CSSProperties,\n};\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav style={styles.toc}>\n <p style={styles.tocTitle}>Table of Contents</p>\n <ul style={styles.tocList}>\n {headings.map((h, i) => (\n <li key={i} style={{ ...styles.tocItem, paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={styles.tocLink}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * Renders a full article page with optional TOC, FAQ, and related articles.\n *\n * ```tsx\n * <ArticlePage article={article} showTableOfContents showFaq />\n * ```\n */\nexport function ArticlePage({\n article,\n showFaq = true,\n showTableOfContents = true,\n showMeta = true,\n showRelated = false,\n relatedArticles,\n onRelatedClick,\n className,\n components,\n}: ArticlePageProps) {\n const H1 = components?.H1 || (({ children }: { children: React.ReactNode }) => <h1 style={styles.h1}>{children}</h1>);\n const Toc = components?.Toc || DefaultToc;\n const FaqComponent = components?.Faq || FaqBlock;\n\n return (\n <article className={className} style={styles.wrapper}>\n {/* Meta badges */}\n {showMeta && (\n <div style={styles.meta}>\n {article.pillar_name && <span style={styles.badge}>{article.pillar_name}</span>}\n {article.content_type && (\n <span style={{ ...styles.badge, background: '#f0fdf4', color: '#16a34a' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n {/* H1 */}\n <H1>{article.h1 || article.title}</H1>\n\n {/* Featured image */}\n {article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={styles.image}\n />\n )}\n\n {/* Table of contents */}\n {showTableOfContents && article.headings?.length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n {/* Article body */}\n <div\n style={styles.content}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {/* FAQ */}\n {showFaq && article.faq?.length > 0 && (\n <>\n <hr style={styles.divider} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {/* Schema.org JSON-LD */}\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {/* Related articles */}\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr style={styles.divider} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} />\n </>\n )}\n </article>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["src_exports","__export","ArticleFeed","ArticlePage","ContentClient","DsaContentProvider","FaqBlock","RelatedArticles","SeoMetaBridge","generateArticleMetadata","useArticle","useArticles","useCategories","useDsaContent","useRelatedArticles","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","import_react","import_jsx_runtime","ContentContext","DsaContentProvider","config","children","client","ContentClient","useDsaContent","import_react","useArticles","filters","client","useDsaContent","state","setState","fetch","s","res","err","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","import_react","import_jsx_runtime","baseStyles","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","React","e","ArticleFeed","articles","columns","onArticleClick","className","renderArticle","gridTemplateColumns","import_react","import_jsx_runtime","styles","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","schemaData","i","import_jsx_runtime","styles","RelatedArticles","articles","title","limit","onArticleClick","className","displayed","a","e","import_jsx_runtime","styles","DefaultToc","headings","h","i","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","components","H1","children","Toc","FaqComponent","FaqBlock","RelatedArticles","import_jsx_runtime","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.tsx","../src/components/ArticlePage.tsx","../src/components/SeoMetaBridge.tsx"],"sourcesContent":["// Types\nexport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleHeading,\n FaqItem,\n InternalLink,\n Category,\n ClusterInfo,\n PaginatedResponse,\n ArticleFilters,\n SitemapEntry,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n// Client\nexport { ContentClient } from './client';\n\n// Provider + context hook\nexport { DsaContentProvider, useDsaContent } from './provider';\nexport type { DsaContentProviderProps } from './provider';\n\n// Data-fetching hooks\nexport { useArticles, useArticle, useRelatedArticles, useCategories } from './hooks';\n\n// UI Components\nexport {\n ArticleFeed,\n ArticlePage,\n FaqBlock,\n RelatedArticles,\n SeoMetaBridge,\n generateArticleMetadata,\n} from './components';\n\nexport type {\n ArticleFeedProps,\n ArticlePageProps,\n FaqBlockProps,\n RelatedArticlesProps,\n} from './components';\n","import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults. Use \"inherit\" to control via your own CSS vars. */\n theme?: 'light' | 'dark' | 'inherit';\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-badge-bg': '#f3f4f6',\n '--dsa-badge-text': '#4b5563',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.08)',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-badge-bg': '#374151',\n '--dsa-badge-text': '#d1d5db',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.3)',\n },\n} as const;\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n const cardStyle: React.CSSProperties = {\n ...(isGrid\n ? { border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }\n : { display: 'flex', border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }),\n ...(hovered ? { boxShadow: 'var(--dsa-hover-shadow)' } : {}),\n };\n\n return (\n <article\n style={cardStyle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid\n ? { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' }\n : { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 }}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? { padding: '1.25rem' } : { padding: '1.25rem', flex: 1 }}>\n <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: 'var(--dsa-text)' }}>\n {article.title}\n </h3>\n {showExcerpt && article.excerpt && (\n <p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'var(--dsa-text-muted)', lineHeight: 1.5 }}>\n {article.excerpt}\n </p>\n )}\n {showMeta && (\n <div style={{ display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: 'var(--dsa-text-faint)', flexWrap: 'wrap' as const }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables (override in your own CSS for full control):\n * --dsa-text, --dsa-text-muted, --dsa-text-faint,\n * --dsa-card-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n theme = 'light',\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns = layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <div\n className={className}\n style={{ display: 'grid', gap: '1.5rem', gridTemplateColumns, ...vars } as React.CSSProperties}\n >\n {(articles ?? []).map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#4b5563',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#d1d5db',\n '--dsa-text-faint': '#6b7280',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--dsa-text)', margin: '0 0 0.5rem' }}>{item.question}</p>\n <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6 }}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <button\n style={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n cursor: 'pointer', background: 'none', border: 'none', width: '100%',\n textAlign: 'left' as const, padding: '0.5rem 0', fontSize: '1rem',\n fontWeight: 600, color: 'var(--dsa-text)', fontFamily: 'inherit',\n }}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: 'var(--dsa-text-faint)', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n &#9660;\n </span>\n </button>\n {open && <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6, paddingTop: '0.5rem' }}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with Schema.org FAQPage markup.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n theme = 'light',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h2 style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n },\n} as const;\n\n/**\n * Related articles widget.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n theme = 'light',\n}: RelatedArticlesProps) {\n const displayed = (articles ?? []).slice(0, limit);\n if (displayed.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h3>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={{ border: '1px solid var(--dsa-card-border)', borderRadius: '0.5rem', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.2s', background: 'var(--dsa-card-bg)' }}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={{ width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' }}\n loading=\"lazy\"\n />\n )}\n <div style={{ padding: '1rem' }}>\n <h4 style={{ fontSize: '0.9375rem', fontWeight: 600, color: 'var(--dsa-text)', margin: 0, lineHeight: 1.3 }}>{a.title}</h4>\n {a.excerpt && <p style={{ fontSize: '0.8125rem', color: 'var(--dsa-text-muted)', marginTop: '0.5rem', lineHeight: 1.4 }}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\nexport interface ArticlePageProps {\n article: Article;\n showFaq?: boolean;\n showTableOfContents?: boolean;\n showMeta?: boolean;\n showRelated?: boolean;\n relatedArticles?: ArticleListItem[];\n onRelatedClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults */\n theme?: 'light' | 'dark' | 'inherit';\n components?: {\n H1?: React.ComponentType<{ children: React.ReactNode }>;\n Toc?: React.ComponentType<{ headings: Article['headings'] }>;\n Faq?: React.ComponentType<{ items: FaqItem[] }>;\n };\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-toc-bg': '#f9fafb',\n '--dsa-badge-bg': '#eff6ff',\n '--dsa-badge-text': '#2563eb',\n '--dsa-badge-alt-bg': '#f0fdf4',\n '--dsa-badge-alt-text': '#16a34a',\n '--dsa-content-text': '#374151',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-toc-bg': '#111827',\n '--dsa-badge-bg': '#1e3a5f',\n '--dsa-badge-text': '#93c5fd',\n '--dsa-badge-alt-bg': '#14532d',\n '--dsa-badge-alt-text': '#86efac',\n '--dsa-content-text': '#d1d5db',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav style={{ background: 'var(--dsa-toc-bg, #f9fafb)', border: '1px solid var(--dsa-card-border, #e5e7eb)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' }}>\n <p style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--dsa-text-muted, #374151)', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>\n Table of Contents\n </p>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>\n {headings.map((h, i) => (\n <li key={i} style={{ padding: '0.25rem 0', paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={{ color: 'var(--dsa-text-muted, #4b5563)', textDecoration: 'none', fontSize: '0.875rem' }}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * Full article page with optional TOC, FAQ, related articles, and JSON-LD.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables: --dsa-text, --dsa-text-muted, --dsa-toc-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-content-text, --dsa-divider\n */\nexport function ArticlePage({\n article,\n showFaq = true,\n showTableOfContents = true,\n showMeta = true,\n showRelated = false,\n relatedArticles,\n onRelatedClick,\n className,\n theme = 'light',\n components,\n}: ArticlePageProps) {\n const H1 = components?.H1 || (({ children }: { children: React.ReactNode }) => (\n <h1 style={{ fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{children}</h1>\n ));\n const Toc = components?.Toc || DefaultToc;\n const FaqComponent = components?.Faq || FaqBlock;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <article className={className} style={{ maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif', ...vars } as React.CSSProperties}>\n {showMeta && (\n <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: 'var(--dsa-text-muted)', marginBottom: '1.5rem' }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', color: 'var(--dsa-badge-text)', fontSize: '0.75rem' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-alt-bg, var(--dsa-badge-bg))', color: 'var(--dsa-badge-alt-text, var(--dsa-badge-text))', fontSize: '0.75rem' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n <H1>{article.h1 || article.title}</H1>\n\n {article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={{ width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' }}\n />\n )}\n\n {showTableOfContents && (article.headings ?? []).length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n <div\n style={{ lineHeight: 1.75, color: 'var(--dsa-content-text)', fontSize: '1.0625rem' }}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {showFaq && (article.faq ?? []).length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} theme={theme} />\n </>\n )}\n </article>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["src_exports","__export","ArticleFeed","ArticlePage","ContentClient","DsaContentProvider","FaqBlock","RelatedArticles","SeoMetaBridge","generateArticleMetadata","useArticle","useArticles","useCategories","useDsaContent","useRelatedArticles","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","import_react","import_jsx_runtime","ContentContext","DsaContentProvider","config","children","client","ContentClient","useDsaContent","import_react","useArticles","filters","client","useDsaContent","state","setState","fetch","s","res","err","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","import_react","import_jsx_runtime","themeVars","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","React","cardStyle","e","ArticleFeed","articles","columns","onArticleClick","className","theme","renderArticle","gridTemplateColumns","vars","import_react","import_jsx_runtime","themeVars","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","theme","vars","schemaData","i","import_jsx_runtime","themeVars","RelatedArticles","articles","title","limit","onArticleClick","className","theme","displayed","vars","a","e","import_jsx_runtime","themeVars","DefaultToc","headings","h","i","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","theme","components","H1","children","Toc","FaqComponent","FaqBlock","vars","RelatedArticles","import_jsx_runtime","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  "use client";
2
- var b=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,r){let o=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([i,l])=>{l!=null&&l!==""&&o.searchParams.set(i,String(l))}),o.searchParams.set("site_key",this.apiKey);let n={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(n.next={revalidate:this.revalidateSeconds});let a=await fetch(o.toString(),n);if(!a.ok){let i=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${i||a.statusText}`)}return a.json()}normalizeArticle(t){return{...t,headings:t.headings??[],faq:t.faq??[],internal_links:t.internal_links??[],secondary_keywords:t.secondary_keywords??[],schema_json:t.schema_json??null,content_json:t.content_json??null}}async getArticles(t){let r=await this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search});return{items:r.items??r.data??[],total:r.total??0,page:r.page??1,per_page:r.per_page??20,total_pages:r.total_pages??r.pages??1}}async getArticleBySlug(t){let r=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(r)}async getRelatedArticles(t,r=3){let o=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r});return o.items??(Array.isArray(o)?o:[])}async getCategories(){let t=await this.request("/api/public/categories");return t.items??(Array.isArray(t)?t:[])}async getSitemap(){let t=await this.request("/api/public/sitemap");return t.items??(Array.isArray(t)?t:[])}};import{createContext as H,useContext as M,useMemo as $}from"react";import{jsx as j}from"react/jsx-runtime";var q=H(null);function N({config:e,children:t}){let r=$(()=>new b(e),[e.apiUrl,e.apiKey]);return j(q.Provider,{value:r,children:t})}function h(){let e=M(q);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}import{useState as A,useEffect as x,useCallback as R}from"react";function W(e){let t=h(),[r,o]=A({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),n=R(()=>{o(a=>({...a,loading:!0,error:null})),t.getArticles(e).then(a=>o({articles:a.items,loading:!1,error:null,pagination:{page:a.page,per_page:a.per_page,total:a.total,total_pages:a.total_pages}})).catch(a=>o(i=>({...i,loading:!1,error:a instanceof Error?a:new Error(String(a))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return x(()=>{n()},[n]),{...r,refetch:n}}function O(e){let t=h(),[r,o]=A({article:null,loading:!0,error:null}),n=R(()=>{if(!e){o({article:null,loading:!1,error:null});return}o(a=>({...a,loading:!0,error:null})),t.getArticleBySlug(e).then(a=>o({article:a,loading:!1,error:null})).catch(a=>o({article:null,loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[t,e]);return x(()=>{n()},[n]),{...r,refetch:n}}function K(e,t=3){let r=h(),[o,n]=A({articles:[],loading:!0,error:null}),a=R(()=>{if(!e){n({articles:[],loading:!1,error:null});return}n(i=>({...i,loading:!0,error:null})),r.getRelatedArticles(e,t).then(i=>n({articles:i,loading:!1,error:null})).catch(i=>n({articles:[],loading:!1,error:i instanceof Error?i:new Error(String(i))}))},[r,e,t]);return x(()=>{a()},[a]),{...o,refetch:a}}function G(){let e=h(),[t,r]=A({categories:[],loading:!0,error:null}),o=R(()=>{r(n=>({...n,loading:!0,error:null})),e.getCategories().then(n=>r({categories:n,loading:!1,error:null})).catch(n=>r({categories:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[e]);return x(()=>{o()},[o]),{...t,refetch:o}}import I from"react";import{jsx as m,jsxs as P}from"react/jsx-runtime";var p={grid:{display:"grid",gap:"1.5rem"},card:{border:"1px solid #e5e7eb",borderRadius:"0.75rem",overflow:"hidden",background:"#fff",cursor:"pointer",transition:"box-shadow 0.2s"},cardHover:{boxShadow:"0 4px 12px rgba(0,0,0,0.08)"},image:{width:"100%",height:"200px",objectFit:"cover",display:"block"},body:{padding:"1.25rem"},title:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"#111827"},excerpt:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"#6b7280",lineHeight:1.5},meta:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"#9ca3af",flexWrap:"wrap"},badge:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"#f3f4f6",fontSize:"0.75rem",color:"#4b5563"},listCard:{display:"flex",border:"1px solid #e5e7eb",borderRadius:"0.75rem",overflow:"hidden",background:"#fff",cursor:"pointer",transition:"box-shadow 0.2s"},listImage:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},listBody:{padding:"1.25rem",flex:1}};function J({article:e,layout:t,showExcerpt:r,showImage:o,showMeta:n,onClick:a}){let i=t==="grid",[l,d]=I.useState(!1);return P("article",{style:{...i?p.card:p.listCard,...l?p.cardHover:{}},onMouseEnter:()=>d(!0),onMouseLeave:()=>d(!1),onClick:a,role:"link",tabIndex:0,onKeyDown:_=>_.key==="Enter"&&a?.(),children:[o&&e.featured_image_url&&m("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:i?p.image:p.listImage,loading:"lazy"}),P("div",{style:i?p.body:p.listBody,children:[m("h3",{style:p.title,children:e.title}),r&&e.excerpt&&m("p",{style:p.excerpt,children:e.excerpt}),n&&P("div",{style:p.meta,children:[e.pillar_name&&m("span",{style:p.badge,children:e.pillar_name}),e.content_type&&m("span",{style:p.badge,children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&P("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&m("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function T({articles:e,layout:t="grid",columns:r=3,showExcerpt:o=!0,showImage:n=!0,showMeta:a=!0,onArticleClick:i,className:l,renderArticle:d}){let _=t==="grid"?`repeat(${r}, 1fr)`:"1fr";return m("div",{className:l,style:{...p.grid,gridTemplateColumns:_},children:e.map(y=>d?m(I.Fragment,{children:d(y)},y.id):m(J,{article:y,layout:t,showExcerpt:o,showImage:n,showMeta:a,onClick:()=>i?.(y.slug)},y.id))})}import{useState as Q}from"react";import{jsx as u,jsxs as w}from"react/jsx-runtime";var g={wrapper:{marginTop:"1rem"},title:{fontSize:"1.5rem",fontWeight:700,color:"#111827",margin:"0 0 1rem"},item:{borderBottom:"1px solid #e5e7eb",padding:"0.75rem 0"},question:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"#1f2937",fontFamily:"inherit"},questionStatic:{fontSize:"1rem",fontWeight:600,color:"#1f2937",margin:"0 0 0.5rem"},chevron:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"#9ca3af"},answer:{fontSize:"0.9375rem",color:"#4b5563",lineHeight:1.6,paddingTop:"0.5rem"}};function X({item:e,collapsible:t,defaultOpen:r}){let[o,n]=Q(r);return t?w("div",{style:g.item,children:[w("button",{style:g.question,onClick:()=>n(!o),"aria-expanded":o,children:[u("span",{children:e.question}),u("span",{style:{...g.chevron,transform:o?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),o&&u("div",{style:g.answer,children:e.answer})]}):w("div",{style:g.item,children:[u("p",{style:g.questionStatic,children:e.question}),u("div",{style:g.answer,children:e.answer})]})}function v({items:e,collapsible:t=!0,defaultOpen:r=!1,className:o,title:n="Frequently Asked Questions"}){if(!e||e.length===0)return null;let a={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(i=>({"@type":"Question",name:i.question,acceptedAnswer:{"@type":"Answer",text:i.answer}}))};return w("section",{className:o,style:g.wrapper,children:[u("h2",{style:g.title,children:n}),e.map((i,l)=>u(X,{item:i,collapsible:t,defaultOpen:r},l)),u("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(a)}})]})}import{jsx as C,jsxs as F}from"react/jsx-runtime";var f={wrapper:{marginTop:"1rem"},title:{fontSize:"1.25rem",fontWeight:700,color:"#111827",margin:"0 0 1rem"},grid:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},card:{border:"1px solid #e5e7eb",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"#fff"},image:{width:"100%",height:"140px",objectFit:"cover",display:"block"},body:{padding:"1rem"},cardTitle:{fontSize:"0.9375rem",fontWeight:600,color:"#111827",margin:0,lineHeight:1.3},excerpt:{fontSize:"0.8125rem",color:"#6b7280",marginTop:"0.5rem",lineHeight:1.4}};function k({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:o,className:n}){let a=e.slice(0,r);return a.length===0?null:F("section",{className:n,style:f.wrapper,children:[C("h3",{style:f.title,children:t}),C("div",{style:f.grid,children:a.map(i=>F("div",{style:f.card,onClick:()=>o?.(i.slug),role:"link",tabIndex:0,onKeyDown:l=>l.key==="Enter"&&o?.(i.slug),children:[i.featured_image_url&&C("img",{src:i.featured_image_url,alt:i.featured_image_alt||i.title,style:f.image,loading:"lazy"}),F("div",{style:f.body,children:[C("h4",{style:f.cardTitle,children:i.title}),i.excerpt&&C("p",{style:f.excerpt,children:i.excerpt})]})]},i.id))})]})}import{Fragment as L,jsx as s,jsxs as S}from"react/jsx-runtime";var c={wrapper:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif"},meta:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"#6b7280",marginBottom:"1.5rem"},badge:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"#eff6ff",color:"#2563eb",fontSize:"0.75rem"},h1:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"#111827",margin:"0 0 1rem"},image:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"},toc:{background:"#f9fafb",border:"1px solid #e5e7eb",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},tocTitle:{fontSize:"0.875rem",fontWeight:600,color:"#374151",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},tocList:{listStyle:"none",padding:0,margin:0},tocItem:{padding:"0.25rem 0"},tocLink:{color:"#4b5563",textDecoration:"none",fontSize:"0.875rem"},content:{lineHeight:1.75,color:"#374151",fontSize:"1.0625rem"},divider:{border:"none",borderTop:"1px solid #e5e7eb",margin:"2.5rem 0"}};function V({headings:e}){return!e||e.length===0?null:S("nav",{style:c.toc,children:[s("p",{style:c.tocTitle,children:"Table of Contents"}),s("ul",{style:c.tocList,children:e.map((t,r)=>s("li",{style:{...c.tocItem,paddingLeft:`${(t.level-2)*1}rem`},children:s("a",{href:`#${t.id}`,style:c.tocLink,children:t.text})},r))})]})}function D({article:e,showFaq:t=!0,showTableOfContents:r=!0,showMeta:o=!0,showRelated:n=!1,relatedArticles:a,onRelatedClick:i,className:l,components:d}){let _=d?.H1||(({children:B})=>s("h1",{style:c.h1,children:B})),y=d?.Toc||V,z=d?.Faq||v;return S("article",{className:l,style:c.wrapper,children:[o&&S("div",{style:c.meta,children:[e.pillar_name&&s("span",{style:c.badge,children:e.pillar_name}),e.content_type&&s("span",{style:{...c.badge,background:"#f0fdf4",color:"#16a34a"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&S("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&s("span",{children:new Date(e.published_at).toLocaleDateString()})]}),s(_,{children:e.h1||e.title}),e.featured_image_url&&s("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:c.image}),r&&e.headings?.length>0&&s(y,{headings:e.headings}),s("div",{style:c.content,dangerouslySetInnerHTML:{__html:e.content_html}}),t&&e.faq?.length>0&&S(L,{children:[s("hr",{style:c.divider}),s(z,{items:e.faq})]}),e.schema_json&&s("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),n&&a&&a.length>0&&S(L,{children:[s("hr",{style:c.divider}),s(k,{articles:a,onArticleClick:i})]})]})}import{jsx as Y}from"react/jsx-runtime";function E(e,t){let r=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...r?{url:r}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:r?{alternates:{canonical:r}}:{}}}function U({article:e,siteUrl:t}){let r=e.schema_json||{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:`${t.replace(/\/+$/,"")}/blog/${e.slug}`}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}};return Y("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})}export{T as ArticleFeed,D as ArticlePage,b as ContentClient,N as DsaContentProvider,v as FaqBlock,k as RelatedArticles,U as SeoMetaBridge,E as generateArticleMetadata,O as useArticle,W as useArticles,G as useCategories,h as useDsaContent,K as useRelatedArticles};
2
+ var y=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,r){let o=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([s,l])=>{l!=null&&l!==""&&o.searchParams.set(s,String(l))}),o.searchParams.set("site_key",this.apiKey);let n={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(n.next={revalidate:this.revalidateSeconds});let a=await fetch(o.toString(),n);if(!a.ok){let s=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${s||a.statusText}`)}return a.json()}normalizeArticle(t){return{...t,headings:t.headings??[],faq:t.faq??[],internal_links:t.internal_links??[],secondary_keywords:t.secondary_keywords??[],schema_json:t.schema_json??null,content_json:t.content_json??null}}async getArticles(t){let r=await this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search});return{items:r.items??r.data??[],total:r.total??0,page:r.page??1,per_page:r.per_page??20,total_pages:r.total_pages??r.pages??1}}async getArticleBySlug(t){let r=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(r)}async getRelatedArticles(t,r=3){let o=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r});return o.items??(Array.isArray(o)?o:[])}async getCategories(){let t=await this.request("/api/public/categories");return t.items??(Array.isArray(t)?t:[])}async getSitemap(){let t=await this.request("/api/public/sitemap");return t.items??(Array.isArray(t)?t:[])}};import{createContext as U,useContext as B,useMemo as H}from"react";import{jsx as $}from"react/jsx-runtime";var P=U(null);function M({config:e,children:t}){let r=H(()=>new y(e),[e.apiUrl,e.apiKey]);return $(P.Provider,{value:r,children:t})}function u(){let e=B(P);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}import{useState as x,useEffect as v,useCallback as _}from"react";function N(e){let t=u(),[r,o]=x({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),n=_(()=>{o(a=>({...a,loading:!0,error:null})),t.getArticles(e).then(a=>o({articles:a.items,loading:!1,error:null,pagination:{page:a.page,per_page:a.per_page,total:a.total,total_pages:a.total_pages}})).catch(a=>o(s=>({...s,loading:!1,error:a instanceof Error?a:new Error(String(a))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return v(()=>{n()},[n]),{...r,refetch:n}}function j(e){let t=u(),[r,o]=x({article:null,loading:!0,error:null}),n=_(()=>{if(!e){o({article:null,loading:!1,error:null});return}o(a=>({...a,loading:!0,error:null})),t.getArticleBySlug(e).then(a=>o({article:a,loading:!1,error:null})).catch(a=>o({article:null,loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[t,e]);return v(()=>{n()},[n]),{...r,refetch:n}}function W(e,t=3){let r=u(),[o,n]=x({articles:[],loading:!0,error:null}),a=_(()=>{if(!e){n({articles:[],loading:!1,error:null});return}n(s=>({...s,loading:!0,error:null})),r.getRelatedArticles(e,t).then(s=>n({articles:s,loading:!1,error:null})).catch(s=>n({articles:[],loading:!1,error:s instanceof Error?s:new Error(String(s))}))},[r,e,t]);return v(()=>{a()},[a]),{...o,refetch:a}}function O(){let e=u(),[t,r]=x({categories:[],loading:!0,error:null}),o=_(()=>{r(n=>({...n,loading:!0,error:null})),e.getCategories().then(n=>r({categories:n,loading:!1,error:null})).catch(n=>r({categories:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[e]);return v(()=>{o()},[o]),{...t,refetch:o}}import F from"react";import{jsx as p,jsxs as A}from"react/jsx-runtime";var K={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-badge-bg":"#f3f4f6","--dsa-badge-text":"#4b5563","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.08)"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-badge-bg":"#374151","--dsa-badge-text":"#d1d5db","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.3)"}};function V({article:e,layout:t,showExcerpt:r,showImage:o,showMeta:n,onClick:a}){let s=t==="grid",[l,i]=F.useState(!1),c={...s?{border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"}:{display:"flex",border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"},...l?{boxShadow:"var(--dsa-hover-shadow)"}:{}};return A("article",{style:c,onMouseEnter:()=>i(!0),onMouseLeave:()=>i(!1),onClick:a,role:"link",tabIndex:0,onKeyDown:h=>h.key==="Enter"&&a?.(),children:[o&&e.featured_image_url&&p("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:s?{width:"100%",height:"200px",objectFit:"cover",display:"block"}:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},loading:"lazy"}),A("div",{style:s?{padding:"1.25rem"}:{padding:"1.25rem",flex:1},children:[p("h3",{style:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"var(--dsa-text)"},children:e.title}),r&&e.excerpt&&p("p",{style:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"var(--dsa-text-muted)",lineHeight:1.5},children:e.excerpt}),n&&A("div",{style:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"var(--dsa-text-faint)",flexWrap:"wrap"},children:[e.pillar_name&&p("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.pillar_name}),e.content_type&&p("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&A("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&p("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function q({articles:e,layout:t="grid",columns:r=3,showExcerpt:o=!0,showImage:n=!0,showMeta:a=!0,onArticleClick:s,className:l,theme:i="light",renderArticle:c}){let h=t==="grid"?`repeat(${r}, 1fr)`:"1fr",k=i!=="inherit"?K[i]:{};return p("div",{className:l,style:{display:"grid",gap:"1.5rem",gridTemplateColumns:h,...k},children:(e??[]).map(m=>c?p(F.Fragment,{children:c(m)},m.id):p(V,{article:m,layout:t,showExcerpt:o,showImage:n,showMeta:a,onClick:()=>s?.(m.slug)},m.id))})}import{useState as G}from"react";import{jsx as g,jsxs as S}from"react/jsx-runtime";var J={light:{"--dsa-text":"#111827","--dsa-text-muted":"#4b5563","--dsa-text-faint":"#9ca3af","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#d1d5db","--dsa-text-faint":"#6b7280","--dsa-divider":"#374151"}};function Q({item:e,collapsible:t,defaultOpen:r}){let[o,n]=G(r);return t?S("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[S("button",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",fontFamily:"inherit"},onClick:()=>n(!o),"aria-expanded":o,children:[g("span",{children:e.question}),g("span",{style:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"var(--dsa-text-faint)",transform:o?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),o&&g("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6,paddingTop:"0.5rem"},children:e.answer})]}):S("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[g("p",{style:{fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",margin:"0 0 0.5rem"},children:e.question}),g("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6},children:e.answer})]})}function C({items:e,collapsible:t=!0,defaultOpen:r=!1,className:o,title:n="Frequently Asked Questions",theme:a="light"}){if(!e||e.length===0)return null;let s=a!=="inherit"?J[a]:{},l={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(i=>({"@type":"Question",name:i.question,acceptedAnswer:{"@type":"Answer",text:i.answer}}))};return S("section",{className:o,style:{marginTop:"1rem",...s},children:[g("h2",{style:{fontSize:"1.5rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:n}),e.map((i,c)=>g(Q,{item:i,collapsible:t,defaultOpen:r},c)),g("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(l)}})]})}import{jsx as b,jsxs as R}from"react/jsx-runtime";var X={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151"}};function w({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:o,className:n,theme:a="light"}){let s=(e??[]).slice(0,r);if(s.length===0)return null;let l=a!=="inherit"?X[a]:{};return R("section",{className:n,style:{marginTop:"1rem",...l},children:[b("h3",{style:{fontSize:"1.25rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:t}),b("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},children:s.map(i=>R("div",{style:{border:"1px solid var(--dsa-card-border)",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"var(--dsa-card-bg)"},onClick:()=>o?.(i.slug),role:"link",tabIndex:0,onKeyDown:c=>c.key==="Enter"&&o?.(i.slug),children:[i.featured_image_url&&b("img",{src:i.featured_image_url,alt:i.featured_image_alt||i.title,style:{width:"100%",height:"140px",objectFit:"cover",display:"block"},loading:"lazy"}),R("div",{style:{padding:"1rem"},children:[b("h4",{style:{fontSize:"0.9375rem",fontWeight:600,color:"var(--dsa-text)",margin:0,lineHeight:1.3},children:i.title}),i.excerpt&&b("p",{style:{fontSize:"0.8125rem",color:"var(--dsa-text-muted)",marginTop:"0.5rem",lineHeight:1.4},children:i.excerpt})]})]},i.id))})]})}import{Fragment as I,jsx as d,jsxs as f}from"react/jsx-runtime";var Y={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-toc-bg":"#f9fafb","--dsa-badge-bg":"#eff6ff","--dsa-badge-text":"#2563eb","--dsa-badge-alt-bg":"#f0fdf4","--dsa-badge-alt-text":"#16a34a","--dsa-content-text":"#374151","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-toc-bg":"#111827","--dsa-badge-bg":"#1e3a5f","--dsa-badge-text":"#93c5fd","--dsa-badge-alt-bg":"#14532d","--dsa-badge-alt-text":"#86efac","--dsa-content-text":"#d1d5db","--dsa-divider":"#374151"}};function Z({headings:e}){return!e||e.length===0?null:f("nav",{style:{background:"var(--dsa-toc-bg, #f9fafb)",border:"1px solid var(--dsa-card-border, #e5e7eb)",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},children:[d("p",{style:{fontSize:"0.875rem",fontWeight:600,color:"var(--dsa-text-muted, #374151)",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Table of Contents"}),d("ul",{style:{listStyle:"none",padding:0,margin:0},children:e.map((t,r)=>d("li",{style:{padding:"0.25rem 0",paddingLeft:`${(t.level-2)*1}rem`},children:d("a",{href:`#${t.id}`,style:{color:"var(--dsa-text-muted, #4b5563)",textDecoration:"none",fontSize:"0.875rem"},children:t.text})},r))})]})}function T({article:e,showFaq:t=!0,showTableOfContents:r=!0,showMeta:o=!0,showRelated:n=!1,relatedArticles:a,onRelatedClick:s,className:l,theme:i="light",components:c}){let h=c?.H1||(({children:E})=>d("h1",{style:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"var(--dsa-text)",margin:"0 0 1rem"},children:E})),k=c?.Toc||Z,m=c?.Faq||C,z=i!=="inherit"?Y[i]:{};return f("article",{className:l,style:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif",...z},children:[o&&f("div",{style:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"var(--dsa-text-muted)",marginBottom:"1.5rem"},children:[e.pillar_name&&d("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",color:"var(--dsa-badge-text)",fontSize:"0.75rem"},children:e.pillar_name}),e.content_type&&d("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-alt-bg, var(--dsa-badge-bg))",color:"var(--dsa-badge-alt-text, var(--dsa-badge-text))",fontSize:"0.75rem"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&f("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&d("span",{children:new Date(e.published_at).toLocaleDateString()})]}),d(h,{children:e.h1||e.title}),e.featured_image_url&&d("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"}}),r&&(e.headings??[]).length>0&&d(k,{headings:e.headings}),d("div",{style:{lineHeight:1.75,color:"var(--dsa-content-text)",fontSize:"1.0625rem"},dangerouslySetInnerHTML:{__html:e.content_html}}),t&&(e.faq??[]).length>0&&f(I,{children:[d("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),d(m,{items:e.faq})]}),e.schema_json&&d("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),n&&a&&a.length>0&&f(I,{children:[d("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),d(w,{articles:a,onArticleClick:s,theme:i})]})]})}import{jsx as ee}from"react/jsx-runtime";function L(e,t){let r=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...r?{url:r}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:r?{alternates:{canonical:r}}:{}}}function D({article:e,siteUrl:t}){let r=e.schema_json||{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:`${t.replace(/\/+$/,"")}/blog/${e.slug}`}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}};return ee("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})}export{q as ArticleFeed,T as ArticlePage,y as ContentClient,M as DsaContentProvider,C as FaqBlock,w as RelatedArticles,D as SeoMetaBridge,L as generateArticleMetadata,j as useArticle,N as useArticles,O as useCategories,u as useDsaContent,W as useRelatedArticles};
3
3
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.tsx","../src/components/ArticlePage.tsx","../src/components/SeoMetaBridge.tsx"],"sourcesContent":["import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst baseStyles = {\n grid: { display: 'grid', gap: '1.5rem' } as React.CSSProperties,\n card: {\n border: '1px solid #e5e7eb',\n borderRadius: '0.75rem',\n overflow: 'hidden',\n background: '#fff',\n cursor: 'pointer',\n transition: 'box-shadow 0.2s',\n } as React.CSSProperties,\n cardHover: { boxShadow: '0 4px 12px rgba(0,0,0,0.08)' },\n image: { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' },\n body: { padding: '1.25rem' } as React.CSSProperties,\n title: { margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: '#111827' } as React.CSSProperties,\n excerpt: { margin: '0 0 0.75rem', fontSize: '0.875rem', color: '#6b7280', lineHeight: 1.5 } as React.CSSProperties,\n meta: { display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: '#9ca3af', flexWrap: 'wrap' as const } as React.CSSProperties,\n badge: {\n display: 'inline-block',\n padding: '0.125rem 0.5rem',\n borderRadius: '9999px',\n background: '#f3f4f6',\n fontSize: '0.75rem',\n color: '#4b5563',\n } as React.CSSProperties,\n listCard: {\n display: 'flex',\n border: '1px solid #e5e7eb',\n borderRadius: '0.75rem',\n overflow: 'hidden',\n background: '#fff',\n cursor: 'pointer',\n transition: 'box-shadow 0.2s',\n } as React.CSSProperties,\n listImage: { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 },\n listBody: { padding: '1.25rem', flex: 1 } as React.CSSProperties,\n};\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n return (\n <article\n style={{\n ...(isGrid ? baseStyles.card : baseStyles.listCard),\n ...(hovered ? baseStyles.cardHover : {}),\n }}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid ? baseStyles.image : baseStyles.listImage}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? baseStyles.body : baseStyles.listBody}>\n <h3 style={baseStyles.title}>{article.title}</h3>\n {showExcerpt && article.excerpt && (\n <p style={baseStyles.excerpt}>{article.excerpt}</p>\n )}\n {showMeta && (\n <div style={baseStyles.meta}>\n {article.pillar_name && <span style={baseStyles.badge}>{article.pillar_name}</span>}\n {article.content_type && (\n <span style={baseStyles.badge}>{article.content_type.replace(/_/g, ' ')}</span>\n )}\n {article.reading_time_minutes && (\n <span>{article.reading_time_minutes} min read</span>\n )}\n {article.published_at && (\n <span>{new Date(article.published_at).toLocaleDateString()}</span>\n )}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n *\n * ```tsx\n * <ArticleFeed articles={articles} layout=\"grid\" columns={3} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />\n * ```\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns =\n layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n\n return (\n <div\n className={className}\n style={{ ...baseStyles.grid, gridTemplateColumns }}\n >\n {articles.map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n}\n\nconst styles = {\n wrapper: { marginTop: '1rem' } as React.CSSProperties,\n title: { fontSize: '1.5rem', fontWeight: 700, color: '#111827', margin: '0 0 1rem' } as React.CSSProperties,\n item: { borderBottom: '1px solid #e5e7eb', padding: '0.75rem 0' } as React.CSSProperties,\n question: {\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n cursor: 'pointer',\n background: 'none',\n border: 'none',\n width: '100%',\n textAlign: 'left' as const,\n padding: '0.5rem 0',\n fontSize: '1rem',\n fontWeight: 600,\n color: '#1f2937',\n fontFamily: 'inherit',\n } as React.CSSProperties,\n questionStatic: {\n fontSize: '1rem',\n fontWeight: 600,\n color: '#1f2937',\n margin: '0 0 0.5rem',\n } as React.CSSProperties,\n chevron: { flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: '#9ca3af' } as React.CSSProperties,\n answer: { fontSize: '0.9375rem', color: '#4b5563', lineHeight: 1.6, paddingTop: '0.5rem' } as React.CSSProperties,\n};\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={styles.item}>\n <p style={styles.questionStatic}>{item.question}</p>\n <div style={styles.answer}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={styles.item}>\n <button\n style={styles.question}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ ...styles.chevron, transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n &#9660;\n </span>\n </button>\n {open && <div style={styles.answer}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with optional collapse behavior and Schema.org FAQPage markup.\n *\n * ```tsx\n * <FaqBlock items={article.faq} collapsible />\n * ```\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: item.answer,\n },\n })),\n };\n\n return (\n <section className={className} style={styles.wrapper}>\n <h2 style={styles.title}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n}\n\nconst styles = {\n wrapper: { marginTop: '1rem' } as React.CSSProperties,\n title: { fontSize: '1.25rem', fontWeight: 700, color: '#111827', margin: '0 0 1rem' } as React.CSSProperties,\n grid: { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' } as React.CSSProperties,\n card: {\n border: '1px solid #e5e7eb',\n borderRadius: '0.5rem',\n overflow: 'hidden',\n cursor: 'pointer',\n transition: 'box-shadow 0.2s',\n background: '#fff',\n } as React.CSSProperties,\n image: { width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' } as React.CSSProperties,\n body: { padding: '1rem' } as React.CSSProperties,\n cardTitle: { fontSize: '0.9375rem', fontWeight: 600, color: '#111827', margin: 0, lineHeight: 1.3 } as React.CSSProperties,\n excerpt: { fontSize: '0.8125rem', color: '#6b7280', marginTop: '0.5rem', lineHeight: 1.4 } as React.CSSProperties,\n};\n\n/**\n * Related articles widget.\n *\n * ```tsx\n * <RelatedArticles articles={related} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />\n * ```\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n}: RelatedArticlesProps) {\n const displayed = articles.slice(0, limit);\n if (displayed.length === 0) return null;\n\n return (\n <section className={className} style={styles.wrapper}>\n <h3 style={styles.title}>{title}</h3>\n <div style={styles.grid}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={styles.card}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={styles.image}\n loading=\"lazy\"\n />\n )}\n <div style={styles.body}>\n <h4 style={styles.cardTitle}>{a.title}</h4>\n {a.excerpt && <p style={styles.excerpt}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\nexport interface ArticlePageProps {\n article: Article;\n showFaq?: boolean;\n showTableOfContents?: boolean;\n showMeta?: boolean;\n showRelated?: boolean;\n relatedArticles?: ArticleListItem[];\n onRelatedClick?: (slug: string) => void;\n className?: string;\n components?: {\n H1?: React.ComponentType<{ children: React.ReactNode }>;\n Toc?: React.ComponentType<{ headings: Article['headings'] }>;\n Faq?: React.ComponentType<{ items: FaqItem[] }>;\n };\n}\n\nconst styles = {\n wrapper: { maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif' } as React.CSSProperties,\n meta: { display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: '#6b7280', marginBottom: '1.5rem' } as React.CSSProperties,\n badge: { display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: '#eff6ff', color: '#2563eb', fontSize: '0.75rem' } as React.CSSProperties,\n h1: { fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: '#111827', margin: '0 0 1rem' } as React.CSSProperties,\n image: { width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' } as React.CSSProperties,\n toc: { background: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' } as React.CSSProperties,\n tocTitle: { fontSize: '0.875rem', fontWeight: 600, color: '#374151', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' } as React.CSSProperties,\n tocList: { listStyle: 'none', padding: 0, margin: 0 } as React.CSSProperties,\n tocItem: { padding: '0.25rem 0' } as React.CSSProperties,\n tocLink: { color: '#4b5563', textDecoration: 'none', fontSize: '0.875rem' } as React.CSSProperties,\n content: { lineHeight: 1.75, color: '#374151', fontSize: '1.0625rem' } as React.CSSProperties,\n divider: { border: 'none', borderTop: '1px solid #e5e7eb', margin: '2.5rem 0' } as React.CSSProperties,\n};\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav style={styles.toc}>\n <p style={styles.tocTitle}>Table of Contents</p>\n <ul style={styles.tocList}>\n {headings.map((h, i) => (\n <li key={i} style={{ ...styles.tocItem, paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={styles.tocLink}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * Renders a full article page with optional TOC, FAQ, and related articles.\n *\n * ```tsx\n * <ArticlePage article={article} showTableOfContents showFaq />\n * ```\n */\nexport function ArticlePage({\n article,\n showFaq = true,\n showTableOfContents = true,\n showMeta = true,\n showRelated = false,\n relatedArticles,\n onRelatedClick,\n className,\n components,\n}: ArticlePageProps) {\n const H1 = components?.H1 || (({ children }: { children: React.ReactNode }) => <h1 style={styles.h1}>{children}</h1>);\n const Toc = components?.Toc || DefaultToc;\n const FaqComponent = components?.Faq || FaqBlock;\n\n return (\n <article className={className} style={styles.wrapper}>\n {/* Meta badges */}\n {showMeta && (\n <div style={styles.meta}>\n {article.pillar_name && <span style={styles.badge}>{article.pillar_name}</span>}\n {article.content_type && (\n <span style={{ ...styles.badge, background: '#f0fdf4', color: '#16a34a' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n {/* H1 */}\n <H1>{article.h1 || article.title}</H1>\n\n {/* Featured image */}\n {article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={styles.image}\n />\n )}\n\n {/* Table of contents */}\n {showTableOfContents && article.headings?.length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n {/* Article body */}\n <div\n style={styles.content}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {/* FAQ */}\n {showFaq && article.faq?.length > 0 && (\n <>\n <hr style={styles.divider} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {/* Schema.org JSON-LD */}\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {/* Related articles */}\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr style={styles.divider} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} />\n </>\n )}\n </article>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","createContext","useContext","useMemo","jsx","ContentContext","createContext","DsaContentProvider","config","children","client","useMemo","ContentClient","useDsaContent","useContext","useState","useEffect","useCallback","useArticles","filters","client","useDsaContent","state","setState","useState","fetch","useCallback","s","res","err","useEffect","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","React","jsx","jsxs","baseStyles","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","e","ArticleFeed","articles","columns","onArticleClick","className","renderArticle","gridTemplateColumns","useState","jsx","jsxs","styles","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","schemaData","i","jsx","jsxs","styles","RelatedArticles","articles","title","limit","onArticleClick","className","displayed","a","e","Fragment","jsx","jsxs","styles","DefaultToc","headings","h","i","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","components","H1","children","Toc","FaqComponent","FaqBlock","RelatedArticles","jsx","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
1
+ {"version":3,"sources":["../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.tsx","../src/components/ArticlePage.tsx","../src/components/SeoMetaBridge.tsx"],"sourcesContent":["import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults. Use \"inherit\" to control via your own CSS vars. */\n theme?: 'light' | 'dark' | 'inherit';\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-badge-bg': '#f3f4f6',\n '--dsa-badge-text': '#4b5563',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.08)',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-badge-bg': '#374151',\n '--dsa-badge-text': '#d1d5db',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.3)',\n },\n} as const;\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n const cardStyle: React.CSSProperties = {\n ...(isGrid\n ? { border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }\n : { display: 'flex', border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }),\n ...(hovered ? { boxShadow: 'var(--dsa-hover-shadow)' } : {}),\n };\n\n return (\n <article\n style={cardStyle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid\n ? { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' }\n : { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 }}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? { padding: '1.25rem' } : { padding: '1.25rem', flex: 1 }}>\n <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: 'var(--dsa-text)' }}>\n {article.title}\n </h3>\n {showExcerpt && article.excerpt && (\n <p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'var(--dsa-text-muted)', lineHeight: 1.5 }}>\n {article.excerpt}\n </p>\n )}\n {showMeta && (\n <div style={{ display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: 'var(--dsa-text-faint)', flexWrap: 'wrap' as const }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables (override in your own CSS for full control):\n * --dsa-text, --dsa-text-muted, --dsa-text-faint,\n * --dsa-card-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n theme = 'light',\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns = layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <div\n className={className}\n style={{ display: 'grid', gap: '1.5rem', gridTemplateColumns, ...vars } as React.CSSProperties}\n >\n {(articles ?? []).map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#4b5563',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#d1d5db',\n '--dsa-text-faint': '#6b7280',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--dsa-text)', margin: '0 0 0.5rem' }}>{item.question}</p>\n <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6 }}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <button\n style={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n cursor: 'pointer', background: 'none', border: 'none', width: '100%',\n textAlign: 'left' as const, padding: '0.5rem 0', fontSize: '1rem',\n fontWeight: 600, color: 'var(--dsa-text)', fontFamily: 'inherit',\n }}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: 'var(--dsa-text-faint)', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n &#9660;\n </span>\n </button>\n {open && <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6, paddingTop: '0.5rem' }}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with Schema.org FAQPage markup.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n theme = 'light',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h2 style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n },\n} as const;\n\n/**\n * Related articles widget.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n theme = 'light',\n}: RelatedArticlesProps) {\n const displayed = (articles ?? []).slice(0, limit);\n if (displayed.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h3>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={{ border: '1px solid var(--dsa-card-border)', borderRadius: '0.5rem', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.2s', background: 'var(--dsa-card-bg)' }}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={{ width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' }}\n loading=\"lazy\"\n />\n )}\n <div style={{ padding: '1rem' }}>\n <h4 style={{ fontSize: '0.9375rem', fontWeight: 600, color: 'var(--dsa-text)', margin: 0, lineHeight: 1.3 }}>{a.title}</h4>\n {a.excerpt && <p style={{ fontSize: '0.8125rem', color: 'var(--dsa-text-muted)', marginTop: '0.5rem', lineHeight: 1.4 }}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\nexport interface ArticlePageProps {\n article: Article;\n showFaq?: boolean;\n showTableOfContents?: boolean;\n showMeta?: boolean;\n showRelated?: boolean;\n relatedArticles?: ArticleListItem[];\n onRelatedClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults */\n theme?: 'light' | 'dark' | 'inherit';\n components?: {\n H1?: React.ComponentType<{ children: React.ReactNode }>;\n Toc?: React.ComponentType<{ headings: Article['headings'] }>;\n Faq?: React.ComponentType<{ items: FaqItem[] }>;\n };\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-toc-bg': '#f9fafb',\n '--dsa-badge-bg': '#eff6ff',\n '--dsa-badge-text': '#2563eb',\n '--dsa-badge-alt-bg': '#f0fdf4',\n '--dsa-badge-alt-text': '#16a34a',\n '--dsa-content-text': '#374151',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-toc-bg': '#111827',\n '--dsa-badge-bg': '#1e3a5f',\n '--dsa-badge-text': '#93c5fd',\n '--dsa-badge-alt-bg': '#14532d',\n '--dsa-badge-alt-text': '#86efac',\n '--dsa-content-text': '#d1d5db',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav style={{ background: 'var(--dsa-toc-bg, #f9fafb)', border: '1px solid var(--dsa-card-border, #e5e7eb)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' }}>\n <p style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--dsa-text-muted, #374151)', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>\n Table of Contents\n </p>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>\n {headings.map((h, i) => (\n <li key={i} style={{ padding: '0.25rem 0', paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={{ color: 'var(--dsa-text-muted, #4b5563)', textDecoration: 'none', fontSize: '0.875rem' }}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * Full article page with optional TOC, FAQ, related articles, and JSON-LD.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables: --dsa-text, --dsa-text-muted, --dsa-toc-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-content-text, --dsa-divider\n */\nexport function ArticlePage({\n article,\n showFaq = true,\n showTableOfContents = true,\n showMeta = true,\n showRelated = false,\n relatedArticles,\n onRelatedClick,\n className,\n theme = 'light',\n components,\n}: ArticlePageProps) {\n const H1 = components?.H1 || (({ children }: { children: React.ReactNode }) => (\n <h1 style={{ fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{children}</h1>\n ));\n const Toc = components?.Toc || DefaultToc;\n const FaqComponent = components?.Faq || FaqBlock;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <article className={className} style={{ maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif', ...vars } as React.CSSProperties}>\n {showMeta && (\n <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: 'var(--dsa-text-muted)', marginBottom: '1.5rem' }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', color: 'var(--dsa-badge-text)', fontSize: '0.75rem' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-alt-bg, var(--dsa-badge-bg))', color: 'var(--dsa-badge-alt-text, var(--dsa-badge-text))', fontSize: '0.75rem' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n <H1>{article.h1 || article.title}</H1>\n\n {article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={{ width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' }}\n />\n )}\n\n {showTableOfContents && (article.headings ?? []).length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n <div\n style={{ lineHeight: 1.75, color: 'var(--dsa-content-text)', fontSize: '1.0625rem' }}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {showFaq && (article.faq ?? []).length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} theme={theme} />\n </>\n )}\n </article>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","createContext","useContext","useMemo","jsx","ContentContext","createContext","DsaContentProvider","config","children","client","useMemo","ContentClient","useDsaContent","useContext","useState","useEffect","useCallback","useArticles","filters","client","useDsaContent","state","setState","useState","fetch","useCallback","s","res","err","useEffect","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","React","jsx","jsxs","themeVars","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","cardStyle","e","ArticleFeed","articles","columns","onArticleClick","className","theme","renderArticle","gridTemplateColumns","vars","useState","jsx","jsxs","themeVars","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","theme","vars","schemaData","i","jsx","jsxs","themeVars","RelatedArticles","articles","title","limit","onArticleClick","className","theme","displayed","vars","a","e","Fragment","jsx","jsxs","themeVars","DefaultToc","headings","h","i","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","theme","components","H1","children","Toc","FaqComponent","FaqBlock","vars","RelatedArticles","jsx","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsaplatform/content-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "React SDK for DSA Content Operating System — fetch and render SEO content from central engine",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -15,6 +15,11 @@
15
15
  "types": "./dist/server.d.ts",
16
16
  "import": "./dist/server.mjs",
17
17
  "require": "./dist/server.js"
18
+ },
19
+ "./headless": {
20
+ "types": "./dist/headless.d.ts",
21
+ "import": "./dist/headless.mjs",
22
+ "require": "./dist/headless.js"
18
23
  }
19
24
  },
20
25
  "files": [