@dsaplatform/content-sdk 1.2.0 → 1.3.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/README.md +72 -0
- package/dist/index.d.mts +41 -7
- package/dist/index.d.ts +41 -7
- package/dist/index.js +24 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +24 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -111,6 +111,78 @@ function BlogPost({ slug }: { slug: string }) {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
## Theming & Styling
|
|
115
|
+
|
|
116
|
+
### Light / Dark theme (works out of the box)
|
|
117
|
+
|
|
118
|
+
The SDK ships built-in prose styles for headings, paragraphs, lists, blockquotes, tables, and links inside the article body. These are scoped via `[data-dsa-article-body]` and injected once via `<style>` — they work even when Tailwind Preflight resets all margins.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<ArticlePage article={article} theme="light" />
|
|
122
|
+
<ArticlePage article={article} theme="dark" />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Override any value with CSS variables on a parent element:
|
|
126
|
+
|
|
127
|
+
```css
|
|
128
|
+
:root {
|
|
129
|
+
--dsa-text: #0f172a;
|
|
130
|
+
--dsa-content-text: #334155;
|
|
131
|
+
--dsa-h2-text: #0f172a;
|
|
132
|
+
--dsa-h3-text: #1e293b;
|
|
133
|
+
--dsa-link: #2563eb;
|
|
134
|
+
--dsa-link-hover: #1d4ed8;
|
|
135
|
+
--dsa-toc-bg: #f8fafc;
|
|
136
|
+
--dsa-card-border: #e2e8f0;
|
|
137
|
+
--dsa-badge-bg: #eff6ff;
|
|
138
|
+
--dsa-badge-text: #2563eb;
|
|
139
|
+
--dsa-badge-alt-bg: #f0fdf4;
|
|
140
|
+
--dsa-badge-alt-text: #16a34a;
|
|
141
|
+
--dsa-divider: #e2e8f0;
|
|
142
|
+
--dsa-blockquote-border: #cbd5e1;
|
|
143
|
+
--dsa-blockquote-text: #475569;
|
|
144
|
+
--dsa-pre-bg: #f1f5f9;
|
|
145
|
+
--dsa-table-border: #e2e8f0;
|
|
146
|
+
--dsa-table-header-bg: #f8fafc;
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
To disable the built-in prose styles (e.g. if you use `@tailwindcss/typography`):
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<ArticlePage article={article} disableProseStyles />
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Inherit theme (full control)
|
|
157
|
+
|
|
158
|
+
`theme="inherit"` removes **all** inline styles and injected `<style>` tags. The SDK renders only semantic HTML with stable CSS classes and `data-*` attributes.
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
<ArticlePage
|
|
162
|
+
article={article}
|
|
163
|
+
theme="inherit"
|
|
164
|
+
className="max-w-3xl mx-auto font-sans"
|
|
165
|
+
contentClassName="prose dark:prose-invert"
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> **Tailwind + Preflight note:** In `inherit` mode, paragraph margins, heading sizes, and list styles are all reset to zero by Preflight. You must provide your own prose styles — for example via `@tailwindcss/typography` (`prose` class) or custom CSS targeting the stable selectors below.
|
|
170
|
+
|
|
171
|
+
### Stable CSS selectors
|
|
172
|
+
|
|
173
|
+
Always present regardless of theme:
|
|
174
|
+
|
|
175
|
+
| Element | CSS class | Data attribute |
|
|
176
|
+
|---------|-----------|----------------|
|
|
177
|
+
| Root | your `className` | `data-dsa-theme="light\|dark\|inherit"` |
|
|
178
|
+
| Article body | `dsa-article-body` + `contentClassName` | `data-dsa-article-body` |
|
|
179
|
+
| TOC nav | `dsa-toc` | `data-dsa-toc` |
|
|
180
|
+
| Meta row | `dsa-meta` | `data-dsa-meta` |
|
|
181
|
+
| H1 | `dsa-h1` | — |
|
|
182
|
+
| Badge | `dsa-badge` | — |
|
|
183
|
+
| Featured image | `dsa-featured-image` | — |
|
|
184
|
+
| Divider | `dsa-divider` | — |
|
|
185
|
+
|
|
114
186
|
## Components
|
|
115
187
|
|
|
116
188
|
| Component | Description |
|
package/dist/index.d.mts
CHANGED
|
@@ -287,6 +287,7 @@ interface ArticleFeedProps {
|
|
|
287
287
|
*/
|
|
288
288
|
declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, theme, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
|
|
289
289
|
|
|
290
|
+
type ArticleTheme = 'light' | 'dark' | 'inherit';
|
|
290
291
|
interface ArticlePageProps {
|
|
291
292
|
article: Article;
|
|
292
293
|
showFaq?: boolean;
|
|
@@ -296,8 +297,22 @@ interface ArticlePageProps {
|
|
|
296
297
|
relatedArticles?: ArticleListItem[];
|
|
297
298
|
onRelatedClick?: (slug: string) => void;
|
|
298
299
|
className?: string;
|
|
299
|
-
/**
|
|
300
|
-
|
|
300
|
+
/** Extra class(es) on the `<div>` that wraps `content_html`. */
|
|
301
|
+
contentClassName?: string;
|
|
302
|
+
/**
|
|
303
|
+
* `"light"` / `"dark"` — SDK applies inline styles + CSS vars + built-in prose rules.
|
|
304
|
+
* `"inherit"` — SDK applies NO inline styles; only CSS classes and
|
|
305
|
+
* `data-*` attributes are rendered so the host site has full control.
|
|
306
|
+
*
|
|
307
|
+
* In all modes the content body div has:
|
|
308
|
+
* - `className="dsa-article-body"`
|
|
309
|
+
* - `data-dsa-article-body`
|
|
310
|
+
*
|
|
311
|
+
* @default "light"
|
|
312
|
+
*/
|
|
313
|
+
theme?: ArticleTheme;
|
|
314
|
+
/** Disable built-in prose styles injected via `<style>`. Works only in light/dark themes. */
|
|
315
|
+
disableProseStyles?: boolean;
|
|
301
316
|
components?: {
|
|
302
317
|
H1?: React.ComponentType<{
|
|
303
318
|
children: React.ReactNode;
|
|
@@ -312,12 +327,31 @@ interface ArticlePageProps {
|
|
|
312
327
|
}
|
|
313
328
|
/**
|
|
314
329
|
* Full article page with optional TOC, FAQ, related articles, and JSON-LD.
|
|
315
|
-
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
316
330
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
331
|
+
* **light / dark** — SDK applies inline styles with `--dsa-*` CSS variable
|
|
332
|
+
* overrides, plus built-in prose rules for the article body.
|
|
333
|
+
*
|
|
334
|
+
* **inherit** — SDK renders only semantic HTML with stable CSS classes
|
|
335
|
+
* and `data-*` attributes. No inline styles, no injected `<style>`.
|
|
336
|
+
*
|
|
337
|
+
* ### Stable CSS selectors (always present)
|
|
338
|
+
*
|
|
339
|
+
* | Element | Class | Data attribute |
|
|
340
|
+
* |---------|-------|----------------|
|
|
341
|
+
* | Root | `className` prop | `data-dsa-theme` |
|
|
342
|
+
* | Body | `dsa-article-body` + `contentClassName` | `data-dsa-article-body` |
|
|
343
|
+
* | TOC | `dsa-toc` | `data-dsa-toc` |
|
|
344
|
+
* | Meta | `dsa-meta` | `data-dsa-meta` |
|
|
345
|
+
*
|
|
346
|
+
* ```tsx
|
|
347
|
+
* // Works out of the box
|
|
348
|
+
* <ArticlePage article={article} />
|
|
349
|
+
*
|
|
350
|
+
* // Inherit — host site provides all styles
|
|
351
|
+
* <ArticlePage article={article} theme="inherit" contentClassName="prose dark:prose-invert" />
|
|
352
|
+
* ```
|
|
319
353
|
*/
|
|
320
|
-
declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, theme, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
|
|
354
|
+
declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, contentClassName, theme, disableProseStyles, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
|
|
321
355
|
|
|
322
356
|
interface FaqBlockProps {
|
|
323
357
|
items: FaqItem[];
|
|
@@ -376,4 +410,4 @@ declare function SeoMetaBridge({ article, siteUrl, }: {
|
|
|
376
410
|
siteUrl?: string;
|
|
377
411
|
}): react_jsx_runtime.JSX.Element;
|
|
378
412
|
|
|
379
|
-
export { type Article, ArticleFeed, type ArticleFeedProps, type ArticleFilters, type ArticleHeading, type ArticleListItem, ArticlePage, type ArticlePageProps, type Category, type ClusterInfo, ContentClient, type DsaContentConfig, DsaContentProvider, type DsaContentProviderProps, FaqBlock, type FaqBlockProps, type FaqItem, type InternalLink, type PaginatedResponse, RelatedArticles, type RelatedArticlesProps, SeoMetaBridge, type SitemapEntry, type UseArticleListState, type UseArticleState, type UseArticlesState, type UseCategoriesState, generateArticleMetadata, useArticle, useArticles, useCategories, useDsaContent, useRelatedArticles };
|
|
413
|
+
export { type Article, ArticleFeed, type ArticleFeedProps, type ArticleFilters, type ArticleHeading, type ArticleListItem, ArticlePage, type ArticlePageProps, type ArticleTheme, type Category, type ClusterInfo, ContentClient, type DsaContentConfig, DsaContentProvider, type DsaContentProviderProps, FaqBlock, type FaqBlockProps, type FaqItem, type InternalLink, type PaginatedResponse, RelatedArticles, type RelatedArticlesProps, SeoMetaBridge, type SitemapEntry, type UseArticleListState, type UseArticleState, type UseArticlesState, type UseCategoriesState, generateArticleMetadata, useArticle, useArticles, useCategories, useDsaContent, useRelatedArticles };
|
package/dist/index.d.ts
CHANGED
|
@@ -287,6 +287,7 @@ interface ArticleFeedProps {
|
|
|
287
287
|
*/
|
|
288
288
|
declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, theme, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
|
|
289
289
|
|
|
290
|
+
type ArticleTheme = 'light' | 'dark' | 'inherit';
|
|
290
291
|
interface ArticlePageProps {
|
|
291
292
|
article: Article;
|
|
292
293
|
showFaq?: boolean;
|
|
@@ -296,8 +297,22 @@ interface ArticlePageProps {
|
|
|
296
297
|
relatedArticles?: ArticleListItem[];
|
|
297
298
|
onRelatedClick?: (slug: string) => void;
|
|
298
299
|
className?: string;
|
|
299
|
-
/**
|
|
300
|
-
|
|
300
|
+
/** Extra class(es) on the `<div>` that wraps `content_html`. */
|
|
301
|
+
contentClassName?: string;
|
|
302
|
+
/**
|
|
303
|
+
* `"light"` / `"dark"` — SDK applies inline styles + CSS vars + built-in prose rules.
|
|
304
|
+
* `"inherit"` — SDK applies NO inline styles; only CSS classes and
|
|
305
|
+
* `data-*` attributes are rendered so the host site has full control.
|
|
306
|
+
*
|
|
307
|
+
* In all modes the content body div has:
|
|
308
|
+
* - `className="dsa-article-body"`
|
|
309
|
+
* - `data-dsa-article-body`
|
|
310
|
+
*
|
|
311
|
+
* @default "light"
|
|
312
|
+
*/
|
|
313
|
+
theme?: ArticleTheme;
|
|
314
|
+
/** Disable built-in prose styles injected via `<style>`. Works only in light/dark themes. */
|
|
315
|
+
disableProseStyles?: boolean;
|
|
301
316
|
components?: {
|
|
302
317
|
H1?: React.ComponentType<{
|
|
303
318
|
children: React.ReactNode;
|
|
@@ -312,12 +327,31 @@ interface ArticlePageProps {
|
|
|
312
327
|
}
|
|
313
328
|
/**
|
|
314
329
|
* Full article page with optional TOC, FAQ, related articles, and JSON-LD.
|
|
315
|
-
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
316
330
|
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
331
|
+
* **light / dark** — SDK applies inline styles with `--dsa-*` CSS variable
|
|
332
|
+
* overrides, plus built-in prose rules for the article body.
|
|
333
|
+
*
|
|
334
|
+
* **inherit** — SDK renders only semantic HTML with stable CSS classes
|
|
335
|
+
* and `data-*` attributes. No inline styles, no injected `<style>`.
|
|
336
|
+
*
|
|
337
|
+
* ### Stable CSS selectors (always present)
|
|
338
|
+
*
|
|
339
|
+
* | Element | Class | Data attribute |
|
|
340
|
+
* |---------|-------|----------------|
|
|
341
|
+
* | Root | `className` prop | `data-dsa-theme` |
|
|
342
|
+
* | Body | `dsa-article-body` + `contentClassName` | `data-dsa-article-body` |
|
|
343
|
+
* | TOC | `dsa-toc` | `data-dsa-toc` |
|
|
344
|
+
* | Meta | `dsa-meta` | `data-dsa-meta` |
|
|
345
|
+
*
|
|
346
|
+
* ```tsx
|
|
347
|
+
* // Works out of the box
|
|
348
|
+
* <ArticlePage article={article} />
|
|
349
|
+
*
|
|
350
|
+
* // Inherit — host site provides all styles
|
|
351
|
+
* <ArticlePage article={article} theme="inherit" contentClassName="prose dark:prose-invert" />
|
|
352
|
+
* ```
|
|
319
353
|
*/
|
|
320
|
-
declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, theme, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
|
|
354
|
+
declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, contentClassName, theme, disableProseStyles, components, }: ArticlePageProps): react_jsx_runtime.JSX.Element;
|
|
321
355
|
|
|
322
356
|
interface FaqBlockProps {
|
|
323
357
|
items: FaqItem[];
|
|
@@ -376,4 +410,4 @@ declare function SeoMetaBridge({ article, siteUrl, }: {
|
|
|
376
410
|
siteUrl?: string;
|
|
377
411
|
}): react_jsx_runtime.JSX.Element;
|
|
378
412
|
|
|
379
|
-
export { type Article, ArticleFeed, type ArticleFeedProps, type ArticleFilters, type ArticleHeading, type ArticleListItem, ArticlePage, type ArticlePageProps, type Category, type ClusterInfo, ContentClient, type DsaContentConfig, DsaContentProvider, type DsaContentProviderProps, FaqBlock, type FaqBlockProps, type FaqItem, type InternalLink, type PaginatedResponse, RelatedArticles, type RelatedArticlesProps, SeoMetaBridge, type SitemapEntry, type UseArticleListState, type UseArticleState, type UseArticlesState, type UseCategoriesState, generateArticleMetadata, useArticle, useArticles, useCategories, useDsaContent, useRelatedArticles };
|
|
413
|
+
export { type Article, ArticleFeed, type ArticleFeedProps, type ArticleFilters, type ArticleHeading, type ArticleListItem, ArticlePage, type ArticlePageProps, type ArticleTheme, type Category, type ClusterInfo, ContentClient, type DsaContentConfig, DsaContentProvider, type DsaContentProviderProps, FaqBlock, type FaqBlockProps, type FaqItem, type InternalLink, type PaginatedResponse, RelatedArticles, type RelatedArticlesProps, SeoMetaBridge, type SitemapEntry, type UseArticleListState, type UseArticleState, type UseArticlesState, type UseCategoriesState, generateArticleMetadata, useArticle, useArticles, useCategories, useDsaContent, useRelatedArticles };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";var $=Object.create;var A=Object.defineProperty;var N=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var W=Object.getPrototypeOf,O=Object.prototype.hasOwnProperty;var K=(e,t)=>{for(var r in t)A(e,r,{get:t[r],enumerable:!0})},F=(e,t,r,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of j(t))!O.call(e,n)&&n!==r&&A(e,n,{get:()=>t[n],enumerable:!(o=N(t,n))||o.enumerable});return e};var V=(e,t,r)=>(r=e!=null?$(W(e)):{},F(t||!e||!e.__esModule?A(r,"default",{value:e,enumerable:!0}):r,e)),G=e=>F(A({},"__esModule",{value:!0}),e);var re={};K(re,{ArticleFeed:()=>w,ArticlePage:()=>k,ContentClient:()=>y,DsaContentProvider:()=>I,FaqBlock:()=>v,RelatedArticles:()=>_,SeoMetaBridge:()=>P,generateArticleMetadata:()=>R,useArticle:()=>D,useArticles:()=>L,useCategories:()=>E,useDsaContent:()=>h,useRelatedArticles:()=>z});module.exports=G(re);var y=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,r){let o=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([i,p])=>{p!=null&&p!==""&&o.searchParams.set(i,String(p))}),o.searchParams.set("site_key",this.apiKey);let n={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(n.next={revalidate:this.revalidateSeconds});let a=await fetch(o.toString(),n);if(!a.ok){let i=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${i||a.statusText}`)}return a.json()}normalizeArticle(t){return{...t,headings:t.headings??[],faq:t.faq??[],internal_links:t.internal_links??[],secondary_keywords:t.secondary_keywords??[],schema_json:t.schema_json??null,content_json:t.content_json??null}}async getArticles(t){let r=await this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search});return{items:r.items??r.data??[],total:r.total??0,page:r.page??1,per_page:r.per_page??20,total_pages:r.total_pages??r.pages??1}}async getArticleBySlug(t){let r=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(r)}async getRelatedArticles(t,r=3){let o=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r});return o.items??(Array.isArray(o)?o:[])}async getCategories(){let t=await this.request("/api/public/categories");return t.items??(Array.isArray(t)?t:[])}async getSitemap(){let t=await this.request("/api/public/sitemap");return t.items??(Array.isArray(t)?t:[])}};var b=require("react");var T=require("react/jsx-runtime"),q=(0,b.createContext)(null);function I({config:e,children:t}){let r=(0,b.useMemo)(()=>new y(e),[e.apiUrl,e.apiKey]);return(0,T.jsx)(q.Provider,{value:r,children:t})}function h(){let e=(0,b.useContext)(q);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}var l=require("react");function L(e){let t=h(),[r,o]=(0,l.useState)({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),n=(0,l.useCallback)(()=>{o(a=>({...a,loading:!0,error:null})),t.getArticles(e).then(a=>o({articles:a.items,loading:!1,error:null,pagination:{page:a.page,per_page:a.per_page,total:a.total,total_pages:a.total_pages}})).catch(a=>o(i=>({...i,loading:!1,error:a instanceof Error?a:new Error(String(a))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return(0,l.useEffect)(()=>{n()},[n]),{...r,refetch:n}}function D(e){let t=h(),[r,o]=(0,l.useState)({article:null,loading:!0,error:null}),n=(0,l.useCallback)(()=>{if(!e){o({article:null,loading:!1,error:null});return}o(a=>({...a,loading:!0,error:null})),t.getArticleBySlug(e).then(a=>o({article:a,loading:!1,error:null})).catch(a=>o({article:null,loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[t,e]);return(0,l.useEffect)(()=>{n()},[n]),{...r,refetch:n}}function z(e,t=3){let r=h(),[o,n]=(0,l.useState)({articles:[],loading:!0,error:null}),a=(0,l.useCallback)(()=>{if(!e){n({articles:[],loading:!1,error:null});return}n(i=>({...i,loading:!0,error:null})),r.getRelatedArticles(e,t).then(i=>n({articles:i,loading:!1,error:null})).catch(i=>n({articles:[],loading:!1,error:i instanceof Error?i:new Error(String(i))}))},[r,e,t]);return(0,l.useEffect)(()=>{a()},[a]),{...o,refetch:a}}function E(){let e=h(),[t,r]=(0,l.useState)({categories:[],loading:!0,error:null}),o=(0,l.useCallback)(()=>{r(n=>({...n,loading:!0,error:null})),e.getCategories().then(n=>r({categories:n,loading:!1,error:null})).catch(n=>r({categories:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[e]);return(0,l.useEffect)(()=>{o()},[o]),{...t,refetch:o}}var C=V(require("react")),c=require("react/jsx-runtime"),J={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-badge-bg":"#f3f4f6","--dsa-badge-text":"#4b5563","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.08)"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-badge-bg":"#374151","--dsa-badge-text":"#d1d5db","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.3)"}};function Q({article:e,layout:t,showExcerpt:r,showImage:o,showMeta:n,onClick:a}){let i=t==="grid",[p,d]=C.default.useState(!1),m={...i?{border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"}:{display:"flex",border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"},...p?{boxShadow:"var(--dsa-hover-shadow)"}:{}};return(0,c.jsxs)("article",{style:m,onMouseEnter:()=>d(!0),onMouseLeave:()=>d(!1),onClick:a,role:"link",tabIndex:0,onKeyDown:x=>x.key==="Enter"&&a?.(),children:[o&&e.featured_image_url&&(0,c.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:i?{width:"100%",height:"200px",objectFit:"cover",display:"block"}:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},loading:"lazy"}),(0,c.jsxs)("div",{style:i?{padding:"1.25rem"}:{padding:"1.25rem",flex:1},children:[(0,c.jsx)("h3",{style:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"var(--dsa-text)"},children:e.title}),r&&e.excerpt&&(0,c.jsx)("p",{style:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"var(--dsa-text-muted)",lineHeight:1.5},children:e.excerpt}),n&&(0,c.jsxs)("div",{style:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"var(--dsa-text-faint)",flexWrap:"wrap"},children:[e.pillar_name&&(0,c.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.pillar_name}),e.content_type&&(0,c.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,c.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,c.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function w({articles:e,layout:t="grid",columns:r=3,showExcerpt:o=!0,showImage:n=!0,showMeta:a=!0,onArticleClick:i,className:p,theme:d="light",renderArticle:m}){let x=t==="grid"?`repeat(${r}, 1fr)`:"1fr",S=d!=="inherit"?J[d]:{};return(0,c.jsx)("div",{className:p,style:{display:"grid",gap:"1.5rem",gridTemplateColumns:x,...S},children:(e??[]).map(f=>m?(0,c.jsx)(C.default.Fragment,{children:m(f)},f.id):(0,c.jsx)(Q,{article:f,layout:t,showExcerpt:o,showImage:n,showMeta:a,onClick:()=>i?.(f.slug)},f.id))})}var U=require("react"),g=require("react/jsx-runtime"),X={light:{"--dsa-text":"#111827","--dsa-text-muted":"#4b5563","--dsa-text-faint":"#9ca3af","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#d1d5db","--dsa-text-faint":"#6b7280","--dsa-divider":"#374151"}};function Y({item:e,collapsible:t,defaultOpen:r}){let[o,n]=(0,U.useState)(r);return t?(0,g.jsxs)("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[(0,g.jsxs)("button",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",fontFamily:"inherit"},onClick:()=>n(!o),"aria-expanded":o,children:[(0,g.jsx)("span",{children:e.question}),(0,g.jsx)("span",{style:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"var(--dsa-text-faint)",transform:o?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),o&&(0,g.jsx)("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6,paddingTop:"0.5rem"},children:e.answer})]}):(0,g.jsxs)("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[(0,g.jsx)("p",{style:{fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",margin:"0 0 0.5rem"},children:e.question}),(0,g.jsx)("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6},children:e.answer})]})}function v({items:e,collapsible:t=!0,defaultOpen:r=!1,className:o,title:n="Frequently Asked Questions",theme:a="light"}){if(!e||e.length===0)return null;let i=a!=="inherit"?X[a]:{},p={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(d=>({"@type":"Question",name:d.question,acceptedAnswer:{"@type":"Answer",text:d.answer}}))};return(0,g.jsxs)("section",{className:o,style:{marginTop:"1rem",...i},children:[(0,g.jsx)("h2",{style:{fontSize:"1.5rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:n}),e.map((d,m)=>(0,g.jsx)(Y,{item:d,collapsible:t,defaultOpen:r},m)),(0,g.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(p)}})]})}var u=require("react/jsx-runtime"),Z={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151"}};function _({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:o,className:n,theme:a="light"}){let i=(e??[]).slice(0,r);if(i.length===0)return null;let p=a!=="inherit"?Z[a]:{};return(0,u.jsxs)("section",{className:n,style:{marginTop:"1rem",...p},children:[(0,u.jsx)("h3",{style:{fontSize:"1.25rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:t}),(0,u.jsx)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},children:i.map(d=>(0,u.jsxs)("div",{style:{border:"1px solid var(--dsa-card-border)",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"var(--dsa-card-bg)"},onClick:()=>o?.(d.slug),role:"link",tabIndex:0,onKeyDown:m=>m.key==="Enter"&&o?.(d.slug),children:[d.featured_image_url&&(0,u.jsx)("img",{src:d.featured_image_url,alt:d.featured_image_alt||d.title,style:{width:"100%",height:"140px",objectFit:"cover",display:"block"},loading:"lazy"}),(0,u.jsxs)("div",{style:{padding:"1rem"},children:[(0,u.jsx)("h4",{style:{fontSize:"0.9375rem",fontWeight:600,color:"var(--dsa-text)",margin:0,lineHeight:1.3},children:d.title}),d.excerpt&&(0,u.jsx)("p",{style:{fontSize:"0.8125rem",color:"var(--dsa-text-muted)",marginTop:"0.5rem",lineHeight:1.4},children:d.excerpt})]})]},d.id))})]})}var s=require("react/jsx-runtime"),ee={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-toc-bg":"#f9fafb","--dsa-badge-bg":"#eff6ff","--dsa-badge-text":"#2563eb","--dsa-badge-alt-bg":"#f0fdf4","--dsa-badge-alt-text":"#16a34a","--dsa-content-text":"#374151","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-toc-bg":"#111827","--dsa-badge-bg":"#1e3a5f","--dsa-badge-text":"#93c5fd","--dsa-badge-alt-bg":"#14532d","--dsa-badge-alt-text":"#86efac","--dsa-content-text":"#d1d5db","--dsa-divider":"#374151"}};function te({headings:e}){return!e||e.length===0?null:(0,s.jsxs)("nav",{style:{background:"var(--dsa-toc-bg, #f9fafb)",border:"1px solid var(--dsa-card-border, #e5e7eb)",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},children:[(0,s.jsx)("p",{style:{fontSize:"0.875rem",fontWeight:600,color:"var(--dsa-text-muted, #374151)",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Table of Contents"}),(0,s.jsx)("ul",{style:{listStyle:"none",padding:0,margin:0},children:e.map((t,r)=>(0,s.jsx)("li",{style:{padding:"0.25rem 0",paddingLeft:`${(t.level-2)*1}rem`},children:(0,s.jsx)("a",{href:`#${t.id}`,style:{color:"var(--dsa-text-muted, #4b5563)",textDecoration:"none",fontSize:"0.875rem"},children:t.text})},r))})]})}function k({article:e,showFaq:t=!0,showTableOfContents:r=!0,showMeta:o=!0,showRelated:n=!1,relatedArticles:a,onRelatedClick:i,className:p,theme:d="light",components:m}){let x=m?.H1||(({children:M})=>(0,s.jsx)("h1",{style:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"var(--dsa-text)",margin:"0 0 1rem"},children:M})),S=m?.Toc||te,f=m?.Faq||v,H=d!=="inherit"?ee[d]:{};return(0,s.jsxs)("article",{className:p,style:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif",...H},children:[o&&(0,s.jsxs)("div",{style:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"var(--dsa-text-muted)",marginBottom:"1.5rem"},children:[e.pillar_name&&(0,s.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",color:"var(--dsa-badge-text)",fontSize:"0.75rem"},children:e.pillar_name}),e.content_type&&(0,s.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-alt-bg, var(--dsa-badge-bg))",color:"var(--dsa-badge-alt-text, var(--dsa-badge-text))",fontSize:"0.75rem"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,s.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,s.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]}),(0,s.jsx)(x,{children:e.h1||e.title}),e.featured_image_url&&(0,s.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"}}),r&&(e.headings??[]).length>0&&(0,s.jsx)(S,{headings:e.headings}),(0,s.jsx)("div",{style:{lineHeight:1.75,color:"var(--dsa-content-text)",fontSize:"1.0625rem"},dangerouslySetInnerHTML:{__html:e.content_html}}),t&&(e.faq??[]).length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),(0,s.jsx)(f,{items:e.faq})]}),e.schema_json&&(0,s.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),n&&a&&a.length>0&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),(0,s.jsx)(_,{articles:a,onArticleClick:i,theme:d})]})]})}var B=require("react/jsx-runtime");function R(e,t){let r=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...r?{url:r}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:r?{alternates:{canonical:r}}:{}}}function P({article:e,siteUrl:t}){let r=e.schema_json||{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:`${t.replace(/\/+$/,"")}/blog/${e.slug}`}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}};return(0,B.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})}0&&(module.exports={ArticleFeed,ArticlePage,ContentClient,DsaContentProvider,FaqBlock,RelatedArticles,SeoMetaBridge,generateArticleMetadata,useArticle,useArticles,useCategories,useDsaContent,useRelatedArticles});
|
|
2
|
+
"use strict";var G=Object.create;var S=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var Q=Object.getOwnPropertyNames;var X=Object.getPrototypeOf,Y=Object.prototype.hasOwnProperty;var Z=(e,t)=>{for(var a in t)S(e,a,{get:t[a],enumerable:!0})},F=(e,t,a,s)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Q(t))!Y.call(e,o)&&o!==a&&S(e,o,{get:()=>t[o],enumerable:!(s=J(t,o))||s.enumerable});return e};var I=(e,t,a)=>(a=e!=null?G(X(e)):{},F(t||!e||!e.__esModule?S(a,"default",{value:e,enumerable:!0}):a,e)),ee=e=>F(S({},"__esModule",{value:!0}),e);var pe={};Z(pe,{ArticleFeed:()=>w,ArticlePage:()=>R,ContentClient:()=>b,DsaContentProvider:()=>N,FaqBlock:()=>_,RelatedArticles:()=>A,SeoMetaBridge:()=>q,generateArticleMetadata:()=>P,useArticle:()=>E,useArticles:()=>L,useCategories:()=>U,useDsaContent:()=>h,useRelatedArticles:()=>D});module.exports=ee(pe);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,a){let s=new URL(`${this.apiUrl}${t}`);a&&Object.entries(a).forEach(([n,m])=>{m!=null&&m!==""&&s.searchParams.set(n,String(m))}),s.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 r=await fetch(s.toString(),o);if(!r.ok){let n=await r.text().catch(()=>"");throw new Error(`DSA Content API error ${r.status}: ${n||r.statusText}`)}return r.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 a=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:a.items??a.data??[],total:a.total??0,page:a.page??1,per_page:a.per_page??20,total_pages:a.total_pages??a.pages??1}}async getArticleBySlug(t){let a=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(a)}async getRelatedArticles(t,a=3){let s=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:a});return s.items??(Array.isArray(s)?s:[])}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 y=require("react");var z=require("react/jsx-runtime"),T=(0,y.createContext)(null);function N({config:e,children:t}){let a=(0,y.useMemo)(()=>new b(e),[e.apiUrl,e.apiKey]);return(0,z.jsx)(T.Provider,{value:a,children:t})}function h(){let e=(0,y.useContext)(T);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}var c=require("react");function L(e){let t=h(),[a,s]=(0,c.useState)({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),o=(0,c.useCallback)(()=>{s(r=>({...r,loading:!0,error:null})),t.getArticles(e).then(r=>s({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=>s(n=>({...n,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(0,c.useEffect)(()=>{o()},[o]),{...a,refetch:o}}function E(e){let t=h(),[a,s]=(0,c.useState)({article:null,loading:!0,error:null}),o=(0,c.useCallback)(()=>{if(!e){s({article:null,loading:!1,error:null});return}s(r=>({...r,loading:!0,error:null})),t.getArticleBySlug(e).then(r=>s({article:r,loading:!1,error:null})).catch(r=>s({article:null,loading:!1,error:r instanceof Error?r:new Error(String(r))}))},[t,e]);return(0,c.useEffect)(()=>{o()},[o]),{...a,refetch:o}}function D(e,t=3){let a=h(),[s,o]=(0,c.useState)({articles:[],loading:!0,error:null}),r=(0,c.useCallback)(()=>{if(!e){o({articles:[],loading:!1,error:null});return}o(n=>({...n,loading:!0,error:null})),a.getRelatedArticles(e,t).then(n=>o({articles:n,loading:!1,error:null})).catch(n=>o({articles:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[a,e,t]);return(0,c.useEffect)(()=>{r()},[r]),{...s,refetch:r}}function U(){let e=h(),[t,a]=(0,c.useState)({categories:[],loading:!0,error:null}),s=(0,c.useCallback)(()=>{a(o=>({...o,loading:!0,error:null})),e.getCategories().then(o=>a({categories:o,loading:!1,error:null})).catch(o=>a({categories:[],loading:!1,error:o instanceof Error?o:new Error(String(o))}))},[e]);return(0,c.useEffect)(()=>{s()},[s]),{...t,refetch:s}}var C=I(require("react")),p=require("react/jsx-runtime"),te={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-badge-bg":"#f3f4f6","--dsa-badge-text":"#4b5563","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.08)"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-badge-bg":"#374151","--dsa-badge-text":"#d1d5db","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.3)"}};function ae({article:e,layout:t,showExcerpt:a,showImage:s,showMeta:o,onClick:r}){let n=t==="grid",[m,d]=C.default.useState(!1),f={...n?{border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"}:{display:"flex",border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"},...m?{boxShadow:"var(--dsa-hover-shadow)"}:{}};return(0,p.jsxs)("article",{style:f,onMouseEnter:()=>d(!0),onMouseLeave:()=>d(!1),onClick:r,role:"link",tabIndex:0,onKeyDown:x=>x.key==="Enter"&&r?.(),children:[s&&e.featured_image_url&&(0,p.jsx)("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:n?{width:"100%",height:"200px",objectFit:"cover",display:"block"}:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},loading:"lazy"}),(0,p.jsxs)("div",{style:n?{padding:"1.25rem"}:{padding:"1.25rem",flex:1},children:[(0,p.jsx)("h3",{style:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"var(--dsa-text)"},children:e.title}),a&&e.excerpt&&(0,p.jsx)("p",{style:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"var(--dsa-text-muted)",lineHeight:1.5},children:e.excerpt}),o&&(0,p.jsxs)("div",{style:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"var(--dsa-text-faint)",flexWrap:"wrap"},children:[e.pillar_name&&(0,p.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.pillar_name}),e.content_type&&(0,p.jsx)("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,p.jsxs)("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,p.jsx)("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function w({articles:e,layout:t="grid",columns:a=3,showExcerpt:s=!0,showImage:o=!0,showMeta:r=!0,onArticleClick:n,className:m,theme:d="light",renderArticle:f}){let x=t==="grid"?`repeat(${a}, 1fr)`:"1fr",v=d!=="inherit"?te[d]:{};return(0,p.jsx)("div",{className:m,style:{display:"grid",gap:"1.5rem",gridTemplateColumns:x,...v},children:(e??[]).map(l=>f?(0,p.jsx)(C.default.Fragment,{children:f(l)},l.id):(0,p.jsx)(ae,{article:l,layout:t,showExcerpt:s,showImage:o,showMeta:r,onClick:()=>n?.(l.slug)},l.id))})}var M=I(require("react"));var B=require("react"),g=require("react/jsx-runtime"),re={light:{"--dsa-text":"#111827","--dsa-text-muted":"#4b5563","--dsa-text-faint":"#9ca3af","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#d1d5db","--dsa-text-faint":"#6b7280","--dsa-divider":"#374151"}};function se({item:e,collapsible:t,defaultOpen:a}){let[s,o]=(0,B.useState)(a);return t?(0,g.jsxs)("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[(0,g.jsxs)("button",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",fontFamily:"inherit"},onClick:()=>o(!s),"aria-expanded":s,children:[(0,g.jsx)("span",{children:e.question}),(0,g.jsx)("span",{style:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"var(--dsa-text-faint)",transform:s?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),s&&(0,g.jsx)("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6,paddingTop:"0.5rem"},children:e.answer})]}):(0,g.jsxs)("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[(0,g.jsx)("p",{style:{fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",margin:"0 0 0.5rem"},children:e.question}),(0,g.jsx)("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6},children:e.answer})]})}function _({items:e,collapsible:t=!0,defaultOpen:a=!1,className:s,title:o="Frequently Asked Questions",theme:r="light"}){if(!e||e.length===0)return null;let n=r!=="inherit"?re[r]:{},m={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(d=>({"@type":"Question",name:d.question,acceptedAnswer:{"@type":"Answer",text:d.answer}}))};return(0,g.jsxs)("section",{className:s,style:{marginTop:"1rem",...n},children:[(0,g.jsx)("h2",{style:{fontSize:"1.5rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:o}),e.map((d,f)=>(0,g.jsx)(se,{item:d,collapsible:t,defaultOpen:a},f)),(0,g.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(m)}})]})}var u=require("react/jsx-runtime"),oe={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151"}};function A({articles:e,title:t="Related Articles",limit:a=3,onArticleClick:s,className:o,theme:r="light"}){let n=(e??[]).slice(0,a);if(n.length===0)return null;let m=r!=="inherit"?oe[r]:{};return(0,u.jsxs)("section",{className:o,style:{marginTop:"1rem",...m},children:[(0,u.jsx)("h3",{style:{fontSize:"1.25rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:t}),(0,u.jsx)("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},children:n.map(d=>(0,u.jsxs)("div",{style:{border:"1px solid var(--dsa-card-border)",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"var(--dsa-card-bg)"},onClick:()=>s?.(d.slug),role:"link",tabIndex:0,onKeyDown:f=>f.key==="Enter"&&s?.(d.slug),children:[d.featured_image_url&&(0,u.jsx)("img",{src:d.featured_image_url,alt:d.featured_image_alt||d.title,style:{width:"100%",height:"140px",objectFit:"cover",display:"block"},loading:"lazy"}),(0,u.jsxs)("div",{style:{padding:"1rem"},children:[(0,u.jsx)("h4",{style:{fontSize:"0.9375rem",fontWeight:600,color:"var(--dsa-text)",margin:0,lineHeight:1.3},children:d.title}),d.excerpt&&(0,u.jsx)("p",{style:{fontSize:"0.8125rem",color:"var(--dsa-text-muted)",marginTop:"0.5rem",lineHeight:1.4},children:d.excerpt})]})]},d.id))})]})}var i=require("react/jsx-runtime"),ie={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-toc-bg":"#f9fafb","--dsa-badge-bg":"#eff6ff","--dsa-badge-text":"#2563eb","--dsa-badge-alt-bg":"#f0fdf4","--dsa-badge-alt-text":"#16a34a","--dsa-content-text":"#374151","--dsa-h2-text":"#111827","--dsa-h3-text":"#1f2937","--dsa-h4-text":"#1f2937","--dsa-link":"#2563eb","--dsa-link-hover":"#1d4ed8","--dsa-blockquote-border":"#d1d5db","--dsa-blockquote-text":"#4b5563","--dsa-pre-bg":"#f3f4f6","--dsa-table-border":"#e5e7eb","--dsa-table-header-bg":"#f9fafb","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-toc-bg":"#111827","--dsa-badge-bg":"#1e3a5f","--dsa-badge-text":"#93c5fd","--dsa-badge-alt-bg":"#14532d","--dsa-badge-alt-text":"#86efac","--dsa-content-text":"#d1d5db","--dsa-h2-text":"#f3f4f6","--dsa-h3-text":"#e5e7eb","--dsa-h4-text":"#e5e7eb","--dsa-link":"#60a5fa","--dsa-link-hover":"#93c5fd","--dsa-blockquote-border":"#4b5563","--dsa-blockquote-text":"#9ca3af","--dsa-pre-bg":"#111827","--dsa-table-border":"#374151","--dsa-table-header-bg":"#111827","--dsa-divider":"#374151"}},H="dsa-article-prose",ne=`
|
|
3
|
+
[data-dsa-article-body] h2 { font-size: 1.5rem; font-weight: 700; line-height: 1.3; color: var(--dsa-h2-text, #111827); margin: 2rem 0 0.75rem; }
|
|
4
|
+
[data-dsa-article-body] h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h3-text, #1f2937); margin: 1.75rem 0 0.5rem; }
|
|
5
|
+
[data-dsa-article-body] h4 { font-size: 1.125rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h4-text, #1f2937); margin: 1.5rem 0 0.5rem; }
|
|
6
|
+
[data-dsa-article-body] p { margin: 0 0 1.25rem; }
|
|
7
|
+
[data-dsa-article-body] ul,
|
|
8
|
+
[data-dsa-article-body] ol { margin: 0 0 1.25rem; padding-left: 1.5rem; }
|
|
9
|
+
[data-dsa-article-body] li { margin: 0 0 0.375rem; }
|
|
10
|
+
[data-dsa-article-body] li > ul,
|
|
11
|
+
[data-dsa-article-body] li > ol { margin: 0.375rem 0 0; }
|
|
12
|
+
[data-dsa-article-body] blockquote { margin: 1.5rem 0; padding: 0.75rem 1.25rem; border-left: 4px solid var(--dsa-blockquote-border, #d1d5db); color: var(--dsa-blockquote-text, #4b5563); font-style: italic; }
|
|
13
|
+
[data-dsa-article-body] pre { margin: 1.5rem 0; padding: 1rem; background: var(--dsa-pre-bg, #f3f4f6); border-radius: 0.5rem; overflow-x: auto; font-size: 0.875rem; }
|
|
14
|
+
[data-dsa-article-body] code { font-size: 0.875em; }
|
|
15
|
+
[data-dsa-article-body] table { width: 100%; margin: 1.5rem 0; border-collapse: collapse; font-size: 0.9375rem; }
|
|
16
|
+
[data-dsa-article-body] th,
|
|
17
|
+
[data-dsa-article-body] td { padding: 0.5rem 0.75rem; border: 1px solid var(--dsa-table-border, #e5e7eb); text-align: left; }
|
|
18
|
+
[data-dsa-article-body] th { background: var(--dsa-table-header-bg, #f9fafb); font-weight: 600; }
|
|
19
|
+
[data-dsa-article-body] img { max-width: 100%; height: auto; border-radius: 0.5rem; margin: 1.5rem 0; }
|
|
20
|
+
[data-dsa-article-body] a { color: var(--dsa-link, #2563eb); text-decoration: underline; text-underline-offset: 2px; }
|
|
21
|
+
[data-dsa-article-body] a:hover { color: var(--dsa-link-hover, #1d4ed8); }
|
|
22
|
+
[data-dsa-article-body] hr { border: none; border-top: 1px solid var(--dsa-divider, #e5e7eb); margin: 2rem 0; }
|
|
23
|
+
[data-dsa-article-body] > *:first-child { margin-top: 0; }
|
|
24
|
+
[data-dsa-article-body] > *:last-child { margin-bottom: 0; }
|
|
25
|
+
`.trim();function de(e){M.default.useEffect(()=>{if(!e||typeof document>"u"||document.getElementById(H))return;let t=document.createElement("style");t.id=H,t.textContent=ne,document.head.appendChild(t)},[e])}function le({headings:e}){return!e||e.length===0?null:(0,i.jsxs)("nav",{className:"dsa-toc","data-dsa-toc":"",style:{background:"var(--dsa-toc-bg, #f9fafb)",border:"1px solid var(--dsa-card-border, #e5e7eb)",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},children:[(0,i.jsx)("p",{style:{fontSize:"0.875rem",fontWeight:600,color:"var(--dsa-text-muted, #374151)",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Table of Contents"}),(0,i.jsx)("ul",{style:{listStyle:"none",padding:0,margin:0},children:e.map((t,a)=>(0,i.jsx)("li",{style:{padding:"0.25rem 0",paddingLeft:`${(t.level-2)*1}rem`},children:(0,i.jsx)("a",{href:`#${t.id}`,style:{color:"var(--dsa-text-muted, #4b5563)",textDecoration:"none",fontSize:"0.875rem"},children:t.text})},a))})]})}function ce({headings:e}){return!e||e.length===0?null:(0,i.jsxs)("nav",{className:"dsa-toc","data-dsa-toc":"",children:[(0,i.jsx)("p",{className:"dsa-toc-title",children:"Table of Contents"}),(0,i.jsx)("ul",{className:"dsa-toc-list",children:e.map((t,a)=>(0,i.jsx)("li",{className:"dsa-toc-item",style:{paddingLeft:`${(t.level-2)*1}rem`},children:(0,i.jsx)("a",{href:`#${t.id}`,className:"dsa-toc-link",children:t.text})},a))})]})}function R({article:e,showFaq:t=!0,showTableOfContents:a=!0,showMeta:s=!0,showRelated:o=!1,relatedArticles:r,onRelatedClick:n,className:m,contentClassName:d,theme:f="light",disableProseStyles:x=!1,components:v}){let l=f==="inherit";de(!l&&!x);let j=v?.H1||(l?({children:k})=>(0,i.jsx)("h1",{className:"dsa-h1",children:k}):({children:k})=>(0,i.jsx)("h1",{className:"dsa-h1",style:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"var(--dsa-text)",margin:"0 0 1rem"},children:k})),O=v?.Toc||(l?ce:le),W=v?.Faq||_,K=l?{}:ie[f],V=["dsa-article-body",d].filter(Boolean).join(" ");return(0,i.jsxs)("article",{className:m,"data-dsa-theme":f,style:l?void 0:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif",...K},children:[s&&(0,i.jsxs)("div",{className:"dsa-meta","data-dsa-meta":"",style:l?void 0:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"var(--dsa-text-muted)",marginBottom:"1.5rem"},children:[e.pillar_name&&(0,i.jsx)("span",{className:"dsa-badge",style:l?void 0:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",color:"var(--dsa-badge-text)",fontSize:"0.75rem"},children:e.pillar_name}),e.content_type&&(0,i.jsx)("span",{className:"dsa-badge dsa-badge--alt",style:l?void 0:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-alt-bg, var(--dsa-badge-bg))",color:"var(--dsa-badge-alt-text, var(--dsa-badge-text))",fontSize:"0.75rem"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&(0,i.jsxs)("span",{className:"dsa-reading-time",children:[e.reading_time_minutes," min read"]}),e.published_at&&(0,i.jsx)("span",{className:"dsa-published-at",children:new Date(e.published_at).toLocaleDateString()})]}),(0,i.jsx)(j,{children:e.h1||e.title}),e.featured_image_url&&(0,i.jsx)("img",{className:"dsa-featured-image",src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:l?void 0:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"}}),a&&(e.headings??[]).length>0&&(0,i.jsx)(O,{headings:e.headings}),(0,i.jsx)("div",{className:V,"data-dsa-article-body":"",style:l?void 0:{lineHeight:1.75,color:"var(--dsa-content-text)",fontSize:"1.0625rem"},dangerouslySetInnerHTML:{__html:e.content_html}}),t&&(e.faq??[]).length>0&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("hr",{className:"dsa-divider",style:l?void 0:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),(0,i.jsx)(W,{items:e.faq})]}),e.schema_json&&(0,i.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),o&&r&&r.length>0&&(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("hr",{className:"dsa-divider",style:l?void 0:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),(0,i.jsx)(A,{articles:r,onArticleClick:n,theme:f})]})]})}var $=require("react/jsx-runtime");function P(e,t){let a=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,...a?{url:a}:{},...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}}:a?{alternates:{canonical:a}}:{}}}function q({article:e,siteUrl:t}){let a=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,$.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(a)}})}0&&(module.exports={ArticleFeed,ArticlePage,ContentClient,DsaContentProvider,FaqBlock,RelatedArticles,SeoMetaBridge,generateArticleMetadata,useArticle,useArticles,useCategories,useDsaContent,useRelatedArticles});
|
|
3
26
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.tsx","../src/components/ArticlePage.tsx","../src/components/SeoMetaBridge.tsx"],"sourcesContent":["// Types\nexport type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleHeading,\n FaqItem,\n InternalLink,\n Category,\n ClusterInfo,\n PaginatedResponse,\n ArticleFilters,\n SitemapEntry,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n// Client\nexport { ContentClient } from './client';\n\n// Provider + context hook\nexport { DsaContentProvider, useDsaContent } from './provider';\nexport type { DsaContentProviderProps } from './provider';\n\n// Data-fetching hooks\nexport { useArticles, useArticle, useRelatedArticles, useCategories } from './hooks';\n\n// UI Components\nexport {\n ArticleFeed,\n ArticlePage,\n FaqBlock,\n RelatedArticles,\n SeoMetaBridge,\n generateArticleMetadata,\n} from './components';\n\nexport type {\n ArticleFeedProps,\n ArticlePageProps,\n FaqBlockProps,\n RelatedArticlesProps,\n} from './components';\n","import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults. Use \"inherit\" to control via your own CSS vars. */\n theme?: 'light' | 'dark' | 'inherit';\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-badge-bg': '#f3f4f6',\n '--dsa-badge-text': '#4b5563',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.08)',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-badge-bg': '#374151',\n '--dsa-badge-text': '#d1d5db',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.3)',\n },\n} as const;\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n const cardStyle: React.CSSProperties = {\n ...(isGrid\n ? { border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }\n : { display: 'flex', border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }),\n ...(hovered ? { boxShadow: 'var(--dsa-hover-shadow)' } : {}),\n };\n\n return (\n <article\n style={cardStyle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid\n ? { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' }\n : { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 }}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? { padding: '1.25rem' } : { padding: '1.25rem', flex: 1 }}>\n <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: 'var(--dsa-text)' }}>\n {article.title}\n </h3>\n {showExcerpt && article.excerpt && (\n <p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'var(--dsa-text-muted)', lineHeight: 1.5 }}>\n {article.excerpt}\n </p>\n )}\n {showMeta && (\n <div style={{ display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: 'var(--dsa-text-faint)', flexWrap: 'wrap' as const }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables (override in your own CSS for full control):\n * --dsa-text, --dsa-text-muted, --dsa-text-faint,\n * --dsa-card-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n theme = 'light',\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns = layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <div\n className={className}\n style={{ display: 'grid', gap: '1.5rem', gridTemplateColumns, ...vars } as React.CSSProperties}\n >\n {(articles ?? []).map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#4b5563',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#d1d5db',\n '--dsa-text-faint': '#6b7280',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--dsa-text)', margin: '0 0 0.5rem' }}>{item.question}</p>\n <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6 }}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <button\n style={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n cursor: 'pointer', background: 'none', border: 'none', width: '100%',\n textAlign: 'left' as const, padding: '0.5rem 0', fontSize: '1rem',\n fontWeight: 600, color: 'var(--dsa-text)', fontFamily: 'inherit',\n }}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: 'var(--dsa-text-faint)', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n ▼\n </span>\n </button>\n {open && <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6, paddingTop: '0.5rem' }}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with Schema.org FAQPage markup.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n theme = 'light',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h2 style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n },\n} as const;\n\n/**\n * Related articles widget.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n theme = 'light',\n}: RelatedArticlesProps) {\n const displayed = (articles ?? []).slice(0, limit);\n if (displayed.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h3>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={{ border: '1px solid var(--dsa-card-border)', borderRadius: '0.5rem', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.2s', background: 'var(--dsa-card-bg)' }}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={{ width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' }}\n loading=\"lazy\"\n />\n )}\n <div style={{ padding: '1rem' }}>\n <h4 style={{ fontSize: '0.9375rem', fontWeight: 600, color: 'var(--dsa-text)', margin: 0, lineHeight: 1.3 }}>{a.title}</h4>\n {a.excerpt && <p style={{ fontSize: '0.8125rem', color: 'var(--dsa-text-muted)', marginTop: '0.5rem', lineHeight: 1.4 }}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\nexport interface ArticlePageProps {\n article: Article;\n showFaq?: boolean;\n showTableOfContents?: boolean;\n showMeta?: boolean;\n showRelated?: boolean;\n relatedArticles?: ArticleListItem[];\n onRelatedClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults */\n theme?: 'light' | 'dark' | 'inherit';\n components?: {\n H1?: React.ComponentType<{ children: React.ReactNode }>;\n Toc?: React.ComponentType<{ headings: Article['headings'] }>;\n Faq?: React.ComponentType<{ items: FaqItem[] }>;\n };\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-toc-bg': '#f9fafb',\n '--dsa-badge-bg': '#eff6ff',\n '--dsa-badge-text': '#2563eb',\n '--dsa-badge-alt-bg': '#f0fdf4',\n '--dsa-badge-alt-text': '#16a34a',\n '--dsa-content-text': '#374151',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-toc-bg': '#111827',\n '--dsa-badge-bg': '#1e3a5f',\n '--dsa-badge-text': '#93c5fd',\n '--dsa-badge-alt-bg': '#14532d',\n '--dsa-badge-alt-text': '#86efac',\n '--dsa-content-text': '#d1d5db',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav style={{ background: 'var(--dsa-toc-bg, #f9fafb)', border: '1px solid var(--dsa-card-border, #e5e7eb)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' }}>\n <p style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--dsa-text-muted, #374151)', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>\n Table of Contents\n </p>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>\n {headings.map((h, i) => (\n <li key={i} style={{ padding: '0.25rem 0', paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={{ color: 'var(--dsa-text-muted, #4b5563)', textDecoration: 'none', fontSize: '0.875rem' }}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * Full article page with optional TOC, FAQ, related articles, and JSON-LD.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables: --dsa-text, --dsa-text-muted, --dsa-toc-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-content-text, --dsa-divider\n */\nexport function ArticlePage({\n article,\n showFaq = true,\n showTableOfContents = true,\n showMeta = true,\n showRelated = false,\n relatedArticles,\n onRelatedClick,\n className,\n theme = 'light',\n components,\n}: ArticlePageProps) {\n const H1 = components?.H1 || (({ children }: { children: React.ReactNode }) => (\n <h1 style={{ fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{children}</h1>\n ));\n const Toc = components?.Toc || DefaultToc;\n const FaqComponent = components?.Faq || FaqBlock;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <article className={className} style={{ maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif', ...vars } as React.CSSProperties}>\n {showMeta && (\n <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: 'var(--dsa-text-muted)', marginBottom: '1.5rem' }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', color: 'var(--dsa-badge-text)', fontSize: '0.75rem' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-alt-bg, var(--dsa-badge-bg))', color: 'var(--dsa-badge-alt-text, var(--dsa-badge-text))', fontSize: '0.75rem' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n <H1>{article.h1 || article.title}</H1>\n\n {article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={{ width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' }}\n />\n )}\n\n {showTableOfContents && (article.headings ?? []).length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n <div\n style={{ lineHeight: 1.75, color: 'var(--dsa-content-text)', fontSize: '1.0625rem' }}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {showFaq && (article.faq ?? []).length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} theme={theme} />\n </>\n )}\n </article>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["src_exports","__export","ArticleFeed","ArticlePage","ContentClient","DsaContentProvider","FaqBlock","RelatedArticles","SeoMetaBridge","generateArticleMetadata","useArticle","useArticles","useCategories","useDsaContent","useRelatedArticles","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","import_react","import_jsx_runtime","ContentContext","DsaContentProvider","config","children","client","ContentClient","useDsaContent","import_react","useArticles","filters","client","useDsaContent","state","setState","fetch","s","res","err","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","import_react","import_jsx_runtime","themeVars","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","React","cardStyle","e","ArticleFeed","articles","columns","onArticleClick","className","theme","renderArticle","gridTemplateColumns","vars","import_react","import_jsx_runtime","themeVars","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","theme","vars","schemaData","i","import_jsx_runtime","themeVars","RelatedArticles","articles","title","limit","onArticleClick","className","theme","displayed","vars","a","e","import_jsx_runtime","themeVars","DefaultToc","headings","h","i","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","theme","components","H1","children","Toc","FaqComponent","FaqBlock","vars","RelatedArticles","import_jsx_runtime","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/ArticlePage.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.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 ArticleTheme,\n FaqBlockProps,\n RelatedArticlesProps,\n} from './components';\n","import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults. Use \"inherit\" to control via your own CSS vars. */\n theme?: 'light' | 'dark' | 'inherit';\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-badge-bg': '#f3f4f6',\n '--dsa-badge-text': '#4b5563',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.08)',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-badge-bg': '#374151',\n '--dsa-badge-text': '#d1d5db',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.3)',\n },\n} as const;\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n const cardStyle: React.CSSProperties = {\n ...(isGrid\n ? { border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }\n : { display: 'flex', border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }),\n ...(hovered ? { boxShadow: 'var(--dsa-hover-shadow)' } : {}),\n };\n\n return (\n <article\n style={cardStyle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid\n ? { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' }\n : { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 }}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? { padding: '1.25rem' } : { padding: '1.25rem', flex: 1 }}>\n <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: 'var(--dsa-text)' }}>\n {article.title}\n </h3>\n {showExcerpt && article.excerpt && (\n <p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'var(--dsa-text-muted)', lineHeight: 1.5 }}>\n {article.excerpt}\n </p>\n )}\n {showMeta && (\n <div style={{ display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: 'var(--dsa-text-faint)', flexWrap: 'wrap' as const }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables (override in your own CSS for full control):\n * --dsa-text, --dsa-text-muted, --dsa-text-faint,\n * --dsa-card-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n theme = 'light',\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns = layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <div\n className={className}\n style={{ display: 'grid', gap: '1.5rem', gridTemplateColumns, ...vars } as React.CSSProperties}\n >\n {(articles ?? []).map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ArticleTheme = 'light' | 'dark' | 'inherit';\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 /** Extra class(es) on the `<div>` that wraps `content_html`. */\n contentClassName?: string;\n /**\n * `\"light\"` / `\"dark\"` — SDK applies inline styles + CSS vars + built-in prose rules.\n * `\"inherit\"` — SDK applies NO inline styles; only CSS classes and\n * `data-*` attributes are rendered so the host site has full control.\n *\n * In all modes the content body div has:\n * - `className=\"dsa-article-body\"`\n * - `data-dsa-article-body`\n *\n * @default \"light\"\n */\n theme?: ArticleTheme;\n /** Disable built-in prose styles injected via `<style>`. Works only in light/dark themes. */\n disableProseStyles?: boolean;\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\n// ---------------------------------------------------------------------------\n// Theme CSS variable presets\n// ---------------------------------------------------------------------------\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-toc-bg': '#f9fafb',\n '--dsa-badge-bg': '#eff6ff',\n '--dsa-badge-text': '#2563eb',\n '--dsa-badge-alt-bg': '#f0fdf4',\n '--dsa-badge-alt-text': '#16a34a',\n '--dsa-content-text': '#374151',\n '--dsa-h2-text': '#111827',\n '--dsa-h3-text': '#1f2937',\n '--dsa-h4-text': '#1f2937',\n '--dsa-link': '#2563eb',\n '--dsa-link-hover': '#1d4ed8',\n '--dsa-blockquote-border': '#d1d5db',\n '--dsa-blockquote-text': '#4b5563',\n '--dsa-pre-bg': '#f3f4f6',\n '--dsa-table-border': '#e5e7eb',\n '--dsa-table-header-bg': '#f9fafb',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-toc-bg': '#111827',\n '--dsa-badge-bg': '#1e3a5f',\n '--dsa-badge-text': '#93c5fd',\n '--dsa-badge-alt-bg': '#14532d',\n '--dsa-badge-alt-text': '#86efac',\n '--dsa-content-text': '#d1d5db',\n '--dsa-h2-text': '#f3f4f6',\n '--dsa-h3-text': '#e5e7eb',\n '--dsa-h4-text': '#e5e7eb',\n '--dsa-link': '#60a5fa',\n '--dsa-link-hover': '#93c5fd',\n '--dsa-blockquote-border': '#4b5563',\n '--dsa-blockquote-text': '#9ca3af',\n '--dsa-pre-bg': '#111827',\n '--dsa-table-border': '#374151',\n '--dsa-table-header-bg': '#111827',\n '--dsa-divider': '#374151',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Built-in prose stylesheet (injected once via <style>)\n// ---------------------------------------------------------------------------\n\nconst PROSE_STYLE_ID = 'dsa-article-prose';\n\nconst proseCSS = /* css */ `\n[data-dsa-article-body] h2 { font-size: 1.5rem; font-weight: 700; line-height: 1.3; color: var(--dsa-h2-text, #111827); margin: 2rem 0 0.75rem; }\n[data-dsa-article-body] h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h3-text, #1f2937); margin: 1.75rem 0 0.5rem; }\n[data-dsa-article-body] h4 { font-size: 1.125rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h4-text, #1f2937); margin: 1.5rem 0 0.5rem; }\n[data-dsa-article-body] p { margin: 0 0 1.25rem; }\n[data-dsa-article-body] ul,\n[data-dsa-article-body] ol { margin: 0 0 1.25rem; padding-left: 1.5rem; }\n[data-dsa-article-body] li { margin: 0 0 0.375rem; }\n[data-dsa-article-body] li > ul,\n[data-dsa-article-body] li > ol { margin: 0.375rem 0 0; }\n[data-dsa-article-body] blockquote { margin: 1.5rem 0; padding: 0.75rem 1.25rem; border-left: 4px solid var(--dsa-blockquote-border, #d1d5db); color: var(--dsa-blockquote-text, #4b5563); font-style: italic; }\n[data-dsa-article-body] pre { margin: 1.5rem 0; padding: 1rem; background: var(--dsa-pre-bg, #f3f4f6); border-radius: 0.5rem; overflow-x: auto; font-size: 0.875rem; }\n[data-dsa-article-body] code { font-size: 0.875em; }\n[data-dsa-article-body] table { width: 100%; margin: 1.5rem 0; border-collapse: collapse; font-size: 0.9375rem; }\n[data-dsa-article-body] th,\n[data-dsa-article-body] td { padding: 0.5rem 0.75rem; border: 1px solid var(--dsa-table-border, #e5e7eb); text-align: left; }\n[data-dsa-article-body] th { background: var(--dsa-table-header-bg, #f9fafb); font-weight: 600; }\n[data-dsa-article-body] img { max-width: 100%; height: auto; border-radius: 0.5rem; margin: 1.5rem 0; }\n[data-dsa-article-body] a { color: var(--dsa-link, #2563eb); text-decoration: underline; text-underline-offset: 2px; }\n[data-dsa-article-body] a:hover { color: var(--dsa-link-hover, #1d4ed8); }\n[data-dsa-article-body] hr { border: none; border-top: 1px solid var(--dsa-divider, #e5e7eb); margin: 2rem 0; }\n[data-dsa-article-body] > *:first-child { margin-top: 0; }\n[data-dsa-article-body] > *:last-child { margin-bottom: 0; }\n`.trim();\n\nfunction useProseStyles(enabled: boolean) {\n React.useEffect(() => {\n if (!enabled) return;\n if (typeof document === 'undefined') return;\n if (document.getElementById(PROSE_STYLE_ID)) return;\n const el = document.createElement('style');\n el.id = PROSE_STYLE_ID;\n el.textContent = proseCSS;\n document.head.appendChild(el);\n }, [enabled]);\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav className=\"dsa-toc\" data-dsa-toc=\"\" style={{ background: 'var(--dsa-toc-bg, #f9fafb)', border: '1px solid var(--dsa-card-border, #e5e7eb)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' }}>\n <p style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--dsa-text-muted, #374151)', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>\n Table of Contents\n </p>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>\n {headings.map((h, i) => (\n <li key={i} style={{ padding: '0.25rem 0', paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={{ color: 'var(--dsa-text-muted, #4b5563)', textDecoration: 'none', fontSize: '0.875rem' }}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\nfunction InheritToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav className=\"dsa-toc\" data-dsa-toc=\"\">\n <p className=\"dsa-toc-title\">Table of Contents</p>\n <ul className=\"dsa-toc-list\">\n {headings.map((h, i) => (\n <li key={i} className=\"dsa-toc-item\" style={{ paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} className=\"dsa-toc-link\">\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main component\n// ---------------------------------------------------------------------------\n\n/**\n * Full article page with optional TOC, FAQ, related articles, and JSON-LD.\n *\n * **light / dark** — SDK applies inline styles with `--dsa-*` CSS variable\n * overrides, plus built-in prose rules for the article body.\n *\n * **inherit** — SDK renders only semantic HTML with stable CSS classes\n * and `data-*` attributes. No inline styles, no injected `<style>`.\n *\n * ### Stable CSS selectors (always present)\n *\n * | Element | Class | Data attribute |\n * |---------|-------|----------------|\n * | Root | `className` prop | `data-dsa-theme` |\n * | Body | `dsa-article-body` + `contentClassName` | `data-dsa-article-body` |\n * | TOC | `dsa-toc` | `data-dsa-toc` |\n * | Meta | `dsa-meta` | `data-dsa-meta` |\n *\n * ```tsx\n * // Works out of the box\n * <ArticlePage article={article} />\n *\n * // Inherit — host site provides all styles\n * <ArticlePage article={article} theme=\"inherit\" contentClassName=\"prose dark:prose-invert\" />\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 contentClassName,\n theme = 'light',\n disableProseStyles = false,\n components,\n}: ArticlePageProps) {\n const inherit = theme === 'inherit';\n useProseStyles(!inherit && !disableProseStyles);\n\n const H1 = components?.H1 || (inherit\n ? ({ children }: { children: React.ReactNode }) => <h1 className=\"dsa-h1\">{children}</h1>\n : ({ children }: { children: React.ReactNode }) => (\n <h1 className=\"dsa-h1\" style={{ fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{children}</h1>\n ));\n const Toc = components?.Toc || (inherit ? InheritToc : DefaultToc);\n const FaqComponent = components?.Faq || FaqBlock;\n const vars = !inherit ? themeVars[theme] : {};\n const bodyClasses = ['dsa-article-body', contentClassName].filter(Boolean).join(' ');\n\n return (\n <article\n className={className}\n data-dsa-theme={theme}\n style={inherit ? undefined : { maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif', ...vars } as React.CSSProperties}\n >\n {showMeta && (\n <div className=\"dsa-meta\" data-dsa-meta=\"\" style={inherit ? undefined : { display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: 'var(--dsa-text-muted)', marginBottom: '1.5rem' }}>\n {article.pillar_name && (\n <span className=\"dsa-badge\" style={inherit ? undefined : { display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', color: 'var(--dsa-badge-text)', fontSize: '0.75rem' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span className=\"dsa-badge dsa-badge--alt\" style={inherit ? undefined : { display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-alt-bg, var(--dsa-badge-bg))', color: 'var(--dsa-badge-alt-text, var(--dsa-badge-text))', fontSize: '0.75rem' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span className=\"dsa-reading-time\">{article.reading_time_minutes} min read</span>}\n {article.published_at && <span className=\"dsa-published-at\">{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n <H1>{article.h1 || article.title}</H1>\n\n {article.featured_image_url && (\n <img\n className=\"dsa-featured-image\"\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={inherit ? undefined : { width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' }}\n />\n )}\n\n {showTableOfContents && (article.headings ?? []).length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n {/* Article body */}\n <div\n className={bodyClasses}\n data-dsa-article-body=\"\"\n style={inherit ? undefined : { lineHeight: 1.75, color: 'var(--dsa-content-text)', fontSize: '1.0625rem' }}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {showFaq && (article.faq ?? []).length > 0 && (\n <>\n <hr className=\"dsa-divider\" style={inherit ? undefined : { border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr className=\"dsa-divider\" style={inherit ? undefined : { border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} theme={theme} />\n </>\n )}\n </article>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#4b5563',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#d1d5db',\n '--dsa-text-faint': '#6b7280',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--dsa-text)', margin: '0 0 0.5rem' }}>{item.question}</p>\n <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6 }}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <button\n style={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n cursor: 'pointer', background: 'none', border: 'none', width: '100%',\n textAlign: 'left' as const, padding: '0.5rem 0', fontSize: '1rem',\n fontWeight: 600, color: 'var(--dsa-text)', fontFamily: 'inherit',\n }}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: 'var(--dsa-text-faint)', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n ▼\n </span>\n </button>\n {open && <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6, paddingTop: '0.5rem' }}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with Schema.org FAQPage markup.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n theme = 'light',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h2 style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n },\n} as const;\n\n/**\n * Related articles widget.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n theme = 'light',\n}: RelatedArticlesProps) {\n const displayed = (articles ?? []).slice(0, limit);\n if (displayed.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h3>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={{ border: '1px solid var(--dsa-card-border)', borderRadius: '0.5rem', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.2s', background: 'var(--dsa-card-bg)' }}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={{ width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' }}\n loading=\"lazy\"\n />\n )}\n <div style={{ padding: '1rem' }}>\n <h4 style={{ fontSize: '0.9375rem', fontWeight: 600, color: 'var(--dsa-text)', margin: 0, lineHeight: 1.3 }}>{a.title}</h4>\n {a.excerpt && <p style={{ fontSize: '0.8125rem', color: 'var(--dsa-text-muted)', marginTop: '0.5rem', lineHeight: 1.4 }}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["src_exports","__export","ArticleFeed","ArticlePage","ContentClient","DsaContentProvider","FaqBlock","RelatedArticles","SeoMetaBridge","generateArticleMetadata","useArticle","useArticles","useCategories","useDsaContent","useRelatedArticles","__toCommonJS","ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","import_react","import_jsx_runtime","ContentContext","DsaContentProvider","config","children","client","ContentClient","useDsaContent","import_react","useArticles","filters","client","useDsaContent","state","setState","fetch","s","res","err","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","import_react","import_jsx_runtime","themeVars","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","React","cardStyle","e","ArticleFeed","articles","columns","onArticleClick","className","theme","renderArticle","gridTemplateColumns","vars","import_react","import_react","import_jsx_runtime","themeVars","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","theme","vars","schemaData","i","import_jsx_runtime","themeVars","RelatedArticles","articles","title","limit","onArticleClick","className","theme","displayed","vars","a","e","import_jsx_runtime","themeVars","PROSE_STYLE_ID","proseCSS","useProseStyles","enabled","React","el","DefaultToc","headings","h","i","InheritToc","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","contentClassName","theme","disableProseStyles","components","inherit","H1","children","Toc","FaqComponent","FaqBlock","vars","bodyClasses","RelatedArticles","import_jsx_runtime","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
var y=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,r){let o=new URL(`${this.apiUrl}${t}`);r&&Object.entries(r).forEach(([s,l])=>{l!=null&&l!==""&&o.searchParams.set(s,String(l))}),o.searchParams.set("site_key",this.apiKey);let n={method:"GET",headers:{"X-API-Key":this.apiKey},cache:this.cacheStrategy};this.revalidateSeconds&&this.cacheStrategy!=="no-cache"&&(n.next={revalidate:this.revalidateSeconds});let a=await fetch(o.toString(),n);if(!a.ok){let s=await a.text().catch(()=>"");throw new Error(`DSA Content API error ${a.status}: ${s||a.statusText}`)}return a.json()}normalizeArticle(t){return{...t,headings:t.headings??[],faq:t.faq??[],internal_links:t.internal_links??[],secondary_keywords:t.secondary_keywords??[],schema_json:t.schema_json??null,content_json:t.content_json??null}}async getArticles(t){let r=await this.request("/api/public/articles",{page:t?.page,per_page:t?.per_page,pillar:t?.pillar,cluster:t?.cluster,content_type:t?.content_type,search:t?.search});return{items:r.items??r.data??[],total:r.total??0,page:r.page??1,per_page:r.per_page??20,total_pages:r.total_pages??r.pages??1}}async getArticleBySlug(t){let r=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(r)}async getRelatedArticles(t,r=3){let o=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:r});return o.items??(Array.isArray(o)?o:[])}async getCategories(){let t=await this.request("/api/public/categories");return t.items??(Array.isArray(t)?t:[])}async getSitemap(){let t=await this.request("/api/public/sitemap");return t.items??(Array.isArray(t)?t:[])}};import{createContext as U,useContext as B,useMemo as H}from"react";import{jsx as $}from"react/jsx-runtime";var P=U(null);function M({config:e,children:t}){let r=H(()=>new y(e),[e.apiUrl,e.apiKey]);return $(P.Provider,{value:r,children:t})}function u(){let e=B(P);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}import{useState as x,useEffect as v,useCallback as _}from"react";function N(e){let t=u(),[r,o]=x({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),n=_(()=>{o(a=>({...a,loading:!0,error:null})),t.getArticles(e).then(a=>o({articles:a.items,loading:!1,error:null,pagination:{page:a.page,per_page:a.per_page,total:a.total,total_pages:a.total_pages}})).catch(a=>o(s=>({...s,loading:!1,error:a instanceof Error?a:new Error(String(a))})))},[t,e?.page,e?.per_page,e?.pillar,e?.cluster,e?.content_type,e?.search]);return v(()=>{n()},[n]),{...r,refetch:n}}function j(e){let t=u(),[r,o]=x({article:null,loading:!0,error:null}),n=_(()=>{if(!e){o({article:null,loading:!1,error:null});return}o(a=>({...a,loading:!0,error:null})),t.getArticleBySlug(e).then(a=>o({article:a,loading:!1,error:null})).catch(a=>o({article:null,loading:!1,error:a instanceof Error?a:new Error(String(a))}))},[t,e]);return v(()=>{n()},[n]),{...r,refetch:n}}function W(e,t=3){let r=u(),[o,n]=x({articles:[],loading:!0,error:null}),a=_(()=>{if(!e){n({articles:[],loading:!1,error:null});return}n(s=>({...s,loading:!0,error:null})),r.getRelatedArticles(e,t).then(s=>n({articles:s,loading:!1,error:null})).catch(s=>n({articles:[],loading:!1,error:s instanceof Error?s:new Error(String(s))}))},[r,e,t]);return v(()=>{a()},[a]),{...o,refetch:a}}function O(){let e=u(),[t,r]=x({categories:[],loading:!0,error:null}),o=_(()=>{r(n=>({...n,loading:!0,error:null})),e.getCategories().then(n=>r({categories:n,loading:!1,error:null})).catch(n=>r({categories:[],loading:!1,error:n instanceof Error?n:new Error(String(n))}))},[e]);return v(()=>{o()},[o]),{...t,refetch:o}}import F from"react";import{jsx as p,jsxs as A}from"react/jsx-runtime";var K={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-badge-bg":"#f3f4f6","--dsa-badge-text":"#4b5563","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.08)"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-badge-bg":"#374151","--dsa-badge-text":"#d1d5db","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.3)"}};function V({article:e,layout:t,showExcerpt:r,showImage:o,showMeta:n,onClick:a}){let s=t==="grid",[l,i]=F.useState(!1),c={...s?{border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"}:{display:"flex",border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"},...l?{boxShadow:"var(--dsa-hover-shadow)"}:{}};return A("article",{style:c,onMouseEnter:()=>i(!0),onMouseLeave:()=>i(!1),onClick:a,role:"link",tabIndex:0,onKeyDown:h=>h.key==="Enter"&&a?.(),children:[o&&e.featured_image_url&&p("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:s?{width:"100%",height:"200px",objectFit:"cover",display:"block"}:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},loading:"lazy"}),A("div",{style:s?{padding:"1.25rem"}:{padding:"1.25rem",flex:1},children:[p("h3",{style:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"var(--dsa-text)"},children:e.title}),r&&e.excerpt&&p("p",{style:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"var(--dsa-text-muted)",lineHeight:1.5},children:e.excerpt}),n&&A("div",{style:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"var(--dsa-text-faint)",flexWrap:"wrap"},children:[e.pillar_name&&p("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.pillar_name}),e.content_type&&p("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&A("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&p("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function q({articles:e,layout:t="grid",columns:r=3,showExcerpt:o=!0,showImage:n=!0,showMeta:a=!0,onArticleClick:s,className:l,theme:i="light",renderArticle:c}){let h=t==="grid"?`repeat(${r}, 1fr)`:"1fr",k=i!=="inherit"?K[i]:{};return p("div",{className:l,style:{display:"grid",gap:"1.5rem",gridTemplateColumns:h,...k},children:(e??[]).map(m=>c?p(F.Fragment,{children:c(m)},m.id):p(V,{article:m,layout:t,showExcerpt:o,showImage:n,showMeta:a,onClick:()=>s?.(m.slug)},m.id))})}import{useState as G}from"react";import{jsx as g,jsxs as S}from"react/jsx-runtime";var J={light:{"--dsa-text":"#111827","--dsa-text-muted":"#4b5563","--dsa-text-faint":"#9ca3af","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#d1d5db","--dsa-text-faint":"#6b7280","--dsa-divider":"#374151"}};function Q({item:e,collapsible:t,defaultOpen:r}){let[o,n]=G(r);return t?S("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[S("button",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",fontFamily:"inherit"},onClick:()=>n(!o),"aria-expanded":o,children:[g("span",{children:e.question}),g("span",{style:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"var(--dsa-text-faint)",transform:o?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),o&&g("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6,paddingTop:"0.5rem"},children:e.answer})]}):S("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[g("p",{style:{fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",margin:"0 0 0.5rem"},children:e.question}),g("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6},children:e.answer})]})}function C({items:e,collapsible:t=!0,defaultOpen:r=!1,className:o,title:n="Frequently Asked Questions",theme:a="light"}){if(!e||e.length===0)return null;let s=a!=="inherit"?J[a]:{},l={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(i=>({"@type":"Question",name:i.question,acceptedAnswer:{"@type":"Answer",text:i.answer}}))};return S("section",{className:o,style:{marginTop:"1rem",...s},children:[g("h2",{style:{fontSize:"1.5rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:n}),e.map((i,c)=>g(Q,{item:i,collapsible:t,defaultOpen:r},c)),g("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(l)}})]})}import{jsx as b,jsxs as R}from"react/jsx-runtime";var X={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151"}};function w({articles:e,title:t="Related Articles",limit:r=3,onArticleClick:o,className:n,theme:a="light"}){let s=(e??[]).slice(0,r);if(s.length===0)return null;let l=a!=="inherit"?X[a]:{};return R("section",{className:n,style:{marginTop:"1rem",...l},children:[b("h3",{style:{fontSize:"1.25rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:t}),b("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},children:s.map(i=>R("div",{style:{border:"1px solid var(--dsa-card-border)",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"var(--dsa-card-bg)"},onClick:()=>o?.(i.slug),role:"link",tabIndex:0,onKeyDown:c=>c.key==="Enter"&&o?.(i.slug),children:[i.featured_image_url&&b("img",{src:i.featured_image_url,alt:i.featured_image_alt||i.title,style:{width:"100%",height:"140px",objectFit:"cover",display:"block"},loading:"lazy"}),R("div",{style:{padding:"1rem"},children:[b("h4",{style:{fontSize:"0.9375rem",fontWeight:600,color:"var(--dsa-text)",margin:0,lineHeight:1.3},children:i.title}),i.excerpt&&b("p",{style:{fontSize:"0.8125rem",color:"var(--dsa-text-muted)",marginTop:"0.5rem",lineHeight:1.4},children:i.excerpt})]})]},i.id))})]})}import{Fragment as I,jsx as d,jsxs as f}from"react/jsx-runtime";var Y={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-toc-bg":"#f9fafb","--dsa-badge-bg":"#eff6ff","--dsa-badge-text":"#2563eb","--dsa-badge-alt-bg":"#f0fdf4","--dsa-badge-alt-text":"#16a34a","--dsa-content-text":"#374151","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-toc-bg":"#111827","--dsa-badge-bg":"#1e3a5f","--dsa-badge-text":"#93c5fd","--dsa-badge-alt-bg":"#14532d","--dsa-badge-alt-text":"#86efac","--dsa-content-text":"#d1d5db","--dsa-divider":"#374151"}};function Z({headings:e}){return!e||e.length===0?null:f("nav",{style:{background:"var(--dsa-toc-bg, #f9fafb)",border:"1px solid var(--dsa-card-border, #e5e7eb)",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},children:[d("p",{style:{fontSize:"0.875rem",fontWeight:600,color:"var(--dsa-text-muted, #374151)",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Table of Contents"}),d("ul",{style:{listStyle:"none",padding:0,margin:0},children:e.map((t,r)=>d("li",{style:{padding:"0.25rem 0",paddingLeft:`${(t.level-2)*1}rem`},children:d("a",{href:`#${t.id}`,style:{color:"var(--dsa-text-muted, #4b5563)",textDecoration:"none",fontSize:"0.875rem"},children:t.text})},r))})]})}function T({article:e,showFaq:t=!0,showTableOfContents:r=!0,showMeta:o=!0,showRelated:n=!1,relatedArticles:a,onRelatedClick:s,className:l,theme:i="light",components:c}){let h=c?.H1||(({children:E})=>d("h1",{style:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"var(--dsa-text)",margin:"0 0 1rem"},children:E})),k=c?.Toc||Z,m=c?.Faq||C,z=i!=="inherit"?Y[i]:{};return f("article",{className:l,style:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif",...z},children:[o&&f("div",{style:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"var(--dsa-text-muted)",marginBottom:"1.5rem"},children:[e.pillar_name&&d("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",color:"var(--dsa-badge-text)",fontSize:"0.75rem"},children:e.pillar_name}),e.content_type&&d("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-alt-bg, var(--dsa-badge-bg))",color:"var(--dsa-badge-alt-text, var(--dsa-badge-text))",fontSize:"0.75rem"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&f("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&d("span",{children:new Date(e.published_at).toLocaleDateString()})]}),d(h,{children:e.h1||e.title}),e.featured_image_url&&d("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"}}),r&&(e.headings??[]).length>0&&d(k,{headings:e.headings}),d("div",{style:{lineHeight:1.75,color:"var(--dsa-content-text)",fontSize:"1.0625rem"},dangerouslySetInnerHTML:{__html:e.content_html}}),t&&(e.faq??[]).length>0&&f(I,{children:[d("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),d(m,{items:e.faq})]}),e.schema_json&&d("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),n&&a&&a.length>0&&f(I,{children:[d("hr",{style:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),d(w,{articles:a,onArticleClick:s,theme:i})]})]})}import{jsx as ee}from"react/jsx-runtime";function L(e,t){let r=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",openGraph:{title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",type:"article",publishedTime:e.published_at||void 0,modifiedTime:e.updated_at||void 0,...r?{url:r}:{},...e.featured_image_url?{images:[{url:e.featured_image_url,alt:e.featured_image_alt||e.title}]}:{}},twitter:{card:"summary_large_image",title:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",...e.featured_image_url?{images:[e.featured_image_url]}:{}},...e.canonical_url?{alternates:{canonical:e.canonical_url}}:r?{alternates:{canonical:r}}:{}}}function D({article:e,siteUrl:t}){let r=e.schema_json||{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:`${t.replace(/\/+$/,"")}/blog/${e.slug}`}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}};return ee("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(r)}})}export{q as ArticleFeed,T as ArticlePage,y as ContentClient,M as DsaContentProvider,C as FaqBlock,w as RelatedArticles,D as SeoMetaBridge,L as generateArticleMetadata,j as useArticle,N as useArticles,O as useCategories,u as useDsaContent,W as useRelatedArticles};
|
|
2
|
+
var y=class{constructor(t){this.apiUrl=t.apiUrl.replace(/\/+$/,""),this.apiKey=t.apiKey,this.cacheStrategy=t.cacheStrategy==="revalidate"?"default":t.cacheStrategy==="force-cache"?"force-cache":"no-cache",this.revalidateSeconds=t.revalidateSeconds}async request(t,a){let s=new URL(`${this.apiUrl}${t}`);a&&Object.entries(a).forEach(([i,c])=>{c!=null&&c!==""&&s.searchParams.set(i,String(c))}),s.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 r=await fetch(s.toString(),o);if(!r.ok){let i=await r.text().catch(()=>"");throw new Error(`DSA Content API error ${r.status}: ${i||r.statusText}`)}return r.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 a=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:a.items??a.data??[],total:a.total??0,page:a.page??1,per_page:a.per_page??20,total_pages:a.total_pages??a.pages??1}}async getArticleBySlug(t){let a=await this.request(`/api/public/articles/${encodeURIComponent(t)}`);return this.normalizeArticle(a)}async getRelatedArticles(t,a=3){let s=await this.request(`/api/public/articles/${encodeURIComponent(t)}/related`,{limit:a});return s.items??(Array.isArray(s)?s:[])}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 $,useContext as j,useMemo as O}from"react";import{jsx as K}from"react/jsx-runtime";var q=$(null);function W({config:e,children:t}){let a=O(()=>new y(e),[e.apiUrl,e.apiKey]);return K(q.Provider,{value:a,children:t})}function u(){let e=j(q);if(!e)throw new Error("useDsaContent() must be used inside <DsaContentProvider>");return e}import{useState as v,useEffect as _,useCallback as A}from"react";function V(e){let t=u(),[a,s]=v({articles:[],loading:!0,error:null,pagination:{page:1,per_page:10,total:0,total_pages:0}}),o=A(()=>{s(r=>({...r,loading:!0,error:null})),t.getArticles(e).then(r=>s({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=>s(i=>({...i,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 _(()=>{o()},[o]),{...a,refetch:o}}function G(e){let t=u(),[a,s]=v({article:null,loading:!0,error:null}),o=A(()=>{if(!e){s({article:null,loading:!1,error:null});return}s(r=>({...r,loading:!0,error:null})),t.getArticleBySlug(e).then(r=>s({article:r,loading:!1,error:null})).catch(r=>s({article:null,loading:!1,error:r instanceof Error?r:new Error(String(r))}))},[t,e]);return _(()=>{o()},[o]),{...a,refetch:o}}function J(e,t=3){let a=u(),[s,o]=v({articles:[],loading:!0,error:null}),r=A(()=>{if(!e){o({articles:[],loading:!1,error:null});return}o(i=>({...i,loading:!0,error:null})),a.getRelatedArticles(e,t).then(i=>o({articles:i,loading:!1,error:null})).catch(i=>o({articles:[],loading:!1,error:i instanceof Error?i:new Error(String(i))}))},[a,e,t]);return _(()=>{r()},[r]),{...s,refetch:r}}function Q(){let e=u(),[t,a]=v({categories:[],loading:!0,error:null}),s=A(()=>{a(o=>({...o,loading:!0,error:null})),e.getCategories().then(o=>a({categories:o,loading:!1,error:null})).catch(o=>a({categories:[],loading:!1,error:o instanceof Error?o:new Error(String(o))}))},[e]);return _(()=>{s()},[s]),{...t,refetch:s}}import F from"react";import{jsx as m,jsxs as S}from"react/jsx-runtime";var X={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-badge-bg":"#f3f4f6","--dsa-badge-text":"#4b5563","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.08)"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-badge-bg":"#374151","--dsa-badge-text":"#d1d5db","--dsa-hover-shadow":"0 4px 12px rgba(0,0,0,0.3)"}};function Y({article:e,layout:t,showExcerpt:a,showImage:s,showMeta:o,onClick:r}){let i=t==="grid",[c,n]=F.useState(!1),p={...i?{border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"}:{display:"flex",border:"1px solid var(--dsa-card-border)",borderRadius:"0.75rem",overflow:"hidden",background:"var(--dsa-card-bg)",cursor:"pointer",transition:"box-shadow 0.2s"},...c?{boxShadow:"var(--dsa-hover-shadow)"}:{}};return S("article",{style:p,onMouseEnter:()=>n(!0),onMouseLeave:()=>n(!1),onClick:r,role:"link",tabIndex:0,onKeyDown:h=>h.key==="Enter"&&r?.(),children:[s&&e.featured_image_url&&m("img",{src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:i?{width:"100%",height:"200px",objectFit:"cover",display:"block"}:{width:"240px",minHeight:"160px",objectFit:"cover",flexShrink:0},loading:"lazy"}),S("div",{style:i?{padding:"1.25rem"}:{padding:"1.25rem",flex:1},children:[m("h3",{style:{margin:"0 0 0.5rem",fontSize:"1.125rem",fontWeight:600,lineHeight:1.3,color:"var(--dsa-text)"},children:e.title}),a&&e.excerpt&&m("p",{style:{margin:"0 0 0.75rem",fontSize:"0.875rem",color:"var(--dsa-text-muted)",lineHeight:1.5},children:e.excerpt}),o&&S("div",{style:{display:"flex",gap:"0.75rem",fontSize:"0.75rem",color:"var(--dsa-text-faint)",flexWrap:"wrap"},children:[e.pillar_name&&m("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.pillar_name}),e.content_type&&m("span",{style:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",fontSize:"0.75rem",color:"var(--dsa-badge-text)"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&S("span",{children:[e.reading_time_minutes," min read"]}),e.published_at&&m("span",{children:new Date(e.published_at).toLocaleDateString()})]})]})]})}function I({articles:e,layout:t="grid",columns:a=3,showExcerpt:s=!0,showImage:o=!0,showMeta:r=!0,onArticleClick:i,className:c,theme:n="light",renderArticle:p}){let h=t==="grid"?`repeat(${a}, 1fr)`:"1fr",b=n!=="inherit"?X[n]:{};return m("div",{className:c,style:{display:"grid",gap:"1.5rem",gridTemplateColumns:h,...b},children:(e??[]).map(l=>p?m(F.Fragment,{children:p(l)},l.id):m(Y,{article:l,layout:t,showExcerpt:s,showImage:o,showMeta:r,onClick:()=>i?.(l.slug)},l.id))})}import re from"react";import{useState as Z}from"react";import{jsx as g,jsxs as k}from"react/jsx-runtime";var ee={light:{"--dsa-text":"#111827","--dsa-text-muted":"#4b5563","--dsa-text-faint":"#9ca3af","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#d1d5db","--dsa-text-faint":"#6b7280","--dsa-divider":"#374151"}};function te({item:e,collapsible:t,defaultOpen:a}){let[s,o]=Z(a);return t?k("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[k("button",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",cursor:"pointer",background:"none",border:"none",width:"100%",textAlign:"left",padding:"0.5rem 0",fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",fontFamily:"inherit"},onClick:()=>o(!s),"aria-expanded":s,children:[g("span",{children:e.question}),g("span",{style:{flexShrink:0,marginLeft:"1rem",transition:"transform 0.2s",fontSize:"1.25rem",color:"var(--dsa-text-faint)",transform:s?"rotate(180deg)":"rotate(0deg)"},children:"\u25BC"})]}),s&&g("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6,paddingTop:"0.5rem"},children:e.answer})]}):k("div",{style:{borderBottom:"1px solid var(--dsa-divider)",padding:"0.75rem 0"},children:[g("p",{style:{fontSize:"1rem",fontWeight:600,color:"var(--dsa-text)",margin:"0 0 0.5rem"},children:e.question}),g("div",{style:{fontSize:"0.9375rem",color:"var(--dsa-text-muted)",lineHeight:1.6},children:e.answer})]})}function C({items:e,collapsible:t=!0,defaultOpen:a=!1,className:s,title:o="Frequently Asked Questions",theme:r="light"}){if(!e||e.length===0)return null;let i=r!=="inherit"?ee[r]:{},c={"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(n=>({"@type":"Question",name:n.question,acceptedAnswer:{"@type":"Answer",text:n.answer}}))};return k("section",{className:s,style:{marginTop:"1rem",...i},children:[g("h2",{style:{fontSize:"1.5rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:o}),e.map((n,p)=>g(te,{item:n,collapsible:t,defaultOpen:a},p)),g("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(c)}})]})}import{jsx as x,jsxs as P}from"react/jsx-runtime";var ae={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151"}};function w({articles:e,title:t="Related Articles",limit:a=3,onArticleClick:s,className:o,theme:r="light"}){let i=(e??[]).slice(0,a);if(i.length===0)return null;let c=r!=="inherit"?ae[r]:{};return P("section",{className:o,style:{marginTop:"1rem",...c},children:[x("h3",{style:{fontSize:"1.25rem",fontWeight:700,color:"var(--dsa-text)",margin:"0 0 1rem"},children:t}),x("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(250px, 1fr))",gap:"1rem"},children:i.map(n=>P("div",{style:{border:"1px solid var(--dsa-card-border)",borderRadius:"0.5rem",overflow:"hidden",cursor:"pointer",transition:"box-shadow 0.2s",background:"var(--dsa-card-bg)"},onClick:()=>s?.(n.slug),role:"link",tabIndex:0,onKeyDown:p=>p.key==="Enter"&&s?.(n.slug),children:[n.featured_image_url&&x("img",{src:n.featured_image_url,alt:n.featured_image_alt||n.title,style:{width:"100%",height:"140px",objectFit:"cover",display:"block"},loading:"lazy"}),P("div",{style:{padding:"1rem"},children:[x("h4",{style:{fontSize:"0.9375rem",fontWeight:600,color:"var(--dsa-text)",margin:0,lineHeight:1.3},children:n.title}),n.excerpt&&x("p",{style:{fontSize:"0.8125rem",color:"var(--dsa-text-muted)",marginTop:"0.5rem",lineHeight:1.4},children:n.excerpt})]})]},n.id))})]})}import{Fragment as N,jsx as d,jsxs as f}from"react/jsx-runtime";var se={light:{"--dsa-text":"#111827","--dsa-text-muted":"#6b7280","--dsa-text-faint":"#9ca3af","--dsa-card-bg":"#fff","--dsa-card-border":"#e5e7eb","--dsa-toc-bg":"#f9fafb","--dsa-badge-bg":"#eff6ff","--dsa-badge-text":"#2563eb","--dsa-badge-alt-bg":"#f0fdf4","--dsa-badge-alt-text":"#16a34a","--dsa-content-text":"#374151","--dsa-h2-text":"#111827","--dsa-h3-text":"#1f2937","--dsa-h4-text":"#1f2937","--dsa-link":"#2563eb","--dsa-link-hover":"#1d4ed8","--dsa-blockquote-border":"#d1d5db","--dsa-blockquote-text":"#4b5563","--dsa-pre-bg":"#f3f4f6","--dsa-table-border":"#e5e7eb","--dsa-table-header-bg":"#f9fafb","--dsa-divider":"#e5e7eb"},dark:{"--dsa-text":"#f3f4f6","--dsa-text-muted":"#9ca3af","--dsa-text-faint":"#6b7280","--dsa-card-bg":"#1f2937","--dsa-card-border":"#374151","--dsa-toc-bg":"#111827","--dsa-badge-bg":"#1e3a5f","--dsa-badge-text":"#93c5fd","--dsa-badge-alt-bg":"#14532d","--dsa-badge-alt-text":"#86efac","--dsa-content-text":"#d1d5db","--dsa-h2-text":"#f3f4f6","--dsa-h3-text":"#e5e7eb","--dsa-h4-text":"#e5e7eb","--dsa-link":"#60a5fa","--dsa-link-hover":"#93c5fd","--dsa-blockquote-border":"#4b5563","--dsa-blockquote-text":"#9ca3af","--dsa-pre-bg":"#111827","--dsa-table-border":"#374151","--dsa-table-header-bg":"#111827","--dsa-divider":"#374151"}},T="dsa-article-prose",oe=`
|
|
3
|
+
[data-dsa-article-body] h2 { font-size: 1.5rem; font-weight: 700; line-height: 1.3; color: var(--dsa-h2-text, #111827); margin: 2rem 0 0.75rem; }
|
|
4
|
+
[data-dsa-article-body] h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h3-text, #1f2937); margin: 1.75rem 0 0.5rem; }
|
|
5
|
+
[data-dsa-article-body] h4 { font-size: 1.125rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h4-text, #1f2937); margin: 1.5rem 0 0.5rem; }
|
|
6
|
+
[data-dsa-article-body] p { margin: 0 0 1.25rem; }
|
|
7
|
+
[data-dsa-article-body] ul,
|
|
8
|
+
[data-dsa-article-body] ol { margin: 0 0 1.25rem; padding-left: 1.5rem; }
|
|
9
|
+
[data-dsa-article-body] li { margin: 0 0 0.375rem; }
|
|
10
|
+
[data-dsa-article-body] li > ul,
|
|
11
|
+
[data-dsa-article-body] li > ol { margin: 0.375rem 0 0; }
|
|
12
|
+
[data-dsa-article-body] blockquote { margin: 1.5rem 0; padding: 0.75rem 1.25rem; border-left: 4px solid var(--dsa-blockquote-border, #d1d5db); color: var(--dsa-blockquote-text, #4b5563); font-style: italic; }
|
|
13
|
+
[data-dsa-article-body] pre { margin: 1.5rem 0; padding: 1rem; background: var(--dsa-pre-bg, #f3f4f6); border-radius: 0.5rem; overflow-x: auto; font-size: 0.875rem; }
|
|
14
|
+
[data-dsa-article-body] code { font-size: 0.875em; }
|
|
15
|
+
[data-dsa-article-body] table { width: 100%; margin: 1.5rem 0; border-collapse: collapse; font-size: 0.9375rem; }
|
|
16
|
+
[data-dsa-article-body] th,
|
|
17
|
+
[data-dsa-article-body] td { padding: 0.5rem 0.75rem; border: 1px solid var(--dsa-table-border, #e5e7eb); text-align: left; }
|
|
18
|
+
[data-dsa-article-body] th { background: var(--dsa-table-header-bg, #f9fafb); font-weight: 600; }
|
|
19
|
+
[data-dsa-article-body] img { max-width: 100%; height: auto; border-radius: 0.5rem; margin: 1.5rem 0; }
|
|
20
|
+
[data-dsa-article-body] a { color: var(--dsa-link, #2563eb); text-decoration: underline; text-underline-offset: 2px; }
|
|
21
|
+
[data-dsa-article-body] a:hover { color: var(--dsa-link-hover, #1d4ed8); }
|
|
22
|
+
[data-dsa-article-body] hr { border: none; border-top: 1px solid var(--dsa-divider, #e5e7eb); margin: 2rem 0; }
|
|
23
|
+
[data-dsa-article-body] > *:first-child { margin-top: 0; }
|
|
24
|
+
[data-dsa-article-body] > *:last-child { margin-bottom: 0; }
|
|
25
|
+
`.trim();function ie(e){re.useEffect(()=>{if(!e||typeof document>"u"||document.getElementById(T))return;let t=document.createElement("style");t.id=T,t.textContent=oe,document.head.appendChild(t)},[e])}function ne({headings:e}){return!e||e.length===0?null:f("nav",{className:"dsa-toc","data-dsa-toc":"",style:{background:"var(--dsa-toc-bg, #f9fafb)",border:"1px solid var(--dsa-card-border, #e5e7eb)",borderRadius:"0.75rem",padding:"1.25rem",marginBottom:"2rem"},children:[d("p",{style:{fontSize:"0.875rem",fontWeight:600,color:"var(--dsa-text-muted, #374151)",margin:"0 0 0.75rem",textTransform:"uppercase",letterSpacing:"0.05em"},children:"Table of Contents"}),d("ul",{style:{listStyle:"none",padding:0,margin:0},children:e.map((t,a)=>d("li",{style:{padding:"0.25rem 0",paddingLeft:`${(t.level-2)*1}rem`},children:d("a",{href:`#${t.id}`,style:{color:"var(--dsa-text-muted, #4b5563)",textDecoration:"none",fontSize:"0.875rem"},children:t.text})},a))})]})}function de({headings:e}){return!e||e.length===0?null:f("nav",{className:"dsa-toc","data-dsa-toc":"",children:[d("p",{className:"dsa-toc-title",children:"Table of Contents"}),d("ul",{className:"dsa-toc-list",children:e.map((t,a)=>d("li",{className:"dsa-toc-item",style:{paddingLeft:`${(t.level-2)*1}rem`},children:d("a",{href:`#${t.id}`,className:"dsa-toc-link",children:t.text})},a))})]})}function z({article:e,showFaq:t=!0,showTableOfContents:a=!0,showMeta:s=!0,showRelated:o=!1,relatedArticles:r,onRelatedClick:i,className:c,contentClassName:n,theme:p="light",disableProseStyles:h=!1,components:b}){let l=p==="inherit";ie(!l&&!h);let D=b?.H1||(l?({children:R})=>d("h1",{className:"dsa-h1",children:R}):({children:R})=>d("h1",{className:"dsa-h1",style:{fontSize:"2.25rem",fontWeight:700,lineHeight:1.2,color:"var(--dsa-text)",margin:"0 0 1rem"},children:R})),U=b?.Toc||(l?de:ne),B=b?.Faq||C,H=l?{}:se[p],M=["dsa-article-body",n].filter(Boolean).join(" ");return f("article",{className:c,"data-dsa-theme":p,style:l?void 0:{maxWidth:"48rem",margin:"0 auto",fontFamily:"system-ui, -apple-system, sans-serif",...H},children:[s&&f("div",{className:"dsa-meta","data-dsa-meta":"",style:l?void 0:{display:"flex",gap:"1rem",flexWrap:"wrap",fontSize:"0.875rem",color:"var(--dsa-text-muted)",marginBottom:"1.5rem"},children:[e.pillar_name&&d("span",{className:"dsa-badge",style:l?void 0:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-bg)",color:"var(--dsa-badge-text)",fontSize:"0.75rem"},children:e.pillar_name}),e.content_type&&d("span",{className:"dsa-badge dsa-badge--alt",style:l?void 0:{display:"inline-block",padding:"0.125rem 0.5rem",borderRadius:"9999px",background:"var(--dsa-badge-alt-bg, var(--dsa-badge-bg))",color:"var(--dsa-badge-alt-text, var(--dsa-badge-text))",fontSize:"0.75rem"},children:e.content_type.replace(/_/g," ")}),e.reading_time_minutes&&f("span",{className:"dsa-reading-time",children:[e.reading_time_minutes," min read"]}),e.published_at&&d("span",{className:"dsa-published-at",children:new Date(e.published_at).toLocaleDateString()})]}),d(D,{children:e.h1||e.title}),e.featured_image_url&&d("img",{className:"dsa-featured-image",src:e.featured_image_url,alt:e.featured_image_alt||e.title,style:l?void 0:{width:"100%",borderRadius:"0.75rem",marginBottom:"2rem"}}),a&&(e.headings??[]).length>0&&d(U,{headings:e.headings}),d("div",{className:M,"data-dsa-article-body":"",style:l?void 0:{lineHeight:1.75,color:"var(--dsa-content-text)",fontSize:"1.0625rem"},dangerouslySetInnerHTML:{__html:e.content_html}}),t&&(e.faq??[]).length>0&&f(N,{children:[d("hr",{className:"dsa-divider",style:l?void 0:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),d(B,{items:e.faq})]}),e.schema_json&&d("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(e.schema_json)}}),o&&r&&r.length>0&&f(N,{children:[d("hr",{className:"dsa-divider",style:l?void 0:{border:"none",borderTop:"1px solid var(--dsa-divider)",margin:"2.5rem 0"}}),d(w,{articles:r,onArticleClick:i,theme:p})]})]})}import{jsx as le}from"react/jsx-runtime";function L(e,t){let a=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,...a?{url:a}:{},...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}}:a?{alternates:{canonical:a}}:{}}}function E({article:e,siteUrl:t}){let a=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 le("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:JSON.stringify(a)}})}export{I as ArticleFeed,z as ArticlePage,y as ContentClient,W as DsaContentProvider,C as FaqBlock,w as RelatedArticles,E as SeoMetaBridge,L as generateArticleMetadata,G as useArticle,V as useArticles,Q as useCategories,u as useDsaContent,J as useRelatedArticles};
|
|
3
26
|
//# 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 /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults. Use \"inherit\" to control via your own CSS vars. */\n theme?: 'light' | 'dark' | 'inherit';\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-badge-bg': '#f3f4f6',\n '--dsa-badge-text': '#4b5563',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.08)',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-badge-bg': '#374151',\n '--dsa-badge-text': '#d1d5db',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.3)',\n },\n} as const;\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n const cardStyle: React.CSSProperties = {\n ...(isGrid\n ? { border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }\n : { display: 'flex', border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }),\n ...(hovered ? { boxShadow: 'var(--dsa-hover-shadow)' } : {}),\n };\n\n return (\n <article\n style={cardStyle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid\n ? { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' }\n : { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 }}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? { padding: '1.25rem' } : { padding: '1.25rem', flex: 1 }}>\n <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: 'var(--dsa-text)' }}>\n {article.title}\n </h3>\n {showExcerpt && article.excerpt && (\n <p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'var(--dsa-text-muted)', lineHeight: 1.5 }}>\n {article.excerpt}\n </p>\n )}\n {showMeta && (\n <div style={{ display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: 'var(--dsa-text-faint)', flexWrap: 'wrap' as const }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables (override in your own CSS for full control):\n * --dsa-text, --dsa-text-muted, --dsa-text-faint,\n * --dsa-card-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n theme = 'light',\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns = layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <div\n className={className}\n style={{ display: 'grid', gap: '1.5rem', gridTemplateColumns, ...vars } as React.CSSProperties}\n >\n {(articles ?? []).map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#4b5563',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#d1d5db',\n '--dsa-text-faint': '#6b7280',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--dsa-text)', margin: '0 0 0.5rem' }}>{item.question}</p>\n <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6 }}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <button\n style={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n cursor: 'pointer', background: 'none', border: 'none', width: '100%',\n textAlign: 'left' as const, padding: '0.5rem 0', fontSize: '1rem',\n fontWeight: 600, color: 'var(--dsa-text)', fontFamily: 'inherit',\n }}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: 'var(--dsa-text-faint)', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n ▼\n </span>\n </button>\n {open && <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6, paddingTop: '0.5rem' }}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with Schema.org FAQPage markup.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n theme = 'light',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h2 style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n },\n} as const;\n\n/**\n * Related articles widget.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n theme = 'light',\n}: RelatedArticlesProps) {\n const displayed = (articles ?? []).slice(0, limit);\n if (displayed.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h3>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={{ border: '1px solid var(--dsa-card-border)', borderRadius: '0.5rem', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.2s', background: 'var(--dsa-card-bg)' }}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={{ width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' }}\n loading=\"lazy\"\n />\n )}\n <div style={{ padding: '1rem' }}>\n <h4 style={{ fontSize: '0.9375rem', fontWeight: 600, color: 'var(--dsa-text)', margin: 0, lineHeight: 1.3 }}>{a.title}</h4>\n {a.excerpt && <p style={{ fontSize: '0.8125rem', color: 'var(--dsa-text-muted)', marginTop: '0.5rem', lineHeight: 1.4 }}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\nexport interface ArticlePageProps {\n article: Article;\n showFaq?: boolean;\n showTableOfContents?: boolean;\n showMeta?: boolean;\n showRelated?: boolean;\n relatedArticles?: ArticleListItem[];\n onRelatedClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults */\n theme?: 'light' | 'dark' | 'inherit';\n components?: {\n H1?: React.ComponentType<{ children: React.ReactNode }>;\n Toc?: React.ComponentType<{ headings: Article['headings'] }>;\n Faq?: React.ComponentType<{ items: FaqItem[] }>;\n };\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-toc-bg': '#f9fafb',\n '--dsa-badge-bg': '#eff6ff',\n '--dsa-badge-text': '#2563eb',\n '--dsa-badge-alt-bg': '#f0fdf4',\n '--dsa-badge-alt-text': '#16a34a',\n '--dsa-content-text': '#374151',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-toc-bg': '#111827',\n '--dsa-badge-bg': '#1e3a5f',\n '--dsa-badge-text': '#93c5fd',\n '--dsa-badge-alt-bg': '#14532d',\n '--dsa-badge-alt-text': '#86efac',\n '--dsa-content-text': '#d1d5db',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav style={{ background: 'var(--dsa-toc-bg, #f9fafb)', border: '1px solid var(--dsa-card-border, #e5e7eb)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' }}>\n <p style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--dsa-text-muted, #374151)', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>\n Table of Contents\n </p>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>\n {headings.map((h, i) => (\n <li key={i} style={{ padding: '0.25rem 0', paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={{ color: 'var(--dsa-text-muted, #4b5563)', textDecoration: 'none', fontSize: '0.875rem' }}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n/**\n * Full article page with optional TOC, FAQ, related articles, and JSON-LD.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables: --dsa-text, --dsa-text-muted, --dsa-toc-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-content-text, --dsa-divider\n */\nexport function ArticlePage({\n article,\n showFaq = true,\n showTableOfContents = true,\n showMeta = true,\n showRelated = false,\n relatedArticles,\n onRelatedClick,\n className,\n theme = 'light',\n components,\n}: ArticlePageProps) {\n const H1 = components?.H1 || (({ children }: { children: React.ReactNode }) => (\n <h1 style={{ fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{children}</h1>\n ));\n const Toc = components?.Toc || DefaultToc;\n const FaqComponent = components?.Faq || FaqBlock;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <article className={className} style={{ maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif', ...vars } as React.CSSProperties}>\n {showMeta && (\n <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: 'var(--dsa-text-muted)', marginBottom: '1.5rem' }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', color: 'var(--dsa-badge-text)', fontSize: '0.75rem' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-alt-bg, var(--dsa-badge-bg))', color: 'var(--dsa-badge-alt-text, var(--dsa-badge-text))', fontSize: '0.75rem' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n <H1>{article.h1 || article.title}</H1>\n\n {article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={{ width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' }}\n />\n )}\n\n {showTableOfContents && (article.headings ?? []).length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n <div\n style={{ lineHeight: 1.75, color: 'var(--dsa-content-text)', fontSize: '1.0625rem' }}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {showFaq && (article.faq ?? []).length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr style={{ border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} theme={theme} />\n </>\n )}\n </article>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","createContext","useContext","useMemo","jsx","ContentContext","createContext","DsaContentProvider","config","children","client","useMemo","ContentClient","useDsaContent","useContext","useState","useEffect","useCallback","useArticles","filters","client","useDsaContent","state","setState","useState","fetch","useCallback","s","res","err","useEffect","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","React","jsx","jsxs","themeVars","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","cardStyle","e","ArticleFeed","articles","columns","onArticleClick","className","theme","renderArticle","gridTemplateColumns","vars","useState","jsx","jsxs","themeVars","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","theme","vars","schemaData","i","jsx","jsxs","themeVars","RelatedArticles","articles","title","limit","onArticleClick","className","theme","displayed","vars","a","e","Fragment","jsx","jsxs","themeVars","DefaultToc","headings","h","i","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","theme","components","H1","children","Toc","FaqComponent","FaqBlock","vars","RelatedArticles","jsx","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/provider.tsx","../src/hooks.ts","../src/components/ArticleFeed.tsx","../src/components/ArticlePage.tsx","../src/components/FaqBlock.tsx","../src/components/RelatedArticles.tsx","../src/components/SeoMetaBridge.tsx"],"sourcesContent":["import type {\n DsaContentConfig,\n Article,\n ArticleListItem,\n ArticleFilters,\n PaginatedResponse,\n Category,\n SitemapEntry,\n} from './types';\n\n/**\n * ContentClient — HTTP client for DSA Content Engine Public API.\n * Works in both Node.js (SSR) and browser environments.\n */\nexport class ContentClient {\n private apiUrl: string;\n private apiKey: string;\n private cacheStrategy: RequestCache;\n private revalidateSeconds?: number;\n\n constructor(config: DsaContentConfig) {\n this.apiUrl = config.apiUrl.replace(/\\/+$/, '');\n this.apiKey = config.apiKey;\n this.cacheStrategy =\n config.cacheStrategy === 'revalidate'\n ? 'default'\n : config.cacheStrategy === 'force-cache'\n ? 'force-cache'\n : 'no-cache';\n this.revalidateSeconds = config.revalidateSeconds;\n }\n\n private async request<T>(path: string, params?: Record<string, string | number | undefined>): Promise<T> {\n const url = new URL(`${this.apiUrl}${path}`);\n if (params) {\n Object.entries(params).forEach(([k, v]) => {\n if (v !== undefined && v !== null && v !== '') {\n url.searchParams.set(k, String(v));\n }\n });\n }\n url.searchParams.set('site_key', this.apiKey);\n\n const fetchOptions: RequestInit & { next?: { revalidate?: number } } = {\n method: 'GET',\n headers: { 'X-API-Key': this.apiKey },\n cache: this.cacheStrategy,\n };\n\n // Next.js ISR revalidation\n if (this.revalidateSeconds && this.cacheStrategy !== 'no-cache') {\n fetchOptions.next = { revalidate: this.revalidateSeconds };\n }\n\n const res = await fetch(url.toString(), fetchOptions);\n\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`DSA Content API error ${res.status}: ${text || res.statusText}`);\n }\n\n return res.json() as Promise<T>;\n }\n\n /** Normalize article array fields to guarantee they're never null/undefined */\n private normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n }\n\n /** Get paginated list of published articles */\n async getArticles(filters?: ArticleFilters): Promise<PaginatedResponse<ArticleListItem>> {\n const raw = await this.request<Record<string, any>>('/api/public/articles', {\n page: filters?.page,\n per_page: filters?.per_page,\n pillar: filters?.pillar,\n cluster: filters?.cluster,\n content_type: filters?.content_type,\n search: filters?.search,\n });\n return {\n items: raw.items ?? raw.data ?? [],\n total: raw.total ?? 0,\n page: raw.page ?? 1,\n per_page: raw.per_page ?? 20,\n total_pages: raw.total_pages ?? raw.pages ?? 1,\n };\n }\n\n /** Get a single article by slug */\n async getArticleBySlug(slug: string): Promise<Article> {\n const article = await this.request<Article>(`/api/public/articles/${encodeURIComponent(slug)}`);\n return this.normalizeArticle(article);\n }\n\n /** Get related articles for a given slug */\n async getRelatedArticles(slug: string, limit = 3): Promise<ArticleListItem[]> {\n const raw = await this.request<Record<string, any>>(\n `/api/public/articles/${encodeURIComponent(slug)}/related`,\n { limit },\n );\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get all categories (pillars + clusters) with article counts */\n async getCategories(): Promise<Category[]> {\n const raw = await this.request<Record<string, any>>('/api/public/categories');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n\n /** Get sitemap data for all published articles */\n async getSitemap(): Promise<SitemapEntry[]> {\n const raw = await this.request<Record<string, any>>('/api/public/sitemap');\n return raw.items ?? (Array.isArray(raw) ? raw : []);\n }\n}\n","'use client';\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport { ContentClient } from './client';\nimport type { DsaContentConfig } from './types';\n\nconst ContentContext = createContext<ContentClient | null>(null);\n\nexport interface DsaContentProviderProps {\n config: DsaContentConfig;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your app (or a subtree) with DsaContentProvider to enable\n * the useDsaContent() hook and all data-fetching hooks.\n *\n * ```tsx\n * <DsaContentProvider config={{ apiUrl: \"...\", apiKey: \"...\" }}>\n * <App />\n * </DsaContentProvider>\n * ```\n */\nexport function DsaContentProvider({ config, children }: DsaContentProviderProps) {\n const client = useMemo(() => new ContentClient(config), [config.apiUrl, config.apiKey]);\n return <ContentContext.Provider value={client}>{children}</ContentContext.Provider>;\n}\n\n/**\n * Access the ContentClient instance from context.\n * Must be called inside a DsaContentProvider.\n */\nexport function useDsaContent(): ContentClient {\n const client = useContext(ContentContext);\n if (!client) {\n throw new Error('useDsaContent() must be used inside <DsaContentProvider>');\n }\n return client;\n}\n","'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useDsaContent } from './provider';\nimport type {\n ArticleFilters,\n UseArticlesState,\n UseArticleState,\n UseArticleListState,\n UseCategoriesState,\n} from './types';\n\n/**\n * Fetch a paginated list of published articles.\n *\n * ```tsx\n * const { articles, loading, error, pagination } = useArticles({ page: 1, per_page: 10 });\n * ```\n */\nexport function useArticles(filters?: ArticleFilters): UseArticlesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticlesState>({\n articles: [],\n loading: true,\n error: null,\n pagination: { page: 1, per_page: 10, total: 0, total_pages: 0 },\n });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticles(filters)\n .then((res) =>\n setState({\n articles: res.items,\n loading: false,\n error: null,\n pagination: {\n page: res.page,\n per_page: res.per_page,\n total: res.total,\n total_pages: res.total_pages,\n },\n }),\n )\n .catch((err) =>\n setState((s) => ({ ...s, loading: false, error: err instanceof Error ? err : new Error(String(err)) })),\n );\n }, [client, filters?.page, filters?.per_page, filters?.pillar, filters?.cluster, filters?.content_type, filters?.search]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch a single article by slug.\n *\n * ```tsx\n * const { article, loading, error } = useArticle(\"my-article-slug\");\n * ```\n */\nexport function useArticle(slug: string | undefined): UseArticleState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleState>({ article: null, loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ article: null, loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getArticleBySlug(slug)\n .then((article) => setState({ article, loading: false, error: null }))\n .catch((err) =>\n setState({ article: null, loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch related articles for a given slug.\n *\n * ```tsx\n * const { articles, loading } = useRelatedArticles(\"my-article-slug\", 4);\n * ```\n */\nexport function useRelatedArticles(slug: string | undefined, limit = 3): UseArticleListState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseArticleListState>({ articles: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n if (!slug) {\n setState({ articles: [], loading: false, error: null });\n return;\n }\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getRelatedArticles(slug, limit)\n .then((articles) => setState({ articles, loading: false, error: null }))\n .catch((err) =>\n setState({ articles: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client, slug, limit]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n\n/**\n * Fetch all categories (pillars + clusters).\n *\n * ```tsx\n * const { categories, loading } = useCategories();\n * ```\n */\nexport function useCategories(): UseCategoriesState & { refetch: () => void } {\n const client = useDsaContent();\n const [state, setState] = useState<UseCategoriesState>({ categories: [], loading: true, error: null });\n\n const fetch = useCallback(() => {\n setState((s) => ({ ...s, loading: true, error: null }));\n client\n .getCategories()\n .then((categories) => setState({ categories, loading: false, error: null }))\n .catch((err) =>\n setState({ categories: [], loading: false, error: err instanceof Error ? err : new Error(String(err)) }),\n );\n }, [client]);\n\n useEffect(() => { fetch(); }, [fetch]);\n\n return { ...state, refetch: fetch };\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface ArticleFeedProps {\n articles: ArticleListItem[];\n layout?: 'grid' | 'list';\n columns?: 1 | 2 | 3;\n showExcerpt?: boolean;\n showImage?: boolean;\n showMeta?: boolean;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" — sets CSS variable defaults. Use \"inherit\" to control via your own CSS vars. */\n theme?: 'light' | 'dark' | 'inherit';\n renderArticle?: (article: ArticleListItem) => React.ReactNode;\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-badge-bg': '#f3f4f6',\n '--dsa-badge-text': '#4b5563',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.08)',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-badge-bg': '#374151',\n '--dsa-badge-text': '#d1d5db',\n '--dsa-hover-shadow': '0 4px 12px rgba(0,0,0,0.3)',\n },\n} as const;\n\nfunction DefaultCard({\n article,\n layout,\n showExcerpt,\n showImage,\n showMeta,\n onClick,\n}: {\n article: ArticleListItem;\n layout: 'grid' | 'list';\n showExcerpt: boolean;\n showImage: boolean;\n showMeta: boolean;\n onClick?: () => void;\n}) {\n const isGrid = layout === 'grid';\n const [hovered, setHovered] = React.useState(false);\n\n const cardStyle: React.CSSProperties = {\n ...(isGrid\n ? { border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }\n : { display: 'flex', border: '1px solid var(--dsa-card-border)', borderRadius: '0.75rem', overflow: 'hidden', background: 'var(--dsa-card-bg)', cursor: 'pointer', transition: 'box-shadow 0.2s' }),\n ...(hovered ? { boxShadow: 'var(--dsa-hover-shadow)' } : {}),\n };\n\n return (\n <article\n style={cardStyle}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n onClick={onClick}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onClick?.()}\n >\n {showImage && article.featured_image_url && (\n <img\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={isGrid\n ? { width: '100%', height: '200px', objectFit: 'cover' as const, display: 'block' }\n : { width: '240px', minHeight: '160px', objectFit: 'cover' as const, flexShrink: 0 }}\n loading=\"lazy\"\n />\n )}\n <div style={isGrid ? { padding: '1.25rem' } : { padding: '1.25rem', flex: 1 }}>\n <h3 style={{ margin: '0 0 0.5rem', fontSize: '1.125rem', fontWeight: 600, lineHeight: 1.3, color: 'var(--dsa-text)' }}>\n {article.title}\n </h3>\n {showExcerpt && article.excerpt && (\n <p style={{ margin: '0 0 0.75rem', fontSize: '0.875rem', color: 'var(--dsa-text-muted)', lineHeight: 1.5 }}>\n {article.excerpt}\n </p>\n )}\n {showMeta && (\n <div style={{ display: 'flex', gap: '0.75rem', fontSize: '0.75rem', color: 'var(--dsa-text-faint)', flexWrap: 'wrap' as const }}>\n {article.pillar_name && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span style={{ display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', fontSize: '0.75rem', color: 'var(--dsa-badge-text)' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span>{article.reading_time_minutes} min read</span>}\n {article.published_at && <span>{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n </div>\n </article>\n );\n}\n\n/**\n * Renders a grid or list of article cards.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n *\n * CSS variables (override in your own CSS for full control):\n * --dsa-text, --dsa-text-muted, --dsa-text-faint,\n * --dsa-card-bg, --dsa-card-border,\n * --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow\n */\nexport function ArticleFeed({\n articles,\n layout = 'grid',\n columns = 3,\n showExcerpt = true,\n showImage = true,\n showMeta = true,\n onArticleClick,\n className,\n theme = 'light',\n renderArticle,\n}: ArticleFeedProps) {\n const gridTemplateColumns = layout === 'grid' ? `repeat(${columns}, 1fr)` : '1fr';\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <div\n className={className}\n style={{ display: 'grid', gap: '1.5rem', gridTemplateColumns, ...vars } as React.CSSProperties}\n >\n {(articles ?? []).map((article) =>\n renderArticle ? (\n <React.Fragment key={article.id}>{renderArticle(article)}</React.Fragment>\n ) : (\n <DefaultCard\n key={article.id}\n article={article}\n layout={layout}\n showExcerpt={showExcerpt}\n showImage={showImage}\n showMeta={showMeta}\n onClick={() => onArticleClick?.(article.slug)}\n />\n ),\n )}\n </div>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { Article, ArticleListItem, FaqItem } from '../types';\nimport { FaqBlock } from './FaqBlock';\nimport { RelatedArticles } from './RelatedArticles';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ArticleTheme = 'light' | 'dark' | 'inherit';\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 /** Extra class(es) on the `<div>` that wraps `content_html`. */\n contentClassName?: string;\n /**\n * `\"light\"` / `\"dark\"` — SDK applies inline styles + CSS vars + built-in prose rules.\n * `\"inherit\"` — SDK applies NO inline styles; only CSS classes and\n * `data-*` attributes are rendered so the host site has full control.\n *\n * In all modes the content body div has:\n * - `className=\"dsa-article-body\"`\n * - `data-dsa-article-body`\n *\n * @default \"light\"\n */\n theme?: ArticleTheme;\n /** Disable built-in prose styles injected via `<style>`. Works only in light/dark themes. */\n disableProseStyles?: boolean;\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\n// ---------------------------------------------------------------------------\n// Theme CSS variable presets\n// ---------------------------------------------------------------------------\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n '--dsa-toc-bg': '#f9fafb',\n '--dsa-badge-bg': '#eff6ff',\n '--dsa-badge-text': '#2563eb',\n '--dsa-badge-alt-bg': '#f0fdf4',\n '--dsa-badge-alt-text': '#16a34a',\n '--dsa-content-text': '#374151',\n '--dsa-h2-text': '#111827',\n '--dsa-h3-text': '#1f2937',\n '--dsa-h4-text': '#1f2937',\n '--dsa-link': '#2563eb',\n '--dsa-link-hover': '#1d4ed8',\n '--dsa-blockquote-border': '#d1d5db',\n '--dsa-blockquote-text': '#4b5563',\n '--dsa-pre-bg': '#f3f4f6',\n '--dsa-table-border': '#e5e7eb',\n '--dsa-table-header-bg': '#f9fafb',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-text-faint': '#6b7280',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n '--dsa-toc-bg': '#111827',\n '--dsa-badge-bg': '#1e3a5f',\n '--dsa-badge-text': '#93c5fd',\n '--dsa-badge-alt-bg': '#14532d',\n '--dsa-badge-alt-text': '#86efac',\n '--dsa-content-text': '#d1d5db',\n '--dsa-h2-text': '#f3f4f6',\n '--dsa-h3-text': '#e5e7eb',\n '--dsa-h4-text': '#e5e7eb',\n '--dsa-link': '#60a5fa',\n '--dsa-link-hover': '#93c5fd',\n '--dsa-blockquote-border': '#4b5563',\n '--dsa-blockquote-text': '#9ca3af',\n '--dsa-pre-bg': '#111827',\n '--dsa-table-border': '#374151',\n '--dsa-table-header-bg': '#111827',\n '--dsa-divider': '#374151',\n },\n} as const;\n\n// ---------------------------------------------------------------------------\n// Built-in prose stylesheet (injected once via <style>)\n// ---------------------------------------------------------------------------\n\nconst PROSE_STYLE_ID = 'dsa-article-prose';\n\nconst proseCSS = /* css */ `\n[data-dsa-article-body] h2 { font-size: 1.5rem; font-weight: 700; line-height: 1.3; color: var(--dsa-h2-text, #111827); margin: 2rem 0 0.75rem; }\n[data-dsa-article-body] h3 { font-size: 1.25rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h3-text, #1f2937); margin: 1.75rem 0 0.5rem; }\n[data-dsa-article-body] h4 { font-size: 1.125rem; font-weight: 600; line-height: 1.4; color: var(--dsa-h4-text, #1f2937); margin: 1.5rem 0 0.5rem; }\n[data-dsa-article-body] p { margin: 0 0 1.25rem; }\n[data-dsa-article-body] ul,\n[data-dsa-article-body] ol { margin: 0 0 1.25rem; padding-left: 1.5rem; }\n[data-dsa-article-body] li { margin: 0 0 0.375rem; }\n[data-dsa-article-body] li > ul,\n[data-dsa-article-body] li > ol { margin: 0.375rem 0 0; }\n[data-dsa-article-body] blockquote { margin: 1.5rem 0; padding: 0.75rem 1.25rem; border-left: 4px solid var(--dsa-blockquote-border, #d1d5db); color: var(--dsa-blockquote-text, #4b5563); font-style: italic; }\n[data-dsa-article-body] pre { margin: 1.5rem 0; padding: 1rem; background: var(--dsa-pre-bg, #f3f4f6); border-radius: 0.5rem; overflow-x: auto; font-size: 0.875rem; }\n[data-dsa-article-body] code { font-size: 0.875em; }\n[data-dsa-article-body] table { width: 100%; margin: 1.5rem 0; border-collapse: collapse; font-size: 0.9375rem; }\n[data-dsa-article-body] th,\n[data-dsa-article-body] td { padding: 0.5rem 0.75rem; border: 1px solid var(--dsa-table-border, #e5e7eb); text-align: left; }\n[data-dsa-article-body] th { background: var(--dsa-table-header-bg, #f9fafb); font-weight: 600; }\n[data-dsa-article-body] img { max-width: 100%; height: auto; border-radius: 0.5rem; margin: 1.5rem 0; }\n[data-dsa-article-body] a { color: var(--dsa-link, #2563eb); text-decoration: underline; text-underline-offset: 2px; }\n[data-dsa-article-body] a:hover { color: var(--dsa-link-hover, #1d4ed8); }\n[data-dsa-article-body] hr { border: none; border-top: 1px solid var(--dsa-divider, #e5e7eb); margin: 2rem 0; }\n[data-dsa-article-body] > *:first-child { margin-top: 0; }\n[data-dsa-article-body] > *:last-child { margin-bottom: 0; }\n`.trim();\n\nfunction useProseStyles(enabled: boolean) {\n React.useEffect(() => {\n if (!enabled) return;\n if (typeof document === 'undefined') return;\n if (document.getElementById(PROSE_STYLE_ID)) return;\n const el = document.createElement('style');\n el.id = PROSE_STYLE_ID;\n el.textContent = proseCSS;\n document.head.appendChild(el);\n }, [enabled]);\n}\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nfunction DefaultToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav className=\"dsa-toc\" data-dsa-toc=\"\" style={{ background: 'var(--dsa-toc-bg, #f9fafb)', border: '1px solid var(--dsa-card-border, #e5e7eb)', borderRadius: '0.75rem', padding: '1.25rem', marginBottom: '2rem' }}>\n <p style={{ fontSize: '0.875rem', fontWeight: 600, color: 'var(--dsa-text-muted, #374151)', margin: '0 0 0.75rem', textTransform: 'uppercase' as const, letterSpacing: '0.05em' }}>\n Table of Contents\n </p>\n <ul style={{ listStyle: 'none', padding: 0, margin: 0 }}>\n {headings.map((h, i) => (\n <li key={i} style={{ padding: '0.25rem 0', paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} style={{ color: 'var(--dsa-text-muted, #4b5563)', textDecoration: 'none', fontSize: '0.875rem' }}>\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\nfunction InheritToc({ headings }: { headings: Article['headings'] }) {\n if (!headings || headings.length === 0) return null;\n return (\n <nav className=\"dsa-toc\" data-dsa-toc=\"\">\n <p className=\"dsa-toc-title\">Table of Contents</p>\n <ul className=\"dsa-toc-list\">\n {headings.map((h, i) => (\n <li key={i} className=\"dsa-toc-item\" style={{ paddingLeft: `${(h.level - 2) * 1}rem` }}>\n <a href={`#${h.id}`} className=\"dsa-toc-link\">\n {h.text}\n </a>\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main component\n// ---------------------------------------------------------------------------\n\n/**\n * Full article page with optional TOC, FAQ, related articles, and JSON-LD.\n *\n * **light / dark** — SDK applies inline styles with `--dsa-*` CSS variable\n * overrides, plus built-in prose rules for the article body.\n *\n * **inherit** — SDK renders only semantic HTML with stable CSS classes\n * and `data-*` attributes. No inline styles, no injected `<style>`.\n *\n * ### Stable CSS selectors (always present)\n *\n * | Element | Class | Data attribute |\n * |---------|-------|----------------|\n * | Root | `className` prop | `data-dsa-theme` |\n * | Body | `dsa-article-body` + `contentClassName` | `data-dsa-article-body` |\n * | TOC | `dsa-toc` | `data-dsa-toc` |\n * | Meta | `dsa-meta` | `data-dsa-meta` |\n *\n * ```tsx\n * // Works out of the box\n * <ArticlePage article={article} />\n *\n * // Inherit — host site provides all styles\n * <ArticlePage article={article} theme=\"inherit\" contentClassName=\"prose dark:prose-invert\" />\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 contentClassName,\n theme = 'light',\n disableProseStyles = false,\n components,\n}: ArticlePageProps) {\n const inherit = theme === 'inherit';\n useProseStyles(!inherit && !disableProseStyles);\n\n const H1 = components?.H1 || (inherit\n ? ({ children }: { children: React.ReactNode }) => <h1 className=\"dsa-h1\">{children}</h1>\n : ({ children }: { children: React.ReactNode }) => (\n <h1 className=\"dsa-h1\" style={{ fontSize: '2.25rem', fontWeight: 700, lineHeight: 1.2, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{children}</h1>\n ));\n const Toc = components?.Toc || (inherit ? InheritToc : DefaultToc);\n const FaqComponent = components?.Faq || FaqBlock;\n const vars = !inherit ? themeVars[theme] : {};\n const bodyClasses = ['dsa-article-body', contentClassName].filter(Boolean).join(' ');\n\n return (\n <article\n className={className}\n data-dsa-theme={theme}\n style={inherit ? undefined : { maxWidth: '48rem', margin: '0 auto', fontFamily: 'system-ui, -apple-system, sans-serif', ...vars } as React.CSSProperties}\n >\n {showMeta && (\n <div className=\"dsa-meta\" data-dsa-meta=\"\" style={inherit ? undefined : { display: 'flex', gap: '1rem', flexWrap: 'wrap' as const, fontSize: '0.875rem', color: 'var(--dsa-text-muted)', marginBottom: '1.5rem' }}>\n {article.pillar_name && (\n <span className=\"dsa-badge\" style={inherit ? undefined : { display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-bg)', color: 'var(--dsa-badge-text)', fontSize: '0.75rem' }}>\n {article.pillar_name}\n </span>\n )}\n {article.content_type && (\n <span className=\"dsa-badge dsa-badge--alt\" style={inherit ? undefined : { display: 'inline-block', padding: '0.125rem 0.5rem', borderRadius: '9999px', background: 'var(--dsa-badge-alt-bg, var(--dsa-badge-bg))', color: 'var(--dsa-badge-alt-text, var(--dsa-badge-text))', fontSize: '0.75rem' }}>\n {article.content_type.replace(/_/g, ' ')}\n </span>\n )}\n {article.reading_time_minutes && <span className=\"dsa-reading-time\">{article.reading_time_minutes} min read</span>}\n {article.published_at && <span className=\"dsa-published-at\">{new Date(article.published_at).toLocaleDateString()}</span>}\n </div>\n )}\n\n <H1>{article.h1 || article.title}</H1>\n\n {article.featured_image_url && (\n <img\n className=\"dsa-featured-image\"\n src={article.featured_image_url}\n alt={article.featured_image_alt || article.title}\n style={inherit ? undefined : { width: '100%', borderRadius: '0.75rem', marginBottom: '2rem' }}\n />\n )}\n\n {showTableOfContents && (article.headings ?? []).length > 0 && (\n <Toc headings={article.headings} />\n )}\n\n {/* Article body */}\n <div\n className={bodyClasses}\n data-dsa-article-body=\"\"\n style={inherit ? undefined : { lineHeight: 1.75, color: 'var(--dsa-content-text)', fontSize: '1.0625rem' }}\n dangerouslySetInnerHTML={{ __html: article.content_html }}\n />\n\n {showFaq && (article.faq ?? []).length > 0 && (\n <>\n <hr className=\"dsa-divider\" style={inherit ? undefined : { border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <FaqComponent items={article.faq} />\n </>\n )}\n\n {article.schema_json && (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(article.schema_json) }}\n />\n )}\n\n {showRelated && relatedArticles && relatedArticles.length > 0 && (\n <>\n <hr className=\"dsa-divider\" style={inherit ? undefined : { border: 'none', borderTop: '1px solid var(--dsa-divider)', margin: '2.5rem 0' }} />\n <RelatedArticles articles={relatedArticles} onArticleClick={onRelatedClick} theme={theme} />\n </>\n )}\n </article>\n );\n}\n","'use client';\n\nimport React, { useState } from 'react';\nimport type { FaqItem } from '../types';\n\nexport interface FaqBlockProps {\n items: FaqItem[];\n collapsible?: boolean;\n defaultOpen?: boolean;\n className?: string;\n title?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#4b5563',\n '--dsa-text-faint': '#9ca3af',\n '--dsa-divider': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#d1d5db',\n '--dsa-text-faint': '#6b7280',\n '--dsa-divider': '#374151',\n },\n} as const;\n\nfunction FaqItemComponent({\n item,\n collapsible,\n defaultOpen,\n}: {\n item: FaqItem;\n collapsible: boolean;\n defaultOpen: boolean;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n if (!collapsible) {\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <p style={{ fontSize: '1rem', fontWeight: 600, color: 'var(--dsa-text)', margin: '0 0 0.5rem' }}>{item.question}</p>\n <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6 }}>{item.answer}</div>\n </div>\n );\n }\n\n return (\n <div style={{ borderBottom: '1px solid var(--dsa-divider)', padding: '0.75rem 0' }}>\n <button\n style={{\n display: 'flex', justifyContent: 'space-between', alignItems: 'center',\n cursor: 'pointer', background: 'none', border: 'none', width: '100%',\n textAlign: 'left' as const, padding: '0.5rem 0', fontSize: '1rem',\n fontWeight: 600, color: 'var(--dsa-text)', fontFamily: 'inherit',\n }}\n onClick={() => setOpen(!open)}\n aria-expanded={open}\n >\n <span>{item.question}</span>\n <span style={{ flexShrink: 0, marginLeft: '1rem', transition: 'transform 0.2s', fontSize: '1.25rem', color: 'var(--dsa-text-faint)', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}>\n ▼\n </span>\n </button>\n {open && <div style={{ fontSize: '0.9375rem', color: 'var(--dsa-text-muted)', lineHeight: 1.6, paddingTop: '0.5rem' }}>{item.answer}</div>}\n </div>\n );\n}\n\n/**\n * FAQ block with Schema.org FAQPage markup.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function FaqBlock({\n items,\n collapsible = true,\n defaultOpen = false,\n className,\n title = 'Frequently Asked Questions',\n theme = 'light',\n}: FaqBlockProps) {\n if (!items || items.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n const schemaData = {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h2 style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h2>\n {items.map((item, i) => (\n <FaqItemComponent key={i} item={item} collapsible={collapsible} defaultOpen={defaultOpen} />\n ))}\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}\n />\n </section>\n );\n}\n","'use client';\n\nimport React from 'react';\nimport type { ArticleListItem } from '../types';\n\nexport interface RelatedArticlesProps {\n articles: ArticleListItem[];\n title?: string;\n limit?: number;\n onArticleClick?: (slug: string) => void;\n className?: string;\n /** \"light\" | \"dark\" | \"inherit\" */\n theme?: 'light' | 'dark' | 'inherit';\n}\n\nconst themeVars = {\n light: {\n '--dsa-text': '#111827',\n '--dsa-text-muted': '#6b7280',\n '--dsa-card-bg': '#fff',\n '--dsa-card-border': '#e5e7eb',\n },\n dark: {\n '--dsa-text': '#f3f4f6',\n '--dsa-text-muted': '#9ca3af',\n '--dsa-card-bg': '#1f2937',\n '--dsa-card-border': '#374151',\n },\n} as const;\n\n/**\n * Related articles widget.\n * Supports theme=\"light\" | \"dark\" | \"inherit\" via CSS variables.\n */\nexport function RelatedArticles({\n articles,\n title = 'Related Articles',\n limit = 3,\n onArticleClick,\n className,\n theme = 'light',\n}: RelatedArticlesProps) {\n const displayed = (articles ?? []).slice(0, limit);\n if (displayed.length === 0) return null;\n const vars = theme !== 'inherit' ? themeVars[theme] : {};\n\n return (\n <section className={className} style={{ marginTop: '1rem', ...vars } as React.CSSProperties}>\n <h3 style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--dsa-text)', margin: '0 0 1rem' }}>{title}</h3>\n <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))', gap: '1rem' }}>\n {displayed.map((a) => (\n <div\n key={a.id}\n style={{ border: '1px solid var(--dsa-card-border)', borderRadius: '0.5rem', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.2s', background: 'var(--dsa-card-bg)' }}\n onClick={() => onArticleClick?.(a.slug)}\n role=\"link\"\n tabIndex={0}\n onKeyDown={(e) => e.key === 'Enter' && onArticleClick?.(a.slug)}\n >\n {a.featured_image_url && (\n <img\n src={a.featured_image_url}\n alt={a.featured_image_alt || a.title}\n style={{ width: '100%', height: '140px', objectFit: 'cover' as const, display: 'block' }}\n loading=\"lazy\"\n />\n )}\n <div style={{ padding: '1rem' }}>\n <h4 style={{ fontSize: '0.9375rem', fontWeight: 600, color: 'var(--dsa-text)', margin: 0, lineHeight: 1.3 }}>{a.title}</h4>\n {a.excerpt && <p style={{ fontSize: '0.8125rem', color: 'var(--dsa-text-muted)', marginTop: '0.5rem', lineHeight: 1.4 }}>{a.excerpt}</p>}\n </div>\n </div>\n ))}\n </div>\n </section>\n );\n}\n","import type { Article } from '../types';\n\n/**\n * Generate Next.js App Router Metadata object from an Article.\n * Use in page.tsx generateMetadata():\n *\n * ```ts\n * import { generateArticleMetadata } from \"@dsa/content-sdk/server\";\n *\n * export async function generateMetadata({ params }) {\n * const article = await fetchArticleBySlug(config, params.slug);\n * return generateArticleMetadata(article, \"https://example.com\");\n * }\n * ```\n */\nexport function generateArticleMetadata(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n\n return {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n openGraph: {\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n type: 'article',\n publishedTime: article.published_at || undefined,\n modifiedTime: article.updated_at || undefined,\n ...(url ? { url } : {}),\n ...(article.featured_image_url\n ? {\n images: [\n {\n url: article.featured_image_url,\n alt: article.featured_image_alt || article.title,\n },\n ],\n }\n : {}),\n },\n twitter: {\n card: 'summary_large_image',\n title: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n ...(article.featured_image_url ? { images: [article.featured_image_url] } : {}),\n },\n ...(article.canonical_url\n ? { alternates: { canonical: article.canonical_url } }\n : url\n ? { alternates: { canonical: url } }\n : {}),\n };\n}\n\n/**\n * Renders JSON-LD structured data for an article.\n * Include this component in your article page layout for SEO.\n *\n * ```tsx\n * <SeoMetaBridge article={article} siteUrl=\"https://example.com\" />\n * ```\n */\nexport function SeoMetaBridge({\n article,\n siteUrl,\n}: {\n article: Article;\n siteUrl?: string;\n}) {\n const schema = article.schema_json || {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(siteUrl ? { url: `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}` } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n\n return (\n <script\n type=\"application/ld+json\"\n dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}\n />\n );\n}\n"],"mappings":"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","names":["ContentClient","config","path","params","url","k","v","fetchOptions","res","text","article","filters","raw","slug","limit","createContext","useContext","useMemo","jsx","ContentContext","createContext","DsaContentProvider","config","children","client","useMemo","ContentClient","useDsaContent","useContext","useState","useEffect","useCallback","useArticles","filters","client","useDsaContent","state","setState","useState","fetch","useCallback","s","res","err","useEffect","useArticle","slug","article","useRelatedArticles","limit","articles","useCategories","categories","React","jsx","jsxs","themeVars","DefaultCard","article","layout","showExcerpt","showImage","showMeta","onClick","isGrid","hovered","setHovered","cardStyle","e","ArticleFeed","articles","columns","onArticleClick","className","theme","renderArticle","gridTemplateColumns","vars","React","useState","jsx","jsxs","themeVars","FaqItemComponent","item","collapsible","defaultOpen","open","setOpen","FaqBlock","items","className","title","theme","vars","schemaData","i","jsx","jsxs","themeVars","RelatedArticles","articles","title","limit","onArticleClick","className","theme","displayed","vars","a","e","Fragment","jsx","jsxs","themeVars","PROSE_STYLE_ID","proseCSS","useProseStyles","enabled","React","el","DefaultToc","headings","h","i","InheritToc","ArticlePage","article","showFaq","showTableOfContents","showMeta","showRelated","relatedArticles","onRelatedClick","className","contentClassName","theme","disableProseStyles","components","inherit","H1","children","Toc","FaqComponent","FaqBlock","vars","bodyClasses","RelatedArticles","jsx","generateArticleMetadata","article","siteUrl","url","SeoMetaBridge","schema"]}
|
package/package.json
CHANGED