@contentgrowth/content-widget 1.3.5 → 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 CHANGED
@@ -320,6 +320,8 @@ A compact card displaying the Featured Summary with customizable styling. Perfec
320
320
  linkPattern="/articles/{slug}"
321
321
  layout="horizontal"
322
322
  borderStyle="dashed"
323
+ borderColor="#e5e7eb"
324
+ padding="20px"
323
325
  ctaText="Read full story"
324
326
  />
325
327
 
@@ -350,11 +352,23 @@ A compact card displaying the Featured Summary with customizable styling. Perfec
350
352
  | `borderColor` | string | '#e5e7eb' | Border color (CSS value) |
351
353
  | `cardBackground` | string | 'none' | Card background ('none' = transparent) |
352
354
  | `itemsBackground` | string | '#f3f4f6' | Background for list/quote section |
355
+ | `padding` | string | - | Custom padding (e.g., '10px', '2rem 3rem') |
353
356
  | `ctaText` | string | 'Read full story' | Call-to-action text |
354
357
  | `showAuthor` | boolean | false | Show author name |
355
358
  | `showReadingTime` | boolean | false | Show reading time |
356
359
  | `linkPattern` | string | '/articles/{slug}' | URL pattern for link |
357
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
+
358
372
  **Featured Cards List:**
359
373
 
360
374
  Display all articles as FeaturedCards in a grid using `displayAs`:
@@ -1,5 +1,4 @@
1
1
  ---
2
- import { marked } from 'marked';
3
2
  import { ContentGrowthClient } from '../core/client';
4
3
  import type { FeaturedContentProps, Article, ArticleWithContent } from '../types';
5
4
 
@@ -78,6 +77,12 @@ interface FeaturedCardProps extends Omit<FeaturedContentProps, 'showBackButton'
78
77
  * @default undefined (same tab)
79
78
  */
80
79
  linkTarget?: string;
80
+
81
+ /**
82
+ * Custom padding for the card content
83
+ * @example "20px" or "0"
84
+ */
85
+ padding?: string;
81
86
  }
82
87
 
83
88
  type Props = FeaturedCardProps & { class?: string };
@@ -102,6 +107,7 @@ const {
102
107
  borderColor = '#e5e7eb',
103
108
  cardBackground = 'none',
104
109
  itemsBackground = '#f3f4f6',
110
+ padding,
105
111
  class: className = ''
106
112
  } = Astro.props;
107
113
 
@@ -146,13 +152,39 @@ const getArticleUrl = (article: any) => {
146
152
  .replace('{category}', article.category || 'uncategorized');
147
153
  };
148
154
 
149
- // Render featured summary (or fallback to regular summary) as HTML
150
- const getSummaryHtml = (article: any) => {
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 => {
151
166
  const summaryText = article.featuredSummary || article.summary;
152
- if (!summaryText) return '';
153
- return marked.parse(summaryText, { async: false }) as string;
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
+ };
154
184
  };
155
185
 
186
+ const summaryData = article ? parseSummary(article) : null;
187
+
156
188
  const layout = propLayout || (article as any)?.featuredSummaryLayout || 'vertical';
157
189
  const layoutClass = layout !== 'vertical' ? `cg-layout-${layout}` : '';
158
190
  const borderClass = borderStyle !== 'none' ? `cg-border-${borderStyle}` : '';
@@ -165,6 +197,7 @@ const customStyles = [
165
197
  borderColor !== '#e5e7eb' ? `--cg-card-border-color: ${borderColor}` : '',
166
198
  cardBackground !== 'none' ? `--cg-card-bg: ${cardBackground}` : '',
167
199
  itemsBackground !== '#f3f4f6' ? `--cg-items-bg: ${itemsBackground}` : '',
200
+ padding ? `--cg-card-padding: ${padding}` : '',
168
201
  ].filter(Boolean).join('; ');
169
202
  ---
170
203
 
