@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
@@ -0,0 +1,252 @@
1
+ ---
2
+ /**
3
+ * ContentViewer Component
4
+ * Displays a single article with full content
5
+ */
6
+ import type { ContentViewerProps } from '../types/index.js';
7
+ import { ContentGrowthClient } from '../core/client.js';
8
+ import { formatDate, calculateReadingTime } from '../core/utils.js';
9
+ import { marked } from 'marked';
10
+
11
+ interface Props extends ContentViewerProps {}
12
+
13
+ const {
14
+ apiKey,
15
+ uuid,
16
+ slug,
17
+ baseUrl,
18
+ theme = 'light',
19
+ showBackButton = false,
20
+ backUrl = '/articles',
21
+ showAiSummary = true,
22
+ class: className = ''
23
+ } = Astro.props;
24
+
25
+ // Validate that either uuid or slug is provided
26
+ if (!uuid && !slug) {
27
+ throw new Error('Either uuid or slug must be provided to ContentViewer');
28
+ }
29
+
30
+ // Fetch article
31
+ const client = new ContentGrowthClient({ apiKey, baseUrl });
32
+ const article = slug
33
+ ? await client.getArticleBySlug(slug)
34
+ : await client.getArticle(uuid!);
35
+
36
+ // Process markdown content to handle custom image syntax
37
+ // Convert: ![alt](url =widthxheight) to ![alt](url){width="width" height="height"}
38
+ function processImageSyntax(markdown: string): string {
39
+ // Match: ![alt](url =widthxheight)
40
+ return markdown.replace(
41
+ /!\[([^\]]*)\]\(([^\s)]+)\s+=(\d+)x(\d+)\)/g,
42
+ (match, alt, url, width, height) => {
43
+ return `![${alt}](${url}){width="${width}" height="${height}"}`;
44
+ }
45
+ );
46
+ }
47
+
48
+ const processedContent = processImageSyntax(article.content);
49
+
50
+ // Configure marked to handle image attributes
51
+ marked.use({
52
+ renderer: {
53
+ image(href, title, text) {
54
+ // Extract width/height from {width="x" height="y"} syntax
55
+ const attrMatch = text.match(/\{width="(\d+)"\s+height="(\d+)"\}/);
56
+ if (attrMatch) {
57
+ const cleanText = text.replace(/\{[^}]+\}/, '').trim();
58
+ return `<img src="${href}" alt="${cleanText}" width="${attrMatch[1]}" height="${attrMatch[2]}" ${title ? `title="${title}"` : ''} />`;
59
+ }
60
+ return `<img src="${href}" alt="${text}" ${title ? `title="${title}"` : ''} />`;
61
+ }
62
+ }
63
+ });
64
+
65
+ // Render markdown to HTML
66
+ const contentHtml = marked(processedContent);
67
+
68
+ // Format metadata
69
+ const publishedDate = formatDate(article.publishedAt);
70
+ const readingTime = calculateReadingTime(article.wordCount);
71
+ ---
72
+
73
+ <article
74
+ class={`cg-content-viewer cg-theme-${theme} ${className}`}
75
+ data-cg-widget="post"
76
+ >
77
+ {showBackButton && (
78
+ <div class="cg-post-header">
79
+ <a href={backUrl} class="cg-back-btn">
80
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
81
+ <path d="M12 16L6 10L12 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
82
+ </svg>
83
+ Back to articles
84
+ </a>
85
+ </div>
86
+ )}
87
+
88
+ <header class="cg-post-meta">
89
+ {article.category && (
90
+ <div class="cg-post-category">
91
+ <span class="cg-category-badge">{article.category}</span>
92
+ </div>
93
+ )}
94
+
95
+ <h1 class="cg-post-title">{article.title}</h1>
96
+
97
+ {showAiSummary && article.summary && (
98
+ <div class="cg-ai-summary">
99
+ <div class="cg-ai-summary-header">
100
+ <svg class="cg-ai-summary-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
101
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
102
+ </svg>
103
+ <span class="cg-ai-summary-label">AI Generated Summary</span>
104
+ </div>
105
+ <p class="cg-ai-summary-text">{article.summary}</p>
106
+ </div>
107
+ )}
108
+
109
+ <div class="cg-post-info">
110
+ <span class="cg-info-author">{article.authorName}</span>
111
+ <span class="cg-info-separator">•</span>
112
+ <time class="cg-info-date" datetime={new Date(article.publishedAt * 1000).toISOString()}>
113
+ {publishedDate}
114
+ </time>
115
+ <span class="cg-info-separator">•</span>
116
+ <span class="cg-info-reading-time">{readingTime}</span>
117
+ </div>
118
+
119
+ {article.tags.length > 0 && (
120
+ <div class="cg-post-tags">
121
+ {article.tags.map((tag) => (
122
+ <span class="cg-tag">{tag}</span>
123
+ ))}
124
+ </div>
125
+ )}
126
+ </header>
127
+
128
+ <div class="cg-post-content" set:html={contentHtml} />
129
+ </article>
130
+
131
+ <style is:global>
132
+ /* Markdown content styling */
133
+ .cg-post-content {
134
+ line-height: 1.7;
135
+ }
136
+
137
+ .cg-post-content h1,
138
+ .cg-post-content h2,
139
+ .cg-post-content h3,
140
+ .cg-post-content h4,
141
+ .cg-post-content h5,
142
+ .cg-post-content h6 {
143
+ margin-top: 2em;
144
+ margin-bottom: 0.5em;
145
+ font-weight: 600;
146
+ line-height: 1.3;
147
+ }
148
+
149
+ .cg-post-content h1 { font-size: 2em; }
150
+ .cg-post-content h2 { font-size: 1.5em; }
151
+ .cg-post-content h3 { font-size: 1.25em; }
152
+ .cg-post-content h4 { font-size: 1.1em; }
153
+
154
+ .cg-post-content p {
155
+ margin-bottom: 1.5em;
156
+ }
157
+
158
+ .cg-post-content a {
159
+ color: #2563eb;
160
+ text-decoration: underline;
161
+ }
162
+
163
+ .cg-post-content a:hover {
164
+ color: #1d4ed8;
165
+ }
166
+
167
+ .cg-post-content ul,
168
+ .cg-post-content ol {
169
+ margin-bottom: 1.5em;
170
+ padding-left: 2em;
171
+ }
172
+
173
+ .cg-post-content li {
174
+ margin-bottom: 0.5em;
175
+ }
176
+
177
+ .cg-post-content code {
178
+ background: #f3f4f6;
179
+ padding: 0.2em 0.4em;
180
+ border-radius: 3px;
181
+ font-size: 0.9em;
182
+ font-family: 'Courier New', monospace;
183
+ }
184
+
185
+ .cg-post-content pre {
186
+ background: #1f2937;
187
+ color: #f9fafb;
188
+ padding: 1.5em;
189
+ border-radius: 8px;
190
+ overflow-x: auto;
191
+ margin-bottom: 1.5em;
192
+ }
193
+
194
+ .cg-post-content pre code {
195
+ background: none;
196
+ padding: 0;
197
+ color: inherit;
198
+ }
199
+
200
+ .cg-post-content blockquote {
201
+ border-left: 4px solid #e5e7eb;
202
+ padding-left: 1.5em;
203
+ margin: 1.5em 0;
204
+ font-style: italic;
205
+ color: #6b7280;
206
+ }
207
+
208
+ .cg-post-content img {
209
+ max-width: 100%;
210
+ height: auto;
211
+ border-radius: 8px;
212
+ margin: 1.5em 0;
213
+ }
214
+
215
+ .cg-post-content table {
216
+ width: 100%;
217
+ border-collapse: collapse;
218
+ margin-bottom: 1.5em;
219
+ }
220
+
221
+ .cg-post-content th,
222
+ .cg-post-content td {
223
+ border: 1px solid #e5e7eb;
224
+ padding: 0.75em;
225
+ text-align: left;
226
+ }
227
+
228
+ .cg-post-content th {
229
+ background: #f9fafb;
230
+ font-weight: 600;
231
+ }
232
+
233
+ /* Dark theme overrides */
234
+ .cg-theme-dark .cg-post-content code {
235
+ background: #374151;
236
+ color: #f9fafb;
237
+ }
238
+
239
+ .cg-theme-dark .cg-post-content blockquote {
240
+ border-left-color: #4b5563;
241
+ color: #9ca3af;
242
+ }
243
+
244
+ .cg-theme-dark .cg-post-content th {
245
+ background: #1f2937;
246
+ }
247
+
248
+ .cg-theme-dark .cg-post-content th,
249
+ .cg-theme-dark .cg-post-content td {
250
+ border-color: #374151;
251
+ }
252
+ </style>
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Content Growth Content Widget - Astro Components
3
+ *
4
+ * Note: Astro components are not compiled by TypeScript.
5
+ * They are copied directly to dist/ during the build process.
6
+ * Import them directly from the .astro files in your Astro project.
7
+ */
8
+ export type { ContentListProps, ContentViewerProps } from '../types/index.js';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/astro/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Content Growth Content Widget - Astro Components
3
+ *
4
+ * Note: Astro components are not compiled by TypeScript.
5
+ * They are copied directly to dist/ during the build process.
6
+ * Import them directly from the .astro files in your Astro project.
7
+ */
8
+ export {};
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Content Growth API Client
3
+ * Framework-agnostic API client for fetching articles
4
+ */
5
+ import type { ClientConfig, ListArticlesOptions, ArticlesResponse, ArticleWithContent, CategoriesResponse, TagsResponse } from '../types/index.js';
6
+ /**
7
+ * Content Growth API Client
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const client = new ContentGrowthClient({
12
+ * apiKey: 'pk_your_key_here'
13
+ * });
14
+ *
15
+ * const { articles, pagination } = await client.listArticles({
16
+ * page: 1,
17
+ * limit: 12,
18
+ * tags: ['tutorial']
19
+ * });
20
+ * ```
21
+ */
22
+ export declare class ContentGrowthClient {
23
+ private config;
24
+ private cache;
25
+ constructor(config: ClientConfig);
26
+ /**
27
+ * List articles with pagination and filtering
28
+ */
29
+ listArticles(options?: ListArticlesOptions): Promise<ArticlesResponse>;
30
+ /**
31
+ * Get a single article by UUID
32
+ */
33
+ getArticle(uuid: string): Promise<ArticleWithContent>;
34
+ /**
35
+ * Get a single article by slug
36
+ */
37
+ getArticleBySlug(slug: string): Promise<ArticleWithContent>;
38
+ /**
39
+ * Get all categories with article counts
40
+ */
41
+ getCategories(): Promise<CategoriesResponse>;
42
+ /**
43
+ * Get all tags with article counts
44
+ */
45
+ getTags(): Promise<TagsResponse>;
46
+ /**
47
+ * Clear the cache
48
+ */
49
+ clearCache(): void;
50
+ /**
51
+ * Internal fetch wrapper with error handling
52
+ */
53
+ private fetch;
54
+ /**
55
+ * Get from cache if not expired
56
+ */
57
+ private getFromCache;
58
+ /**
59
+ * Set cache entry
60
+ */
61
+ private setCache;
62
+ /**
63
+ * Debug logging
64
+ */
65
+ private log;
66
+ }
67
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/core/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;;;;;;;;;GAeG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,KAAK,CAA+B;gBAEhC,MAAM,EAAE,YAAY;IAmBhC;;OAEG;IACG,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAwChF;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmB3D;;OAEG;IACG,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmBjE;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAelD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;IAetC;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;OAEG;YACW,KAAK;IAoDnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAcpB;;OAEG;IACH,OAAO,CAAC,QAAQ;IAQhB;;OAEG;IACH,OAAO,CAAC,GAAG;CAKZ"}
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Content Growth API Client
3
+ * Framework-agnostic API client for fetching articles
4
+ */
5
+ import { ContentGrowthError } from '../types/index.js';
6
+ /**
7
+ * Content Growth API Client
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const client = new ContentGrowthClient({
12
+ * apiKey: 'pk_your_key_here'
13
+ * });
14
+ *
15
+ * const { articles, pagination } = await client.listArticles({
16
+ * page: 1,
17
+ * limit: 12,
18
+ * tags: ['tutorial']
19
+ * });
20
+ * ```
21
+ */
22
+ export class ContentGrowthClient {
23
+ config;
24
+ cache;
25
+ constructor(config) {
26
+ this.config = {
27
+ apiKey: config.apiKey,
28
+ baseUrl: config.baseUrl || 'https://api.content-growth.com',
29
+ cacheTTL: config.cacheTTL ?? 300000, // 5 minutes default
30
+ debug: config.debug ?? false
31
+ };
32
+ this.cache = new Map();
33
+ if (!this.config.apiKey) {
34
+ throw new ContentGrowthError('API key is required');
35
+ }
36
+ if (!this.config.apiKey.startsWith('pk_')) {
37
+ console.warn('[ContentGrowth] API key should start with "pk_" for public use');
38
+ }
39
+ }
40
+ /**
41
+ * List articles with pagination and filtering
42
+ */
43
+ async listArticles(options = {}) {
44
+ const { page = 1, limit = 12, tags = [], category } = options;
45
+ this.log('[ContentGrowthClient] listArticles called with options:', options);
46
+ // Build query params
47
+ const params = new URLSearchParams({
48
+ page: page.toString(),
49
+ limit: limit.toString()
50
+ });
51
+ if (tags.length > 0) {
52
+ params.set('tags', tags.join(','));
53
+ }
54
+ if (category) {
55
+ params.set('category', category);
56
+ }
57
+ const cacheKey = `articles:${params.toString()}`;
58
+ const cached = this.getFromCache(cacheKey);
59
+ if (cached) {
60
+ this.log('[ContentGrowthClient] Cache hit:', cacheKey);
61
+ return cached;
62
+ }
63
+ const url = `${this.config.baseUrl}/widget/articles?${params}`;
64
+ this.log('[ContentGrowthClient] Fetching from URL:', url);
65
+ const data = await this.fetch(url);
66
+ this.log('[ContentGrowthClient] Response received:', data);
67
+ this.setCache(cacheKey, data);
68
+ return data;
69
+ }
70
+ /**
71
+ * Get a single article by UUID
72
+ */
73
+ async getArticle(uuid) {
74
+ if (!uuid) {
75
+ throw new ContentGrowthError('Article UUID is required');
76
+ }
77
+ const cacheKey = `article:${uuid}`;
78
+ const cached = this.getFromCache(cacheKey);
79
+ if (cached) {
80
+ this.log('Cache hit:', cacheKey);
81
+ return cached;
82
+ }
83
+ const url = `${this.config.baseUrl}/widget/articles/${uuid}`;
84
+ const data = await this.fetch(url);
85
+ this.setCache(cacheKey, data);
86
+ return data;
87
+ }
88
+ /**
89
+ * Get a single article by slug
90
+ */
91
+ async getArticleBySlug(slug) {
92
+ if (!slug) {
93
+ throw new ContentGrowthError('Article slug is required');
94
+ }
95
+ const cacheKey = `article:slug:${slug}`;
96
+ const cached = this.getFromCache(cacheKey);
97
+ if (cached) {
98
+ this.log('Cache hit:', cacheKey);
99
+ return cached;
100
+ }
101
+ const url = `${this.config.baseUrl}/widget/articles/slug/${slug}`;
102
+ const data = await this.fetch(url);
103
+ this.setCache(cacheKey, data);
104
+ return data;
105
+ }
106
+ /**
107
+ * Get all categories with article counts
108
+ */
109
+ async getCategories() {
110
+ const cacheKey = 'categories';
111
+ const cached = this.getFromCache(cacheKey);
112
+ if (cached) {
113
+ this.log('Cache hit:', cacheKey);
114
+ return cached;
115
+ }
116
+ const url = `${this.config.baseUrl}/widget/categories`;
117
+ const data = await this.fetch(url);
118
+ this.setCache(cacheKey, data);
119
+ return data;
120
+ }
121
+ /**
122
+ * Get all tags with article counts
123
+ */
124
+ async getTags() {
125
+ const cacheKey = 'tags';
126
+ const cached = this.getFromCache(cacheKey);
127
+ if (cached) {
128
+ this.log('Cache hit:', cacheKey);
129
+ return cached;
130
+ }
131
+ const url = `${this.config.baseUrl}/widget/tags`;
132
+ const data = await this.fetch(url);
133
+ this.setCache(cacheKey, data);
134
+ return data;
135
+ }
136
+ /**
137
+ * Clear the cache
138
+ */
139
+ clearCache() {
140
+ this.cache.clear();
141
+ this.log('Cache cleared');
142
+ }
143
+ /**
144
+ * Internal fetch wrapper with error handling
145
+ */
146
+ async fetch(url) {
147
+ console.log('[ContentGrowthClient] Fetching:', url);
148
+ console.log('[ContentGrowthClient] API Key:', this.config.apiKey);
149
+ try {
150
+ const response = await fetch(url, {
151
+ headers: {
152
+ 'X-API-Key': this.config.apiKey,
153
+ 'Content-Type': 'application/json'
154
+ }
155
+ });
156
+ console.log('[ContentGrowthClient] Response status:', response.status, response.statusText);
157
+ if (!response.ok) {
158
+ const errorText = await response.text();
159
+ console.error('[ContentGrowthClient] Error response:', errorText);
160
+ let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
161
+ try {
162
+ const errorJson = JSON.parse(errorText);
163
+ errorMessage = errorJson.error || errorJson.message || errorMessage;
164
+ }
165
+ catch {
166
+ // Use default error message
167
+ }
168
+ throw new ContentGrowthError(errorMessage, response.status, errorText);
169
+ }
170
+ const data = await response.json();
171
+ console.log('[ContentGrowthClient] Response data:', data);
172
+ return data;
173
+ }
174
+ catch (error) {
175
+ if (error instanceof ContentGrowthError) {
176
+ console.error('[ContentGrowthClient] ContentGrowthError:', error);
177
+ throw error;
178
+ }
179
+ // Network or parsing error
180
+ console.error('[ContentGrowthClient] Network/Parse error:', error);
181
+ throw new ContentGrowthError(`Failed to fetch from Content Growth API: ${error.message}`, undefined, error);
182
+ }
183
+ }
184
+ /**
185
+ * Get from cache if not expired
186
+ */
187
+ getFromCache(key) {
188
+ const entry = this.cache.get(key);
189
+ if (!entry)
190
+ return null;
191
+ const now = Date.now();
192
+ if (now - entry.timestamp > this.config.cacheTTL) {
193
+ this.cache.delete(key);
194
+ this.log('Cache expired:', key);
195
+ return null;
196
+ }
197
+ return entry.data;
198
+ }
199
+ /**
200
+ * Set cache entry
201
+ */
202
+ setCache(key, data) {
203
+ this.cache.set(key, {
204
+ data,
205
+ timestamp: Date.now()
206
+ });
207
+ this.log('Cache set:', key);
208
+ }
209
+ /**
210
+ * Debug logging
211
+ */
212
+ log(...args) {
213
+ if (this.config.debug) {
214
+ console.log('[ContentGrowth]', ...args);
215
+ }
216
+ }
217
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Content Growth Content Widget - Core API Client
3
+ * Framework-agnostic API client for fetching articles
4
+ */
5
+ export { ContentGrowthClient } from './client.js';
6
+ export * from './utils.js';
7
+ export * from '../types/index.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Content Growth Content Widget - Core API Client
3
+ * Framework-agnostic API client for fetching articles
4
+ */
5
+ export { ContentGrowthClient } from './client.js';
6
+ export * from './utils.js';
7
+ export * from '../types/index.js';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Utility functions for the Content Growth widget
3
+ */
4
+ /**
5
+ * Format a Unix timestamp to a readable date string
6
+ */
7
+ export declare function formatDate(timestamp: number, locale?: string): string;
8
+ /**
9
+ * Calculate reading time based on word count
10
+ */
11
+ export declare function calculateReadingTime(wordCount: number, wordsPerMinute?: number): string;
12
+ /**
13
+ * Truncate text to a maximum length
14
+ */
15
+ export declare function truncate(text: string, maxLength: number): string;
16
+ /**
17
+ * Generate excerpt from content
18
+ */
19
+ export declare function generateExcerpt(content: string, maxLength?: number): string;
20
+ /**
21
+ * Slugify a string for URLs
22
+ */
23
+ export declare function slugify(text: string): string;
24
+ /**
25
+ * Parse tags from comma-separated string
26
+ */
27
+ export declare function parseTags(tags: string | string[]): string[];
28
+ /**
29
+ * Build article URL
30
+ */
31
+ export declare function buildArticleUrl(uuid: string, pattern?: string): string;
32
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/core/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,MAAgB,GAAG,MAAM,CAO9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,GAAE,MAAY,GAAG,MAAM,CAG5F;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAGhE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,MAAM,CAahF;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO5C;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAG3D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,MAA2B,GAAG,MAAM,CAE1F"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Utility functions for the Content Growth widget
3
+ */
4
+ /**
5
+ * Format a Unix timestamp to a readable date string
6
+ */
7
+ export function formatDate(timestamp, locale = 'en-US') {
8
+ const date = new Date(timestamp * 1000);
9
+ return date.toLocaleDateString(locale, {
10
+ year: 'numeric',
11
+ month: 'long',
12
+ day: 'numeric'
13
+ });
14
+ }
15
+ /**
16
+ * Calculate reading time based on word count
17
+ */
18
+ export function calculateReadingTime(wordCount, wordsPerMinute = 200) {
19
+ const minutes = Math.ceil(wordCount / wordsPerMinute);
20
+ return `${minutes} min read`;
21
+ }
22
+ /**
23
+ * Truncate text to a maximum length
24
+ */
25
+ export function truncate(text, maxLength) {
26
+ if (text.length <= maxLength)
27
+ return text;
28
+ return text.substring(0, maxLength).trim() + '...';
29
+ }
30
+ /**
31
+ * Generate excerpt from content
32
+ */
33
+ export function generateExcerpt(content, maxLength = 200) {
34
+ // Remove markdown formatting
35
+ const plainText = content
36
+ .replace(/#{1,6}\s/g, '') // Remove headers
37
+ .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold
38
+ .replace(/\*(.+?)\*/g, '$1') // Remove italic
39
+ .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links
40
+ .replace(/`(.+?)`/g, '$1') // Remove inline code
41
+ .replace(/```[\s\S]*?```/g, '') // Remove code blocks
42
+ .replace(/\n+/g, ' ') // Replace newlines with spaces
43
+ .trim();
44
+ return truncate(plainText, maxLength);
45
+ }
46
+ /**
47
+ * Slugify a string for URLs
48
+ */
49
+ export function slugify(text) {
50
+ return text
51
+ .toLowerCase()
52
+ .trim()
53
+ .replace(/[^\w\s-]/g, '')
54
+ .replace(/[\s_-]+/g, '-')
55
+ .replace(/^-+|-+$/g, '');
56
+ }
57
+ /**
58
+ * Parse tags from comma-separated string
59
+ */
60
+ export function parseTags(tags) {
61
+ if (Array.isArray(tags))
62
+ return tags;
63
+ return tags.split(',').map(t => t.trim()).filter(Boolean);
64
+ }
65
+ /**
66
+ * Build article URL
67
+ */
68
+ export function buildArticleUrl(uuid, pattern = '/articles/{uuid}') {
69
+ return pattern.replace('{uuid}', uuid).replace('{id}', uuid);
70
+ }