@dsaplatform/content-sdk 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +4 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1 -1
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -56,10 +56,14 @@ interface ArticleListItem {
|
|
|
56
56
|
excerpt: string | null;
|
|
57
57
|
featured_image_url: string | null;
|
|
58
58
|
featured_image_alt: string | null;
|
|
59
|
+
target_keyword: string | null;
|
|
59
60
|
published_at: string | null;
|
|
60
61
|
pillar_name?: string;
|
|
61
62
|
cluster_name?: string;
|
|
62
63
|
content_type?: string;
|
|
64
|
+
funnel_stage?: string;
|
|
65
|
+
intent?: string;
|
|
66
|
+
word_count: number | null;
|
|
63
67
|
reading_time_minutes: number | null;
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
@@ -183,6 +187,8 @@ declare class ContentClient {
|
|
|
183
187
|
private revalidateSeconds?;
|
|
184
188
|
constructor(config: DsaContentConfig);
|
|
185
189
|
private request;
|
|
190
|
+
/** Normalize article array fields to guarantee they're never null/undefined */
|
|
191
|
+
private normalizeArticle;
|
|
186
192
|
/** Get paginated list of published articles */
|
|
187
193
|
getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>>;
|
|
188
194
|
/** Get a single article by slug */
|
package/dist/index.d.ts
CHANGED
|
@@ -56,10 +56,14 @@ interface ArticleListItem {
|
|
|
56
56
|
excerpt: string | null;
|
|
57
57
|
featured_image_url: string | null;
|
|
58
58
|
featured_image_alt: string | null;
|
|
59
|
+
target_keyword: string | null;
|
|
59
60
|
published_at: string | null;
|
|
60
61
|
pillar_name?: string;
|
|
61
62
|
cluster_name?: string;
|
|
62
63
|
content_type?: string;
|
|
64
|
+
funnel_stage?: string;
|
|
65
|
+
intent?: string;
|
|
66
|
+
word_count: number | null;
|
|
63
67
|
reading_time_minutes: number | null;
|
|
64
68
|
}
|
|
65
69
|
/**
|
|
@@ -183,6 +187,8 @@ declare class ContentClient {
|
|
|
183
187
|
private revalidateSeconds?;
|
|
184
188
|
constructor(config: DsaContentConfig);
|
|
185
189
|
private request;
|
|
190
|
+
/** Normalize article array fields to guarantee they're never null/undefined */
|
|
191
|
+
private normalizeArticle;
|
|
186
192
|
/** Get paginated list of published articles */
|
|
187
193
|
getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>>;
|
|
188
194
|
/** Get a single article by slug */
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var W=Object.create;var R=Object.defineProperty;var O=Object.getOwnPropertyDescriptor;var K=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,G=Object.prototype.hasOwnProperty;var J=(e,t)=>{for(var r in t)R(e,r,{get:t[r],enumerable:!0})},q=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let a of K(t))!G.call(e,a)&&a!==r&&R(e,a,{get:()=>t[a],enumerable:!(i=O(t,a))||i.enumerable});return e};var Q=(e,t,r)=>(r=e!=null?W(j(e)):{},q(t||!e||!e.__esModule?R(r,"default",{value:e,enumerable:!0}):r,e)),X=e=>q(R({},"__esModule",{value:!0}),e);var ee={};J(ee,{ArticleFeed:()=>w,ArticlePage:()=>k,ContentClient:()=>C,DsaContentProvider:()=>T,FaqBlock:()=>x,RelatedArticles:()=>P,SeoMetaBridge:()=>I,generateArticleMetadata:()=>F,useArticle:()=>U,useArticles:()=>E,useCategories:()=>B,useDsaContent:()=>b,useRelatedArticles:()=>z});module.exports=X(ee);var C=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 i=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([n,p])=>{p!=null&&p!==""&&i.searchParams.set(n,String(p))}),i.searchParams.set("site_key",this.apiKey);let a={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(a.next={revalidate:this.revalidateSeconds});let o=await fetch(i.toString(),a);if(!o.ok){let n=await o.text().catch(()=>"");throw new Error(`DSA Content API error ${o.status}: ${n||o.statusText}`)}return o.json()}async getArticles(t){return 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})}async getArticleBySlug(t){return this.request(`/api/public/articles/${encodeURIComponent(t)}`)}async getRelatedArticles(t,r=3){return this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r})}async getCategories(){return this.request("/api/public/categories")}async getSitemap(){return this.request("/api/public/sitemap")}};var _=require("react");var D=require("react/jsx-runtime"),L=(0,_.createContext)(null);function T({config:e,children:t}){let r=(0,_.useMemo)(()=>new C(e),[e.apiUrl,e.apiKey]);return(0,D.jsx)(L.Provider,{value:r,children:t})}function b(){let e=(0,_.useContext)(L);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}var l=require("react");function E(e){let t=b(),[r,i]=(0,l.useState)({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),a=(0,l.useCallback)(()=>{i(o=>({...o,loading:!0,error:null})),t.getArticles(e).then(o=>i({articles:o.items,loading:!1,error:null,pagination:{page:o.page,per_page:o.per_page,total:o.total,total_pages:o.total_pages}})).catch(o=>i(n=>({...n,loading:!1,error:o instanceof Error?o:new Error(String(o))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return(0,l.useEffect)(()=>{a()},[a]),{...r,refetch:a}}function U(e){let t=b(),[r,i]=(0,l.useState)({article:null,loading:!0,error:null}),a=(0,l.useCallback)(()=>{if(!e){i({article:null,loading:!1,error:null});return}i(o=>({...o,loading:!0,error:null})),t.getArticleBySlug(e).then(o=>i({article:o,loading:!1,error:null})).catch(o=>i({article:null,loading:!1,error:o instanceof Error?o:new Error(String(o))}))},[t,e]);return(0,l.useEffect)(()=>{a()},[a]),{...r,refetch:a}}function z(e,t=3){let r=b(),[i,a]=(0,l.useState)({articles:[],loading:!0,error:null}),o=(0,l.useCallback)(()=>{if(!e){a({articles:[],loading:!1,error:null});return}a(n=>({...n,loading:!0,error:null})),r.getRelatedArticles(e,t).then(n=>a({articles:n,loading:!1,error:null})).catch(n=>a({articles:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[r,e,t]);return(0,l.useEffect)(()=>{o()},[o]),{...i,refetch:o}}function B(){let e=b(),[t,r]=(0,l.useState)({categories:[],loading:!0,error:null}),i=(0,l.useCallback)(()=>{r(a=>({...a,loading:!0,error:null})),e.getCategories().then(a=>r({categories:a,loading:!1,error:null})).catch(a=>r({categories:[],loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[e]);return(0,l.useEffect)(()=>{i()},[i]),{...t,refetch:i}}var v=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:i,showMeta:a,onClick:o}){let n=t==="grid",[p,f]=v.default.useState(!1);return(0,c.jsxs)("article",{style:{...n?g.card:g.listCard,...p?g.cardHover:{}},onMouseEnter:()=>f(!0),onMouseLeave:()=>f(!1),onClick:o,role:"link",tabIndex:0,onKeyDown:A=>A.key==="Enter"&&o?.(),children:[i&&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}),a&&(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 w({articles:e,layout:t="grid",columns:r=3,showExcerpt:i=!0,showImage:a=!0,showMeta:o=!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)(v.default.Fragment,{children:f(S)},S.id):(0,c.jsx)(V,{article:S,layout:t,showExcerpt:i,showImage:a,showMeta:o,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[i,a]=(0,H.useState)(r);return t?(0,d.jsxs)("div",{style:y.item,children:[(0,d.jsxs)("button",{style:y.question,onClick:()=>a(!i),"aria-expanded":i,children:[(0,d.jsx)("span",{children:e.question}),(0,d.jsx)("span",{style:{...y.chevron,transform:i?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),i&&(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:i,title:a="Frequently Asked Questions"}){if(!e||e.length===0)return null;let o={"@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:i,style:y.wrapper,children:[(0,d.jsx)("h2",{style:y.title,children:a}),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(o)}})]})}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 P({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:i,className:a}){let o=e.slice(0,r);return o.length===0?null:(0,u.jsxs)("section",{className:a,style:h.wrapper,children:[(0,u.jsx)("h3",{style:h.title,children:t}),(0,u.jsx)("div",{style:h.grid,children:o.map(n=>(0,u.jsxs)("div",{style:h.card,onClick:()=>i?.(n.slug),role:"link",tabIndex:0,onKeyDown:p=>p.key==="Enter"&&i?.(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:i=!0,showRelated:a=!1,relatedArticles:o,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:[i&&(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)}}),a&&o&&o.length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:m.divider}),(0,s.jsx)(P,{articles:o,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 I({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 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});
|
|
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 /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n return this.request<PaginatedResponse<ArticleListItem>>('/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 }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n return this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n return this.request<ArticleListItem[]>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n return this.request<Category[]>('/api/public/categories');\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n return this.request<SitemapEntry[]>('/api/public/sitemap');\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 ▼\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","filters","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 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 ▼\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"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
var C=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,o){let n=new URL(`${this.apiUrl}${t}`);o&&Object.entries(o).forEach(([a,l])=>{l!=null&&l!==""&&n.searchParams.set(a,String(l))}),n.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 r=await fetch(n.toString(),i);if(!r.ok){let a=await r.text().catch(()=>"");throw new Error(`DSA Content API error ${r.status}: ${a||r.statusText}`)}return r.json()}async getArticles(t){return 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})}async getArticleBySlug(t){return this.request(`/api/public/articles/${encodeURIComponent(t)}`)}async getRelatedArticles(t,o=3){return this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:o})}async getCategories(){return this.request("/api/public/categories")}async getSitemap(){return this.request("/api/public/sitemap")}};import{createContext as H,useContext as M,useMemo as $}from"react";import{jsx as W}from"react/jsx-runtime";var I=H(null);function N({config:e,children:t}){let o=$(()=>new C(e),[e.apiUrl,e.apiKey]);return W(I.Provider,{value:o,children:t})}function h(){let e=M(I);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}import{useState as A,useEffect as x,useCallback as P}from"react";function O(e){let t=h(),[o,n]=A({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),i=P(()=>{n(r=>({...r,loading:!0,error:null})),t.getArticles(e).then(r=>n({articles:r.items,loading:!1,error:null,pagination:{page:r.page,per_page:r.per_page,total:r.total,total_pages:r.total_pages}})).catch(r=>n(a=>({...a,loading:!1,error:r instanceof Error?r:new Error(String(r))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return x(()=>{i()},[i]),{...o,refetch:i}}function K(e){let t=h(),[o,n]=A({article:null,loading:!0,error:null}),i=P(()=>{if(!e){n({article:null,loading:!1,error:null});return}n(r=>({...r,loading:!0,error:null})),t.getArticleBySlug(e).then(r=>n({article:r,loading:!1,error:null})).catch(r=>n({article:null,loading:!1,error:r instanceof Error?r:new Error(String(r))}))},[t,e]);return x(()=>{i()},[i]),{...o,refetch:i}}function j(e,t=3){let o=h(),[n,i]=A({articles:[],loading:!0,error:null}),r=P(()=>{if(!e){i({articles:[],loading:!1,error:null});return}i(a=>({...a,loading:!0,error:null})),o.getRelatedArticles(e,t).then(a=>i({articles:a,loading:!1,error:null})).catch(a=>i({articles:[],loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[o,e,t]);return x(()=>{r()},[r]),{...n,refetch:r}}function G(){let e=h(),[t,o]=A({categories:[],loading:!0,error:null}),n=P(()=>{o(i=>({...i,loading:!0,error:null})),e.getCategories().then(i=>o({categories:i,loading:!1,error:null})).catch(i=>o({categories:[],loading:!1,error:i instanceof Error?i:new Error(String(i))}))},[e]);return x(()=>{n()},[n]),{...t,refetch:n}}import q from"react";import{jsx as m,jsxs as R}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:o,showImage:n,showMeta:i,onClick:r}){let a=t==="grid",[l,d]=q.useState(!1);return R("article",{style:{...a?p.card:p.listCard,...l?p.cardHover:{}},onMouseEnter:()=>d(!0),onMouseLeave:()=>d(!1),onClick:r,role:"link",tabIndex:0,onKeyDown:b=>b.key==="Enter"&&r?.(),children:[n&&e.featured_image_url&&m("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:a?p.image:p.listImage,loading:"lazy"}),R("div",{style:a?p.body:p.listBody,children:[m("h3",{style:p.title,children:e.title}),o&&e.excerpt&&m("p",{style:p.excerpt,children:e.excerpt}),i&&R("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&&R("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&m("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function L({articles:e,layout:t="grid",columns:o=3,showExcerpt:n=!0,showImage:i=!0,showMeta:r=!0,onArticleClick:a,className:l,renderArticle:d}){let b=t==="grid"?`repeat(${o}, 1fr)`:"1fr";return m("div",{className:l,style:{...p.grid,gridTemplateColumns:b},children:e.map(y=>d?m(q.Fragment,{children:d(y)},y.id):m(J,{article:y,layout:t,showExcerpt:n,showImage:i,showMeta:r,onClick:()=>a?.(y.slug)},y.id))})}import{useState as Q}from"react";import{jsx as u,jsxs as v}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:o}){let[n,i]=Q(o);return t?v("div",{style:g.item,children:[v("button",{style:g.question,onClick:()=>i(!n),"aria-expanded":n,children:[u("span",{children:e.question}),u("span",{style:{...g.chevron,transform:n?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),n&&u("div",{style:g.answer,children:e.answer})]}):v("div",{style:g.item,children:[u("p",{style:g.questionStatic,children:e.question}),u("div",{style:g.answer,children:e.answer})]})}function w({items:e,collapsible:t=!0,defaultOpen:o=!1,className:n,title:i="Frequently Asked Questions"}){if(!e||e.length===0)return null;let r={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(a=>({"@type":"Question",name:a.question,acceptedAnswer:{"@type":"Answer",text:a.answer}}))};return v("section",{className:n,style:g.wrapper,children:[u("h2",{style:g.title,children:i}),e.map((a,l)=>u(X,{item:a,collapsible:t,defaultOpen:o},l)),u("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})]})}import{jsx as _,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:o=3,onArticleClick:n,className:i}){let r=e.slice(0,o);return r.length===0?null:F("section",{className:i,style:f.wrapper,children:[_("h3",{style:f.title,children:t}),_("div",{style:f.grid,children:r.map(a=>F("div",{style:f.card,onClick:()=>n?.(a.slug),role:"link",tabIndex:0,onKeyDown:l=>l.key==="Enter"&&n?.(a.slug),children:[a.featured_image_url&&_("img",{src:a.featured_image_url,alt:a.featured_image_alt||a.title,style:f.image,loading:"lazy"}),F("div",{style:f.body,children:[_("h4",{style:f.cardTitle,children:a.title}),a.excerpt&&_("p",{style:f.excerpt,children:a.excerpt})]})]},a.id))})]})}import{Fragment as T,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,o)=>s("li",{style:{...c.tocItem,paddingLeft:`${(t.level-2)*1}rem`},children:s("a",{href:`#${t.id}`,style:c.tocLink,children:t.text})},o))})]})}function D({article:e,showFaq:t=!0,showTableOfContents:o=!0,showMeta:n=!0,showRelated:i=!1,relatedArticles:r,onRelatedClick:a,className:l,components:d}){let b=d?.H1||(({children:B})=>s("h1",{style:c.h1,children:B})),y=d?.Toc||V,z=d?.Faq||w;return S("article",{className:l,style:c.wrapper,children:[n&&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(b,{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}),o&&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(T,{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)}}),i&&r&&r.length>0&&S(T,{children:[s("hr",{style:c.divider}),s(k,{articles:r,onArticleClick:a})]})]})}import{jsx as Y}from"react/jsx-runtime";function E(e,t){let o=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,...o?{url:o}:{},...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}}:o?{alternates:{canonical:o}}:{}}}function U({article:e,siteUrl:t}){let o=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(o)}})}export{L as ArticleFeed,D as ArticlePage,C as ContentClient,N as DsaContentProvider,w as FaqBlock,k as RelatedArticles,U as SeoMetaBridge,E as generateArticleMetadata,K as useArticle,O as useArticles,G as useCategories,h as useDsaContent,j as useRelatedArticles};
|
|
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};
|
|
3
3
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -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 /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n return this.request<PaginatedResponse<ArticleListItem>>('/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 }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n return this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n return this.request<ArticleListItem[]>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n return this.request<Category[]>('/api/public/categories');\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n return this.request<SitemapEntry[]>('/api/public/sitemap');\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 ▼\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","filters","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","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 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 ▼\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"]}
|
package/dist/server.d.mts
CHANGED
|
@@ -53,10 +53,14 @@ interface ArticleListItem {
|
|
|
53
53
|
excerpt: string | null;
|
|
54
54
|
featured_image_url: string | null;
|
|
55
55
|
featured_image_alt: string | null;
|
|
56
|
+
target_keyword: string | null;
|
|
56
57
|
published_at: string | null;
|
|
57
58
|
pillar_name?: string;
|
|
58
59
|
cluster_name?: string;
|
|
59
60
|
content_type?: string;
|
|
61
|
+
funnel_stage?: string;
|
|
62
|
+
intent?: string;
|
|
63
|
+
word_count: number | null;
|
|
60
64
|
reading_time_minutes: number | null;
|
|
61
65
|
}
|
|
62
66
|
/**
|
package/dist/server.d.ts
CHANGED
|
@@ -53,10 +53,14 @@ interface ArticleListItem {
|
|
|
53
53
|
excerpt: string | null;
|
|
54
54
|
featured_image_url: string | null;
|
|
55
55
|
featured_image_alt: string | null;
|
|
56
|
+
target_keyword: string | null;
|
|
56
57
|
published_at: string | null;
|
|
57
58
|
pillar_name?: string;
|
|
58
59
|
cluster_name?: string;
|
|
59
60
|
content_type?: string;
|
|
61
|
+
funnel_stage?: string;
|
|
62
|
+
intent?: string;
|
|
63
|
+
word_count: number | null;
|
|
60
64
|
reading_time_minutes: number | null;
|
|
61
65
|
}
|
|
62
66
|
/**
|
package/dist/server.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var l=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var
|
|
1
|
+
"use strict";var l=Object.defineProperty;var p=Object.getOwnPropertyDescriptor;var g=Object.getOwnPropertyNames;var u=Object.prototype.hasOwnProperty;var d=(t,e)=>{for(var i in e)l(t,i,{get:e[i],enumerable:!0})},m=(t,e,i,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of g(e))!u.call(t,n)&&n!==i&&l(t,n,{get:()=>e[n],enumerable:!(r=p(e,n))||r.enumerable});return t};var y=t=>m(l({},"__esModule",{value:!0}),t);var w={};d(w,{fetchArticleBySlug:()=>h,fetchArticleList:()=>A,fetchCategories:()=>f,fetchRelatedArticles:()=>_,fetchSitemap:()=>C,generateArticleMetadata:()=>S});module.exports=y(w);var a=class{constructor(e){this.apiUrl=e.apiUrl.replace(/\/+$/,""),this.apiKey=e.apiKey,this.cacheStrategy=e.cacheStrategy==="revalidate"?"default":e.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=e.revalidateSeconds}async request(e,i){let r=new URL(`${this.apiUrl}${e}`);i&&Object.entries(i).forEach(([o,c])=>{c!=null&&c!==""&&r.searchParams.set(o,String(c))}),r.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 s=await fetch(r.toString(),n);if(!s.ok){let o=await s.text().catch(()=>"");throw new Error(`DSA Content API error ${s.status}: ${o||s.statusText}`)}return s.json()}normalizeArticle(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}}async getArticles(e){let i=await this.request("/api/public/articles",{page:e?.page,per_page:e?.per_page,pillar:e?.pillar,cluster:e?.cluster,content_type:e?.content_type,search:e?.search});return{items:i.items??i.data??[],total:i.total??0,page:i.page??1,per_page:i.per_page??20,total_pages:i.total_pages??i.pages??1}}async getArticleBySlug(e){let i=await this.request(`/api/public/articles/${encodeURIComponent(e)}`);return this.normalizeArticle(i)}async getRelatedArticles(e,i=3){let r=await this.request(`/api/public/articles/${encodeURIComponent(e)}/related`,{limit:i});return r.items??(Array.isArray(r)?r:[])}async getCategories(){let e=await this.request("/api/public/categories");return e.items??(Array.isArray(e)?e:[])}async getSitemap(){let e=await this.request("/api/public/sitemap");return e.items??(Array.isArray(e)?e:[])}};async function h(t,e){return new a(t).getArticleBySlug(e)}async function A(t,e){return new a(t).getArticles(e)}async function _(t,e,i=3){return new a(t).getRelatedArticles(e,i)}async function f(t){return new a(t).getCategories()}async function C(t){return new a(t).getSitemap()}function S(t,e){let i=e?`${e.replace(/\/+$/,"")}/blog/${t.slug}`:void 0;return{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",openGraph:{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",type:"article",publishedTime:t.published_at||void 0,modifiedTime:t.updated_at||void 0,...i?{url:i}:{},...t.featured_image_url?{images:[{url:t.featured_image_url,alt:t.featured_image_alt||t.title}]}:{}},twitter:{card:"summary_large_image",title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",...t.featured_image_url?{images:[t.featured_image_url]}:{}},...t.canonical_url?{alternates:{canonical:t.canonical_url}}:i?{alternates:{canonical:i}}:{}}}0&&(module.exports={fetchArticleBySlug,fetchArticleList,fetchCategories,fetchRelatedArticles,fetchSitemap,generateArticleMetadata});
|
|
2
2
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/client.ts"],"sourcesContent":["/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\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 ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\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 ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\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 /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n return this.request<PaginatedResponse<ArticleListItem>>('/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 }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n return this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n return this.request<ArticleListItem[]>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n return this.request<Category[]>('/api/public/categories');\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n return this.request<SitemapEntry[]>('/api/public/sitemap');\n }\n}\n"],"mappings":"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","names":["server_exports","__export","fetchArticleBySlug","fetchArticleList","fetchCategories","fetchRelatedArticles","fetchSitemap","generateArticleMetadata","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","filters","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/client.ts"],"sourcesContent":["/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\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 ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\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 ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\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"],"mappings":"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","names":["server_exports","__export","fetchArticleBySlug","fetchArticleList","fetchCategories","fetchRelatedArticles","fetchSitemap","generateArticleMetadata","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
|
package/dist/server.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var
|
|
1
|
+
var n=class{constructor(e){this.apiUrl=e.apiUrl.replace(/\/+$/,""),this.apiKey=e.apiKey,this.cacheStrategy=e.cacheStrategy==="revalidate"?"default":e.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=e.revalidateSeconds}async request(e,i){let r=new URL(`${this.apiUrl}${e}`);i&&Object.entries(i).forEach(([c,s])=>{s!=null&&s!==""&&r.searchParams.set(c,String(s))}),r.searchParams.set("site_key",this.apiKey);let o={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(o.next={revalidate:this.revalidateSeconds});let a=await fetch(r.toString(),o);if(!a.ok){let c=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${c||a.statusText}`)}return a.json()}normalizeArticle(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}}async getArticles(e){let i=await this.request("/api/public/articles",{page:e?.page,per_page:e?.per_page,pillar:e?.pillar,cluster:e?.cluster,content_type:e?.content_type,search:e?.search});return{items:i.items??i.data??[],total:i.total??0,page:i.page??1,per_page:i.per_page??20,total_pages:i.total_pages??i.pages??1}}async getArticleBySlug(e){let i=await this.request(`/api/public/articles/${encodeURIComponent(e)}`);return this.normalizeArticle(i)}async getRelatedArticles(e,i=3){let r=await this.request(`/api/public/articles/${encodeURIComponent(e)}/related`,{limit:i});return r.items??(Array.isArray(r)?r:[])}async getCategories(){let e=await this.request("/api/public/categories");return e.items??(Array.isArray(e)?e:[])}async getSitemap(){let e=await this.request("/api/public/sitemap");return e.items??(Array.isArray(e)?e:[])}};async function g(t,e){return new n(t).getArticleBySlug(e)}async function u(t,e){return new n(t).getArticles(e)}async function d(t,e,i=3){return new n(t).getRelatedArticles(e,i)}async function m(t){return new n(t).getCategories()}async function y(t){return new n(t).getSitemap()}function h(t,e){let i=e?`${e.replace(/\/+$/,"")}/blog/${t.slug}`:void 0;return{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",openGraph:{title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",type:"article",publishedTime:t.published_at||void 0,modifiedTime:t.updated_at||void 0,...i?{url:i}:{},...t.featured_image_url?{images:[{url:t.featured_image_url,alt:t.featured_image_alt||t.title}]}:{}},twitter:{card:"summary_large_image",title:t.meta_title||t.title,description:t.meta_description||t.excerpt||"",...t.featured_image_url?{images:[t.featured_image_url]}:{}},...t.canonical_url?{alternates:{canonical:t.canonical_url}}:i?{alternates:{canonical:i}}:{}}}export{g as fetchArticleBySlug,u as fetchArticleList,m as fetchCategories,d as fetchRelatedArticles,y as fetchSitemap,h as generateArticleMetadata};
|
|
2
2
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client.ts","../src/server.ts"],"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 /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n return this.request<PaginatedResponse<ArticleListItem>>('/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 }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n return this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n return this.request<ArticleListItem[]>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n return this.request<Category[]>('/api/public/categories');\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n return this.request<SitemapEntry[]>('/api/public/sitemap');\n }\n}\n","/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\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 ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\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 ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","filters","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/server.ts"],"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","/**\n * Server-side helpers for Next.js SSR/SSG.\n * Import from \"@dsa/content-sdk/server\" — no React dependency.\n */\nimport { ContentClient } from './client';\nimport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\nexport type { DsaContentConfig, Article, ArticleListItem, ArticleFilters, PaginatedResponse, Category, SitemapEntry };\n\n/** Fetch a single article by slug (server-side) */\nexport async function fetchArticleBySlug(\n config: DsaContentConfig,\n slug: string,\n): Promise<Article> {\n const client = new ContentClient(config);\n return client.getArticleBySlug(slug);\n}\n\n/** Fetch paginated article list (server-side) */\nexport async function fetchArticleList(\n config: DsaContentConfig,\n filters?: ArticleFilters,\n): Promise<PaginatedResponse<ArticleListItem>> {\n const client = new ContentClient(config);\n return client.getArticles(filters);\n}\n\n/** Fetch related articles for a slug (server-side) */\nexport async function fetchRelatedArticles(\n config: DsaContentConfig,\n slug: string,\n limit = 3,\n): Promise<ArticleListItem[]> {\n const client = new ContentClient(config);\n return client.getRelatedArticles(slug, limit);\n}\n\n/** Fetch all categories (pillars + clusters) (server-side) */\nexport async function fetchCategories(\n config: DsaContentConfig,\n): Promise<Category[]> {\n const client = new ContentClient(config);\n return client.getCategories();\n}\n\n/** Fetch sitemap entries (server-side) */\nexport async function fetchSitemap(\n config: DsaContentConfig,\n): Promise<SitemapEntry[]> {\n const client = new ContentClient(config);\n return client.getSitemap();\n}\n\n/**\n * Generate Next.js App Router metadata from an Article object.\n * Usage in page.tsx:\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\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 ? { images: [{ url: article.featured_image_url, alt: article.featured_image_alt || article.title }] }\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 ? { alternates: { canonical: article.canonical_url } } : url ? { alternates: { canonical: url } } : {}),\n };\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","fetchArticleBySlug","config","slug","ContentClient","fetchArticleList","filters","fetchRelatedArticles","limit","fetchCategories","fetchSitemap","generateArticleMetadata","article","siteUrl","url"]}
|
package/package.json
CHANGED