@content-streamline/nextjs 0.0.1

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.
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import type { PostDetailResponse } from '../types';
3
+ export interface PostDetailProps {
4
+ post: PostDetailResponse;
5
+ /**
6
+ * Base URL for back link (e.g., '/blog')
7
+ */
8
+ backUrl?: string;
9
+ /**
10
+ * Language to use for post translation (default: 'en')
11
+ */
12
+ language?: string;
13
+ /**
14
+ * Custom className for the container
15
+ */
16
+ className?: string;
17
+ /**
18
+ * Show back link (default: true)
19
+ */
20
+ showBackLink?: boolean;
21
+ /**
22
+ * Show post date (default: true)
23
+ */
24
+ showDate?: boolean;
25
+ /**
26
+ * Show post type badge (default: true)
27
+ */
28
+ showType?: boolean;
29
+ /**
30
+ * Show featured image (default: true)
31
+ */
32
+ showImage?: boolean;
33
+ /**
34
+ * Show gallery images (default: true)
35
+ */
36
+ showGallery?: boolean;
37
+ /**
38
+ * Render markdown content as HTML (requires markdown renderer)
39
+ */
40
+ renderContent?: (content: string) => React.ReactNode;
41
+ /**
42
+ * Custom render function for post title
43
+ */
44
+ renderTitle?: (post: PostDetailResponse, translation: PostDetailResponse['post_translations'][0]) => React.ReactNode;
45
+ }
46
+ export declare function PostDetail({ post, backUrl, language, className, showBackLink, showDate, showType, showImage, showGallery, renderContent, renderTitle, }: PostDetailProps): import("react/jsx-runtime").JSX.Element;
47
+ //# sourceMappingURL=PostDetail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostDetail.d.ts","sourceRoot":"","sources":["../../src/components/PostDetail.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,kBAAkB,CAAC;IACzB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;IACrD;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,EAAE,WAAW,EAAE,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CACtH;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,OAAa,EACb,QAAe,EACf,SAAc,EACd,YAAmB,EACnB,QAAe,EACf,QAAe,EACf,SAAgB,EAChB,WAAkB,EAClB,aAAa,EACb,WAAW,GACZ,EAAE,eAAe,2CAoGjB"}
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function PostDetail({ post, backUrl = '/', language = 'en', className = '', showBackLink = true, showDate = true, showType = true, showImage = true, showGallery = true, renderContent, renderTitle, }) {
4
+ const translation = post.post_translations.find((t) => t.language === language) ||
5
+ post.post_translations[0];
6
+ if (!translation) {
7
+ return (_jsx("div", { className: `content-streamline-post-not-found ${className}`, children: _jsx("p", { children: "Post not found." }) }));
8
+ }
9
+ const date = new Date(post.created_at);
10
+ const featuredImage = post.images.find((img) => img.ordering === 0) || post.images[0];
11
+ const galleryImages = post.images.filter((img) => img.ordering !== 0);
12
+ return (_jsxs("article", { className: `content-streamline-post-detail ${className}`, children: [_jsxs("header", { className: "content-streamline-post-header", children: [showBackLink && (_jsx("a", { href: backUrl, className: "content-streamline-back-link", children: "\u2190 Back" })), _jsx("h1", { className: "content-streamline-post-title", children: renderTitle ? renderTitle(post, translation) : translation.title }), (showDate || showType) && (_jsxs("div", { className: "content-streamline-post-meta", children: [showDate && (_jsx("time", { className: "content-streamline-post-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
13
+ year: 'numeric',
14
+ month: 'long',
15
+ day: 'numeric',
16
+ }) })), showType && post.content_type && (_jsx("span", { className: "content-streamline-post-type", children: post.content_type }))] })), showImage && featuredImage && (_jsx("div", { className: "content-streamline-post-featured-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title }) }))] }), translation.content && (_jsx("div", { className: "content-streamline-post-content", children: renderContent ? (renderContent(translation.content)) : (_jsx("div", { dangerouslySetInnerHTML: {
17
+ __html: translation.content
18
+ .replace(/&/g, '&')
19
+ .replace(/</g, '&lt;')
20
+ .replace(/>/g, '&gt;')
21
+ .replace(/\n/g, '<br />'),
22
+ } })) })), showGallery && galleryImages.length > 0 && (_jsxs("aside", { className: "content-streamline-post-gallery", children: [_jsx("h2", { children: "Images" }), _jsx("div", { className: "content-streamline-gallery-grid", children: galleryImages.map((image) => (_jsxs("figure", { className: "content-streamline-gallery-item", children: [_jsx("img", { src: image.url, alt: image.alt || translation.title, loading: "lazy" }), image.alt && _jsx("figcaption", { children: image.alt })] }, image.id))) })] }))] }));
23
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import type { Post } from '../types';
3
+ export interface PostsListProps {
4
+ posts: Post[];
5
+ /**
6
+ * Base URL for post links (e.g., '/blog' or '/posts')
7
+ * Post URL will be: {baseUrl}/{postId}/{slug}
8
+ */
9
+ baseUrl?: string;
10
+ /**
11
+ * Language to use for post translations (default: 'en')
12
+ */
13
+ language?: string;
14
+ /**
15
+ * Custom className for the container
16
+ */
17
+ className?: string;
18
+ /**
19
+ * Custom className for post cards
20
+ */
21
+ cardClassName?: string;
22
+ /**
23
+ * Show post date (default: true)
24
+ */
25
+ showDate?: boolean;
26
+ /**
27
+ * Show post excerpt/description (default: true)
28
+ */
29
+ showExcerpt?: boolean;
30
+ /**
31
+ * Show post images (default: true)
32
+ */
33
+ showImages?: boolean;
34
+ /**
35
+ * Custom render function for post title
36
+ */
37
+ renderTitle?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
38
+ /**
39
+ * Custom render function for post excerpt
40
+ */
41
+ renderExcerpt?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
42
+ }
43
+ export declare function PostsList({ posts, baseUrl, language, className, cardClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: PostsListProps): import("react/jsx-runtime").JSX.Element;
44
+ //# sourceMappingURL=PostsList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostsList.d.ts","sourceRoot":"","sources":["../../src/components/PostsList.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;IACzF;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,SAAS,CAAC;CAC5F;AAED,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,OAAkB,EAClB,QAAe,EACf,SAAc,EACd,aAAkB,EAClB,QAAe,EACf,WAAkB,EAClB,UAAiB,EACjB,WAAW,EACX,aAAa,GACd,EAAE,cAAc,2CA8EhB"}
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function PostsList({ posts, baseUrl = '/posts', language = 'en', className = '', cardClassName = '', showDate = true, showExcerpt = true, showImages = true, renderTitle, renderExcerpt, }) {
4
+ if (posts.length === 0) {
5
+ return (_jsx("div", { className: `content-streamline-empty ${className}`, children: _jsx("p", { children: "No posts found." }) }));
6
+ }
7
+ return (_jsx("div", { className: `content-streamline-posts-list ${className}`, children: posts.map((post) => {
8
+ const translation = post.post_translations.find((t) => t.language === language) ||
9
+ post.post_translations[0];
10
+ if (!translation || !translation.slug)
11
+ return null;
12
+ const featuredImage = post.images.find((img) => img.ordering === 0) || post.images[0];
13
+ const date = new Date(post.created_at);
14
+ return (_jsxs("article", { className: `content-streamline-post-card ${cardClassName}`, children: [showImages && featuredImage && (_jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-post-image-link", children: _jsx("div", { className: "content-streamline-post-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title, loading: "lazy" }) }) })), _jsxs("div", { className: "content-streamline-post-content", children: [showDate && (_jsx("time", { className: "content-streamline-post-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
15
+ year: 'numeric',
16
+ month: 'long',
17
+ day: 'numeric',
18
+ }) })), _jsx("h2", { className: "content-streamline-post-title", children: _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, children: renderTitle ? renderTitle(post, translation) : translation.title }) }), showExcerpt && translation.meta_description && (_jsx("p", { className: "content-streamline-post-excerpt", children: renderExcerpt
19
+ ? renderExcerpt(post, translation)
20
+ : translation.meta_description })), _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-post-link", children: "Read more" })] })] }, post.id));
21
+ }) }));
22
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Content Streamline React Components
3
+ */
4
+ export { PostsList } from './PostsList';
5
+ export { PostDetail } from './PostDetail';
6
+ export type { PostsListProps } from './PostsList';
7
+ export type { PostDetailProps } from './PostDetail';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Content Streamline React Components
3
+ */
4
+ export { PostsList } from './PostsList';
5
+ export { PostDetail } from './PostDetail';
6
+ // Styles are available at '@content-streamline/nextjs/components/styles.css'
7
+ // Users should import them in their app: import '@content-streamline/nextjs/components/styles.css'
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Default styles for Content Streamline components
3
+ * You can override these with your own CSS
4
+ */
5
+
6
+ /* Posts List */
7
+ .content-streamline-posts-list {
8
+ display: grid;
9
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
10
+ gap: 2rem;
11
+ margin: 2rem 0;
12
+ }
13
+
14
+ .content-streamline-post-card {
15
+ background: #ffffff;
16
+ border-radius: 12px;
17
+ overflow: hidden;
18
+ border: 1px solid #e5e7eb;
19
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
20
+ transition: transform 0.2s, box-shadow 0.2s;
21
+ display: flex;
22
+ flex-direction: column;
23
+ }
24
+
25
+ .content-streamline-post-card:hover {
26
+ transform: translateY(-4px);
27
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
28
+ }
29
+
30
+ .content-streamline-post-image-link {
31
+ display: block;
32
+ text-decoration: none;
33
+ width: 100%;
34
+ }
35
+
36
+ .content-streamline-post-image {
37
+ width: 100%;
38
+ aspect-ratio: 16 / 10;
39
+ overflow: hidden;
40
+ background: #f3f4f6;
41
+ }
42
+
43
+ .content-streamline-post-image img {
44
+ width: 100%;
45
+ height: 100%;
46
+ object-fit: cover;
47
+ transition: transform 0.3s;
48
+ }
49
+
50
+ .content-streamline-post-card:hover .content-streamline-post-image img {
51
+ transform: scale(1.05);
52
+ }
53
+
54
+ .content-streamline-post-content {
55
+ padding: 1.5rem;
56
+ flex: 1;
57
+ display: flex;
58
+ flex-direction: column;
59
+ }
60
+
61
+ .content-streamline-post-date {
62
+ font-size: 0.875rem;
63
+ color: #6b7280;
64
+ margin-bottom: 0.75rem;
65
+ text-transform: uppercase;
66
+ letter-spacing: 0.05em;
67
+ }
68
+
69
+ .content-streamline-post-title {
70
+ font-size: 1.5rem;
71
+ font-weight: 600;
72
+ margin: 0 0 1rem 0;
73
+ line-height: 1.3;
74
+ }
75
+
76
+ .content-streamline-post-title a {
77
+ color: #111827;
78
+ text-decoration: none;
79
+ transition: color 0.2s;
80
+ }
81
+
82
+ .content-streamline-post-title a:hover {
83
+ color: #3b82f6;
84
+ }
85
+
86
+ .content-streamline-post-excerpt {
87
+ color: #6b7280;
88
+ line-height: 1.6;
89
+ margin: 0 0 1rem 0;
90
+ flex: 1;
91
+ }
92
+
93
+ .content-streamline-post-link {
94
+ color: #3b82f6;
95
+ text-decoration: none;
96
+ font-weight: 500;
97
+ margin-top: auto;
98
+ transition: color 0.2s;
99
+ }
100
+
101
+ .content-streamline-post-link:hover {
102
+ color: #2563eb;
103
+ text-decoration: underline;
104
+ }
105
+
106
+ /* Post Detail */
107
+ .content-streamline-post-detail {
108
+ max-width: 800px;
109
+ margin: 0 auto;
110
+ padding: 2rem 1rem;
111
+ }
112
+
113
+ .content-streamline-back-link {
114
+ display: inline-block;
115
+ color: #3b82f6;
116
+ text-decoration: none;
117
+ font-weight: 500;
118
+ margin-bottom: 2rem;
119
+ transition: color 0.2s;
120
+ }
121
+
122
+ .content-streamline-back-link:hover {
123
+ color: #2563eb;
124
+ }
125
+
126
+ .content-streamline-post-header {
127
+ margin-bottom: 3rem;
128
+ }
129
+
130
+ .content-streamline-post-detail .content-streamline-post-title {
131
+ font-size: 2.5rem;
132
+ font-weight: 700;
133
+ line-height: 1.2;
134
+ margin: 0 0 1.5rem 0;
135
+ color: #111827;
136
+ }
137
+
138
+ .content-streamline-post-meta {
139
+ display: flex;
140
+ gap: 1rem;
141
+ align-items: center;
142
+ margin-bottom: 2rem;
143
+ flex-wrap: wrap;
144
+ }
145
+
146
+ .content-streamline-post-detail .content-streamline-post-date {
147
+ color: #6b7280;
148
+ font-size: 0.875rem;
149
+ margin: 0;
150
+ }
151
+
152
+ .content-streamline-post-type {
153
+ background: #f3f4f6;
154
+ color: #374151;
155
+ padding: 0.25rem 0.75rem;
156
+ border-radius: 6px;
157
+ font-size: 0.875rem;
158
+ text-transform: capitalize;
159
+ }
160
+
161
+ .content-streamline-post-featured-image {
162
+ width: 100%;
163
+ margin-top: 2rem;
164
+ border-radius: 12px;
165
+ overflow: hidden;
166
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
167
+ }
168
+
169
+ .content-streamline-post-featured-image img {
170
+ width: 100%;
171
+ height: auto;
172
+ display: block;
173
+ }
174
+
175
+ .content-streamline-post-content {
176
+ font-size: 1.125rem;
177
+ line-height: 1.8;
178
+ color: #374151;
179
+ }
180
+
181
+ .content-streamline-post-content h1,
182
+ .content-streamline-post-content h2,
183
+ .content-streamline-post-content h3,
184
+ .content-streamline-post-content h4 {
185
+ margin-top: 2rem;
186
+ margin-bottom: 1rem;
187
+ font-weight: 600;
188
+ color: #111827;
189
+ }
190
+
191
+ .content-streamline-post-content h1 {
192
+ font-size: 2rem;
193
+ }
194
+
195
+ .content-streamline-post-content h2 {
196
+ font-size: 1.75rem;
197
+ }
198
+
199
+ .content-streamline-post-content h3 {
200
+ font-size: 1.5rem;
201
+ }
202
+
203
+ .content-streamline-post-content p {
204
+ margin-bottom: 1.5rem;
205
+ }
206
+
207
+ .content-streamline-post-content ul,
208
+ .content-streamline-post-content ol {
209
+ margin-bottom: 1.5rem;
210
+ padding-left: 2rem;
211
+ }
212
+
213
+ .content-streamline-post-content li {
214
+ margin-bottom: 0.5rem;
215
+ }
216
+
217
+ .content-streamline-post-content blockquote {
218
+ border-left: 4px solid #3b82f6;
219
+ padding-left: 1.5rem;
220
+ margin: 2rem 0;
221
+ color: #6b7280;
222
+ font-style: italic;
223
+ }
224
+
225
+ .content-streamline-post-content code {
226
+ background: #f3f4f6;
227
+ padding: 0.125rem 0.375rem;
228
+ border-radius: 4px;
229
+ font-size: 0.9em;
230
+ font-family: 'Monaco', 'Courier New', monospace;
231
+ }
232
+
233
+ .content-streamline-post-content pre {
234
+ background: #1f2937;
235
+ color: #f9fafb;
236
+ padding: 1.5rem;
237
+ border-radius: 8px;
238
+ overflow-x: auto;
239
+ margin: 2rem 0;
240
+ }
241
+
242
+ .content-streamline-post-content pre code {
243
+ background: transparent;
244
+ padding: 0;
245
+ color: inherit;
246
+ }
247
+
248
+ .content-streamline-post-content img {
249
+ max-width: 100%;
250
+ height: auto;
251
+ border-radius: 8px;
252
+ margin: 2rem 0;
253
+ }
254
+
255
+ .content-streamline-post-content a {
256
+ color: #3b82f6;
257
+ text-decoration: underline;
258
+ }
259
+
260
+ .content-streamline-post-content a:hover {
261
+ color: #2563eb;
262
+ }
263
+
264
+ .content-streamline-post-gallery {
265
+ margin-top: 4rem;
266
+ padding-top: 2rem;
267
+ border-top: 1px solid #e5e7eb;
268
+ }
269
+
270
+ .content-streamline-post-gallery h2 {
271
+ font-size: 1.5rem;
272
+ margin-bottom: 1.5rem;
273
+ color: #111827;
274
+ }
275
+
276
+ .content-streamline-gallery-grid {
277
+ display: grid;
278
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
279
+ gap: 1rem;
280
+ }
281
+
282
+ .content-streamline-gallery-item {
283
+ margin: 0;
284
+ }
285
+
286
+ .content-streamline-gallery-item img {
287
+ width: 100%;
288
+ height: auto;
289
+ border-radius: 8px;
290
+ display: block;
291
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
292
+ }
293
+
294
+ .content-streamline-gallery-item figcaption {
295
+ margin-top: 0.5rem;
296
+ font-size: 0.875rem;
297
+ color: #6b7280;
298
+ text-align: center;
299
+ }
300
+
301
+ .content-streamline-empty,
302
+ .content-streamline-post-not-found {
303
+ text-align: center;
304
+ padding: 3rem 2rem;
305
+ color: #6b7280;
306
+ }
307
+
308
+ /* Responsive */
309
+ @media (max-width: 768px) {
310
+ .content-streamline-posts-list {
311
+ grid-template-columns: 1fr;
312
+ gap: 1.5rem;
313
+ }
314
+
315
+ .content-streamline-post-detail {
316
+ padding: 1.5rem 1rem;
317
+ }
318
+
319
+ .content-streamline-post-detail .content-streamline-post-title {
320
+ font-size: 2rem;
321
+ }
322
+
323
+ .content-streamline-post-content {
324
+ font-size: 1rem;
325
+ }
326
+
327
+ .content-streamline-gallery-grid {
328
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
329
+ }
330
+ }
331
+
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Content Streamline Next.js Library
3
+ * Main entry point
4
+ */
5
+ export type { Post, PostDetailResponse, PostTranslation, PostImage, PostsResponse, Pagination, } from './types';
6
+ export { getAllPosts, getPost, getPostBySlug, } from './server';
7
+ export { PostsList, PostDetail, } from './components';
8
+ export type { PostsListProps, PostDetailProps, } from './components';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,WAAW,EACX,OAAO,EACP,aAAa,GACd,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,SAAS,EACT,UAAU,GACX,MAAM,cAAc,CAAC;AAEtB,YAAY,EACV,cAAc,EACd,eAAe,GAChB,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Content Streamline Next.js Library
3
+ * Main entry point
4
+ */
5
+ // Export server functions
6
+ export { getAllPosts, getPost, getPostBySlug, } from './server';
7
+ // Export components
8
+ export { PostsList, PostDetail, } from './components';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Server-side functions for Next.js
3
+ * Use these in Server Components, API Routes, or getServerSideProps
4
+ */
5
+ import type { Post, PostDetailResponse } from '../types';
6
+ /**
7
+ * Get all posts (with automatic cache refresh)
8
+ * Use this in Server Components or API Routes
9
+ */
10
+ export declare function getAllPosts(options?: {
11
+ maxCacheAge?: number;
12
+ forceRefresh?: boolean;
13
+ }): Promise<Post[]>;
14
+ /**
15
+ * Get a single post by ID
16
+ * Use this in Server Components or API Routes
17
+ */
18
+ export declare function getPost(id: number, options?: {
19
+ language?: string;
20
+ contentFormat?: 'markdown' | 'html';
21
+ maxCacheAge?: number;
22
+ forceRefresh?: boolean;
23
+ }): Promise<PostDetailResponse | null>;
24
+ /**
25
+ * Get post by slug
26
+ * Use this in Server Components or API Routes
27
+ */
28
+ export declare function getPostBySlug(slug: string, options?: {
29
+ language?: string;
30
+ contentFormat?: 'markdown' | 'html';
31
+ maxCacheAge?: number;
32
+ forceRefresh?: boolean;
33
+ }): Promise<PostDetailResponse | null>;
34
+ export type { Post, PostDetailResponse, PostTranslation, PostImage, PostsResponse, Pagination, } from '../types';
35
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAUH,OAAO,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAkBzD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAiClB;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAiCpC;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA0BpC;AAGD,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,UAAU,CAAC"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Server-side functions for Next.js
3
+ * Use these in Server Components, API Routes, or getServerSideProps
4
+ */
5
+ import { createAPIClient } from '../api/client';
6
+ import { getAllCachedPosts, getCachedPost, cachePost, updatePostsIndex, shouldRefreshCache, } from '../cache/file-cache';
7
+ /**
8
+ * Get API configuration from environment variables
9
+ */
10
+ function getAPIConfig() {
11
+ const baseUrl = process.env.CONTENT_STREAMLINE_API_BASE_URL || process.env.API_BASE_URL;
12
+ const accessToken = process.env.CONTENT_STREAMLINE_API_ACCESS_TOKEN || process.env.API_ACCESS_TOKEN;
13
+ if (!baseUrl || !accessToken) {
14
+ throw new Error('Missing API configuration. Set CONTENT_STREAMLINE_API_BASE_URL and CONTENT_STREAMLINE_API_ACCESS_TOKEN environment variables.');
15
+ }
16
+ return { baseUrl, accessToken };
17
+ }
18
+ /**
19
+ * Get all posts (with automatic cache refresh)
20
+ * Use this in Server Components or API Routes
21
+ */
22
+ export async function getAllPosts(options) {
23
+ const { maxCacheAge = 60, forceRefresh = false } = options || {};
24
+ // Check cache freshness
25
+ if (!forceRefresh) {
26
+ const needsRefresh = await shouldRefreshCache(maxCacheAge);
27
+ if (!needsRefresh) {
28
+ const cached = await getAllCachedPosts();
29
+ if (cached.length > 0) {
30
+ return cached;
31
+ }
32
+ }
33
+ }
34
+ // Fetch from API
35
+ const { baseUrl, accessToken } = getAPIConfig();
36
+ const api = createAPIClient(baseUrl, accessToken);
37
+ const posts = await api.getAllPosts();
38
+ // Cache all posts
39
+ for (const post of posts) {
40
+ try {
41
+ const fullPost = await api.getPost(post.id, 'markdown');
42
+ await cachePost(fullPost);
43
+ }
44
+ catch (error) {
45
+ console.error(`Failed to cache post ${post.id}:`, error);
46
+ }
47
+ }
48
+ // Update index
49
+ await updatePostsIndex(posts);
50
+ return posts;
51
+ }
52
+ /**
53
+ * Get a single post by ID
54
+ * Use this in Server Components or API Routes
55
+ */
56
+ export async function getPost(id, options) {
57
+ const { language = 'en', contentFormat = 'markdown', maxCacheAge = 60, forceRefresh = false, } = options || {};
58
+ // Try cache first
59
+ if (!forceRefresh) {
60
+ const needsRefresh = await shouldRefreshCache(maxCacheAge);
61
+ if (!needsRefresh) {
62
+ const cached = await getCachedPost(id, language);
63
+ if (cached) {
64
+ return cached;
65
+ }
66
+ }
67
+ }
68
+ // Fetch from API
69
+ try {
70
+ const { baseUrl, accessToken } = getAPIConfig();
71
+ const api = createAPIClient(baseUrl, accessToken);
72
+ const post = await api.getPost(id, contentFormat);
73
+ // Cache it
74
+ await cachePost(post);
75
+ return post;
76
+ }
77
+ catch (error) {
78
+ console.error(`Failed to fetch post ${id}:`, error);
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Get post by slug
84
+ * Use this in Server Components or API Routes
85
+ */
86
+ export async function getPostBySlug(slug, options) {
87
+ // First, get all posts to find the one with matching slug
88
+ const posts = await getAllPosts({
89
+ maxCacheAge: options?.maxCacheAge,
90
+ forceRefresh: options?.forceRefresh,
91
+ });
92
+ const language = options?.language || 'en';
93
+ // Find post with matching slug
94
+ for (const post of posts) {
95
+ const translation = post.post_translations.find(t => t.slug === slug && t.language === language);
96
+ if (translation) {
97
+ return getPost(post.id, {
98
+ language,
99
+ contentFormat: options?.contentFormat,
100
+ maxCacheAge: options?.maxCacheAge,
101
+ forceRefresh: options?.forceRefresh,
102
+ });
103
+ }
104
+ }
105
+ return null;
106
+ }