@content-streamline/nextjs 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,11 +5,12 @@ Next.js library for Content Streamline API with reusable, responsive components.
5
5
  ## Features
6
6
 
7
7
  - 🚀 **Easy Setup** - Configure via environment variables
8
- - 📦 **Reusable Components** - Pre-built React components for posts list and detail
8
+ - 📦 **Reusable Components** - Pre-built React components for posts
9
9
  - 💾 **Smart Caching** - Automatic file-based caching with refresh control
10
10
  - 🎨 **Customizable** - Responsive components with configurable props
11
11
  - 📱 **Mobile-Friendly** - Built-in responsive design
12
12
  - 🔧 **TypeScript** - Full TypeScript support
13
+ - 🏷️ **Platform Filtering** - Filter posts by platform (blog, linkedin, x, etc.)
13
14
 
14
15
  ## Installation
15
16
 
@@ -48,21 +49,32 @@ import '@content-streamline/nextjs/components/styles.css';
48
49
 
49
50
  ### 3. Use in Server Components (App Router)
50
51
 
51
- **Blog List Page** (`app/blog/page.tsx`):
52
+ **Blog Page with Featured Posts** (`app/blog/page.tsx`):
52
53
 
53
54
  ```tsx
54
55
  import { getAllPosts } from '@content-streamline/nextjs/server';
55
- import { PostsList } from '@content-streamline/nextjs/components';
56
+ import { LatestPosts, PostsList } from '@content-streamline/nextjs/components';
56
57
 
57
58
  export const revalidate = 3600; // Revalidate every hour (ISR)
58
59
 
59
60
  export default async function BlogPage() {
60
- const posts = await getAllPosts();
61
+ const posts = await getAllPosts({ platform: 'blog' });
61
62
 
62
63
  return (
63
64
  <div>
64
65
  <h1>Blog</h1>
65
- <PostsList posts={posts} baseUrl="/blog" />
66
+
67
+ {/* Featured section with latest 3 posts */}
68
+ <section>
69
+ <h2>Latest Posts</h2>
70
+ <LatestPosts posts={posts} count={3} baseUrl="/blog" />
71
+ </section>
72
+
73
+ {/* Remaining posts in vertical list */}
74
+ <section>
75
+ <h2>All Posts</h2>
76
+ <PostsList posts={posts} skipFirst={3} baseUrl="/blog" />
77
+ </section>
66
78
  </div>
67
79
  );
68
80
  }
@@ -77,7 +89,7 @@ import { notFound } from 'next/navigation';
77
89
 
78
90
  export async function generateStaticParams() {
79
91
  const { getAllPosts } = await import('@content-streamline/nextjs/server');
80
- const posts = await getAllPosts();
92
+ const posts = await getAllPosts({ platform: 'blog' });
81
93
 
82
94
  return posts.flatMap(post =>
83
95
  post.post_translations
@@ -94,7 +106,7 @@ export default async function PostPage({
94
106
  }: {
95
107
  params: { id: string; slug: string };
96
108
  }) {
97
- const post = await getPostBySlug(params.slug);
109
+ const post = await getPostBySlug(params.slug, { platform: 'blog' });
98
110
 
99
111
  if (!post) {
100
112
  notFound();
@@ -108,15 +120,16 @@ export default async function PostPage({
108
120
 
109
121
  ### 4. Use in Pages Router
110
122
 
111
- **Blog List Page** (`pages/blog/index.tsx`):
123
+ **Blog Page** (`pages/blog/index.tsx`):
112
124
 
113
125
  ```tsx
114
126
  import { getAllPosts } from '@content-streamline/nextjs/server';
115
- import { PostsList } from '@content-streamline/nextjs/components';
127
+ import { LatestPosts, PostsList } from '@content-streamline/nextjs/components';
116
128
  import type { GetStaticProps } from 'next';
129
+ import type { Post } from '@content-streamline/nextjs';
117
130
 
