@dsaplatform/content-sdk 1.1.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/headless.d.mts +101 -0
- package/dist/headless.d.ts +101 -0
- package/dist/headless.js +2 -0
- package/dist/headless.js.map +1 -0
- package/dist/headless.mjs +2 -0
- package/dist/headless.mjs.map +1 -0
- package/dist/index.d.mts +57 -19
- package/dist/index.d.ts +57 -19
- 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 +6 -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 |
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full article response from Content Engine
|
|
3
|
+
*/
|
|
4
|
+
interface Article {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
excerpt: string | null;
|
|
9
|
+
content_html: string;
|
|
10
|
+
content_json: any | null;
|
|
11
|
+
h1: string | null;
|
|
12
|
+
meta_title: string | null;
|
|
13
|
+
meta_description: string | null;
|
|
14
|
+
canonical_url?: string | null;
|
|
15
|
+
target_keyword: string | null;
|
|
16
|
+
secondary_keywords: string[];
|
|
17
|
+
headings: ArticleHeading[];
|
|
18
|
+
faq: FaqItem[];
|
|
19
|
+
internal_links: InternalLink[];
|
|
20
|
+
schema_json: any | null;
|
|
21
|
+
featured_image_url: string | null;
|
|
22
|
+
featured_image_alt: string | null;
|
|
23
|
+
publish_status: string;
|
|
24
|
+
published_at: string | null;
|
|
25
|
+
updated_at?: string | null;
|
|
26
|
+
word_count: number | null;
|
|
27
|
+
reading_time_minutes: number | null;
|
|
28
|
+
seo_score: number | null;
|
|
29
|
+
pillar_name?: string;
|
|
30
|
+
cluster_name?: string;
|
|
31
|
+
content_type?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Article heading (from H1-H6 tags)
|
|
35
|
+
*/
|
|
36
|
+
interface ArticleHeading {
|
|
37
|
+
level: number;
|
|
38
|
+
text: string;
|
|
39
|
+
id: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* FAQ item
|
|
43
|
+
*/
|
|
44
|
+
interface FaqItem {
|
|
45
|
+
question: string;
|
|
46
|
+
answer: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Internal link reference
|
|
50
|
+
*/
|
|
51
|
+
interface InternalLink {
|
|
52
|
+
slug: string;
|
|
53
|
+
anchor_text: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Headless utilities — data transformers with zero UI / zero inline styles.
|
|
58
|
+
* Use these to build your own components with shadcn/Tailwind/custom CSS.
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/** TOC entry with indentation level for rendering */
|
|
66
|
+
interface TocEntry {
|
|
67
|
+
id: string;
|
|
68
|
+
text: string;
|
|
69
|
+
level: number;
|
|
70
|
+
/** Depth relative to minimum heading level (0-based) */
|
|
71
|
+
depth: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build TOC data from article headings.
|
|
75
|
+
* Normalizes depth so the shallowest heading is depth=0.
|
|
76
|
+
*/
|
|
77
|
+
declare function buildTocData(headings: ArticleHeading[]): TocEntry[];
|
|
78
|
+
/**
|
|
79
|
+
* Build Schema.org FAQPage JSON-LD object from FAQ items.
|
|
80
|
+
* Returns the object (not stringified) — you inject it into <script type="application/ld+json">.
|
|
81
|
+
*/
|
|
82
|
+
declare function buildFaqSchema(items: FaqItem[]): Record<string, any> | null;
|
|
83
|
+
/**
|
|
84
|
+
* Build Schema.org Article JSON-LD object.
|
|
85
|
+
*/
|
|
86
|
+
declare function buildArticleSchema(article: Article, siteUrl?: string): Record<string, any>;
|
|
87
|
+
/**
|
|
88
|
+
* Normalize an article response — ensures all array fields are arrays, never null.
|
|
89
|
+
*/
|
|
90
|
+
declare function normalizeArticle(article: Article): Article;
|
|
91
|
+
/**
|
|
92
|
+
* Extract plain text from content_html (strip tags).
|
|
93
|
+
* Useful for excerpts, search indexing, reading time calculation.
|
|
94
|
+
*/
|
|
95
|
+
declare function htmlToPlainText(html: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* Calculate reading time from HTML content.
|
|
98
|
+
*/
|
|
99
|
+
declare function calculateReadingTime(html: string, wordsPerMinute?: number): number;
|
|
100
|
+
|
|
101
|
+
export { type Article, type ArticleHeading, type FaqItem, type TocEntry, buildArticleSchema, buildFaqSchema, buildTocData, calculateReadingTime, htmlToPlainText, normalizeArticle };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full article response from Content Engine
|
|
3
|
+
*/
|
|
4
|
+
interface Article {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
excerpt: string | null;
|
|
9
|
+
content_html: string;
|
|
10
|
+
content_json: any | null;
|
|
11
|
+
h1: string | null;
|
|
12
|
+
meta_title: string | null;
|
|
13
|
+
meta_description: string | null;
|
|
14
|
+
canonical_url?: string | null;
|
|
15
|
+
target_keyword: string | null;
|
|
16
|
+
secondary_keywords: string[];
|
|
17
|
+
headings: ArticleHeading[];
|
|
18
|
+
faq: FaqItem[];
|
|
19
|
+
internal_links: InternalLink[];
|
|
20
|
+
schema_json: any | null;
|
|
21
|
+
featured_image_url: string | null;
|
|
22
|
+
featured_image_alt: string | null;
|
|
23
|
+
publish_status: string;
|
|
24
|
+
published_at: string | null;
|
|
25
|
+
updated_at?: string | null;
|
|
26
|
+
word_count: number | null;
|
|
27
|
+
reading_time_minutes: number | null;
|
|
28
|
+
seo_score: number | null;
|
|
29
|
+
pillar_name?: string;
|
|
30
|
+
cluster_name?: string;
|
|
31
|
+
content_type?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Article heading (from H1-H6 tags)
|
|
35
|
+
*/
|
|
36
|
+
interface ArticleHeading {
|
|
37
|
+
level: number;
|
|
38
|
+
text: string;
|
|
39
|
+
id: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* FAQ item
|
|
43
|
+
*/
|
|
44
|
+
interface FaqItem {
|
|
45
|
+
question: string;
|
|
46
|
+
answer: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Internal link reference
|
|
50
|
+
*/
|
|
51
|
+
interface InternalLink {
|
|
52
|
+
slug: string;
|
|
53
|
+
anchor_text: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Headless utilities — data transformers with zero UI / zero inline styles.
|
|
58
|
+
* Use these to build your own components with shadcn/Tailwind/custom CSS.
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/** TOC entry with indentation level for rendering */
|
|
66
|
+
interface TocEntry {
|
|
67
|
+
id: string;
|
|
68
|
+
text: string;
|
|
69
|
+
level: number;
|
|
70
|
+
/** Depth relative to minimum heading level (0-based) */
|
|
71
|
+
depth: number;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Build TOC data from article headings.
|
|
75
|
+
* Normalizes depth so the shallowest heading is depth=0.
|
|
76
|
+
*/
|
|
77
|
+
declare function buildTocData(headings: ArticleHeading[]): TocEntry[];
|
|
78
|
+
/**
|
|
79
|
+
* Build Schema.org FAQPage JSON-LD object from FAQ items.
|
|
80
|
+
* Returns the object (not stringified) — you inject it into <script type="application/ld+json">.
|
|
81
|
+
*/
|
|
82
|
+
declare function buildFaqSchema(items: FaqItem[]): Record<string, any> | null;
|
|
83
|
+
/**
|
|
84
|
+
* Build Schema.org Article JSON-LD object.
|
|
85
|
+
*/
|
|
86
|
+
declare function buildArticleSchema(article: Article, siteUrl?: string): Record<string, any>;
|
|
87
|
+
/**
|
|
88
|
+
* Normalize an article response — ensures all array fields are arrays, never null.
|
|
89
|
+
*/
|
|
90
|
+
declare function normalizeArticle(article: Article): Article;
|
|
91
|
+
/**
|
|
92
|
+
* Extract plain text from content_html (strip tags).
|
|
93
|
+
* Useful for excerpts, search indexing, reading time calculation.
|
|
94
|
+
*/
|
|
95
|
+
declare function htmlToPlainText(html: string): string;
|
|
96
|
+
/**
|
|
97
|
+
* Calculate reading time from HTML content.
|
|
98
|
+
*/
|
|
99
|
+
declare function calculateReadingTime(html: string, wordsPerMinute?: number): number;
|
|
100
|
+
|
|
101
|
+
export { type Article, type ArticleHeading, type FaqItem, type TocEntry, buildArticleSchema, buildFaqSchema, buildTocData, calculateReadingTime, htmlToPlainText, normalizeArticle };
|
package/dist/headless.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var i=Object.defineProperty;var d=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var l=Object.prototype.hasOwnProperty;var u=(e,t)=>{for(var n in t)i(e,n,{get:t[n],enumerable:!0})},c=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of a(t))!l.call(e,r)&&r!==n&&i(e,r,{get:()=>t[r],enumerable:!(o=d(t,r))||o.enumerable});return e};var p=e=>c(i({},"__esModule",{value:!0}),e);var f={};u(f,{buildArticleSchema:()=>_,buildFaqSchema:()=>g,buildTocData:()=>m,calculateReadingTime:()=>h,htmlToPlainText:()=>s,normalizeArticle:()=>y});module.exports=p(f);function m(e){if(!e||e.length===0)return[];let t=Math.min(...e.map(n=>n.level));return e.map(n=>({id:n.id,text:n.text,level:n.level,depth:n.level-t}))}function g(e){return!e||e.length===0?null:{"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(t=>({"@type":"Question",name:t.question,acceptedAnswer:{"@type":"Answer",text:t.answer}}))}}function _(e,t){if(e.schema_json&&Object.keys(e.schema_json).length>0)return e.schema_json;let n=t?`${t.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...n?{url:n}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}}}function y(e){return{...e,headings:e.headings??[],faq:e.faq??[],internal_links:e.internal_links??[],secondary_keywords:e.secondary_keywords??[],schema_json:e.schema_json??null,content_json:e.content_json??null}}function s(e){return e.replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim()}function h(e,t=200){let o=s(e).split(/\s+/).filter(Boolean).length;return Math.max(1,Math.ceil(o/t))}0&&(module.exports={buildArticleSchema,buildFaqSchema,buildTocData,calculateReadingTime,htmlToPlainText,normalizeArticle});
|
|
2
|
+
//# sourceMappingURL=headless.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/headless.ts"],"sourcesContent":["/**\n * Headless utilities — data transformers with zero UI / zero inline styles.\n * Use these to build your own components with shadcn/Tailwind/custom CSS.\n *\n * ```ts\n * import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';\n * ```\n */\nimport type { Article, ArticleHeading, FaqItem } from './types';\n\nexport type { Article, ArticleHeading, FaqItem };\n\n/** TOC entry with indentation level for rendering */\nexport interface TocEntry {\n id: string;\n text: string;\n level: number;\n /** Depth relative to minimum heading level (0-based) */\n depth: number;\n}\n\n/**\n * Build TOC data from article headings.\n * Normalizes depth so the shallowest heading is depth=0.\n */\nexport function buildTocData(headings: ArticleHeading[]): TocEntry[] {\n if (!headings || headings.length === 0) return [];\n const minLevel = Math.min(...headings.map((h) => h.level));\n return headings.map((h) => ({\n id: h.id,\n text: h.text,\n level: h.level,\n depth: h.level - minLevel,\n }));\n}\n\n/**\n * Build Schema.org FAQPage JSON-LD object from FAQ items.\n * Returns the object (not stringified) — you inject it into <script type=\"application/ld+json\">.\n */\nexport function buildFaqSchema(items: FaqItem[]): Record<string, any> | null {\n if (!items || items.length === 0) return null;\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n}\n\n/**\n * Build Schema.org Article JSON-LD object.\n */\nexport function buildArticleSchema(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n if (article.schema_json && Object.keys(article.schema_json).length > 0) {\n return article.schema_json;\n }\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(url ? { url } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n}\n\n/**\n * Normalize an article response — ensures all array fields are arrays, never null.\n */\nexport function normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n}\n\n/**\n * Extract plain text from content_html (strip tags).\n * Useful for excerpts, search indexing, reading time calculation.\n */\nexport function htmlToPlainText(html: string): string {\n return html.replace(/<[^>]+>/g, '').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Calculate reading time from HTML content.\n */\nexport function calculateReadingTime(html: string, wordsPerMinute = 200): number {\n const text = htmlToPlainText(html);\n const words = text.split(/\\s+/).filter(Boolean).length;\n return Math.max(1, Math.ceil(words / wordsPerMinute));\n}\n"],"mappings":"yaAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,wBAAAE,EAAA,mBAAAC,EAAA,iBAAAC,EAAA,yBAAAC,EAAA,oBAAAC,EAAA,qBAAAC,IAAA,eAAAC,EAAAR,GAyBO,SAASI,EAAaK,EAAwC,CACnE,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,CAAC,EAChD,IAAMC,EAAW,KAAK,IAAI,GAAGD,EAAS,IAAKE,GAAMA,EAAE,KAAK,CAAC,EACzD,OAAOF,EAAS,IAAKE,IAAO,CAC1B,GAAIA,EAAE,GACN,KAAMA,EAAE,KACR,MAAOA,EAAE,MACT,MAAOA,EAAE,MAAQD,CACnB,EAAE,CACJ,CAMO,SAASP,EAAeS,EAA8C,CAC3E,MAAI,CAACA,GAASA,EAAM,SAAW,EAAU,KAClC,CACL,WAAY,qBACZ,QAAS,UACT,WAAYA,EAAM,IAAKC,IAAU,CAC/B,QAAS,WACT,KAAMA,EAAK,SACX,eAAgB,CAAE,QAAS,SAAU,KAAMA,EAAK,MAAO,CACzD,EAAE,CACJ,CACF,CAKO,SAASX,EACdY,EACAC,EACqB,CACrB,GAAID,EAAQ,aAAe,OAAO,KAAKA,EAAQ,WAAW,EAAE,OAAS,EACnE,OAAOA,EAAQ,YAEjB,IAAME,EAAMD,EACR,GAAGA,EAAQ,QAAQ,OAAQ,EAAE,CAAC,SAASD,EAAQ,IAAI,GACnD,OACJ,MAAO,CACL,WAAY,qBACZ,QAAS,UACT,SAAUA,EAAQ,YAAcA,EAAQ,MACxC,YAAaA,EAAQ,kBAAoBA,EAAQ,SAAW,GAC5D,cAAeA,EAAQ,cAAgB,OACvC,aAAcA,EAAQ,YAAcA,EAAQ,cAAgB,OAC5D,GAAIA,EAAQ,mBAAqB,CAAE,MAAOA,EAAQ,kBAAmB,EAAI,CAAC,EAC1E,GAAIE,EAAM,CAAE,IAAAA,CAAI,EAAI,CAAC,EACrB,GAAIF,EAAQ,eACR,CAAE,SAAU,CAACA,EAAQ,eAAgB,GAAIA,EAAQ,oBAAsB,CAAC,CAAE,EAAE,KAAK,IAAI,CAAE,EACvF,CAAC,CACP,CACF,CAKO,SAASP,EAAiBO,EAA2B,CAC1D,MAAO,CACL,GAAGA,EACH,SAAUA,EAAQ,UAAY,CAAC,EAC/B,IAAKA,EAAQ,KAAO,CAAC,EACrB,eAAgBA,EAAQ,gBAAkB,CAAC,EAC3C,mBAAoBA,EAAQ,oBAAsB,CAAC,EACnD,YAAaA,EAAQ,aAAe,KACpC,aAAcA,EAAQ,cAAgB,IACxC,CACF,CAMO,SAASR,EAAgBW,EAAsB,CACpD,OAAOA,EAAK,QAAQ,WAAY,EAAE,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,CAChE,CAKO,SAASZ,EAAqBY,EAAcC,EAAiB,IAAa,CAE/E,IAAMC,EADOb,EAAgBW,CAAI,EACd,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,OAChD,OAAO,KAAK,IAAI,EAAG,KAAK,KAAKE,EAAQD,CAAc,CAAC,CACtD","names":["headless_exports","__export","buildArticleSchema","buildFaqSchema","buildTocData","calculateReadingTime","htmlToPlainText","normalizeArticle","__toCommonJS","headings","minLevel","h","items","item","article","siteUrl","url","html","wordsPerMinute","words"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function i(e){if(!e||e.length===0)return[];let n=Math.min(...e.map(t=>t.level));return e.map(t=>({id:t.id,text:t.text,level:t.level,depth:t.level-n}))}function s(e){return!e||e.length===0?null:{"@context":"https://schema.org","@type":"FAQPage",mainEntity:e.map(n=>({"@type":"Question",name:n.question,acceptedAnswer:{"@type":"Answer",text:n.answer}}))}}function d(e,n){if(e.schema_json&&Object.keys(e.schema_json).length>0)return e.schema_json;let t=n?`${n.replace(/\/+$/,"")}/blog/${e.slug}`:void 0;return{"@context":"https://schema.org","@type":"Article",headline:e.meta_title||e.title,description:e.meta_description||e.excerpt||"",datePublished:e.published_at||void 0,dateModified:e.updated_at||e.published_at||void 0,...e.featured_image_url?{image:e.featured_image_url}:{},...t?{url:t}:{},...e.target_keyword?{keywords:[e.target_keyword,...e.secondary_keywords||[]].join(", ")}:{}}}function a(e){return{...e,headings:e.headings??[],faq:e.faq??[],internal_links:e.internal_links??[],secondary_keywords:e.secondary_keywords??[],schema_json:e.schema_json??null,content_json:e.content_json??null}}function o(e){return e.replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim()}function l(e,n=200){let r=o(e).split(/\s+/).filter(Boolean).length;return Math.max(1,Math.ceil(r/n))}export{d as buildArticleSchema,s as buildFaqSchema,i as buildTocData,l as calculateReadingTime,o as htmlToPlainText,a as normalizeArticle};
|
|
2
|
+
//# sourceMappingURL=headless.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/headless.ts"],"sourcesContent":["/**\n * Headless utilities — data transformers with zero UI / zero inline styles.\n * Use these to build your own components with shadcn/Tailwind/custom CSS.\n *\n * ```ts\n * import { buildTocData, buildFaqSchema, normalizeArticle } from '@dsaplatform/content-sdk/headless';\n * ```\n */\nimport type { Article, ArticleHeading, FaqItem } from './types';\n\nexport type { Article, ArticleHeading, FaqItem };\n\n/** TOC entry with indentation level for rendering */\nexport interface TocEntry {\n id: string;\n text: string;\n level: number;\n /** Depth relative to minimum heading level (0-based) */\n depth: number;\n}\n\n/**\n * Build TOC data from article headings.\n * Normalizes depth so the shallowest heading is depth=0.\n */\nexport function buildTocData(headings: ArticleHeading[]): TocEntry[] {\n if (!headings || headings.length === 0) return [];\n const minLevel = Math.min(...headings.map((h) => h.level));\n return headings.map((h) => ({\n id: h.id,\n text: h.text,\n level: h.level,\n depth: h.level - minLevel,\n }));\n}\n\n/**\n * Build Schema.org FAQPage JSON-LD object from FAQ items.\n * Returns the object (not stringified) — you inject it into <script type=\"application/ld+json\">.\n */\nexport function buildFaqSchema(items: FaqItem[]): Record<string, any> | null {\n if (!items || items.length === 0) return null;\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n mainEntity: items.map((item) => ({\n '@type': 'Question',\n name: item.question,\n acceptedAnswer: { '@type': 'Answer', text: item.answer },\n })),\n };\n}\n\n/**\n * Build Schema.org Article JSON-LD object.\n */\nexport function buildArticleSchema(\n article: Article,\n siteUrl?: string,\n): Record<string, any> {\n if (article.schema_json && Object.keys(article.schema_json).length > 0) {\n return article.schema_json;\n }\n const url = siteUrl\n ? `${siteUrl.replace(/\\/+$/, '')}/blog/${article.slug}`\n : undefined;\n return {\n '@context': 'https://schema.org',\n '@type': 'Article',\n headline: article.meta_title || article.title,\n description: article.meta_description || article.excerpt || '',\n datePublished: article.published_at || undefined,\n dateModified: article.updated_at || article.published_at || undefined,\n ...(article.featured_image_url ? { image: article.featured_image_url } : {}),\n ...(url ? { url } : {}),\n ...(article.target_keyword\n ? { keywords: [article.target_keyword, ...(article.secondary_keywords || [])].join(', ') }\n : {}),\n };\n}\n\n/**\n * Normalize an article response — ensures all array fields are arrays, never null.\n */\nexport function normalizeArticle(article: Article): Article {\n return {\n ...article,\n headings: article.headings ?? [],\n faq: article.faq ?? [],\n internal_links: article.internal_links ?? [],\n secondary_keywords: article.secondary_keywords ?? [],\n schema_json: article.schema_json ?? null,\n content_json: article.content_json ?? null,\n };\n}\n\n/**\n * Extract plain text from content_html (strip tags).\n * Useful for excerpts, search indexing, reading time calculation.\n */\nexport function htmlToPlainText(html: string): string {\n return html.replace(/<[^>]+>/g, '').replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Calculate reading time from HTML content.\n */\nexport function calculateReadingTime(html: string, wordsPerMinute = 200): number {\n const text = htmlToPlainText(html);\n const words = text.split(/\\s+/).filter(Boolean).length;\n return Math.max(1, Math.ceil(words / wordsPerMinute));\n}\n"],"mappings":"AAyBO,SAASA,EAAaC,EAAwC,CACnE,GAAI,CAACA,GAAYA,EAAS,SAAW,EAAG,MAAO,CAAC,EAChD,IAAMC,EAAW,KAAK,IAAI,GAAGD,EAAS,IAAKE,GAAMA,EAAE,KAAK,CAAC,EACzD,OAAOF,EAAS,IAAKE,IAAO,CAC1B,GAAIA,EAAE,GACN,KAAMA,EAAE,KACR,MAAOA,EAAE,MACT,MAAOA,EAAE,MAAQD,CACnB,EAAE,CACJ,CAMO,SAASE,EAAeC,EAA8C,CAC3E,MAAI,CAACA,GAASA,EAAM,SAAW,EAAU,KAClC,CACL,WAAY,qBACZ,QAAS,UACT,WAAYA,EAAM,IAAKC,IAAU,CAC/B,QAAS,WACT,KAAMA,EAAK,SACX,eAAgB,CAAE,QAAS,SAAU,KAAMA,EAAK,MAAO,CACzD,EAAE,CACJ,CACF,CAKO,SAASC,EACdC,EACAC,EACqB,CACrB,GAAID,EAAQ,aAAe,OAAO,KAAKA,EAAQ,WAAW,EAAE,OAAS,EACnE,OAAOA,EAAQ,YAEjB,IAAME,EAAMD,EACR,GAAGA,EAAQ,QAAQ,OAAQ,EAAE,CAAC,SAASD,EAAQ,IAAI,GACnD,OACJ,MAAO,CACL,WAAY,qBACZ,QAAS,UACT,SAAUA,EAAQ,YAAcA,EAAQ,MACxC,YAAaA,EAAQ,kBAAoBA,EAAQ,SAAW,GAC5D,cAAeA,EAAQ,cAAgB,OACvC,aAAcA,EAAQ,YAAcA,EAAQ,cAAgB,OAC5D,GAAIA,EAAQ,mBAAqB,CAAE,MAAOA,EAAQ,kBAAmB,EAAI,CAAC,EAC1E,GAAIE,EAAM,CAAE,IAAAA,CAAI,EAAI,CAAC,EACrB,GAAIF,EAAQ,eACR,CAAE,SAAU,CAACA,EAAQ,eAAgB,GAAIA,EAAQ,oBAAsB,CAAC,CAAE,EAAE,KAAK,IAAI,CAAE,EACvF,CAAC,CACP,CACF,CAKO,SAASG,EAAiBH,EAA2B,CAC1D,MAAO,CACL,GAAGA,EACH,SAAUA,EAAQ,UAAY,CAAC,EAC/B,IAAKA,EAAQ,KAAO,CAAC,EACrB,eAAgBA,EAAQ,gBAAkB,CAAC,EAC3C,mBAAoBA,EAAQ,oBAAsB,CAAC,EACnD,YAAaA,EAAQ,aAAe,KACpC,aAAcA,EAAQ,cAAgB,IACxC,CACF,CAMO,SAASI,EAAgBC,EAAsB,CACpD,OAAOA,EAAK,QAAQ,WAAY,EAAE,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,CAChE,CAKO,SAASC,EAAqBD,EAAcE,EAAiB,IAAa,CAE/E,IAAMC,EADOJ,EAAgBC,CAAI,EACd,MAAM,KAAK,EAAE,OAAO,OAAO,EAAE,OAChD,OAAO,KAAK,IAAI,EAAG,KAAK,KAAKG,EAAQD,CAAc,CAAC,CACtD","names":["buildTocData","headings","minLevel","h","buildFaqSchema","items","item","buildArticleSchema","article","siteUrl","url","normalizeArticle","htmlToPlainText","html","calculateReadingTime","wordsPerMinute","words"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -272,17 +272,22 @@ interface ArticleFeedProps {
|
|
|
272
272
|
showMeta?: boolean;
|
|
273
273
|
onArticleClick?: (slug: string) => void;
|
|
274
274
|
className?: string;
|
|
275
|
+
/** "light" | "dark" | "inherit" — sets CSS variable defaults. Use "inherit" to control via your own CSS vars. */
|
|
276
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
275
277
|
renderArticle?: (article: ArticleListItem) => React.ReactNode;
|
|
276
278
|
}
|
|
277
279
|
/**
|
|
278
280
|
* Renders a grid or list of article cards.
|
|
281
|
+
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
279
282
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
+
* CSS variables (override in your own CSS for full control):
|
|
284
|
+
* --dsa-text, --dsa-text-muted, --dsa-text-faint,
|
|
285
|
+
* --dsa-card-bg, --dsa-card-border,
|
|
286
|
+
* --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow
|
|
283
287
|
*/
|
|
284
|
-
declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
|
|
288
|
+
declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, theme, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
|
|
285
289
|
|
|
290
|
+
type ArticleTheme = 'light' | 'dark' | 'inherit';
|
|
286
291
|
interface ArticlePageProps {
|
|
287
292
|
article: Article;
|
|
288
293
|
showFaq?: boolean;
|
|
@@ -292,6 +297,22 @@ interface ArticlePageProps {
|
|
|
292
297
|
relatedArticles?: ArticleListItem[];
|
|
293
298
|
onRelatedClick?: (slug: string) => void;
|
|
294
299
|
className?: string;
|
|
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;
|
|
295
316
|
components?: {
|
|
296
317
|
H1?: React.ComponentType<{
|
|
297
318
|
children: React.ReactNode;
|
|
@@ -305,13 +326,32 @@ interface ArticlePageProps {
|
|
|
305
326
|
};
|
|
306
327
|
}
|
|
307
328
|
/**
|
|
308
|
-
*
|
|
329
|
+
* Full article page with optional TOC, FAQ, related articles, and JSON-LD.
|
|
330
|
+
*
|
|
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` |
|
|
309
345
|
*
|
|
310
346
|
* ```tsx
|
|
311
|
-
*
|
|
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" />
|
|
312
352
|
* ```
|
|
313
353
|
*/
|
|
314
|
-
declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, 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;
|
|
315
355
|
|
|
316
356
|
interface FaqBlockProps {
|
|
317
357
|
items: FaqItem[];
|
|
@@ -319,15 +359,14 @@ interface FaqBlockProps {
|
|
|
319
359
|
defaultOpen?: boolean;
|
|
320
360
|
className?: string;
|
|
321
361
|
title?: string;
|
|
362
|
+
/** "light" | "dark" | "inherit" */
|
|
363
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
322
364
|
}
|
|
323
365
|
/**
|
|
324
|
-
* FAQ block with
|
|
325
|
-
*
|
|
326
|
-
* ```tsx
|
|
327
|
-
* <FaqBlock items={article.faq} collapsible />
|
|
328
|
-
* ```
|
|
366
|
+
* FAQ block with Schema.org FAQPage markup.
|
|
367
|
+
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
329
368
|
*/
|
|
330
|
-
declare function FaqBlock({ items, collapsible, defaultOpen, className, title, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
|
|
369
|
+
declare function FaqBlock({ items, collapsible, defaultOpen, className, title, theme, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
|
|
331
370
|
|
|
332
371
|
interface RelatedArticlesProps {
|
|
333
372
|
articles: ArticleListItem[];
|
|
@@ -335,15 +374,14 @@ interface RelatedArticlesProps {
|
|
|
335
374
|
limit?: number;
|
|
336
375
|
onArticleClick?: (slug: string) => void;
|
|
337
376
|
className?: string;
|
|
377
|
+
/** "light" | "dark" | "inherit" */
|
|
378
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
338
379
|
}
|
|
339
380
|
/**
|
|
340
381
|
* Related articles widget.
|
|
341
|
-
*
|
|
342
|
-
* ```tsx
|
|
343
|
-
* <RelatedArticles articles={related} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />
|
|
344
|
-
* ```
|
|
382
|
+
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
345
383
|
*/
|
|
346
|
-
declare function RelatedArticles({ articles, title, limit, onArticleClick, className, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
|
|
384
|
+
declare function RelatedArticles({ articles, title, limit, onArticleClick, className, theme, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
|
|
347
385
|
|
|
348
386
|
/**
|
|
349
387
|
* Generate Next.js App Router Metadata object from an Article.
|
|
@@ -372,4 +410,4 @@ declare function SeoMetaBridge({ article, siteUrl, }: {
|
|
|
372
410
|
siteUrl?: string;
|
|
373
411
|
}): react_jsx_runtime.JSX.Element;
|
|
374
412
|
|
|
375
|
-
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
|
@@ -272,17 +272,22 @@ interface ArticleFeedProps {
|
|
|
272
272
|
showMeta?: boolean;
|
|
273
273
|
onArticleClick?: (slug: string) => void;
|
|
274
274
|
className?: string;
|
|
275
|
+
/** "light" | "dark" | "inherit" — sets CSS variable defaults. Use "inherit" to control via your own CSS vars. */
|
|
276
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
275
277
|
renderArticle?: (article: ArticleListItem) => React.ReactNode;
|
|
276
278
|
}
|
|
277
279
|
/**
|
|
278
280
|
* Renders a grid or list of article cards.
|
|
281
|
+
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
279
282
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
+
* CSS variables (override in your own CSS for full control):
|
|
284
|
+
* --dsa-text, --dsa-text-muted, --dsa-text-faint,
|
|
285
|
+
* --dsa-card-bg, --dsa-card-border,
|
|
286
|
+
* --dsa-badge-bg, --dsa-badge-text, --dsa-hover-shadow
|
|
283
287
|
*/
|
|
284
|
-
declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
|
|
288
|
+
declare function ArticleFeed({ articles, layout, columns, showExcerpt, showImage, showMeta, onArticleClick, className, theme, renderArticle, }: ArticleFeedProps): react_jsx_runtime.JSX.Element;
|
|
285
289
|
|
|
290
|
+
type ArticleTheme = 'light' | 'dark' | 'inherit';
|
|
286
291
|
interface ArticlePageProps {
|
|
287
292
|
article: Article;
|
|
288
293
|
showFaq?: boolean;
|
|
@@ -292,6 +297,22 @@ interface ArticlePageProps {
|
|
|
292
297
|
relatedArticles?: ArticleListItem[];
|
|
293
298
|
onRelatedClick?: (slug: string) => void;
|
|
294
299
|
className?: string;
|
|
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;
|
|
295
316
|
components?: {
|
|
296
317
|
H1?: React.ComponentType<{
|
|
297
318
|
children: React.ReactNode;
|
|
@@ -305,13 +326,32 @@ interface ArticlePageProps {
|
|
|
305
326
|
};
|
|
306
327
|
}
|
|
307
328
|
/**
|
|
308
|
-
*
|
|
329
|
+
* Full article page with optional TOC, FAQ, related articles, and JSON-LD.
|
|
330
|
+
*
|
|
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` |
|
|
309
345
|
*
|
|
310
346
|
* ```tsx
|
|
311
|
-
*
|
|
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" />
|
|
312
352
|
* ```
|
|
313
353
|
*/
|
|
314
|
-
declare function ArticlePage({ article, showFaq, showTableOfContents, showMeta, showRelated, relatedArticles, onRelatedClick, className, 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;
|
|
315
355
|
|
|
316
356
|
interface FaqBlockProps {
|
|
317
357
|
items: FaqItem[];
|
|
@@ -319,15 +359,14 @@ interface FaqBlockProps {
|
|
|
319
359
|
defaultOpen?: boolean;
|
|
320
360
|
className?: string;
|
|
321
361
|
title?: string;
|
|
362
|
+
/** "light" | "dark" | "inherit" */
|
|
363
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
322
364
|
}
|
|
323
365
|
/**
|
|
324
|
-
* FAQ block with
|
|
325
|
-
*
|
|
326
|
-
* ```tsx
|
|
327
|
-
* <FaqBlock items={article.faq} collapsible />
|
|
328
|
-
* ```
|
|
366
|
+
* FAQ block with Schema.org FAQPage markup.
|
|
367
|
+
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
329
368
|
*/
|
|
330
|
-
declare function FaqBlock({ items, collapsible, defaultOpen, className, title, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
|
|
369
|
+
declare function FaqBlock({ items, collapsible, defaultOpen, className, title, theme, }: FaqBlockProps): react_jsx_runtime.JSX.Element | null;
|
|
331
370
|
|
|
332
371
|
interface RelatedArticlesProps {
|
|
333
372
|
articles: ArticleListItem[];
|
|
@@ -335,15 +374,14 @@ interface RelatedArticlesProps {
|
|
|
335
374
|
limit?: number;
|
|
336
375
|
onArticleClick?: (slug: string) => void;
|
|
337
376
|
className?: string;
|
|
377
|
+
/** "light" | "dark" | "inherit" */
|
|
378
|
+
theme?: 'light' | 'dark' | 'inherit';
|
|
338
379
|
}
|
|
339
380
|
/**
|
|
340
381
|
* Related articles widget.
|
|
341
|
-
*
|
|
342
|
-
* ```tsx
|
|
343
|
-
* <RelatedArticles articles={related} onArticleClick={(slug) => router.push(`/blog/${slug}`)} />
|
|
344
|
-
* ```
|
|
382
|
+
* Supports theme="light" | "dark" | "inherit" via CSS variables.
|
|
345
383
|
*/
|
|
346
|
-
declare function RelatedArticles({ articles, title, limit, onArticleClick, className, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
|
|
384
|
+
declare function RelatedArticles({ articles, title, limit, onArticleClick, className, theme, }: RelatedArticlesProps): react_jsx_runtime.JSX.Element | null;
|
|
347
385
|
|
|
348
386
|
/**
|
|
349
387
|
* Generate Next.js App Router Metadata object from an Article.
|
|
@@ -372,4 +410,4 @@ declare function SeoMetaBridge({ article, siteUrl, }: {
|
|
|
372
410
|
siteUrl?: string;
|
|
373
411
|
}): react_jsx_runtime.JSX.Element;
|
|
374
412
|
|
|
375
|
-
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 };
|