@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.
- package/LICENSE +21 -0
- package/README.md +442 -0
- package/dist/astro/ContentList.astro +177 -0
- package/dist/astro/ContentViewer.astro +252 -0
- package/dist/astro/index.d.ts +9 -0
- package/dist/astro/index.d.ts.map +1 -0
- package/dist/astro/index.js +8 -0
- package/dist/core/client.d.ts +67 -0
- package/dist/core/client.d.ts.map +1 -0
- package/dist/core/client.js +217 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +7 -0
- package/dist/core/utils.d.ts +32 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/core/utils.js +70 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/react/ContentList.d.ts +12 -0
- package/dist/react/ContentList.d.ts.map +1 -0
- package/dist/react/ContentList.js +106 -0
- package/dist/react/ContentViewer.d.ts +12 -0
- package/dist/react/ContentViewer.d.ts.map +1 -0
- package/dist/react/ContentViewer.js +97 -0
- package/dist/react/hooks.d.ts +63 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +140 -0
- package/dist/react/index.d.ts +9 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/styles.css +970 -0
- package/dist/types/index.d.ts +271 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/vue/ContentList.vue +166 -0
- package/dist/vue/ContentViewer.vue +137 -0
- package/dist/vue/composables.d.ts +64 -0
- package/dist/vue/composables.d.ts.map +1 -0
- package/dist/vue/composables.js +165 -0
- package/dist/vue/index.d.ts +10 -0
- package/dist/vue/index.d.ts.map +1 -0
- package/dist/vue/index.js +8 -0
- package/dist/widget/content-card.js +190 -0
- package/dist/widget/content-list.js +289 -0
- package/dist/widget/content-viewer.js +230 -0
- package/dist/widget/index.js +40 -0
- package/dist/widget/utils/api-client.js +154 -0
- package/dist/widget/utils/helpers.js +71 -0
- package/dist/widget/widget-js/content-card.js +190 -0
- package/dist/widget/widget-js/content-list.js +289 -0
- package/dist/widget/widget-js/content-viewer.js +230 -0
- package/dist/widget/widget-js/index.js +40 -0
- package/dist/widget/widget-js/utils/api-client.js +154 -0
- package/dist/widget/widget-js/utils/helpers.js +71 -0
- package/dist/widget/widget-js/widget.d.ts +24 -0
- package/dist/widget/widget-js/widget.js +240 -0
- package/dist/widget/widget.d.ts +24 -0
- package/dist/widget/widget.js +240 -0
- package/package.json +99 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Growth Content Widget - TypeScript Types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Article metadata
|
|
6
|
+
*/
|
|
7
|
+
export interface Article {
|
|
8
|
+
uuid: string;
|
|
9
|
+
slug: string;
|
|
10
|
+
title: string;
|
|
11
|
+
category: string | null;
|
|
12
|
+
authorName: string;
|
|
13
|
+
publishedAt: number;
|
|
14
|
+
summary: string | null;
|
|
15
|
+
tags: string[];
|
|
16
|
+
wordCount: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Full article with content
|
|
20
|
+
*/
|
|
21
|
+
export interface ArticleWithContent extends Article {
|
|
22
|
+
content: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pagination metadata
|
|
26
|
+
*/
|
|
27
|
+
export interface Pagination {
|
|
28
|
+
page: number;
|
|
29
|
+
limit: number;
|
|
30
|
+
total: number;
|
|
31
|
+
totalPages: number;
|
|
32
|
+
hasNext: boolean;
|
|
33
|
+
hasPrev: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Articles list response
|
|
37
|
+
*/
|
|
38
|
+
export interface ArticlesResponse {
|
|
39
|
+
articles: Article[];
|
|
40
|
+
pagination: Pagination;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Category with count
|
|
44
|
+
*/
|
|
45
|
+
export interface Category {
|
|
46
|
+
name: string;
|
|
47
|
+
count: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Tag with count
|
|
51
|
+
*/
|
|
52
|
+
export interface Tag {
|
|
53
|
+
name: string;
|
|
54
|
+
normalized: string;
|
|
55
|
+
count: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Categories response
|
|
59
|
+
*/
|
|
60
|
+
export interface CategoriesResponse {
|
|
61
|
+
categories: Category[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Tags response
|
|
65
|
+
*/
|
|
66
|
+
export interface TagsResponse {
|
|
67
|
+
tags: Tag[];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Client configuration
|
|
71
|
+
*/
|
|
72
|
+
export interface ClientConfig {
|
|
73
|
+
/**
|
|
74
|
+
* Your Content Growth API key (pk_xxx)
|
|
75
|
+
*/
|
|
76
|
+
apiKey: string;
|
|
77
|
+
/**
|
|
78
|
+
* API base URL
|
|
79
|
+
* @default 'https://api.content-growth.com'
|
|
80
|
+
*/
|
|
81
|
+
baseUrl?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Cache TTL in milliseconds
|
|
84
|
+
* @default 300000 (5 minutes)
|
|
85
|
+
*/
|
|
86
|
+
cacheTTL?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Enable debug logging
|
|
89
|
+
* @default false
|
|
90
|
+
*/
|
|
91
|
+
debug?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Options for listing articles
|
|
95
|
+
*/
|
|
96
|
+
export interface ListArticlesOptions {
|
|
97
|
+
/**
|
|
98
|
+
* Page number (1-indexed)
|
|
99
|
+
* @default 1
|
|
100
|
+
*/
|
|
101
|
+
page?: number;
|
|
102
|
+
/**
|
|
103
|
+
* Number of articles per page
|
|
104
|
+
* @default 12
|
|
105
|
+
*/
|
|
106
|
+
limit?: number;
|
|
107
|
+
/**
|
|
108
|
+
* Filter by tags (array of tag names)
|
|
109
|
+
*/
|
|
110
|
+
tags?: string[];
|
|
111
|
+
/**
|
|
112
|
+
* Filter by category
|
|
113
|
+
*/
|
|
114
|
+
category?: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Layout mode for displaying articles
|
|
118
|
+
*/
|
|
119
|
+
export type LayoutMode = 'cards' | 'rows';
|
|
120
|
+
/**
|
|
121
|
+
* Display density mode
|
|
122
|
+
*/
|
|
123
|
+
export type DisplayMode = 'compact' | 'comfortable' | 'spacious';
|
|
124
|
+
/**
|
|
125
|
+
* Theme mode
|
|
126
|
+
*/
|
|
127
|
+
export type Theme = 'light' | 'dark';
|
|
128
|
+
/**
|
|
129
|
+
* Component props for ContentList
|
|
130
|
+
*/
|
|
131
|
+
export interface ContentListProps {
|
|
132
|
+
/**
|
|
133
|
+
* Your Content Growth API key
|
|
134
|
+
*/
|
|
135
|
+
apiKey: string;
|
|
136
|
+
/**
|
|
137
|
+
* API base URL
|
|
138
|
+
* @default 'https://api.content-growth.com'
|
|
139
|
+
*/
|
|
140
|
+
baseUrl?: string;
|
|
141
|
+
/**
|
|
142
|
+
* Layout mode
|
|
143
|
+
* @default 'cards'
|
|
144
|
+
*/
|
|
145
|
+
layout?: LayoutMode;
|
|
146
|
+
/**
|
|
147
|
+
* Display density
|
|
148
|
+
* @default 'comfortable'
|
|
149
|
+
*/
|
|
150
|
+
displayMode?: DisplayMode;
|
|
151
|
+
/**
|
|
152
|
+
* Theme
|
|
153
|
+
* @default 'light'
|
|
154
|
+
*/
|
|
155
|
+
theme?: Theme;
|
|
156
|
+
/**
|
|
157
|
+
* Number of articles per page
|
|
158
|
+
* @default 12
|
|
159
|
+
*/
|
|
160
|
+
pageSize?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Filter by tags
|
|
163
|
+
*/
|
|
164
|
+
tags?: string[];
|
|
165
|
+
/**
|
|
166
|
+
* Filter by category
|
|
167
|
+
*/
|
|
168
|
+
category?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Show pagination controls
|
|
171
|
+
* @default true
|
|
172
|
+
*/
|
|
173
|
+
showPagination?: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* URL pattern for article links
|
|
176
|
+
* Supports placeholders: {uuid}, {slug}, {category}
|
|
177
|
+
* @default '/articles/{uuid}'
|
|
178
|
+
* @example '/blog/{category}/{slug}'
|
|
179
|
+
*/
|
|
180
|
+
linkPattern?: string;
|
|
181
|
+
/**
|
|
182
|
+
* Show article tags
|
|
183
|
+
* @default false
|
|
184
|
+
*/
|
|
185
|
+
showTags?: boolean;
|
|
186
|
+
/**
|
|
187
|
+
* Show AI-generated summary in cards/rows
|
|
188
|
+
* @default true
|
|
189
|
+
*/
|
|
190
|
+
showAiSummary?: boolean;
|
|
191
|
+
/**
|
|
192
|
+
* Maximum length of summary text in cards/rows (in characters)
|
|
193
|
+
* If not set, shows full summary
|
|
194
|
+
* @default undefined (no limit)
|
|
195
|
+
* @example 150
|
|
196
|
+
*/
|
|
197
|
+
summaryMaxLength?: number;
|
|
198
|
+
/**
|
|
199
|
+
* Link target attribute
|
|
200
|
+
* Supports placeholders: {uuid}, {id} for article ID
|
|
201
|
+
* @default undefined (same tab)
|
|
202
|
+
* @example '_blank' for new tab
|
|
203
|
+
* @example '{uuid}' for article ID as target
|
|
204
|
+
*/
|
|
205
|
+
linkTarget?: string;
|
|
206
|
+
/**
|
|
207
|
+
* Custom CSS class
|
|
208
|
+
*/
|
|
209
|
+
class?: string;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Component props for ContentViewer
|
|
213
|
+
*/
|
|
214
|
+
export interface ContentViewerProps {
|
|
215
|
+
/**
|
|
216
|
+
* Your Content Growth API key
|
|
217
|
+
*/
|
|
218
|
+
apiKey: string;
|
|
219
|
+
/**
|
|
220
|
+
* Article UUID (use either uuid or slug)
|
|
221
|
+
*/
|
|
222
|
+
uuid?: string;
|
|
223
|
+
/**
|
|
224
|
+
* Article slug (use either uuid or slug)
|
|
225
|
+
*/
|
|
226
|
+
slug?: string;
|
|
227
|
+
/**
|
|
228
|
+
* API base URL
|
|
229
|
+
* @default 'https://api.content-growth.com'
|
|
230
|
+
*/
|
|
231
|
+
baseUrl?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Theme
|
|
234
|
+
* @default 'light'
|
|
235
|
+
*/
|
|
236
|
+
theme?: Theme;
|
|
237
|
+
/**
|
|
238
|
+
* Show back button
|
|
239
|
+
* @default false
|
|
240
|
+
*/
|
|
241
|
+
showBackButton?: boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Back button URL
|
|
244
|
+
*/
|
|
245
|
+
backUrl?: string;
|
|
246
|
+
/**
|
|
247
|
+
* Show AI-generated summary
|
|
248
|
+
* @default true
|
|
249
|
+
*/
|
|
250
|
+
showAiSummary?: boolean;
|
|
251
|
+
/**
|
|
252
|
+
* Custom CSS class
|
|
253
|
+
*/
|
|
254
|
+
class?: string;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Cache entry
|
|
258
|
+
*/
|
|
259
|
+
export interface CacheEntry<T> {
|
|
260
|
+
data: T;
|
|
261
|
+
timestamp: number;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* API Error
|
|
265
|
+
*/
|
|
266
|
+
export declare class ContentGrowthError extends Error {
|
|
267
|
+
statusCode?: number | undefined;
|
|
268
|
+
response?: any | undefined;
|
|
269
|
+
constructor(message: string, statusCode?: number | undefined, response?: any | undefined);
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Growth Content Widget - TypeScript Types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* API Error
|
|
6
|
+
*/
|
|
7
|
+
export class ContentGrowthError extends Error {
|
|
8
|
+
statusCode;
|
|
9
|
+
response;
|
|
10
|
+
constructor(message, statusCode, response) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
this.response = response;
|
|
14
|
+
this.name = 'ContentGrowthError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="`cg-content-list cg-layout-${layout} cg-display-${displayMode} cg-theme-${theme} ${className}`"
|
|
4
|
+
data-cg-widget="list"
|
|
5
|
+
>
|
|
6
|
+
<div v-if="loading" class="cg-empty-state">
|
|
7
|
+
<p>Loading...</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-else-if="articles.length === 0" class="cg-empty-state">
|
|
10
|
+
<p>No articles found.</p>
|
|
11
|
+
</div>
|
|
12
|
+
<template v-else>
|
|
13
|
+
<div :class="`cg-articles-grid ${layout === 'cards' ? 'cg-grid' : 'cg-list'}`">
|
|
14
|
+
<article v-for="article in articles" :key="article.uuid" class="cg-article-card">
|
|
15
|
+
<a :href="buildArticleUrl(article)" :target="buildLinkTarget(article)" class="cg-card-link">
|
|
16
|
+
<div class="cg-card-content">
|
|
17
|
+
<div v-if="article.category" class="cg-card-category">
|
|
18
|
+
<span class="cg-category-badge">{{ article.category }}</span>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<h2 class="cg-card-title">{{ article.title }}</h2>
|
|
22
|
+
|
|
23
|
+
<p v-if="showAiSummary && article.summary" class="cg-card-summary">
|
|
24
|
+
{{ truncateSummary(article.summary, summaryMaxLength) }}
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
<div class="cg-card-meta">
|
|
28
|
+
<span class="cg-meta-author">{{ article.authorName }}</span>
|
|
29
|
+
<span class="cg-meta-separator">•</span>
|
|
30
|
+
<time class="cg-meta-date" :datetime="new Date(article.publishedAt * 1000).toISOString()">
|
|
31
|
+
{{ formatDate(article.publishedAt) }}
|
|
32
|
+
</time>
|
|
33
|
+
<span class="cg-meta-separator">•</span>
|
|
34
|
+
<span class="cg-meta-reading-time">{{ calculateReadingTime(article.wordCount) }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div v-if="showTags && article.tags && article.tags.length > 0" class="cg-card-tags">
|
|
38
|
+
<span v-for="tag in article.tags" :key="tag" class="cg-tag">{{ tag }}</span>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</a>
|
|
42
|
+
</article>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div v-if="showPagination && totalPages > 1" class="cg-pagination">
|
|
46
|
+
<button
|
|
47
|
+
class="cg-pagination-btn"
|
|
48
|
+
@click="currentPage = Math.max(1, currentPage - 1)"
|
|
49
|
+
:disabled="currentPage === 1"
|
|
50
|
+
>
|
|
51
|
+
Previous
|
|
52
|
+
</button>
|
|
53
|
+
|
|
54
|
+
<span class="cg-pagination-info">
|
|
55
|
+
Page {{ currentPage }} of {{ totalPages }}
|
|
56
|
+
</span>
|
|
57
|
+
|
|
58
|
+
<button
|
|
59
|
+
class="cg-pagination-btn"
|
|
60
|
+
@click="currentPage = Math.min(totalPages, currentPage + 1)"
|
|
61
|
+
:disabled="currentPage === totalPages"
|
|
62
|
+
>
|
|
63
|
+
Next
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
</div>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<script setup lang="ts">
|
|
71
|
+
import { ref, watch, onMounted } from 'vue';
|
|
72
|
+
import { ContentGrowthClient } from '../core/client.js';
|
|
73
|
+
import { formatDate, calculateReadingTime } from '../core/utils.js';
|
|
74
|
+
import type { ContentListProps, Article } from '../types/index.js';
|
|
75
|
+
|
|
76
|
+
export interface VueContentListProps extends Omit<ContentListProps, 'class'> {
|
|
77
|
+
className?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const props = withDefaults(defineProps<VueContentListProps>(), {
|
|
81
|
+
layout: 'cards',
|
|
82
|
+
displayMode: 'comfortable',
|
|
83
|
+
theme: 'light',
|
|
84
|
+
pageSize: 12,
|
|
85
|
+
tags: () => [],
|
|
86
|
+
showPagination: true,
|
|
87
|
+
linkPattern: '/articles/{uuid}',
|
|
88
|
+
showTags: false,
|
|
89
|
+
showAiSummary: true,
|
|
90
|
+
className: ''
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const articles = ref<Article[]>([]);
|
|
94
|
+
const currentPage = ref(1);
|
|
95
|
+
const totalPages = ref(1);
|
|
96
|
+
const loading = ref(true);
|
|
97
|
+
|
|
98
|
+
const fetchArticles = async () => {
|
|
99
|
+
loading.value = true;
|
|
100
|
+
try {
|
|
101
|
+
const client = new ContentGrowthClient({
|
|
102
|
+
apiKey: props.apiKey,
|
|
103
|
+
baseUrl: props.baseUrl
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Process tags
|
|
107
|
+
let processedTags: string[] | undefined;
|
|
108
|
+
const tagsProp = props.tags as string[] | string | undefined;
|
|
109
|
+
if (tagsProp) {
|
|
110
|
+
if (Array.isArray(tagsProp)) {
|
|
111
|
+
processedTags = tagsProp;
|
|
112
|
+
} else if (typeof tagsProp === 'string') {
|
|
113
|
+
processedTags = tagsProp.split(',').map((t: string) => t.trim()).filter(Boolean);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const result = await client.listArticles({
|
|
118
|
+
page: currentPage.value,
|
|
119
|
+
limit: props.pageSize,
|
|
120
|
+
tags: processedTags,
|
|
121
|
+
category: props.category
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
articles.value = result.articles;
|
|
125
|
+
totalPages.value = result.pagination.totalPages;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('Error fetching articles:', error);
|
|
128
|
+
} finally {
|
|
129
|
+
loading.value = false;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// Truncate summary text
|
|
134
|
+
const truncateSummary = (text: string | null, maxLength?: number): string => {
|
|
135
|
+
if (!text) return '';
|
|
136
|
+
if (!maxLength || text.length <= maxLength) return text;
|
|
137
|
+
return text.substring(0, maxLength).trim() + '...';
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Build article URL from pattern
|
|
141
|
+
const buildArticleUrl = (article: Article): string => {
|
|
142
|
+
return props.linkPattern
|
|
143
|
+
.replace('{uuid}', article.uuid)
|
|
144
|
+
.replace('{slug}', article.slug)
|
|
145
|
+
.replace('{category}', article.category || '');
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Build link target from pattern
|
|
149
|
+
const buildLinkTarget = (article: Article): string | undefined => {
|
|
150
|
+
if (!props.linkTarget) return undefined;
|
|
151
|
+
return props.linkTarget
|
|
152
|
+
.replace('{uuid}', article.uuid)
|
|
153
|
+
.replace('{id}', article.uuid);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
onMounted(() => {
|
|
157
|
+
fetchArticles();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
watch(
|
|
161
|
+
() => [props.apiKey, props.baseUrl, currentPage.value, props.pageSize, props.tags, props.category],
|
|
162
|
+
() => {
|
|
163
|
+
fetchArticles();
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
</script>
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="`cg-content-viewer cg-theme-${theme} ${className}`"
|
|
4
|
+
data-cg-widget="post"
|
|
5
|
+
>
|
|
6
|
+
<div v-if="loading" class="cg-empty-state">
|
|
7
|
+
<p>Loading...</p>
|
|
8
|
+
</div>
|
|
9
|
+
<div v-else-if="error || !article" class="cg-empty-state">
|
|
10
|
+
<p>{{ error || 'Article not found' }}</p>
|
|
11
|
+
</div>
|
|
12
|
+
<article v-else>
|
|
13
|
+
<div v-if="showBackButton" class="cg-post-header">
|
|
14
|
+
<a :href="backUrl" class="cg-back-btn">
|
|
15
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
16
|
+
<path d="M12 16L6 10L12 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
17
|
+
</svg>
|
|
18
|
+
Back to articles
|
|
19
|
+
</a>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<header class="cg-post-meta">
|
|
23
|
+
<div v-if="article.category" class="cg-post-category">
|
|
24
|
+
<span class="cg-category-badge">{{ article.category }}</span>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<h1 class="cg-post-title">{{ article.title }}</h1>
|
|
28
|
+
|
|
29
|
+
<div v-if="showAiSummary && article.summary" class="cg-ai-summary">
|
|
30
|
+
<div class="cg-ai-summary-header">
|
|
31
|
+
<svg class="cg-ai-summary-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
32
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
33
|
+
</svg>
|
|
34
|
+
<span class="cg-ai-summary-label">AI Generated Summary</span>
|
|
35
|
+
</div>
|
|
36
|
+
<p class="cg-ai-summary-text">{{ article.summary }}</p>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div class="cg-post-info">
|
|
40
|
+
<span class="cg-info-author">{{ article.authorName }}</span>
|
|
41
|
+
<span class="cg-info-separator">•</span>
|
|
42
|
+
<time class="cg-info-date" :datetime="new Date(article.publishedAt * 1000).toISOString()">
|
|
43
|
+
{{ formatDate(article.publishedAt) }}
|
|
44
|
+
</time>
|
|
45
|
+
<span class="cg-info-separator">•</span>
|
|
46
|
+
<span class="cg-info-reading-time">{{ calculateReadingTime(article.wordCount) }}</span>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<div v-if="article.tags.length > 0" class="cg-post-tags">
|
|
50
|
+
<span v-for="tag in article.tags" :key="tag" class="cg-tag">{{ tag }}</span>
|
|
51
|
+
</div>
|
|
52
|
+
</header>
|
|
53
|
+
|
|
54
|
+
<div class="cg-post-content" v-html="contentHtml"></div>
|
|
55
|
+
</article>
|
|
56
|
+
</div>
|
|
57
|
+
</template>
|
|
58
|
+
|
|
59
|
+
<script setup lang="ts">
|
|
60
|
+
import { ref, onMounted, computed } from 'vue';
|
|
61
|
+
import { ContentGrowthClient } from '../core/client.js';
|
|
62
|
+
import { formatDate, calculateReadingTime } from '../core/utils.js';
|
|
63
|
+
import { marked } from 'marked';
|
|
64
|
+
import type { ContentViewerProps, ArticleWithContent } from '../types/index.js';
|
|
65
|
+
|
|
66
|
+
export interface VueContentViewerProps extends Omit<ContentViewerProps, 'class'> {
|
|
67
|
+
className?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const props = withDefaults(defineProps<VueContentViewerProps>(), {
|
|
71
|
+
theme: 'light',
|
|
72
|
+
showBackButton: false,
|
|
73
|
+
backUrl: '/articles',
|
|
74
|
+
showAiSummary: true,
|
|
75
|
+
className: ''
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const article = ref<ArticleWithContent | null>(null);
|
|
79
|
+
const loading = ref(true);
|
|
80
|
+
const error = ref<string | null>(null);
|
|
81
|
+
|
|
82
|
+
// Process markdown content to handle custom image syntax
|
|
83
|
+
const processImageSyntax = (markdown: string): string => {
|
|
84
|
+
// Match: 
|
|
85
|
+
return markdown.replace(
|
|
86
|
+
/!\[([^\]]*)\]\(([^\s)]+)\s+=(\d+)x(\d+)\)/g,
|
|
87
|
+
(match, alt, url, width, height) => {
|
|
88
|
+
return `{width="${width}" height="${height}"}`;
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Configure marked to handle image attributes
|
|
94
|
+
marked.use({
|
|
95
|
+
renderer: {
|
|
96
|
+
image(href, title, text) {
|
|
97
|
+
// Extract width/height from {width="x" height="y"} syntax
|
|
98
|
+
const attrMatch = text.match(/\{width="(\d+)"\s+height="(\d+)"\}/);
|
|
99
|
+
if (attrMatch) {
|
|
100
|
+
const cleanText = text.replace(/\{[^}]+\}/, '').trim();
|
|
101
|
+
return `<img src="${href}" alt="${cleanText}" width="${attrMatch[1]}" height="${attrMatch[2]}" ${title ? `title="${title}"` : ''} />`;
|
|
102
|
+
}
|
|
103
|
+
return `<img src="${href}" alt="${text}" ${title ? `title="${title}"` : ''} />`;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const contentHtml = computed(() => {
|
|
109
|
+
if (!article.value) return '';
|
|
110
|
+
const processedContent = processImageSyntax(article.value.content);
|
|
111
|
+
return marked(processedContent);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
onMounted(async () => {
|
|
115
|
+
if (!props.uuid && !props.slug) {
|
|
116
|
+
error.value = 'Either uuid or slug must be provided';
|
|
117
|
+
loading.value = false;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
loading.value = true;
|
|
122
|
+
try {
|
|
123
|
+
const client = new ContentGrowthClient({
|
|
124
|
+
apiKey: props.apiKey,
|
|
125
|
+
baseUrl: props.baseUrl
|
|
126
|
+
});
|
|
127
|
+
const fetchedArticle = props.slug
|
|
128
|
+
? await client.getArticleBySlug(props.slug)
|
|
129
|
+
: await client.getArticle(props.uuid!);
|
|
130
|
+
article.value = fetchedArticle;
|
|
131
|
+
} catch (err) {
|
|
132
|
+
error.value = err instanceof Error ? err.message : 'Failed to load article';
|
|
133
|
+
} finally {
|
|
134
|
+
loading.value = false;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
</script>
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue Composables for Content Growth API
|
|
3
|
+
*/
|
|
4
|
+
import { type Ref } from 'vue';
|
|
5
|
+
import type { Article, ArticleWithContent, Pagination, Category, Tag, ListArticlesOptions } from '../types/index.js';
|
|
6
|
+
export interface UseArticlesOptions extends ListArticlesOptions {
|
|
7
|
+
apiKey: string;
|
|
8
|
+
baseUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface UseArticlesResult {
|
|
11
|
+
articles: Ref<Article[]>;
|
|
12
|
+
pagination: Ref<Pagination | null>;
|
|
13
|
+
loading: Ref<boolean>;
|
|
14
|
+
error: Ref<Error | null>;
|
|
15
|
+
refetch: () => Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Composable to fetch articles list
|
|
19
|
+
*/
|
|
20
|
+
export declare function useArticles(options: UseArticlesOptions): UseArticlesResult;
|
|
21
|
+
export interface UseArticleOptions {
|
|
22
|
+
apiKey: string;
|
|
23
|
+
uuid: string;
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface UseArticleResult {
|
|
27
|
+
article: Ref<ArticleWithContent | null>;
|
|
28
|
+
loading: Ref<boolean>;
|
|
29
|
+
error: Ref<Error | null>;
|
|
30
|
+
refetch: () => Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Composable to fetch a single article
|
|
34
|
+
*/
|
|
35
|
+
export declare function useArticle(options: UseArticleOptions): UseArticleResult;
|
|
36
|
+
export interface UseCategoriesOptions {
|
|
37
|
+
apiKey: string;
|
|
38
|
+
baseUrl?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface UseCategoriesResult {
|
|
41
|
+
categories: Ref<Category[]>;
|
|
42
|
+
loading: Ref<boolean>;
|
|
43
|
+
error: Ref<Error | null>;
|
|
44
|
+
refetch: () => Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Composable to fetch categories
|
|
48
|
+
*/
|
|
49
|
+
export declare function useCategories(options: UseCategoriesOptions): UseCategoriesResult;
|
|
50
|
+
export interface UseTagsOptions {
|
|
51
|
+
apiKey: string;
|
|
52
|
+
baseUrl?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface UseTagsResult {
|
|
55
|
+
tags: Ref<Tag[]>;
|
|
56
|
+
loading: Ref<boolean>;
|
|
57
|
+
error: Ref<Error | null>;
|
|
58
|
+
refetch: () => Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Composable to fetch tags
|
|
62
|
+
*/
|
|
63
|
+
export declare function useTags(options: UseTagsOptions): UseTagsResult;
|
|
64
|
+
//# sourceMappingURL=composables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"composables.d.ts","sourceRoot":"","sources":["../../src/vue/composables.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAc,KAAK,GAAG,EAAE,MAAM,KAAK,CAAC;AAE3C,OAAO,KAAK,EACV,OAAO,EACP,kBAAkB,EAClB,UAAU,EACV,QAAQ,EACR,GAAG,EACH,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACzB,UAAU,EAAE,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IACnC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,iBAAiB,CAgD1E;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,GAAG,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IACxC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAwCvE;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5B,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAwChF;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IACjB,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACzB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CAwC9D"}
|