118
131
  export const getStaticProps: GetStaticProps = async () => {
119
- const posts = await getAllPosts();
132
+ const posts = await getAllPosts({ platform: 'blog' });
120
133
 
121
134
  return {
122
135
  props: { posts },
@@ -128,7 +141,8 @@ export default function BlogPage({ posts }: { posts: Post[] }) {
128
141
  return (
129
142
  <div>
130
143
  <h1>Blog</h1>
131
- <PostsList posts={posts} baseUrl="/blog" />
144
+ <LatestPosts posts={posts} count={3} baseUrl="/blog" />
145
+ <PostsList posts={posts} skipFirst={3} baseUrl="/blog" />
132
146
  </div>
133
147
  );
134
148
  }
@@ -140,9 +154,10 @@ export default function BlogPage({ posts }: { posts: Post[] }) {
140
154
  import { getPostBySlug, getAllPosts } from '@content-streamline/nextjs/server';
141
155
  import { PostDetail } from '@content-streamline/nextjs/components';
142
156
  import type { GetStaticProps, GetStaticPaths } from 'next';
157
+ import type { PostDetailResponse } from '@content-streamline/nextjs';
143
158
 
144
159
  export const getStaticPaths: GetStaticPaths = async () => {
145
- const posts = await getAllPosts();
160
+ const posts = await getAllPosts({ platform: 'blog' });
146
161
 
147
162
  const paths = posts.flatMap(post =>
148
163
  post.post_translations
@@ -159,7 +174,7 @@ export const getStaticPaths: GetStaticPaths = async () => {
159
174
  };
160
175
 
161
176
  export const getStaticProps: GetStaticProps = async ({ params }) => {
162
- const post = await getPostBySlug(params!.slug as string);
177
+ const post = await getPostBySlug(params!.slug as string, { platform: 'blog' });
163
178
 
164
179
  if (!post) {
165
180
  return { notFound: true };
@@ -188,11 +203,14 @@ Get all posts with automatic caching.
188
203
  import { getAllPosts } from '@content-streamline/nextjs/server';
189
204
 
190
205
  const posts = await getAllPosts({
191
- maxCacheAge: 60, // Cache age in minutes (default: 60)
192
- forceRefresh: false, // Force API fetch (default: false)
206
+ platform: 'blog', // Filter by platform (default: undefined = all)
207
+ maxCacheAge: 60, // Cache age in minutes (default: 60)
208
+ forceRefresh: false, // Force API fetch (default: false)
193
209
  });
194
210
  ```
195
211
 
212
+ **Platform values**: `'blog'`, `'x'`, `'linkedin'`, `'facebook'`, `'instagram'`, `'other'`
213
+
196
214
  #### `getPost(id, options?)`
197
215
 
198
216
  Get a single post by ID.
@@ -218,6 +236,7 @@ import { getPostBySlug } from '@content-streamline/nextjs/server';
218
236
  const post = await getPostBySlug('my-post-slug', {
219
237
  language: 'en',
220
238
  contentFormat: 'markdown',
239
+ platform: 'blog',
221
240
  maxCacheAge: 60,
222
241
  forceRefresh: false,
223
242
  });
@@ -225,30 +244,66 @@ const post = await getPostBySlug('my-post-slug', {
225
244
 
226
245
  ### Components
227
246
 
247
+ #### `<LatestPosts />`
248
+
249
+ Display featured/hero posts with the first post highlighted. Perfect for homepage or blog landing sections.
250
+
251
+ ```tsx
252
+ import { LatestPosts } from '@content-streamline/nextjs/components';
253
+
254
+ <LatestPosts
255
+ posts={posts}
256
+ count={3} // Number of posts to show (default: 3)
257
+ baseUrl="/blog" // Base URL for post links
258
+ platform="blog" // Filter by platform (default: 'blog')
259
+ language="en" // Language for translations (default: 'en')
260
+ className="custom-class" // Custom container class
261
+ cardClassName="custom" // Custom card class
262
+ showDate={true} // Show post date (default: true)
263
+ showExcerpt={true} // Show post excerpt (default: true)
264
+ showImages={true} // Show post images (default: true)
265
+ renderTitle={(post, translation) => <CustomTitle />} // Custom title renderer
266
+ renderExcerpt={(post, translation) => <CustomExcerpt />} // Custom excerpt renderer
267
+ />
268
+ ```
269
+
270
+ **Layout behavior**:
271
+ - First post is displayed as a featured card (larger, with image on left)
272
+ - Remaining posts are displayed in a responsive grid below
273
+ - Fully responsive - stacks vertically on mobile
274
+
228
275
  #### `<PostsList />`
229
276
 
230
- Display a list of posts.
277
+ Display posts in a vertical list with thumbnails on the left. Ideal for archive pages or "more posts" sections.
231
278
 
232
279
  ```tsx
233
280
  import { PostsList } from '@content-streamline/nextjs/components';
234
281
 
235
282
  <PostsList
236
283
  posts={posts}
237
- baseUrl="/blog" // Base URL for post links
238
- language="en" // Language for translations
239
- className="custom-class" // Custom container class
240
- cardClassName="custom" // Custom card class
241
- showDate={true} // Show post date
242
- showExcerpt={true} // Show post excerpt
243
- showImages={true} // Show post images
244
- renderTitle={(post, translation) => <CustomTitle />} // Custom title renderer
245
- renderExcerpt={(post, translation) => <CustomExcerpt />} // Custom excerpt renderer
284
+ baseUrl="/blog" // Base URL for post links
285
+ platform="blog" // Filter by platform (default: 'blog')
286
+ skipFirst={3} // Skip first N posts (default: 0)
287
+ language="en" // Language for translations (default: 'en')
288
+ className="custom-class" // Custom container class
289
+ itemClassName="custom" // Custom item class
290
+ showDate={true} // Show post date (default: true)
291
+ showExcerpt={true} // Show post excerpt (default: true)
292
+ showImages={true} // Show post images (default: true)
293
+ renderTitle={(post, translation) => <CustomTitle />} // Custom title renderer
294
+ renderExcerpt={(post, translation) => <CustomExcerpt />} // Custom excerpt renderer
246
295
  />
247
296
  ```
248
297
 
298
+ **Layout behavior**:
299
+ - Vertical list with image thumbnail on the left
300
+ - Title and excerpt on the right
301
+ - Compact design for browsing many posts
302
+ - `skipFirst` prop allows combining with `LatestPosts` to avoid duplicates
303
+
249
304
  #### `<PostDetail />`
250
305
 
251
- Display a single post.
306
+ Display a single post with full content.
252
307
 
253
308
  ```tsx
254
309
  import { PostDetail } from '@content-streamline/nextjs/components';
@@ -256,18 +311,69 @@ import { PostDetail } from '@content-streamline/nextjs/components';
256
311
  <PostDetail
257
312
  post={post}
258
313
  backUrl="/blog" // URL for back link
259
- language="en" // Language for translation
314
+ language="en" // Language for translation (default: 'en')
260
315
  className="custom-class" // Custom container class
261
- showBackLink={true} // Show back link
262
- showDate={true} // Show post date
263
- showType={true} // Show post type badge
264
- showImage={true} // Show featured image
265
- showGallery={true} // Show gallery images
266
- renderContent={(content) => <MarkdownRenderer content={content} />} // Custom content renderer
267
- renderTitle={(post, translation) => <CustomTitle />} // Custom title renderer
316
+ showBackLink={true} // Show back link (default: true)
317
+ showDate={true} // Show post date (default: true)
318
+ showType={true} // Show post type badge (default: true)
319
+ showImage={true} // Show featured image (default: true)
320
+ showGallery={true} // Show gallery images (default: true)
321
+ renderContent={(content) => <MarkdownRenderer content={content} />} // Custom content renderer
322
+ renderTitle={(post, translation) => <CustomTitle />} // Custom title renderer
268
323
  />
269
324
  ```
270
325
 
326
+ ## Common Patterns
327
+
328
+ ### Featured Posts + Archive List
329
+
330
+ Combine `LatestPosts` and `PostsList` for a complete blog layout:
331
+
332
+ ```tsx
333
+ import { getAllPosts } from '@content-streamline/nextjs/server';
334
+ import { LatestPosts, PostsList } from '@content-streamline/nextjs/components';
335
+
336
+ export default async function BlogPage() {
337
+ const posts = await getAllPosts({ platform: 'blog' });
338
+
339
+ return (
340
+ <main>
341
+ {/* Hero section with 3 featured posts */}
342
+ <section className="hero">
343
+ <LatestPosts posts={posts} count={3} baseUrl="/blog" />
344
+ </section>
345
+
346
+ {/* Archive list (skip the 3 featured posts) */}
347
+ <section className="archive">
348
+ <h2>More Articles</h2>
349
+ <PostsList posts={posts} skipFirst={3} baseUrl="/blog" />
350
+ </section>
351
+ </main>
352
+ );
353
+ }
354
+ ```
355
+
356
+ ### Multi-Platform Content
357
+
358
+ Display content from different platforms:
359
+
360
+ ```tsx
361
+ // Blog posts
362
+ const blogPosts = await getAllPosts({ platform: 'blog' });
363
+
364
+ // LinkedIn posts
365
+ const linkedinPosts = await getAllPosts({ platform: 'linkedin' });
366
+
367
+ // All posts (no filter)
368
+ const allPosts = await getAllPosts();
369
+ ```
370
+
371
+ ### Homepage with Single Featured Post
372
+
373
+ ```tsx
374
+ <LatestPosts posts={posts} count={1} baseUrl="/blog" />
375
+ ```
376
+
271
377
  ## Customization
272
378
 
273
379
  ### Styling
@@ -275,15 +381,26 @@ import { PostDetail } from '@content-streamline/nextjs/components';
275
381
  Components use CSS classes with the `content-streamline-` prefix. You can override styles:
276
382
 
277
383
  ```css
278
- /* Override post card styles */
279
- .content-streamline-post-card {
280
- border-radius: 16px;
281
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
384
+ /* Override latest posts card */
385
+ .content-streamline-latest-card {
386
+ border-radius: 20px;
387
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
388
+ }
389
+
390
+ /* Override featured card */
391
+ .content-streamline-latest-card--featured {
392
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
393
+ }
394
+
395
+ /* Override list items */
396
+ .content-streamline-list-item {
397
+ border-left: 4px solid #3b82f6;
282
398
  }
283
399
 
284
400
  /* Override post title */
285
- .content-streamline-post-title a {
286
- color: #your-color;
401
+ .content-streamline-latest-title a,
402
+ .content-streamline-list-title a {
403
+ color: #your-brand-color;
287
404
  }
288
405
  ```
289
406
 
@@ -292,16 +409,17 @@ Components use CSS classes with the `content-streamline-` prefix. You can overri
292
409
  Use render props for complete control:
293
410
 
294
411
  ```tsx
295
- <PostsList
412
+ <LatestPosts
296
413
  posts={posts}
297
414
  renderTitle={(post, translation) => (
298
415
  <h2 className="my-custom-title">
416
+ <span className="category">{post.content_type}</span>
299
417
  {translation.title}
300
418
  </h2>
301
419
  )}
302
420
  renderExcerpt={(post, translation) => (
303
421
  <p className="my-custom-excerpt">
304
- {translation.meta_description}
422
+ {translation.meta_description?.slice(0, 100)}...
305
423
  </p>
306
424
  )}
307
425
  />
@@ -341,60 +459,57 @@ const posts = await getAllPosts({ forceRefresh: true });
341
459
  Full TypeScript support is included:
342
460
 
343
461
  ```tsx
344
- import type { Post, PostDetailResponse } from '@content-streamline/nextjs';
462
+ import type {
463
+ Post,
464
+ PostDetailResponse,
465
+ PostsListProps,
466
+ LatestPostsProps,
467
+ PostDetailProps,
468
+ } from '@content-streamline/nextjs';
345
469
 
346
470
  function MyComponent({ post }: { post: PostDetailResponse }) {
347
- // ...
471
+ // Full type safety
348
472
  }
349
473
  ```
350
474
 
351
- ## Examples
352
-
353
- ### Basic Usage
354
-
355
- ```tsx
356
- // app/blog/page.tsx
357
- import { getAllPosts } from '@content-streamline/nextjs/server';
358
- import { PostsList } from '@content-streamline/nextjs/components';
359
-
360
- export default async function Blog() {
361
- const posts = await getAllPosts();
362
- return <PostsList posts={posts} baseUrl="/blog" />;
363
- }
364
- ```
365
-
366
- ### With Custom Styling
367
-
368
- ```tsx
369
- <PostsList
370
- posts={posts}
371
- baseUrl="/blog"
372
- className="my-blog-list"
373
- cardClassName="my-post-card"
374
- showImages={false}
375
- />
376
- ```
377
-
378
- ### With Markdown Rendering
379
-
380
- ```tsx
381
- import ReactMarkdown from 'react-markdown';
382
- import { getPostBySlug } from '@content-streamline/nextjs/server';
383
- import { PostDetail } from '@content-streamline/nextjs/components';
384
-
385
- export default async function PostPage({ params }) {
386
- const post = await getPostBySlug(params.slug);
387
-
388
- return (
389
- <PostDetail
390
- post={post}
391
- renderContent={(content) => <ReactMarkdown>{content}</ReactMarkdown>}
392
- />
393
- );
394
- }
395
- ```
475
+ ## CSS Classes Reference
476
+
477
+ ### LatestPosts
478
+ - `.content-streamline-latest-posts` - Container
479
+ - `.content-streamline-latest-card` - Individual card
480
+ - `.content-streamline-latest-card--featured` - First (featured) card
481
+ - `.content-streamline-latest-image` - Image container
482
+ - `.content-streamline-latest-content` - Content area
483
+ - `.content-streamline-latest-date` - Date element
484
+ - `.content-streamline-latest-title` - Title
485
+ - `.content-streamline-latest-excerpt` - Excerpt text
486
+ - `.content-streamline-latest-link` - "Read more" link
487
+
488
+ ### PostsList
489
+ - `.content-streamline-list` - Container
490
+ - `.content-streamline-list-item` - Individual item
491
+ - `.content-streamline-list-image` - Image thumbnail
492
+ - `.content-streamline-list-content` - Content area
493
+ - `.content-streamline-list-date` - Date element
494
+ - `.content-streamline-list-title` - Title
495
+ - `.content-streamline-list-excerpt` - Excerpt text
496
+
497
+ ### PostDetail
498
+ - `.content-streamline-post-detail` - Container
499
+ - `.content-streamline-back-link` - Back navigation link
500
+ - `.content-streamline-post-header` - Header section
501
+ - `.content-streamline-post-title` - Title
502
+ - `.content-streamline-post-meta` - Meta information
503
+ - `.content-streamline-post-date` - Date
504
+ - `.content-streamline-post-type` - Type badge
505
+ - `.content-streamline-post-featured-image` - Featured image
506
+ - `.content-streamline-post-content` - Content area
507
+ - `.content-streamline-post-gallery` - Image gallery
508
+
509
+ ### Common
510
+ - `.content-streamline-empty` - Empty state message
511
+ - `.content-streamline-post-not-found` - Not found message
396
512
 
397
513
  ## License
398
514
 
399
515
  MIT
400
-
@@ -10,11 +10,11 @@ declare class ContentStreamlineAPI {
10
10
  /**
11
11
  * List all posts with pagination
12
12
  */
13
- listPosts(page?: number): Promise<PostsResponse>;
13
+ listPosts(page?: number, platform?: string): Promise<PostsResponse>;
14
14
  /**
15
15
  * Get all posts by fetching all pages
16
16
  */
17
- getAllPosts(): Promise<Post[]>;
17
+ getAllPosts(platform?: string): Promise<Post[]>;
18
18
  /**
19
19
  * Get a single post by ID
20
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,IAAI,EACJ,kBAAkB,EAClB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B,cAAM,oBAAoB;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;YAKlC,KAAK;IAiCnB;;OAEG;IACG,SAAS,CAAC,IAAI,GAAE,MAAU,GAAG,OAAO,CAAC,aAAa,CAAC;IAIzD;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAgBpC;;OAEG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,GAAE,UAAU,GAAG,MAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAIxG;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAE1F"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,IAAI,EACJ,kBAAkB,EAClB,aAAa,EACd,MAAM,mBAAmB,CAAC;AAE3B,cAAM,oBAAoB;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,WAAW,CAAS;gBAEhB,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;YAKlC,KAAK;IAiCnB;;OAEG;IACG,SAAS,CAAC,IAAI,GAAE,MAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAS5E;;OAEG;IACG,WAAW,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAgBrD;;OAEG;IACG,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,aAAa,GAAE,UAAU,GAAG,MAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;CAIxG;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,oBAAoB,CAE1F"}
@@ -38,18 +38,23 @@ class ContentStreamlineAPI {
38
38
  /**
39
39
  * List all posts with pagination
40
40
  */
41
- async listPosts(page = 1) {
42
- return this.fetch(`/public/v1/posts?page=${page}`);
41
+ async listPosts(page = 1, platform) {
42
+ const params = new URLSearchParams();
43
+ params.set('page', page.toString());
44
+ if (platform) {
45
+ params.set('platform', platform);
46
+ }
47
+ return this.fetch(`/public/v1/posts?${params.toString()}`);
43
48
  }
44
49
  /**
45
50
  * Get all posts by fetching all pages
46
51
  */
47
- async getAllPosts() {
52
+ async getAllPosts(platform) {
48
53
  const allPosts = [];
49
54
  let currentPage = 1;
50
55
  let hasMore = true;
51
56
  while (hasMore) {
52
- const response = await this.listPosts(currentPage);
57
+ const response = await this.listPosts(currentPage, platform);
53
58
  allPosts.push(...response.data);
54
59
  hasMore = response.pagination.next_page !== null;
55
60
  currentPage = response.pagination.next_page || currentPage + 1;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import type { Post } from '../types/index.js';
3
+ export interface LatestPostsProps {
4
+ posts: Post[];
5
+ /**
6
+ * Number of posts to display (default: 3)
7
+ */
8
+ count?: number;
9
+ /**
10
+ * Base URL for post links (e.g., '/blog' or '/posts')
11
+ * Post URL will be: {baseUrl}/{postId}/{slug}
12
+ */
13
+ baseUrl?: string;
14
+ /**
15
+ * Platform filter for posts (default: 'blog')
16
+ * Valid values: 'x', 'linkedin', 'blog', 'facebook', 'instagram', 'other'
17
+ */
18
+ platform?: string;
19
+ /**
20
+ * Language to use for post translations (default: 'en')
21
+ */
22
+ language?: string;
23
+ /**
24
+ * Custom className for the container
25
+ */
26
+ className?: string;
27
+ /**
28
+ * Custom className for post cards
29
+ */
30
+ cardClassName?: string;
31
+ /**
32
+ * Show post date (default: true)
33
+ */
34
+ showDate?: boolean;
35
+ /**
36
+ * Show post excerpt/description (default: true)
37
+ */
38
+ showExcerpt?: boolean;
39
+ /**
40
+ * Show post images (default: true)
41
+ */
42
+ showImages?: boolean;
43
+ /**
44
+ * Custom render function for post title
45
+ */
46
+ renderTitle?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
47
+ /**
48
+ * Custom render function for post excerpt
49
+ */
50
+ renderExcerpt?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
51
+ }
52
+ export declare function LatestPosts({ posts, count, baseUrl, platform, language, className, cardClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: LatestPostsProps): import("react/jsx-runtime").JSX.Element;
53
+ //# sourceMappingURL=LatestPosts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LatestPosts.d.ts","sourceRoot":"","sources":["../../src/components/LatestPosts.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;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,WAAW,CAAC,EAC1B,KAAK,EACL,KAAS,EACT,OAAkB,EAClB,QAAiB,EACjB,QAAe,EACf,SAAc,EACd,aAAkB,EAClB,QAAe,EACf,WAAkB,EAClB,UAAiB,EACjB,WAAW,EACX,aAAa,GACd,EAAE,gBAAgB,2CAuFlB"}
@@ -0,0 +1,29 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ export function LatestPosts({ posts, count = 3, baseUrl = '/posts', platform = 'blog', language = 'en', className = '', cardClassName = '', showDate = true, showExcerpt = true, showImages = true, renderTitle, renderExcerpt, }) {
4
+ // Filter posts by platform if specified
5
+ const filteredPosts = platform
6
+ ? posts.filter(post => post.platform.toLowerCase() === platform.toLowerCase())
7
+ : posts;
8
+ // Take only the first N posts
9
+ const latestPosts = filteredPosts.slice(0, count);
10
+ if (latestPosts.length === 0) {
11
+ return (_jsx("div", { className: `content-streamline-empty ${className}`, children: _jsx("p", { children: "No posts found." }) }));
12
+ }
13
+ return (_jsx("div", { className: `content-streamline-latest-posts ${className}`, children: latestPosts.map((post, index) => {
14
+ const translation = post.post_translations.find((t) => t.language === language) ||
15
+ post.post_translations[0];
16
+ if (!translation || !translation.slug)
17
+ return null;
18
+ const featuredImage = post.images.find((img) => img.ordering === 0) || post.images[0];
19
+ const date = new Date(post.created_at);
20
+ const isFirst = index === 0;
21
+ return (_jsxs("article", { className: `content-streamline-latest-card ${isFirst ? 'content-streamline-latest-card--featured' : ''} ${cardClassName}`, children: [showImages && featuredImage && (_jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-latest-image-link", children: _jsx("div", { className: "content-streamline-latest-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title, loading: isFirst ? 'eager' : 'lazy' }) }) })), _jsxs("div", { className: "content-streamline-latest-content", children: [showDate && (_jsx("time", { className: "content-streamline-latest-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
22
+ year: 'numeric',
23
+ month: 'long',
24
+ day: 'numeric',
25
+ }) })), _jsx("h2", { className: "content-streamline-latest-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-latest-excerpt", children: renderExcerpt
26
+ ? renderExcerpt(post, translation)
27
+ : translation.meta_description })), _jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-latest-link", children: "Read more \u2192" })] })] }, post.id));
28
+ }) }));
29
+ }
@@ -7,6 +7,18 @@ export interface PostsListProps {
7
7
  * Post URL will be: {baseUrl}/{postId}/{slug}
8
8
  */
9
9
  baseUrl?: string;
10
+ /**
11
+ * Platform filter for posts (default: 'blog')
12
+ * Valid values: 'x', 'linkedin', 'blog', 'facebook', 'instagram', 'other'
13
+ * Filters the posts to only show those matching this platform.
14
+ * For best performance, also pass platform to getAllPosts() when fetching.
15
+ */
16
+ platform?: string;
17
+ /**
18
+ * Number of posts to skip from the beginning (default: 0)
19
+ * Useful when using LatestPosts component to display the first N posts separately.
20
+ */
21
+ skipFirst?: number;
10
22
  /**
11
23
  * Language to use for post translations (default: 'en')
12
24
  */
@@ -16,9 +28,9 @@ export interface PostsListProps {
16
28
  */
17
29
  className?: string;
18
30
  /**
19
- * Custom className for post cards
31
+ * Custom className for post items
20
32
  */
21
- cardClassName?: string;
33
+ itemClassName?: string;
22
34
  /**
23
35
  * Show post date (default: true)
24
36
  */
@@ -40,5 +52,5 @@ export interface PostsListProps {
40
52
  */
41
53
  renderExcerpt?: (post: Post, translation: Post['post_translations'][0]) => React.ReactNode;
42
54
  }
43
- export declare function PostsList({ posts, baseUrl, language, className, cardClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: PostsListProps): import("react/jsx-runtime").JSX.Element;
55
+ export declare function PostsList({ posts, baseUrl, platform, skipFirst, language, className, itemClassName, showDate, showExcerpt, showImages, renderTitle, renderExcerpt, }: PostsListProps): import("react/jsx-runtime").JSX.Element;
44
56
  //# sourceMappingURL=PostsList.d.ts.map
@@ -1 +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,mBAAmB,CAAC;AAE9C,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"}
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,mBAAmB,CAAC;AAE9C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;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,QAAiB,EACjB,SAAa,EACb,QAAe,EACf,SAAc,EACd,aAAkB,EAClB,QAAe,EACf,WAAkB,EAClB,UAAiB,EACjB,WAAW,EACX,aAAa,GACd,EAAE,cAAc,2CAgFhB"}
@@ -1,22 +1,28 @@
1
1
  'use client';
2
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) {
3
+ export function PostsList({ posts, baseUrl = '/posts', platform = 'blog', skipFirst = 0, language = 'en', className = '', itemClassName = '', showDate = true, showExcerpt = true, showImages = true, renderTitle, renderExcerpt, }) {
4
+ // Filter posts by platform if specified
5
+ const filteredPosts = platform
6
+ ? posts.filter(post => post.platform.toLowerCase() === platform.toLowerCase())
7
+ : posts;
8
+ // Skip first N posts
9
+ const displayPosts = skipFirst > 0 ? filteredPosts.slice(skipFirst) : filteredPosts;
10
+ if (displayPosts.length === 0) {
5
11
  return (_jsx("div", { className: `content-streamline-empty ${className}`, children: _jsx("p", { children: "No posts found." }) }));
6
12
  }
7
- return (_jsx("div", { className: `content-streamline-posts-list ${className}`, children: posts.map((post) => {
13
+ return (_jsx("div", { className: `content-streamline-list ${className}`, children: displayPosts.map((post) => {
8
14
  const translation = post.post_translations.find((t) => t.language === language) ||
9
15
  post.post_translations[0];
10
16
  if (!translation || !translation.slug)
11
17
  return null;
12
18
  const featuredImage = post.images.find((img) => img.ordering === 0) || post.images[0];
13
19
  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', {
20
+ return (_jsxs("article", { className: `content-streamline-list-item ${itemClassName}`, children: [showImages && featuredImage && (_jsx("a", { href: `${baseUrl}/${post.id}/${translation.slug}`, className: "content-streamline-list-image-link", children: _jsx("div", { className: "content-streamline-list-image", children: _jsx("img", { src: featuredImage.url, alt: featuredImage.alt || translation.title, loading: "lazy" }) }) })), _jsxs("div", { className: "content-streamline-list-content", children: [showDate && (_jsx("time", { className: "content-streamline-list-date", dateTime: post.created_at, children: date.toLocaleDateString('en-US', {
15
21
  year: 'numeric',
16
- month: 'long',
22
+ month: 'short',
17
23
  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
24
+ }) })), _jsx("h3", { className: "content-streamline-list-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-list-excerpt", children: renderExcerpt
19
25
  ? 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));
26
+ : translation.meta_description }))] })] }, post.id));
21
27
  }) }));
22
28
  }
@@ -3,6 +3,8 @@
3
3
  */
4
4
  export { PostsList } from './PostsList.js';
5
5
  export { PostDetail } from './PostDetail.js';
6
+ export { LatestPosts } from './LatestPosts.js';
6
7
  export type { PostsListProps } from './PostsList.js';
7
8
  export type { PostDetailProps } from './PostDetail.js';
9
+ export type { LatestPostsProps } from './LatestPosts.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACrD,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -3,5 +3,6 @@
3
3
  */
4
4
  export { PostsList } from './PostsList.js';
5
5
  export { PostDetail } from './PostDetail.js';
6
+ export { LatestPosts } from './LatestPosts.js';
6
7
  // Styles are available at '@content-streamline/nextjs/components/styles.css'
7
8
  // Users should import them in their app: import '@content-streamline/nextjs/components/styles.css'
package/dist/index.d.ts CHANGED
@@ -4,6 +4,6 @@
4
4
  */
5
5
  export type { Post, PostDetailResponse, PostTranslation, PostImage, PostsResponse, Pagination, } from './types/index.js';
6
6
  export { getAllPosts, getPost, getPostBySlug, } from './server/index.js';
7
- export { PostsList, PostDetail, } from './components/index.js';
8
- export type { PostsListProps, PostDetailProps, } from './components/index.js';
7
+ export { PostsList, PostDetail, LatestPosts, } from './components/index.js';
8
+ export type { PostsListProps, PostDetailProps, LatestPostsProps, } from './components/index.js';
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,OAAO,EACP,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,UAAU,GACX,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,cAAc,EACd,eAAe,GAChB,MAAM,uBAAuB,CAAC"}
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,kBAAkB,CAAC;AAG1B,OAAO,EACL,WAAW,EACX,OAAO,EACP,aAAa,GACd,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,UAAU,EACV,WAAW,GACZ,MAAM,uBAAuB,CAAC;AAE/B,YAAY,EACV,cAAc,EACd,eAAe,EACf,gBAAgB,GACjB,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -5,4 +5,4 @@
5
5
  // Export server functions
6
6
  export { getAllPosts, getPost, getPostBySlug, } from './server/index.js';
7
7
  // Export components
8
- export { PostsList, PostDetail, } from './components/index.js';
8
+ export { PostsList, PostDetail, LatestPosts, } from './components/index.js';
@@ -10,6 +10,7 @@ import type { Post, PostDetailResponse } from '../types/index.js';
10
10
  export declare function getAllPosts(options?: {
11
11
  maxCacheAge?: number;
12
12
  forceRefresh?: boolean;
13
+ platform?: string;
13
14
  }): Promise<Post[]>;
14
15
  /**
15
16
  * Get a single post by ID
@@ -30,6 +31,7 @@ export declare function getPostBySlug(slug: string, options?: {
30
31
  contentFormat?: 'markdown' | 'html';
31
32
  maxCacheAge?: number;
32
33
  forceRefresh?: boolean;
34
+ platform?: string;
33
35
  }): Promise<PostDetailResponse | null>;
34
36
  export type { Post, PostDetailResponse, PostTranslation, PostImage, PostsResponse, Pagination, } from '../types/index.js';
35
37
  //# sourceMappingURL=index.d.ts.map
@@ -1 +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,mBAAmB,CAAC;AAkBlE;;;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,mBAAmB,CAAC"}
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,mBAAmB,CAAC;AAkBlE;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CA2ClB;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;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,GACA,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CA2BpC;AAGD,YAAY,EACV,IAAI,EACJ,kBAAkB,EAClB,eAAe,EACf,SAAS,EACT,aAAa,EACb,UAAU,GACX,MAAM,mBAAmB,CAAC"}
@@ -20,21 +20,30 @@ function getAPIConfig() {
20
20
  * Use this in Server Components or API Routes
21
21
  */
22
22
  export async function getAllPosts(options) {
23
- const { maxCacheAge = 60, forceRefresh = false } = options || {};
23
+ const { maxCacheAge = 15, forceRefresh = false, platform } = options || {};
24
24
  // Check cache freshness
25
25
  if (!forceRefresh) {
26
26
  const needsRefresh = await shouldRefreshCache(maxCacheAge);
27
27
  if (!needsRefresh) {
28
28
  const cached = await getAllCachedPosts();
29
29
  if (cached.length > 0) {
30
- return cached;
30
+ // Filter cached posts by platform if specified
31
+ if (platform) {
32
+ const filtered = cached.filter(post => post.platform.toLowerCase() === platform.toLowerCase());
33
+ if (filtered.length > 0) {
34
+ return filtered;
35
+ }
36
+ }
37
+ else {
38
+ return cached;
39
+ }
31
40
  }
32
41
  }
33
42
  }
34
43
  // Fetch from API
35
44
  const { baseUrl, accessToken } = getAPIConfig();
36
45
  const api = createAPIClient(baseUrl, accessToken);
37
- const posts = await api.getAllPosts();
46
+ const posts = await api.getAllPosts(platform);
38
47
  // Cache all posts
39
48
  for (const post of posts) {
40
49
  try {
@@ -54,7 +63,7 @@ export async function getAllPosts(options) {
54
63
  * Use this in Server Components or API Routes
55
64
  */
56
65
  export async function getPost(id, options) {
57
- const { language = 'en', contentFormat = 'markdown', maxCacheAge = 60, forceRefresh = false, } = options || {};
66
+ const { language = 'en', contentFormat = 'markdown', maxCacheAge = 15, forceRefresh = false, } = options || {};
58
67
  // Try cache first
59
68
  if (!forceRefresh) {
60
69
  const needsRefresh = await shouldRefreshCache(maxCacheAge);
@@ -88,6 +97,7 @@ export async function getPostBySlug(slug, options) {
88
97
  const posts = await getAllPosts({
89
98
  maxCacheAge: options?.maxCacheAge,
90
99
  forceRefresh: options?.forceRefresh,
100
+ platform: options?.platform,
91
101
  });
92
102
  const language = options?.language || 'en';
93
103
  // Find post with matching slug
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@content-streamline/nextjs",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Next.js library for Content Streamline API with reusable components",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -49,4 +49,3 @@
49
49
  "README.md"
50
50
  ]
51
51
  }
52
-