@contentgrowth/content-widget 1.1.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +442 -0
  3. package/dist/astro/ContentList.astro +177 -0
  4. package/dist/astro/ContentViewer.astro +252 -0
  5. package/dist/astro/index.d.ts +9 -0
  6. package/dist/astro/index.d.ts.map +1 -0
  7. package/dist/astro/index.js +8 -0
  8. package/dist/core/client.d.ts +67 -0
  9. package/dist/core/client.d.ts.map +1 -0
  10. package/dist/core/client.js +217 -0
  11. package/dist/core/index.d.ts +8 -0
  12. package/dist/core/index.d.ts.map +1 -0
  13. package/dist/core/index.js +7 -0
  14. package/dist/core/utils.d.ts +32 -0
  15. package/dist/core/utils.d.ts.map +1 -0
  16. package/dist/core/utils.js +70 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +6 -0
  20. package/dist/react/ContentList.d.ts +12 -0
  21. package/dist/react/ContentList.d.ts.map +1 -0
  22. package/dist/react/ContentList.js +106 -0
  23. package/dist/react/ContentViewer.d.ts +12 -0
  24. package/dist/react/ContentViewer.d.ts.map +1 -0
  25. package/dist/react/ContentViewer.js +97 -0
  26. package/dist/react/hooks.d.ts +63 -0
  27. package/dist/react/hooks.d.ts.map +1 -0
  28. package/dist/react/hooks.js +140 -0
  29. package/dist/react/index.d.ts +9 -0
  30. package/dist/react/index.d.ts.map +1 -0
  31. package/dist/react/index.js +6 -0
  32. package/dist/styles.css +970 -0
  33. package/dist/types/index.d.ts +271 -0
  34. package/dist/types/index.d.ts.map +1 -0
  35. package/dist/types/index.js +16 -0
  36. package/dist/vue/ContentList.vue +166 -0
  37. package/dist/vue/ContentViewer.vue +137 -0
  38. package/dist/vue/composables.d.ts +64 -0
  39. package/dist/vue/composables.d.ts.map +1 -0
  40. package/dist/vue/composables.js +165 -0
  41. package/dist/vue/index.d.ts +10 -0
  42. package/dist/vue/index.d.ts.map +1 -0
  43. package/dist/vue/index.js +8 -0
  44. package/dist/widget/content-card.js +190 -0
  45. package/dist/widget/content-list.js +289 -0
  46. package/dist/widget/content-viewer.js +230 -0
  47. package/dist/widget/index.js +40 -0
  48. package/dist/widget/utils/api-client.js +154 -0
  49. package/dist/widget/utils/helpers.js +71 -0
  50. package/dist/widget/widget-js/content-card.js +190 -0
  51. package/dist/widget/widget-js/content-list.js +289 -0
  52. package/dist/widget/widget-js/content-viewer.js +230 -0
  53. package/dist/widget/widget-js/index.js +40 -0
  54. package/dist/widget/widget-js/utils/api-client.js +154 -0
  55. package/dist/widget/widget-js/utils/helpers.js +71 -0
  56. package/dist/widget/widget-js/widget.d.ts +24 -0
  57. package/dist/widget/widget-js/widget.js +240 -0
  58. package/dist/widget/widget.d.ts +24 -0
  59. package/dist/widget/widget.js +240 -0
  60. package/package.json +99 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Content Growth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,442 @@