@@ -178,42 +211,83 @@ const customStyles = [
178
211
  rel={linkTarget === '_blank' ? 'noopener noreferrer' : undefined}
179
212
  >
180
213
  <article class="cg-featured-card-inner">
181
- {/* Header with category badge */}
182
- {showCategory && article.category && (
183
- <div class="cg-featured-card-category">
184
- <span class="cg-category-badge">{article.category}</span>
185
- </div>
186
- )}
187
-
188
- {/* Title */}
189
- <h3 class="cg-featured-card-title">{article.title}</h3>
190
-
191
- {/* Featured Summary */}
192
- {(article.featuredSummary || article.summary) && (
193
- <div
194
- class="cg-featured-card-summary"
195
- set:html={getSummaryHtml(article)}
196
- />
197
- )}
198
-
199
- {/* Footer with meta info */}
200
- {(showAuthor || showReadingTime) && (
201
- <div class="cg-featured-card-footer">
202
- {showAuthor && <span class="cg-featured-card-author">{article.authorName}</span>}
203
-
204
- {showAuthor && showReadingTime && (
205
- <span class="cg-featured-card-separator">•</span>
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>
206
280
  )}
207
281
 
208
- {showReadingTime && (
209
- <span class="cg-featured-card-reading-time">
210
- {Math.ceil(article.wordCount / 200)} min read
211
- </span>
282
+ {summaryData.type === 'quote' && (
283
+ <blockquote>
284
+ {summaryData.quote}
285
+ </blockquote>
212
286
  )}
213
287
  </div>
214
288
  )}
215
-
216
- {/* Read more indicator */}
289
+
290
+ {/* Read more indicator - Now at bottom */}
217
291
  <div class="cg-featured-card-cta">
218
292
  <span>{ctaText}</span>
219
293
  <svg class="cg-featured-card-arrow" fill="none" stroke="currentColor" viewBox="0 0 24 24" width="16" height="16">
@@ -58,11 +58,20 @@ interface FeaturedCardProps extends Partial<Omit<FeaturedContentProps, 'showBack
58
58
  * @default 'none'
59
59
  */
60
60
  cardBackground?: string;
61
+ /**
62
+ * Background color for list/quote section (the items area)
63
+ * @default '#f3f4f6'
64
+ */
61
65
  /**
62
66
  * Background color for list/quote section (the items area)
63
67
  * @default '#f3f4f6'
64
68
  */
65
69
  itemsBackground?: string;
70
+ /**
71
+ * Custom padding for the card content
72
+ * @example "20px" or "0"
73
+ */
74
+ padding?: string;
66
75
  }
67
76
  export declare const FeaturedCard: React.FC<FeaturedCardProps>;
68
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;AAGnD,OAAO,KAAK,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAElF,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,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA0KpD,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"}
@@ -1,7 +1,27 @@
1
1
  import React, { useEffect, useState } from 'react';
2
- import { marked } from 'marked';
3
2
  import { ContentGrowthClient } from '../core/client';
4
- export const FeaturedCard = ({ apiKey, baseUrl, article: providedArticle, slug, uuid, tags = [], category, excludeTags = [], showCategory = true, showReadingTime = false, showAuthor = false, ctaText: propCtaText, linkPattern = '/articles/{slug}', linkTarget, layout: propLayout, borderStyle = 'none', borderColor = '#e5e7eb', cardBackground = 'none', itemsBackground = '#f3f4f6', className = '' }) => {
3
+ // Parse featured summary - supports both JSON (new) and plain text (legacy)
4
+ const parseSummary = (article) => {
5
+ const summaryText = article.featuredSummary || article.summary;
6
+ if (!summaryText)
7
+ return null;
8
+ // Try to parse as JSON
9
+ try {
10
+ const parsed = JSON.parse(summaryText);
11
+ if (parsed.type) {
12
+ return parsed;
13
+ }
14
+ }
15
+ catch (e) {
16
+ // Not JSON, treat as legacy markdown/plain text
17
+ }
18
+ // Legacy fallback - render as plain text
19
+ return {
20
+ type: 'legacy',
21
+ text: summaryText
22
+ };
23
+ };
24
+ export const FeaturedCard = ({ apiKey, baseUrl, article: providedArticle, slug, uuid, tags = [], category, excludeTags = [], showCategory = true, showReadingTime = false, showAuthor = false, ctaText: propCtaText, linkPattern = '/articles/{slug}', linkTarget, layout: propLayout, borderStyle = 'none', borderColor = '#e5e7eb', cardBackground = 'none', itemsBackground = '#f3f4f6', padding, className = '' }) => {
5
25
  const [article, setArticle] = useState(providedArticle || null);
6
26
  const [loading, setLoading] = useState(!providedArticle);
7
27
  const [error, setError] = useState(null);
@@ -56,13 +76,6 @@ export const FeaturedCard = ({ apiKey, baseUrl, article: providedArticle, slug,
56
76
  .replace('{slug}', article.slug || article.uuid || '')
57
77
  .replace('{category}', article.category || 'uncategorized');
58
78
  };
59
- // Render featured summary (or fallback to regular summary) as HTML
60
- const getSummaryHtml = (article) => {
61
- const summaryText = article.featuredSummary || article.summary;
62
- if (!summaryText)
63
- return '';
64
- return marked.parse(summaryText, { async: false });
65
- };
66
79
  if (loading) {
67
80
  return (React.createElement("div", { className: `cg-widget cg-loading ${className}` },
68
81
  React.createElement("div", { className: "cg-spinner" })));
@@ -70,7 +83,6 @@ export const FeaturedCard = ({ apiKey, baseUrl, article: providedArticle, slug,
70
83
  if (error || !article) {
71
84
  return (React.createElement("div", { className: `cg-widget cg-error ${className}` }, error || 'No featured content found'));
72
85
  }
73
- const summaryHtml = getSummaryHtml(article);
74
86
  const layout = propLayout || article.featuredSummaryLayout || 'vertical';
75
87
  const readingTime = Math.ceil(article.wordCount / 200);
76
88
  const borderClass = borderStyle !== 'none' ? `cg-border-${borderStyle}` : '';
@@ -84,18 +96,52 @@ export const FeaturedCard = ({ apiKey, baseUrl, article: providedArticle, slug,
84
96
  customStyles['--cg-card-bg'] = cardBackground;
85
97
  if (itemsBackground !== '#f3f4f6')
86
98
  customStyles['--cg-items-bg'] = itemsBackground;
99
+ if (padding)
100
+ customStyles['--cg-card-padding'] = padding;
87
101
  return (React.createElement("a", { href: getArticleUrl(article), className: `cg-widget cg-featured-card ${className} ${layoutClass} ${borderClass}`, style: Object.keys(customStyles).length > 0 ? customStyles : undefined, "data-cg-widget": "featured-card", target: linkTarget, rel: linkTarget === '_blank' ? 'noopener noreferrer' : undefined },
88
102
  React.createElement("article", { className: "cg-featured-card-inner" },
89
- showCategory && article.category && (React.createElement("div", { className: "cg-featured-card-category" },
90
- React.createElement("span", { className: "cg-category-badge" }, article.category))),
91
- React.createElement("h3", { className: "cg-featured-card-title" }, article.title),
92
- summaryHtml && (React.createElement("div", { className: "cg-featured-card-summary", dangerouslySetInnerHTML: { __html: summaryHtml } })),
93
- (showAuthor || showReadingTime) && (React.createElement("div", { className: "cg-featured-card-footer" },
94
- showAuthor && React.createElement("span", { className: "cg-featured-card-author" }, article.authorName),
95
- showAuthor && showReadingTime && (React.createElement("span", { className: "cg-featured-card-separator" }, "\u2022")),
96
- showReadingTime && (React.createElement("span", { className: "cg-featured-card-reading-time" },
97
- readingTime,
98
- " min read")))),
103
+ React.createElement("div", { className: "cg-card-primary" },
104
+ showCategory && article.category && (React.createElement("div", { className: "cg-featured-card-category" },
105
+ React.createElement("span", { className: "cg-category-badge" }, article.category))),
106
+ React.createElement("h3", { className: "cg-featured-card-title" }, article.title),
107
+ parseSummary(article) && (React.createElement("div", { className: "cg-featured-card-summary" }, (() => {
108
+ const summaryData = parseSummary(article);
109
+ if (summaryData.type === 'classic') {
110
+ return React.createElement("p", null, summaryData.text);
111
+ }
112
+ if ((summaryData.type === 'list' || summaryData.type === 'steps') && summaryData.intro) {
113
+ return React.createElement("p", null, summaryData.intro);
114
+ }
115
+ if (summaryData.type === 'quote' && summaryData.quote) {
116
+ return React.createElement("p", null, summaryData.intro);
117
+ }
118
+ // Legacy
119
+ return React.createElement("p", null, summaryData.text);
120
+ })())),
121
+ (showAuthor || showReadingTime) && (React.createElement("div", { className: "cg-featured-card-footer" },
122
+ showAuthor && React.createElement("span", { className: "cg-featured-card-author" }, article.authorName),
123
+ showAuthor && showReadingTime && (React.createElement("span", { className: "cg-featured-card-separator" }, "\u2022")),
124
+ showReadingTime && (React.createElement("span", { className: "cg-featured-card-reading-time" },
125
+ readingTime,
126
+ " min read"))))),
127
+ (() => {
128
+ const summaryData = parseSummary(article);
129
+ if (!summaryData)
130
+ return null;
131
+ if ((summaryData.type === 'list' || summaryData.type === 'steps') && summaryData.items) {
132
+ return (React.createElement("div", { className: "cg-card-secondary" },
133
+ React.createElement("ul", { className: "cg-summary-items" }, summaryData.items.map((item, index) => (React.createElement("li", { key: index },
134
+ React.createElement("span", { className: "cg-item-number" }, index + 1),
135
+ React.createElement("div", { className: "cg-item-content" },
136
+ React.createElement("strong", { className: "cg-item-title" }, item.title),
137
+ React.createElement("span", { className: "cg-item-description" }, item.description))))))));
138
+ }
139
+ if (summaryData.type === 'quote' && summaryData.quote) {
140
+ return (React.createElement("div", { className: "cg-card-secondary" },
141
+ React.createElement("blockquote", null, summaryData.quote)));
142
+ }
143
+ return null;
144
+ })(),
99
145
  React.createElement("div", { className: "cg-featured-card-cta" },
100
146
  React.createElement("span", null, ctaText),
101
147
  React.createElement("svg", { className: "cg-featured-card-arrow", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", width: "16", height: "16" },