@contentgrowth/content-widget 1.3.4 → 1.3.6
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 +90 -2
- package/dist/astro/ContentCard.astro +170 -0
- package/dist/astro/ContentList.astro +30 -47
- package/dist/astro/FeaturedCard.astro +171 -57
- package/dist/react/ContentCard.d.ts +59 -0
- package/dist/react/ContentCard.d.ts.map +1 -0
- package/dist/react/ContentCard.js +84 -0
- package/dist/react/ContentList.d.ts.map +1 -1
- package/dist/react/ContentList.js +12 -21
- package/dist/react/FeaturedCard.d.ts +23 -2
- package/dist/react/FeaturedCard.d.ts.map +1 -1
- package/dist/react/FeaturedCard.js +101 -32
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/styles.css +191 -68
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/vue/ContentCard.vue +148 -0
- package/dist/vue/ContentList.vue +35 -33
- package/dist/vue/FeaturedCard.vue +184 -62
- package/dist/widget/widget.css +1 -1
- package/dist/widget/widget.dev.css +191 -68
- package/dist/widget/widget.dev.js +3 -3
- package/dist/widget/widget.js +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { marked } from 'marked';
|
|
3
2
|
import { ContentGrowthClient } from '../core/client';
|
|
4
|
-
import type { FeaturedContentProps } from '../types';
|
|
3
|
+
import type { FeaturedContentProps, Article, ArticleWithContent } from '../types';
|
|
5
4
|
|
|
6
5
|
interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton' | 'backUrl'> {
|
|
6
|
+
/**
|
|
7
|
+
* Pre-loaded article data (bypasses API fetch - used by ContentList)
|
|
8
|
+
*/
|
|
9
|
+
article?: Article | ArticleWithContent;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load specific article by slug (bypasses category/tags search)
|
|
13
|
+
*/
|
|
14
|
+
slug?: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Load specific article by UUID (bypasses category/tags search)
|
|
18
|
+
*/
|
|
19
|
+
uuid?: string;
|
|
20
|
+
|
|
7
21
|
/**
|
|
8
22
|
* URL pattern for the article link
|
|
9
23
|
* Supports placeholders: {uuid}, {slug}, {category}
|
|
@@ -57,6 +71,18 @@ interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton'
|
|
|
57
71
|
* @default '#f3f4f6'
|
|
58
72
|
*/
|
|
59
73
|
itemsBackground?: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Link target attribute
|
|
77
|
+
* @default undefined (same tab)
|
|
78
|
+
*/
|
|
79
|
+
linkTarget?: string;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Custom padding for the card content
|
|
83
|
+
* @example "20px" or "0"
|
|
84
|
+
*/
|
|
85
|
+
padding?: string;
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
type Props = FeaturedCardProps & { class?: string };
|
|
@@ -64,6 +90,9 @@ type Props = FeaturedCardProps & { class?: string };
|
|
|
64
90
|
const {
|
|
65
91
|
apiKey,
|
|
66
92
|
baseUrl,
|
|
93
|
+
article: providedArticle,
|
|
94
|
+
slug,
|
|
95
|
+
uuid,
|
|
67
96
|
tags = [],
|
|
68
97
|
category,
|
|
69
98
|
excludeTags = [],
|
|
@@ -72,29 +101,46 @@ const {
|
|
|
72
101
|
showAuthor = false,
|
|
73
102
|
ctaText: propCtaText,
|
|
74
103
|
linkPattern = '/articles/{slug}',
|
|
104
|
+
linkTarget,
|
|
75
105
|
layout: propLayout,
|
|
76
106
|
borderStyle = 'none',
|
|
77
107
|
borderColor = '#e5e7eb',
|
|
78
108
|
cardBackground = 'none',
|
|
79
109
|
itemsBackground = '#f3f4f6',
|
|
110
|
+
padding,
|
|
80
111
|
class: className = ''
|
|
81
112
|
} = Astro.props;
|
|
82
113
|
|
|
83
|
-
//
|
|
84
|
-
|
|
85
|
-
let
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
article =
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
// Determine article source
|
|
115
|
+
let article: Article | ArticleWithContent | null = null;
|
|
116
|
+
let error: string | null = null;
|
|
117
|
+
|
|
118
|
+
if (providedArticle) {
|
|
119
|
+
// Mode 1: Article provided directly (from ContentList)
|
|
120
|
+
article = providedArticle;
|
|
121
|
+
} else if (apiKey) {
|
|
122
|
+
// Need to fetch article from API
|
|
123
|
+
const client = new ContentGrowthClient({ apiKey, baseUrl });
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
if (uuid) {
|
|
127
|
+
// Mode 2: Load by UUID
|
|
128
|
+
article = await client.getArticle(uuid, { excludeTags });
|
|
129
|
+
} else if (slug) {
|
|
130
|
+
// Mode 3: Load by slug
|
|
131
|
+
article = await client.getArticleBySlug(slug, { excludeTags });
|
|
132
|
+
} else {
|
|
133
|
+
// Mode 4: Find featured article by category/tags (original behavior)
|
|
134
|
+
article = await client.getFeaturedArticle({
|
|
135
|
+
tags,
|
|
136
|
+
category,
|
|
137
|
+
excludeTags
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.error('Failed to fetch article:', e);
|
|
142
|
+
error = e instanceof Error ? e.message : 'Failed to load article';
|
|
143
|
+
}
|
|
98
144
|
}
|
|
99
145
|
|
|
100
146
|
// Generate article URL
|
|
@@ -106,25 +152,52 @@ const getArticleUrl = (article: any) => {
|
|
|
106
152
|
.replace('{category}', article.category || 'uncategorized');
|
|
107
153
|
};
|
|
108
154
|
|
|
109
|
-
//
|
|
110
|
-
|
|
155
|
+
// Parse featured summary - supports both JSON (new) and plain text (legacy)
|
|
156
|
+
interface SummaryData {
|
|
157
|
+
type: 'classic' | 'list' | 'steps' | 'quote' | 'legacy';
|
|
158
|
+
text?: string;
|
|
159
|
+
intro?: string;
|
|
160
|
+
items?: Array<{ title: string; description: string }>;
|
|
161
|
+
quote?: string;
|
|
162
|
+
highlight?: string;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const parseSummary = (article: any): SummaryData | null => {
|
|
111
166
|
const summaryText = article.featuredSummary || article.summary;
|
|
112
|
-
if (!summaryText) return
|
|
113
|
-
|
|
167
|
+
if (!summaryText) return null;
|
|
168
|
+
|
|
169
|
+
// Try to parse as JSON
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(summaryText);
|
|
172
|
+
if (parsed.type) {
|
|
173
|
+
return parsed as SummaryData;
|
|
174
|
+
}
|
|
175
|
+
} catch (e) {
|
|
176
|
+
// Not JSON, treat as legacy markdown/plain text
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Legacy fallback - render as plain text
|
|
180
|
+
return {
|
|
181
|
+
type: 'legacy',
|
|
182
|
+
text: summaryText
|
|
183
|
+
};
|
|
114
184
|
};
|
|
115
185
|
|
|
116
|
-
const
|
|
117
|
-
|
|
186
|
+
const summaryData = article ? parseSummary(article) : null;
|
|
187
|
+
|
|
188
|
+
const layout = propLayout || (article as any)?.featuredSummaryLayout || 'vertical';
|
|
189
|
+
const layoutClass = layout !== 'vertical' ? `cg-layout-${layout}` : '';
|
|
118
190
|
const borderClass = borderStyle !== 'none' ? `cg-border-${borderStyle}` : '';
|
|
119
191
|
|
|
120
192
|
// Use ctaText from prop, or from article data, or fallback to default
|
|
121
|
-
const ctaText = propCtaText || article?.featuredCtaText || 'Read full story';
|
|
193
|
+
const ctaText = propCtaText || (article as any)?.featuredCtaText || 'Read full story';
|
|
122
194
|
|
|
123
195
|
// Custom CSS properties for styling
|
|
124
196
|
const customStyles = [
|
|
125
197
|
borderColor !== '#e5e7eb' ? `--cg-card-border-color: ${borderColor}` : '',
|
|
126
198
|
cardBackground !== 'none' ? `--cg-card-bg: ${cardBackground}` : '',
|
|
127
199
|
itemsBackground !== '#f3f4f6' ? `--cg-items-bg: ${itemsBackground}` : '',
|
|
200
|
+
padding ? `--cg-card-padding: ${padding}` : '',
|
|
128
201
|
].filter(Boolean).join('; ');
|
|
129
202
|
---
|
|
130
203
|
|
|
@@ -134,44 +207,87 @@ const customStyles = [
|
|
|
134
207
|
class={`cg-widget cg-featured-card ${className} ${layoutClass} ${borderClass}`}
|
|
135
208
|
style={customStyles || undefined}
|
|
136
209
|
data-cg-widget="featured-card"
|
|
210
|
+
target={linkTarget}
|
|
211
|
+
rel={linkTarget === '_blank' ? 'noopener noreferrer' : undefined}
|
|
137
212
|
>
|
|
138
213
|
<article class="cg-featured-card-inner">
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
class="cg-featured-card-summary"
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
214
|
+
<div class="cg-card-primary">
|
|
215
|
+
{/* Header with category badge */}
|
|
216
|
+
{showCategory && article.category && (
|
|
217
|
+
<div class="cg-featured-card-category">
|
|
218
|
+
<span class="cg-category-badge">{article.category}</span>
|
|
219
|
+
</div>
|
|
220
|
+
)}
|
|
221
|
+
|
|
222
|
+
{/* Title */}
|
|
223
|
+
<h3 class="cg-featured-card-title">{article.title}</h3>
|
|
224
|
+
|
|
225
|
+
{/* Featured Summary - Intro / Text Part */}
|
|
226
|
+
{summaryData && (
|
|
227
|
+
<div class="cg-featured-card-summary">
|
|
228
|
+
{/* Intro for structured types */}
|
|
229
|
+
{(summaryData.type === 'list' || summaryData.type === 'steps' || summaryData.type === 'quote') && summaryData.intro && (
|
|
230
|
+
<p>{summaryData.intro}</p>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Classic type - just text */}
|
|
234
|
+
{summaryData.type === 'classic' && (
|
|
235
|
+
<p>{summaryData.text}</p>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
{/* Legacy markdown - render as plain text */}
|
|
239
|
+
{summaryData.type === 'legacy' && (
|
|
240
|
+
<p>{summaryData.text}</p>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
|
|
245
|
+
{/* Footer with meta info */}
|
|
246
|
+
{(showAuthor || showReadingTime) && (
|
|
247
|
+
<div class="cg-featured-card-footer">
|
|
248
|
+
{showAuthor && <span class="cg-featured-card-author">{article.authorName}</span>}
|
|
249
|
+
|
|
250
|
+
{showAuthor && showReadingTime && (
|
|
251
|
+
<span class="cg-featured-card-separator">•</span>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{showReadingTime && (
|
|
255
|
+
<span class="cg-featured-card-reading-time">
|
|
256
|
+
{Math.ceil(article.wordCount / 200)} min read
|
|
257
|
+
</span>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
|
|
262
|
+
{/* Read more indicator */}
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
{/* Right Panel - Structured Visual Items (List/Quote) */}
|
|
266
|
+
{summaryData && (summaryData.type === 'list' || summaryData.type === 'steps' || summaryData.type === 'quote') && (
|
|
267
|
+
<div class="cg-card-secondary">
|
|
268
|
+
{(summaryData.type === 'list' || summaryData.type === 'steps') && (
|
|
269
|
+
<ul class="cg-summary-items">
|
|
270
|
+
{summaryData.items?.map((item, index) => (
|
|
271
|
+
<li>
|
|
272
|
+
<span class="cg-item-number">{index + 1}</span>
|
|
273
|
+
<div class="cg-item-content">
|
|
274
|
+
<strong class="cg-item-title">{item.title}</strong>
|
|
275
|
+
<span class="cg-item-description">{item.description}</span>
|
|
276
|
+
</div>
|
|
277
|
+
</li>
|
|
278
|
+
))}
|
|
279
|
+
</ul>
|
|
164
280
|
)}
|
|
165
281
|
|
|
166
|
-
{
|
|
167
|
-
<
|
|
168
|
-
{
|
|
169
|
-
</
|
|
282
|
+
{summaryData.type === 'quote' && (
|
|
283
|
+
<blockquote>
|
|
284
|
+
{summaryData.quote}
|
|
285
|
+
</blockquote>
|
|
170
286
|
)}
|
|
171
287
|
</div>
|
|
172
288
|
)}
|
|
173
|
-
|
|
174
|
-
{/* Read more indicator */}
|
|
289
|
+
|
|
290
|
+
{/* Read more indicator - Now at bottom */}
|
|
175
291
|
<div class="cg-featured-card-cta">
|
|
176
292
|
<span>{ctaText}</span>
|
|
177
293
|
<svg class="cg-featured-card-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="16" height="16">
|
|
@@ -187,5 +303,3 @@ const customStyles = [
|
|
|
187
303
|
</div>
|
|
188
304
|
) : null
|
|
189
305
|
)}
|
|
190
|
-
|
|
191
|
-
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Article, ArticleWithContent } from '../types';
|
|
3
|
+
export interface ContentCardProps {
|
|
4
|
+
/**
|
|
5
|
+
* Pre-loaded article data (bypasses API fetch)
|
|
6
|
+
*/
|
|
7
|
+
article?: Article | ArticleWithContent;
|
|
8
|
+
/**
|
|
9
|
+
* Load specific article by slug
|
|
10
|
+
*/
|
|
11
|
+
slug?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Load specific article by UUID
|
|
14
|
+
*/
|
|
15
|
+
uuid?: string;
|
|
16
|
+
/**
|
|
17
|
+
* API key (required if fetching article)
|
|
18
|
+
*/
|
|
19
|
+
apiKey?: string;
|
|
20
|
+
/**
|
|
21
|
+
* API base URL
|
|
22
|
+
*/
|
|
23
|
+
baseUrl?: string;
|
|
24
|
+
/**
|
|
25
|
+
* URL pattern for the article link
|
|
26
|
+
* @default '/articles/{slug}'
|
|
27
|
+
*/
|
|
28
|
+
linkPattern?: string;
|
|
29
|
+
/**
|
|
30
|
+
* Link target attribute
|
|
31
|
+
*/
|
|
32
|
+
linkTarget?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Show article summary
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
showSummary?: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Maximum length of summary text
|
|
40
|
+
*/
|
|
41
|
+
summaryMaxLength?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Show article tags
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
showTags?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Show article category
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
showCategory?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Additional CSS class
|
|
54
|
+
*/
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
export declare const ContentCard: React.FC<ContentCardProps>;
|
|
58
|
+
export default ContentCard;
|
|
59
|
+
//# sourceMappingURL=ContentCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentCard.d.ts","sourceRoot":"","sources":["../../src/react/ContentCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,KAAK,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAE5D,MAAM,WAAW,gBAAgB;IAC7B;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAAC;IAEvC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAgIlD,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { ContentGrowthClient } from '../core/client';
|
|
3
|
+
import { formatDate, calculateReadingTime } from '../core/utils';
|
|
4
|
+
export const ContentCard = ({ apiKey, baseUrl, article: providedArticle, slug, uuid, linkPattern = '/articles/{slug}', linkTarget, showSummary = true, summaryMaxLength, showTags = false, showCategory = true, className = '' }) => {
|
|
5
|
+
const [article, setArticle] = useState(providedArticle || null);
|
|
6
|
+
const [loading, setLoading] = useState(!providedArticle && !!(apiKey && (slug || uuid)));
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
// If article is provided, use it directly
|
|
10
|
+
if (providedArticle) {
|
|
11
|
+
setArticle(providedArticle);
|
|
12
|
+
setLoading(false);
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Need API key and identifier to fetch
|
|
16
|
+
if (!apiKey || (!slug && !uuid)) {
|
|
17
|
+
setLoading(false);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const fetchArticle = async () => {
|
|
21
|
+
setLoading(true);
|
|
22
|
+
try {
|
|
23
|
+
const client = new ContentGrowthClient({ apiKey, baseUrl });
|
|
24
|
+
if (uuid) {
|
|
25
|
+
const fetchedArticle = await client.getArticle(uuid);
|
|
26
|
+
setArticle(fetchedArticle);
|
|
27
|
+
}
|
|
28
|
+
else if (slug) {
|
|
29
|
+
const fetchedArticle = await client.getArticleBySlug(slug);
|
|
30
|
+
setArticle(fetchedArticle);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
setError(err instanceof Error ? err.message : 'Failed to load article');
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
setLoading(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
fetchArticle();
|
|
41
|
+
}, [apiKey, baseUrl, providedArticle, uuid, slug]);
|
|
42
|
+
// Generate article URL
|
|
43
|
+
const getArticleUrl = (article) => {
|
|
44
|
+
return linkPattern
|
|
45
|
+
.replace('{uuid}', article.uuid || '')
|
|
46
|
+
.replace('{slug}', article.slug || article.uuid || '')
|
|
47
|
+
.replace('{category}', article.category || 'uncategorized');
|
|
48
|
+
};
|
|
49
|
+
// Truncate summary text
|
|
50
|
+
const truncateSummary = (text, maxLength) => {
|
|
51
|
+
if (!text)
|
|
52
|
+
return '';
|
|
53
|
+
if (!maxLength || text.length <= maxLength)
|
|
54
|
+
return text;
|
|
55
|
+
return text.substring(0, maxLength).trim() + '...';
|
|
56
|
+
};
|
|
57
|
+
if (loading) {
|
|
58
|
+
return (React.createElement("div", { className: `cg-widget cg-loading ${className}` },
|
|
59
|
+
React.createElement("div", { className: "cg-spinner" })));
|
|
60
|
+
}
|
|
61
|
+
if (error || !article) {
|
|
62
|
+
if (error) {
|
|
63
|
+
return (React.createElement("div", { className: `cg-widget cg-error ${className}` }, error));
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const readingTime = calculateReadingTime(article.wordCount);
|
|
68
|
+
const publishedDate = formatDate(article.publishedAt);
|
|
69
|
+
return (React.createElement("article", { className: `cg-article-card ${className}` },
|
|
70
|
+
React.createElement("a", { href: getArticleUrl(article), target: linkTarget, className: "cg-card-link" },
|
|
71
|
+
React.createElement("div", { className: "cg-card-content" },
|
|
72
|
+
showCategory && article.category && (React.createElement("div", { className: "cg-card-category" },
|
|
73
|
+
React.createElement("span", { className: "cg-category-badge" }, article.category))),
|
|
74
|
+
React.createElement("h2", { className: "cg-card-title" }, article.title),
|
|
75
|
+
showSummary && article.summary && (React.createElement("p", { className: "cg-card-summary" }, truncateSummary(article.summary, summaryMaxLength))),
|
|
76
|
+
React.createElement("div", { className: "cg-card-meta" },
|
|
77
|
+
React.createElement("span", { className: "cg-meta-author" }, article.authorName),
|
|
78
|
+
React.createElement("span", { className: "cg-meta-separator" }, "\u2022"),
|
|
79
|
+
React.createElement("time", { className: "cg-meta-date", dateTime: new Date(article.publishedAt * 1000).toISOString() }, publishedDate),
|
|
80
|
+
React.createElement("span", { className: "cg-meta-separator" }, "\u2022"),
|
|
81
|
+
React.createElement("span", { className: "cg-meta-reading-time" }, readingTime)),
|
|
82
|
+
showTags && article.tags && article.tags.length > 0 && (React.createElement("div", { className: "cg-card-tags" }, article.tags.map((tag) => (React.createElement("span", { key: tag, className: "cg-tag" }, tag)))))))));
|
|
83
|
+
};
|
|
84
|
+
export default ContentCard;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentList.d.ts","sourceRoot":"","sources":["../../src/react/ContentList.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"ContentList.d.ts","sourceRoot":"","sources":["../../src/react/ContentList.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA8B,MAAM,OAAO,CAAC;AAInD,OAAO,KAAK,EAAE,gBAAgB,EAAW,MAAM,mBAAmB,CAAC;AAEnE,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAsKvD,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import React, { useEffect, useState } from 'react';
|
|
6
6
|
import { ContentGrowthClient } from '../core/client.js';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import { FeaturedCard } from './FeaturedCard.js';
|
|
8
|
+
import { ContentCard } from './ContentCard.js';
|
|
9
|
+
export const ContentList = ({ apiKey, baseUrl, layout = 'cards', displayMode = 'comfortable', displayAs = 'default', theme = 'light', pageSize = 12, tags = [], category, showPagination = true, linkPattern = '/articles/{uuid}', showTags = false, showAiSummary = true, summaryMaxLength, linkTarget, className = '' }) => {
|
|
9
10
|
const [articles, setArticles] = useState([]);
|
|
10
11
|
const [currentPage, setCurrentPage] = useState(1);
|
|
11
12
|
const [totalPages, setTotalPages] = useState(1);
|
|
12
13
|
const [loading, setLoading] = useState(true);
|
|
14
|
+
const isFeaturedCardsMode = displayAs === 'featured-cards';
|
|
13
15
|
useEffect(() => {
|
|
14
16
|
const fetchArticles = async () => {
|
|
15
17
|
setLoading(true);
|
|
@@ -73,27 +75,16 @@ export const ContentList = ({ apiKey, baseUrl, layout = 'cards', displayMode = '
|
|
|
73
75
|
React.createElement("div", { className: "cg-empty-state" },
|
|
74
76
|
React.createElement("p", null, "Loading..."))));
|
|
75
77
|
}
|
|
76
|
-
return (React.createElement("div", { className: `cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${className}`, "data-cg-widget": "list" }, articles.length === 0 ? (React.createElement("div", { className: "cg-empty-state" },
|
|
78
|
+
return (React.createElement("div", { className: `cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${isFeaturedCardsMode ? 'cg-featured-cards-list' : ''} ${className}`, "data-cg-widget": "list" }, articles.length === 0 ? (React.createElement("div", { className: "cg-empty-state" },
|
|
77
79
|
React.createElement("p", null, "No articles found."))) : (React.createElement(React.Fragment, null,
|
|
78
|
-
React.createElement("div", { className: `cg-articles-grid ${layout === 'cards' ? 'cg-grid' : 'cg-list'}` }, articles.map((article) => {
|
|
79
|
-
const articleUrl = buildArticleUrl(article);
|
|
80
|
+
React.createElement("div", { className: `cg-articles-grid ${isFeaturedCardsMode ? 'cg-featured-cards-grid' : (layout === 'cards' ? 'cg-grid' : 'cg-list')}` }, articles.map((article) => {
|
|
80
81
|
const articleTarget = buildLinkTarget(article);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
React.createElement("span", { className: "cg-category-badge" }, article.category))),
|
|
88
|
-
React.createElement("h2", { className: "cg-card-title" }, article.title),
|
|
89
|
-
showAiSummary && article.summary && (React.createElement("p", { className: "cg-card-summary" }, truncateSummary(article.summary, summaryMaxLength))),
|
|
90
|
-
React.createElement("div", { className: "cg-card-meta" },
|
|
91
|
-
React.createElement("span", { className: "cg-meta-author" }, article.authorName),
|
|
92
|
-
React.createElement("span", { className: "cg-meta-separator" }, "\u2022"),
|
|
93
|
-
React.createElement("time", { className: "cg-meta-date", dateTime: new Date(article.publishedAt * 1000).toISOString() }, publishedDate),
|
|
94
|
-
React.createElement("span", { className: "cg-meta-separator" }, "\u2022"),
|
|
95
|
-
React.createElement("span", { className: "cg-meta-reading-time" }, readingTime)),
|
|
96
|
-
showTags && article.tags && article.tags.length > 0 && (React.createElement("div", { className: "cg-card-tags" }, article.tags.map((tag) => (React.createElement("span", { key: tag, className: "cg-tag" }, tag)))))))));
|
|
82
|
+
// Featured Cards Mode - Use FeaturedCard component
|
|
83
|
+
if (isFeaturedCardsMode) {
|
|
84
|
+
return (React.createElement(FeaturedCard, { key: article.uuid, article: article, linkPattern: linkPattern, linkTarget: articleTarget, showCategory: true }));
|
|
85
|
+
}
|
|
86
|
+
// Default Card Mode - Use ContentCard component
|
|
87
|
+
return (React.createElement(ContentCard, { key: article.uuid, article: article, linkPattern: linkPattern, linkTarget: articleTarget, showSummary: showAiSummary, summaryMaxLength: summaryMaxLength, showTags: showTags, showCategory: true }));
|
|
97
88
|
})),
|
|
98
89
|
showPagination && totalPages > 1 && (React.createElement("div", { className: "cg-pagination" },
|
|
99
90
|
React.createElement("button", { className: "cg-pagination-btn", onClick: () => setCurrentPage(p => Math.max(1, p - 1)), disabled: currentPage === 1 }, "Previous"),
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { FeaturedContentProps } from '../types';
|
|
3
|
-
interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton' | 'backUrl' | 'showAiSummary' | 'showTags'
|
|
2
|
+
import type { FeaturedContentProps, ArticleWithContent, Article } from '../types';
|
|
3
|
+
interface FeaturedCardProps extends Partial<Omit<FeaturedContentProps, 'showBackButton' | 'backUrl' | 'showAiSummary' | 'showTags'>> {
|
|
4
|
+
/**
|
|
5
|
+
* Pre-loaded article data (bypasses API fetch - used by ContentList)
|
|
6
|
+
*/
|
|
7
|
+
article?: Article | ArticleWithContent;
|
|
8
|
+
/**
|
|
9
|
+
* Load specific article by slug (bypasses category/tags search)
|
|
10
|
+
*/
|
|
11
|
+
slug?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Load specific article by UUID (bypasses category/tags search)
|
|
14
|
+
*/
|
|
15
|
+
uuid?: string;
|
|
4
16
|
/**
|
|
5
17
|
* URL pattern for the article link
|
|
6
18
|
* Supports placeholders: {uuid}, {slug}, {category}
|
|
@@ -46,11 +58,20 @@ interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton'
|
|
|
46
58
|
* @default 'none'
|
|
47
59
|
*/
|
|
48
60
|
cardBackground?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Background color for list/quote section (the items area)
|
|
63
|
+
* @default '#f3f4f6'
|
|
64
|
+
*/
|
|
49
65
|
/**
|
|
50
66
|
* Background color for list/quote section (the items area)
|
|
51
67
|
* @default '#f3f4f6'
|
|
52
68
|
*/
|
|
53
69
|
itemsBackground?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Custom padding for the card content
|
|
72
|
+
* @example "20px" or "0"
|
|
73
|
+
*/
|
|
74
|
+
padding?: string;
|
|
54
75
|
}
|
|
55
76
|
export declare const FeaturedCard: React.FC<FeaturedCardProps>;
|
|
56
77
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FeaturedCard.d.ts","sourceRoot":"","sources":["../../src/react/FeaturedCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"FeaturedCard.d.ts","sourceRoot":"","sources":["../../src/react/FeaturedCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAEnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAkClF,UAAU,iBAAkB,SAAQ,OAAO,CAAC,IAAI,CAAC,oBAAoB,EAAE,gBAAgB,GAAG,SAAS,GAAG,eAAe,GAAG,UAAU,CAAC,CAAC;IAChI;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAAC;IAEvC;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,MAAM,CAAC,EAAE,UAAU,GAAG,YAAY,CAAC;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;IAEzC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;OAGG;IACH;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA4NpD,CAAC"}
|