@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
package/README.md
CHANGED
|
@@ -268,25 +268,83 @@ const widget = new ContentGrowthWidget(container, {
|
|
|
268
268
|
/>
|
|
269
269
|
```
|
|
270
270
|
|
|
271
|
+
**ContentCard:**
|
|
272
|
+
|
|
273
|
+
A standalone article card component. Use it to display individual articles outside of ContentList.
|
|
274
|
+
|
|
275
|
+
```astro
|
|
276
|
+
<!-- Load by slug -->
|
|
277
|
+
<ContentCard
|
|
278
|
+
apiKey="pk_your_key_here"
|
|
279
|
+
slug="my-article-slug"
|
|
280
|
+
linkPattern="/articles/{slug}"
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
<!-- Load by UUID -->
|
|
284
|
+
<ContentCard
|
|
285
|
+
apiKey="pk_your_key_here"
|
|
286
|
+
uuid="article-uuid"
|
|
287
|
+
/>
|
|
288
|
+
|
|
289
|
+
<!-- Use pre-loaded article data -->
|
|
290
|
+
<ContentCard
|
|
291
|
+
article={articleData}
|
|
292
|
+
linkPattern="/articles/{slug}"
|
|
293
|
+
showSummary={true}
|
|
294
|
+
showTags={true}
|
|
295
|
+
/>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
| Prop | Type | Default | Description |
|
|
299
|
+
|------|------|---------|-------------|
|
|
300
|
+
| `apiKey` | string | - | Your API key (required unless `article` is provided) |
|
|
301
|
+
| `article` | Article | - | Pre-loaded article data (skips API fetch) |
|
|
302
|
+
| `slug` | string | - | Load specific article by slug |
|
|
303
|
+
| `uuid` | string | - | Load specific article by UUID |
|
|
304
|
+
| `linkPattern` | string | '/articles/{slug}' | URL pattern for link |
|
|
305
|
+
| `linkTarget` | string | - | Link target attribute |
|
|
306
|
+
| `showSummary` | boolean | true | Show article summary |
|
|
307
|
+
| `summaryMaxLength` | number | - | Truncate summary at length |
|
|
308
|
+
| `showTags` | boolean | false | Show article tags |
|
|
309
|
+
| `showCategory` | boolean | true | Show category badge |
|
|
310
|
+
|
|
271
311
|
**FeaturedCard:**
|
|
272
312
|
|
|
273
313
|
A compact card displaying the Featured Summary with customizable styling. Perfect for landing pages.
|
|
274
314
|
|
|
275
315
|
```astro
|
|
316
|
+
<!-- Find latest article in category -->
|
|
276
317
|
<FeaturedCard
|
|
277
318
|
apiKey="pk_your_key_here"
|
|
278
319
|
category="announce"
|
|
279
320
|
linkPattern="/articles/{slug}"
|
|
280
321
|
layout="horizontal"
|
|
281
322
|
borderStyle="dashed"
|
|
282
|
-
|
|
323
|
+
borderColor="#e5e7eb"
|
|
324
|
+
padding="20px"
|
|
283
325
|
ctaText="Read full story"
|
|
284
326
|
/>
|
|
327
|
+
|
|
328
|
+
<!-- Load specific article by slug -->
|
|
329
|
+
<FeaturedCard
|
|
330
|
+
apiKey="pk_your_key_here"
|
|
331
|
+
slug="my-article-slug"
|
|
332
|
+
linkPattern="/articles/{slug}"
|
|
333
|
+
/>
|
|
334
|
+
|
|
335
|
+
<!-- Use pre-loaded article data (for lists) -->
|
|
336
|
+
<FeaturedCard
|
|
337
|
+
article={articleData}
|
|
338
|
+
linkPattern="/articles/{slug}"
|
|
339
|
+
/>
|
|
285
340
|
```
|
|
286
341
|
|
|
287
342
|
| Prop | Type | Default | Description |
|
|
288
343
|
|------|------|---------|-------------|
|
|
289
|
-
| `apiKey` | string | - | Your API key |
|
|
344
|
+
| `apiKey` | string | - | Your API key (required unless `article` is provided) |
|
|
345
|
+
| `article` | Article | - | Pre-loaded article data (skips API fetch) |
|
|
346
|
+
| `slug` | string | - | Load specific article by slug |
|
|
347
|
+
| `uuid` | string | - | Load specific article by UUID |
|
|
290
348
|
| `category` | string | - | Filter by category |
|
|
291
349
|
| `tags` | string[] | [] | Filter by tags |
|
|
292
350
|
| `layout` | 'vertical' \| 'horizontal' | auto | Card layout (auto uses article setting) |
|
|
@@ -294,11 +352,41 @@ A compact card displaying the Featured Summary with customizable styling. Perfec
|
|
|
294
352
|
| `borderColor` | string | '#e5e7eb' | Border color (CSS value) |
|
|
295
353
|
| `cardBackground` | string | 'none' | Card background ('none' = transparent) |
|
|
296
354
|
| `itemsBackground` | string | '#f3f4f6' | Background for list/quote section |
|
|
355
|
+
| `padding` | string | - | Custom padding (e.g., '10px', '2rem 3rem') |
|
|
297
356
|
| `ctaText` | string | 'Read full story' | Call-to-action text |
|
|
298
357
|
| `showAuthor` | boolean | false | Show author name |
|
|
299
358
|
| `showReadingTime` | boolean | false | Show reading time |
|
|
300
359
|
| `linkPattern` | string | '/articles/{slug}' | URL pattern for link |
|
|
301
360
|
|
|
361
|
+
**Featured Summary Types:**
|
|
362
|
+
|
|
363
|
+
FeaturedCard supports structured JSON summaries generated via the portal wizard:
|
|
364
|
+
|
|
365
|
+
| Type | Description |
|
|
366
|
+
|------|-------------|
|
|
367
|
+
| `list` | Intro text on left, bulleted key points on right |
|
|
368
|
+
| `steps` | Intro text on left, numbered action steps on right |
|
|
369
|
+
| `quote` | Intro text on left, styled pullquote on right |
|
|
370
|
+
| `classic` | Simple text summary (legacy) |
|
|
371
|
+
|
|
372
|
+
**Featured Cards List:**
|
|
373
|
+
|
|
374
|
+
Display all articles as FeaturedCards in a grid using `displayAs`:
|
|
375
|
+
|
|
376
|
+
```astro
|
|
377
|
+
<ContentList
|
|
378
|
+
apiKey="pk_your_key_here"
|
|
379
|
+
displayAs="featured-cards"
|
|
380
|
+
linkPattern="/articles/{slug}"
|
|
381
|
+
pageSize={12}
|
|
382
|
+
/>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
| displayAs Value | Description |
|
|
386
|
+
|-----------------|-------------|
|
|
387
|
+
| `'default'` | Standard card/row layout |
|
|
388
|
+
| `'featured-cards'` | Renders each article as a FeaturedCard |
|
|
389
|
+
|
|
302
390
|
## API Client
|
|
303
391
|
|
|
304
392
|
### ContentGrowthClient
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { marked } from 'marked';
|
|
3
|
+
import { ContentGrowthClient } from '../core/client';
|
|
4
|
+
import { formatDate, calculateReadingTime } from '../core/utils';
|
|
5
|
+
import type { Article, ArticleWithContent } from '../types';
|
|
6
|
+
|
|
7
|
+
interface ContentCardProps {
|
|
8
|
+
/**
|
|
9
|
+
* Pre-loaded article data (bypasses API fetch)
|
|
10
|
+
*/
|
|
11
|
+
article?: Article | ArticleWithContent;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load specific article by slug
|
|
15
|
+
*/
|
|
16
|
+
slug?: string;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Load specific article by UUID
|
|
20
|
+
*/
|
|
21
|
+
uuid?: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* API key (required if fetching article)
|
|
25
|
+
*/
|
|
26
|
+
apiKey?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* API base URL
|
|
30
|
+
*/
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* URL pattern for the article link
|
|
35
|
+
* Supports placeholders: {uuid}, {slug}, {category}
|
|
36
|
+
* @default '/articles/{slug}'
|
|
37
|
+
*/
|
|
38
|
+
linkPattern?: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Link target attribute
|
|
42
|
+
*/
|
|
43
|
+
linkTarget?: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Show article summary
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
49
|
+
showSummary?: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Maximum length of summary text
|
|
53
|
+
*/
|
|
54
|
+
summaryMaxLength?: number;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Show article tags
|
|
58
|
+
* @default false
|
|
59
|
+
*/
|
|
60
|
+
showTags?: boolean;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Show article category
|
|
64
|
+
* @default true
|
|
65
|
+
*/
|
|
66
|
+
showCategory?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type Props = ContentCardProps & { class?: string };
|
|
70
|
+
|
|
71
|
+
const {
|
|
72
|
+
apiKey,
|
|
73
|
+
baseUrl,
|
|
74
|
+
article: providedArticle,
|
|
75
|
+
slug,
|
|
76
|
+
uuid,
|
|
77
|
+
linkPattern = '/articles/{slug}',
|
|
78
|
+
linkTarget,
|
|
79
|
+
showSummary = true,
|
|
80
|
+
summaryMaxLength,
|
|
81
|
+
showTags = false,
|
|
82
|
+
showCategory = true,
|
|
83
|
+
class: className = ''
|
|
84
|
+
} = Astro.props;
|
|
85
|
+
|
|
86
|
+
// Determine article source
|
|
87
|
+
let article: Article | ArticleWithContent | null = null;
|
|
88
|
+
let error: string | null = null;
|
|
89
|
+
|
|
90
|
+
if (providedArticle) {
|
|
91
|
+
// Mode 1: Article provided directly
|
|
92
|
+
article = providedArticle;
|
|
93
|
+
} else if (apiKey) {
|
|
94
|
+
// Need to fetch article from API
|
|
95
|
+
const client = new ContentGrowthClient({ apiKey, baseUrl });
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
if (uuid) {
|
|
99
|
+
// Mode 2: Load by UUID
|
|
100
|
+
article = await client.getArticle(uuid);
|
|
101
|
+
} else if (slug) {
|
|
102
|
+
// Mode 3: Load by slug
|
|
103
|
+
article = await client.getArticleBySlug(slug);
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error('Failed to fetch article:', e);
|
|
107
|
+
error = e instanceof Error ? e.message : 'Failed to load article';
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Generate article URL
|
|
112
|
+
const getArticleUrl = (article: any) => {
|
|
113
|
+
if (!article) return '#';
|
|
114
|
+
return linkPattern
|
|
115
|
+
.replace('{uuid}', article.uuid || '')
|
|
116
|
+
.replace('{slug}', article.slug || article.uuid || '')
|
|
117
|
+
.replace('{category}', article.category || 'uncategorized');
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Truncate summary text
|
|
121
|
+
const truncateSummary = (text: string | null, maxLength?: number): string => {
|
|
122
|
+
if (!text) return '';
|
|
123
|
+
if (!maxLength || text.length <= maxLength) return text;
|
|
124
|
+
return text.substring(0, maxLength).trim() + '...';
|
|
125
|
+
};
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
{article ? (
|
|
129
|
+
<article class={`cg-article-card ${className}`}>
|
|
130
|
+
<a href={getArticleUrl(article)} target={linkTarget} class="cg-card-link">
|
|
131
|
+
<div class="cg-card-content">
|
|
132
|
+
{showCategory && article.category && (
|
|
133
|
+
<div class="cg-card-category">
|
|
134
|
+
<span class="cg-category-badge">{article.category}</span>
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
<h2 class="cg-card-title">{article.title}</h2>
|
|
139
|
+
|
|
140
|
+
{showSummary && article.summary && (
|
|
141
|
+
<p class="cg-card-summary">{truncateSummary(article.summary, summaryMaxLength)}</p>
|
|
142
|
+
)}
|
|
143
|
+
|
|
144
|
+
<div class="cg-card-meta">
|
|
145
|
+
<span class="cg-meta-author">{article.authorName}</span>
|
|
146
|
+
<span class="cg-meta-separator">•</span>
|
|
147
|
+
<time class="cg-meta-date" datetime={new Date(article.publishedAt * 1000).toISOString()}>
|
|
148
|
+
{formatDate(article.publishedAt)}
|
|
149
|
+
</time>
|
|
150
|
+
<span class="cg-meta-separator">•</span>
|
|
151
|
+
<span class="cg-meta-reading-time">{calculateReadingTime(article.wordCount)}</span>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{showTags && article.tags && article.tags.length > 0 && (
|
|
155
|
+
<div class="cg-card-tags">
|
|
156
|
+
{article.tags.map((tag) => (
|
|
157
|
+
<span class="cg-tag">{tag}</span>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</a>
|
|
163
|
+
</article>
|
|
164
|
+
) : (
|
|
165
|
+
error ? (
|
|
166
|
+
<div class={`cg-widget cg-error ${className}`}>
|
|
167
|
+
{error}
|
|
168
|
+
</div>
|
|
169
|
+
) : null
|
|
170
|
+
)}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ContentListProps } from '../types/index.js';
|
|
7
7
|
import { ContentGrowthClient } from '../core/client.js';
|
|
8
|
-
import
|
|
8
|
+
import FeaturedCard from './FeaturedCard.astro';
|
|
9
|
+
import ContentCard from './ContentCard.astro';
|
|
9
10
|
|
|
10
11
|
interface Props extends ContentListProps {}
|
|
11
12
|
|
|
@@ -14,6 +15,7 @@ const {
|
|
|
14
15
|
baseUrl,
|
|
15
16
|
layout = 'cards',
|
|
16
17
|
displayMode = 'comfortable',
|
|
18
|
+
displayAs = 'default',
|
|
17
19
|
theme = 'light',
|
|
18
20
|
pageSize = 12,
|
|
19
21
|
tags = [],
|
|
@@ -67,10 +69,13 @@ function truncateSummary(text: string | null, maxLength?: number): string {
|
|
|
67
69
|
if (!maxLength || text.length <= maxLength) return text;
|
|
68
70
|
return text.substring(0, maxLength).trim() + '...';
|
|
69
71
|
}
|
|
72
|
+
|
|
73
|
+
// Check if we're in featured-cards mode
|
|
74
|
+
const isFeaturedCardsMode = displayAs === 'featured-cards';
|
|
70
75
|
---
|
|
71
76
|
|
|
72
77
|
<div
|
|
73
|
-
class={`cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${className}`}
|
|
78
|
+
class={`cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${isFeaturedCardsMode ? 'cg-featured-cards-list' : ''} ${className}`}
|
|
74
79
|
data-cg-widget="list"
|
|
75
80
|
>
|
|
76
81
|
{articles.length === 0 ? (
|
|
@@ -79,60 +84,38 @@ function truncateSummary(text: string | null, maxLength?: number): string {
|
|
|
79
84
|
</div>
|
|
80
85
|
) : (
|
|
81
86
|
<>
|
|
82
|
-
<div class={`cg-articles-grid ${layout === 'cards' ? 'cg-grid' : 'cg-list'}`}>
|
|
87
|
+
<div class={`cg-articles-grid ${isFeaturedCardsMode ? 'cg-featured-cards-grid' : (layout === 'cards' ? 'cg-grid' : 'cg-list')}`}>
|
|
83
88
|
{articles.map((article) => {
|
|
84
|
-
// Build article URL from pattern
|
|
85
|
-
const articleUrl = linkPattern
|
|
86
|
-
.replace('{uuid}', article.uuid)
|
|
87
|
-
.replace('{slug}', article.slug)
|
|
88
|
-
.replace('{category}', article.category || '');
|
|
89
|
-
|
|
90
89
|
// Build link target from pattern
|
|
91
90
|
const articleTarget = linkTarget
|
|
92
91
|
? linkTarget
|
|
93
92
|
.replace('{uuid}', article.uuid)
|
|
94
93
|
.replace('{id}', article.uuid)
|
|
95
94
|
: undefined;
|
|
96
|
-
|
|
97
|
-
const readingTime = calculateReadingTime(article.wordCount);
|
|
98
|
-
const publishedDate = formatDate(article.publishedAt);
|
|
99
95
|
|
|
96
|
+
// Featured Cards Mode - Use FeaturedCard component
|
|
97
|
+
if (isFeaturedCardsMode) {
|
|
98
|
+
return (
|
|
99
|
+
<FeaturedCard
|
|
100
|
+
article={article}
|
|
101
|
+
linkPattern={linkPattern}
|
|
102
|
+
linkTarget={articleTarget}
|
|
103
|
+
showCategory={true}
|
|
104
|
+
/>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Default Card Mode - Use ContentCard component
|
|
100
109
|
return (
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<h2 class="cg-card-title">{article.title}</h2>
|
|
111
|
-
|
|
112
|
-
{showAiSummary && article.summary && (
|
|
113
|
-
<p class="cg-card-summary">{truncateSummary(article.summary, summaryMaxLength)}</p>
|
|
114
|
-
)}
|
|
115
|
-
|
|
116
|
-
<div class="cg-card-meta">
|
|
117
|
-
<span class="cg-meta-author">{article.authorName}</span>
|
|
118
|
-
<span class="cg-meta-separator">•</span>
|
|
119
|
-
<time class="cg-meta-date" datetime={new Date(article.publishedAt * 1000).toISOString()}>
|
|
120
|
-
{publishedDate}
|
|
121
|
-
</time>
|
|
122
|
-
<span class="cg-meta-separator">•</span>
|
|
123
|
-
<span class="cg-meta-reading-time">{readingTime}</span>
|
|
124
|
-
</div>
|
|
125
|
-
|
|
126
|
-
{showTags && article.tags && article.tags.length > 0 && (
|
|
127
|
-
<div class="cg-card-tags">
|
|
128
|
-
{article.tags.map((tag) => (
|
|
129
|
-
<span class="cg-tag">{tag}</span>
|
|
130
|
-
))}
|
|
131
|
-
</div>
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
134
|
-
</a>
|
|
135
|
-
</article>
|
|
110
|
+
<ContentCard
|
|
111
|
+
article={article}
|
|
112
|
+
linkPattern={linkPattern}
|
|
113
|
+
linkTarget={articleTarget}
|
|
114
|
+
showSummary={showAiSummary}
|
|
115
|
+
summaryMaxLength={summaryMaxLength}
|
|
116
|
+
showTags={showTags}
|
|
117
|
+
showCategory={true}
|
|
118
|
+
/>
|
|
136
119
|
);
|
|
137
120
|
})}
|
|
138
121
|
</div>
|