1
+ # @content-growth/content-widget
2
+
3
+ Embed your Content Growth content anywhere with beautiful, customizable components.
4
+
5
+ ## Features
6
+
7
+ - 🎨 **Beautiful UI** - Pre-styled components with light/dark themes
8
+ - 🚀 **Multi-Framework** - React, Vue, Astro, and Vanilla JS support
9
+ - 📦 **Zero Config** - Works out of the box with sensible defaults
10
+ - 🎯 **Type Safe** - Full TypeScript support
11
+ - âš¡ **Fast** - Built-in caching and optimized performance
12
+ - 🎨 **Customizable** - Multiple layouts and display modes
13
+ - 📱 **Responsive** - Mobile-first design
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @content-growth/content-widget
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Vanilla JavaScript
24
+
25
+ ```html
26
+ <!-- Add CSS -->
27
+ <link rel="stylesheet" href="node_modules/@content-growth/content-widget/dist/styles.css">
28
+
29
+ <!-- Add container -->
30
+ <div id="blog"></div>
31
+
32
+ <!-- Initialize widget -->
33
+ <script type="module">
34
+ import { ContentGrowthWidget } from '@content-growth/content-widget/widget';
35
+
36
+ new ContentGrowthWidget(document.getElementById('blog'), {
37
+ apiKey: 'pk_your_key_here',
38
+ layout: 'cards'
39
+ });
40
+ </script>
41
+ ```
42
+
43
+ ### React
44
+
45
+ ```jsx
46
+ import { ContentList } from '@content-growth/content-widget/react';
47
+ import '@content-growth/content-widget/styles.css';
48
+
49
+ function App() {
50
+ return <ContentList apiKey="pk_your_key_here" layout="cards" />;
51
+ }
52
+ ```
53
+
54
+ **With Hooks:**
55
+
56
+ ```jsx
57
+ import { useArticles } from '@content-growth/content-widget/react';
58
+
59
+ function CustomContentList() {
60
+ const { articles, loading, error } = useArticles({
61
+ apiKey: 'pk_your_key_here',
62
+ page: 1,
63
+ limit: 12
64
+ });
65
+
66
+ if (loading) return <div>Loading...</div>;
67
+ if (error) return <div>Error: {error.message}</div>;
68
+
69
+ return (
70
+ <div>
71
+ {articles.map(article => (
72
+ <div key={article.uuid}>
73
+ <h2>{article.title}</h2>
74
+ <p>{article.summary}</p>
75
+ </div>
76
+ ))}
77
+ </div>
78
+ );
79
+ }
80
+ ```
81
+
82
+ ### Vue
83
+
84
+ ```vue
85
+ <template>
86
+ <ContentList api-key="pk_your_key_here" layout="cards" />
87
+ </template>
88
+
89
+ <script setup>
90
+ import { ContentList } from '@content-growth/content-widget/vue';
91
+ import '@content-growth/content-widget/styles.css';
92
+ </script>
93
+ ```
94
+
95
+ **With Composables:**
96
+
97
+ ```vue
98
+ <template>
99
+ <div>
100
+ <div v-if="loading">Loading...</div>
101
+ <div v-else-if="error">Error: {{ error.message }}</div>
102
+ <div v-else>
103
+ <div v-for="article in articles" :key="article.uuid">
104
+ <h2>{{ article.title }}</h2>
105
+ <p>{{ article.summary }}</p>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </template>
110
+
111
+ <script setup>
112
+ import { useArticles } from '@content-growth/content-widget/vue';
113
+
114
+ const { articles, loading, error } = useArticles({
115
+ apiKey: 'pk_your_key_here',
116
+ page: 1,
117
+ limit: 12
118
+ });
119
+ </script>
120
+ ```
121
+
122
+ ### Astro
123
+
124
+ ```astro
125
+ ---
126
+ import { ContentList } from '@content-growth/content-widget/astro';
127
+ import '@content-growth/content-widget/styles.css';
128
+ ---
129
+
130
+ <ContentList apiKey="pk_your_key_here" />
131
+ ```
132
+
133
+ ### API Client (Framework Agnostic)
134
+
135
+ ```typescript
136
+ import { ContentGrowthClient } from '@content-growth/content-widget/core';
137
+
138
+ const client = new ContentGrowthClient({
139
+ apiKey: 'pk_your_key_here'
140
+ });
141
+
142
+ const { articles, pagination } = await client.listArticles({
143
+ page: 1,
144
+ limit: 12,
145
+ tags: ['tutorial']
146
+ });
147
+ ```
148
+
149
+ ## Components
150
+
151
+ ### Vanilla JS Widget
152
+
153
+ ```javascript
154
+ import { ContentGrowthWidget } from '@content-growth/content-widget/widget';
155
+
156
+ const widget = new ContentGrowthWidget(container, {
157
+ apiKey: 'pk_your_key_here',
158
+ baseUrl: 'https://api.content-growth.com',
159
+ layoutMode: 'cards', // 'cards' | 'rows'
160
+ displayMode: 'comfortable', // 'compact' | 'comfortable' | 'spacious'
161
+ theme: 'light', // 'light' | 'dark'
162
+ pageSize: 12,
163
+ tags: ['tutorial', 'guide'],
164
+ category: 'guides',
165
+ viewerMode: 'inline', // 'inline' | 'modal' | 'external'
166
+ mode: 'list' // 'list' | 'article-only'
167
+ });
168
+ ```
169
+
170
+ ### React Components
171
+
172
+ **ContentList:**
173
+
174
+ ```jsx
175
+ <ContentList
176
+ apiKey="pk_your_key_here"
177
+ layout="cards"
178
+ displayMode="comfortable"
179
+ theme="light"
180
+ pageSize={12}
181
+ tags={['tutorial', 'guide']}
182
+ category="guides"
183
+ showPagination={true}
184
+ />
185
+ ```
186
+
187
+ **ContentViewer:**
188
+
189
+ ```jsx
190
+ <ContentViewer
191
+ apiKey="pk_your_key_here"
192
+ uuid="article-uuid"
193
+ theme="light"
194
+ showBackButton={true}
195
+ backUrl="/articles"
196
+ />
197
+ ```
198
+
199
+ **Hooks:**
200
+
201
+ - `useArticles(options)` - Fetch articles list
202
+ - `useArticle(options)` - Fetch single article
203
+ - `useCategories(options)` - Fetch categories
204
+ - `useTags(options)` - Fetch tags
205
+
206
+ ### Vue Components
207
+
208
+ **ContentList:**
209
+
210
+ ```vue
211
+ <ContentList
212
+ api-key="pk_your_key_here"
213
+ layout="cards"
214
+ display-mode="comfortable"
215
+ theme="light"
216
+ :page-size="12"
217
+ :tags="['tutorial', 'guide']"
218
+ category="guides"
219
+ :show-pagination="true"
220
+ />
221
+ ```
222
+
223
+ **ContentViewer:**
224
+
225
+ ```vue
226
+ <ContentViewer
227
+ api-key="pk_your_key_here"
228
+ uuid="article-uuid"
229
+ theme="light"
230
+ :show-back-button="true"
231
+ back-url="/articles"
232
+ />
233
+ ```
234
+
235
+ **Composables:**
236
+
237
+ - `useArticles(options)` - Fetch articles list
238
+ - `useArticle(options)` - Fetch single article
239
+ - `useCategories(options)` - Fetch categories
240
+ - `useTags(options)` - Fetch tags
241
+
242
+ ### Astro Components
243
+
244
+ **ContentList:**
245
+
246
+ ```astro
247
+ <ContentList
248
+ apiKey="pk_your_key_here"
249
+ layout="cards"
250
+ displayMode="comfortable"
251
+ theme="light"
252
+ pageSize={12}
253
+ tags={['tutorial', 'guide']}
254
+ category="guides"
255
+ showPagination={true}
256
+ />
257
+ ```
258
+
259
+ **ContentViewer:**
260
+
261
+ ```astro
262
+ <ContentViewer
263
+ apiKey="pk_your_key_here"
264
+ uuid={uuid}
265
+ theme="light"
266
+ showBackButton={true}
267
+ backUrl="/articles"
268
+ />
269
+ ```
270
+
271
+ ## API Client
272
+
273
+ ### ContentGrowthClient
274
+
275
+ ```typescript
276
+ import { ContentGrowthClient } from '@content-growth/content-widget/core';
277
+
278
+ const client = new ContentGrowthClient({
279
+ apiKey: 'pk_your_key_here',
280
+ baseUrl: 'https://api.content-growth.com', // optional
281
+ cacheTTL: 300000, // 5 minutes, optional
282
+ debug: false // optional
283
+ });
284
+ ```
285
+
286
+ ### Methods
287
+
288
+ #### `listArticles(options?)`
289
+
290
+ ```typescript
291
+ const { articles, pagination } = await client.listArticles({
292
+ page: 1,
293
+ limit: 12,
294
+ tags: ['tutorial', 'guide'],
295
+ category: 'guides'
296
+ });
297
+ ```
298
+
299
+ #### `getArticle(uuid)`
300
+
301
+ ```typescript
302
+ const article = await client.getArticle('article-uuid');
303
+ ```
304
+
305
+ #### `getCategories()`
306
+
307
+ ```typescript
308
+ const { categories } = await client.getCategories();
309
+ ```
310
+
311
+ #### `getTags()`
312
+
313
+ ```typescript
314
+ const { tags } = await client.getTags();
315
+ ```
316
+
317
+ #### `clearCache()`
318
+
319
+ ```typescript
320
+ client.clearCache();
321
+ ```
322
+
323
+ ## Styling
324
+
325
+ Import the CSS file:
326
+
327
+ ```javascript
328
+ import '@content-growth/content-widget/styles.css';
329
+ ```
330
+
331
+ ### Customization
332
+
333
+ Override CSS variables:
334
+
335
+ ```css
336
+ .cg-content-list {
337
+ --cg-primary: #3b82f6;
338
+ --cg-primary-hover: #2563eb;
339
+ --cg-bg: #ffffff;
340
+ --cg-bg-secondary: #f9fafb;
341
+ --cg-text: #1f2937;
342
+ --cg-text-secondary: #6b7280;
343
+ --cg-border: #e5e7eb;
344
+ --cg-radius: 12px;
345
+ }
346
+ ```
347
+
348
+ ## TypeScript
349
+
350
+ Full TypeScript support with exported types:
351
+
352
+ ```typescript
353
+ import type {
354
+ Article,
355
+ ArticleWithContent,
356
+ Pagination,
357
+ Category,
358
+ Tag,
359
+ ClientConfig,
360
+ ListArticlesOptions,
361
+ ContentListProps,
362
+ ContentViewerProps
363
+ } from '@content-growth/content-widget/core';
364
+ ```
365
+
366
+ ## Getting Your API Key
367
+
368
+ 1. Sign in to [Content Growth](https://app.content-growth.com)
369
+ 2. Go to Settings → API Keys
370
+ 3. Create a new API key
371
+ 4. Copy the key (starts with `pk_`)
372
+
373
+ ## Examples
374
+
375
+ ### Next.js App Router
376
+
377
+ ```tsx
378
+ // app/articles/page.tsx
379
+ import { ContentList } from '@content-growth/content-widget/react';
380
+ import '@content-growth/content-widget/styles.css';
381
+
382
+ export default function ArticlesPage() {
383
+ return (
384
+ <main>
385
+ <h1>Articles</h1>
386
+ <ContentList apiKey={process.env.CG_API_KEY!} />
387
+ </main>
388
+ );
389
+ }
390
+ ```
391
+
392
+ ### Nuxt 3
393
+
394
+ ```vue
395
+ <!-- pages/articles.vue -->
396
+ <template>
397
+ <div>
398
+ <h1>Articles</h1>
399
+ <ContentList :api-key="apiKey" />
400
+ </div>
401
+ </template>
402
+
403
+ <script setup>
404
+ import { ContentList } from '@content-growth/content-widget/vue';
405
+ import '@content-growth/content-widget/styles.css';
406
+
407
+ const config = useRuntimeConfig();
408
+ const apiKey = config.public.cgApiKey;
409
+ </script>
410
+ ```
411
+
412
+ ### Vanilla HTML
413
+
414
+ ```html
415
+ <!DOCTYPE html>
416
+ <html>
417
+ <head>
418
+ <link rel="stylesheet" href="https://unpkg.com/@content-growth/content-widget/dist/styles.css">
419
+ </head>
420
+ <body>
421
+ <div id="blog"></div>
422
+
423
+ <script type="module">
424
+ import { ContentGrowthWidget } from 'https://unpkg.com/@content-growth/content-widget/dist/widget/index.js';
425
+
426
+ new ContentGrowthWidget(document.getElementById('blog'), {
427
+ apiKey: 'pk_your_key_here'
428
+ });
429
+ </script>
430
+ </body>
431
+ </html>
432
+ ```
433
+
434
+ ## License
435
+
436
+ MIT
437
+
438
+ ## Support
439
+
440
+ - [Documentation](https://docs.content-growth.com)
441
+ - [GitHub Issues](https://github.com/ContentGrowth/content-package/issues)
442
+ - [Email Support](mailto:support@content-growth.com)
@@ -0,0 +1,177 @@
1
+ ---
2
+ /**
3
+ * ContentList Component
4
+ * Displays a list of articles with pagination
5
+ */
6
+ import type { ContentListProps } from '../types/index.js';
7
+ import { ContentGrowthClient } from '../core/client.js';
8
+ import { formatDate, calculateReadingTime } from '../core/utils.js';
9
+
10
+ interface Props extends ContentListProps {}
11
+
12
+ const {
13
+ apiKey,
14
+ baseUrl,
15
+ layout = 'cards',
16
+ displayMode = 'comfortable',
17
+ theme = 'light',
18
+ pageSize = 12,
19
+ tags = [],
20
+ category,
21
+ showPagination = true,
22
+ linkPattern = '/articles/{uuid}',
23
+ showTags = false,
24
+ showAiSummary = true,
25
+ summaryMaxLength,
26
+ linkTarget,
27
+ class: className = ''
28
+ } = Astro.props;
29
+
30
+ // Get current page from URL
31
+ const url = new URL(Astro.request.url);
32
+ const currentPage = parseInt(url.searchParams.get('page') || '1', 10);
33
+
34
+ // Fetch articles
35
+ const client = new ContentGrowthClient({ apiKey, baseUrl });
36
+
37
+ // Process tags
38
+ let processedTags: string[] | undefined;
39
+ if (tags) {
40
+ if (Array.isArray(tags)) {
41
+ processedTags = tags;
42
+ } else {
43
+ processedTags = tags.split(',').map(t => t.trim()).filter(Boolean);
44
+ }
45
+ }
46
+
47
+ const options = {
48
+ page: currentPage,
49
+ limit: pageSize,
50
+ tags: processedTags,
51
+ category: category
52
+ };
53
+ const result = await client.listArticles(options);
54
+ const articles = result.articles;
55
+ const pagination = result.pagination;
56
+
57
+ // Build pagination URLs
58
+ function buildPageUrl(page: number): string {
59
+ const newUrl = new URL(url);
60
+ newUrl.searchParams.set('page', page.toString());
61
+ return newUrl.pathname + newUrl.search;
62
+ }
63
+
64
+ // Truncate summary text
65
+ function truncateSummary(text: string | null, maxLength?: number): string {
66
+ if (!text) return '';
67
+ if (!maxLength || text.length <= maxLength) return text;
68
+ return text.substring(0, maxLength).trim() + '...';
69
+ }
70
+ ---
71
+
72
+ <div
73
+ class={`cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${className}`}
74
+ data-cg-widget="list"
75
+ >
76
+ {articles.length === 0 ? (
77
+ <div class="cg-empty-state">
78
+ <p>No articles found.</p>
79
+ </div>
80
+ ) : (
81
+ <>
82
+ <div class={`cg-articles-grid ${layout === 'cards' ? 'cg-grid' : 'cg-list'}`}>
83
+ {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
+ // Build link target from pattern
91
+ const articleTarget = linkTarget
92
+ ? linkTarget
93
+ .replace('{uuid}', article.uuid)
94
+ .replace('{id}', article.uuid)
95
+ : undefined;
96
+
97
+ const readingTime = calculateReadingTime(article.wordCount);
98
+ const publishedDate = formatDate(article.publishedAt);
99
+
100
+ return (
101
+ <article class="cg-article-card">
102
+ <a href={articleUrl} target={articleTarget} class="cg-card-link">
103
+ <div class="cg-card-content">
104
+ {article.category && (
105
+ <div class="cg-card-category">
106
+ <span class="cg-category-badge">{article.category}</span>
107
+ </div>
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>
136
+ );
137
+ })}
138
+ </div>
139
+
140
+ {showPagination && pagination.totalPages > 1 && (
141
+ <nav class="cg-pagination" aria-label="Article pagination">
142
+ <div class="cg-pagination-info">
143
+ Page {pagination.page} of {pagination.totalPages}
144
+ </div>
145
+
146
+ <div class="cg-pagination-controls">
147
+ {pagination.hasPrev && (
148
+ <a
149
+ href={buildPageUrl(pagination.page - 1)}
150
+ class="cg-pagination-btn cg-pagination-prev"
151
+ aria-label="Previous page"
152
+ >
153
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
154
+ <path d="M12 16L6 10L12 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
155
+ </svg>
156
+ Previous
157
+ </a>
158
+ )}
159
+
160
+ {pagination.hasNext && (
161
+ <a
162
+ href={buildPageUrl(pagination.page + 1)}
163
+ class="cg-pagination-btn cg-pagination-next"
164
+ aria-label="Next page"
165
+ >
166
+ Next
167
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
168
+ <path d="M8 4L14 10L8 16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
169
+ </svg>
170
+ </a>
171
+ )}
172
+ </div>
173
+ </nav>
174
+ )}
175
+ </>
176
+ )}
177
+ </div>