@jhits/plugin-blog 0.0.3 → 0.0.5

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/package.json CHANGED
@@ -1,57 +1,57 @@
1
1
  {
2
- "name": "@jhits/plugin-blog",
3
- "version": "0.0.3",
4
- "description": "Professional blog management system for the JHITS ecosystem",
5
- "publishConfig": {
6
- "access": "public"
2
+ "name": "@jhits/plugin-blog",
3
+ "version": "0.0.5",
4
+ "description": "Professional blog management system for the JHITS ecosystem",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "main": "./src/index.tsx",
9
+ "types": "./src/index.tsx",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./src/index.tsx",
13
+ "default": "./src/index.tsx"
7
14
  },
8
- "main": "./src/index.tsx",
9
- "types": "./src/index.tsx",
10
- "exports": {
11
- ".": {
12
- "types": "./src/index.tsx",
13
- "default": "./src/index.tsx"
14
- },
15
- "./server": {
16
- "types": "./src/index.server.ts",
17
- "default": "./src/index.server.ts"
18
- }
19
- },
20
- "dependencies": {
21
- "@jhits/plugin-core": "^0.0.1",
22
- "@jhits/plugin-content": "^0.0.1",
23
- "@jhits/plugin-images": "^0.0.1",
24
- "bcrypt": "^6.0.0",
25
- "framer-motion": "^12.23.26",
26
- "lucide-react": "^0.562.0",
27
- "mongodb": "^7.0.0",
28
- "next-auth": "^4.24.13"
29
- },
30
- "peerDependencies": {
31
- "next": ">=15.0.0",
32
- "next-intl": ">=4.0.0",
33
- "next-themes": ">=0.4.0",
34
- "react": ">=18.0.0",
35
- "react-dom": ">=18.0.0"
36
- },
37
- "devDependencies": {
38
- "@tailwindcss/postcss": "^4",
39
- "@types/bcrypt": "^6.0.0",
40
- "@types/node": "^20.19.27",
41
- "@types/react": "^19",
42
- "@types/react-dom": "^19",
43
- "eslint": "^9",
44
- "eslint-config-next": "16.1.1",
45
- "next": "16.1.1",
46
- "next-intl": "4.6.1",
47
- "next-themes": "0.4.6",
48
- "react": "19.2.3",
49
- "react-dom": "19.2.3",
50
- "tailwindcss": "^4",
51
- "typescript": "^5"
52
- },
53
- "files": [
54
- "src",
55
- "package.json"
56
- ]
57
- }
15
+ "./server": {
16
+ "types": "./src/index.server.ts",
17
+ "default": "./src/index.server.ts"
18
+ }
19
+ },
20
+ "dependencies": {
21
+ "@jhits/plugin-core": "^0.0.1",
22
+ "@jhits/plugin-content": "^0.0.1",
23
+ "@jhits/plugin-images": "^0.0.1",
24
+ "bcrypt": "^6.0.0",
25
+ "framer-motion": "^12.23.26",
26
+ "lucide-react": "^0.562.0",
27
+ "mongodb": "^7.0.0",
28
+ "next-auth": "^4.24.13"
29
+ },
30
+ "peerDependencies": {
31
+ "next": ">=15.0.0",
32
+ "next-intl": ">=4.0.0",
33
+ "next-themes": ">=0.4.0",
34
+ "react": ">=18.0.0",
35
+ "react-dom": ">=18.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@tailwindcss/postcss": "^4",
39
+ "@types/bcrypt": "^6.0.0",
40
+ "@types/node": "^20.19.27",
41
+ "@types/react": "^19",
42
+ "@types/react-dom": "^19",
43
+ "eslint": "^9",
44
+ "eslint-config-next": "16.1.1",
45
+ "next": "16.1.1",
46
+ "next-intl": "4.6.1",
47
+ "next-themes": "0.4.6",
48
+ "react": "19.2.3",
49
+ "react-dom": "19.2.3",
50
+ "tailwindcss": "^4",
51
+ "typescript": "^5"
52
+ },
53
+ "files": [
54
+ "src",
55
+ "package.json"
56
+ ]
57
+ }
@@ -8,7 +8,6 @@
8
8
  */
9
9
 
10
10
  import { NextRequest, NextResponse } from 'next/server';
11
- import { APIBlogDocument, apiToBlogPost, editorStateToAPI } from '../lib/mappers/apiMapper';
12
11
  import { slugify } from '../lib/utils/slugify';
13
12
 
14
13
  export interface BlogApiConfig {
@@ -68,7 +67,7 @@ export async function GET(req: NextRequest, config: BlogApiConfig): Promise<Next
68
67
  .find(query)
69
68
  .sort({ 'publicationData.date': -1 })
70
69
  .skip(skip)
71
- .limit(limit)
70
+ .limit(isAdminView ? 0 : limit)
72
71
  .toArray(),
73
72
  blogs.countDocuments(query),
74
73
  ]);
@@ -292,7 +291,7 @@ export async function PUT_BY_SLUG(
292
291
  const hasContent =
293
292
  (contentBlocks && Array.isArray(contentBlocks) && contentBlocks.length > 0) ||
294
293
  (content && Array.isArray(content) && content.length > 0);
295
-
294
+
296
295
  // Collect all missing fields for better error messages
297
296
  const missingFields: string[] = [];
298
297
  if (!summary?.trim()) missingFields.push('summary');
@@ -303,7 +302,7 @@ export async function PUT_BY_SLUG(
303
302
  missingFields.push('category');
304
303
  }
305
304
  if (!hasContent) missingFields.push('content');
306
-
305
+
307
306
  if (missingFields.length > 0) {
308
307
  console.log('[BlogAPI] PUT_BY_SLUG validation failed:', {
309
308
  isPublishing,
@@ -316,9 +315,9 @@ export async function PUT_BY_SLUG(
316
315
  contentLength: content?.length || 0,
317
316
  });
318
317
  return NextResponse.json(
319
- {
318
+ {
320
319
  message: `Missing required fields for publishing: ${missingFields.join(', ')}`,
321
- missingFields
320
+ missingFields
322
321
  },
323
322
  { status: 400 }
324
323
  );
@@ -341,7 +340,7 @@ export async function PUT_BY_SLUG(
341
340
  } else if (publicationData?.status === 'draft') {
342
341
  finalStatus = 'concept';
343
342
  }
344
-
343
+
345
344
  const updateData = {
346
345
  title: title.trim(),
347
346
  summary: (summary || '').trim(),
package/src/api/router.ts CHANGED
@@ -37,21 +37,18 @@ export async function handleBlogApi(
37
37
  getUserId: config.getUserId,
38
38
  collectionName: config.collectionName || 'blogs',
39
39
  });
40
-
40
+
41
41
  const method = req.method;
42
42
  // Handle empty path array - means we're at /api/plugin-blog
43
43
  // Ensure path is always an array
44
44
  const safePath = Array.isArray(path) ? path : [];
45
45
  const route = safePath.length > 0 ? safePath[0] : '';
46
46
 
47
- console.log(`[BlogApiRouter] method=${method}, path=${JSON.stringify(safePath)}, route=${route}, url=${req.url}`);
48
-
49
47
  try {
50
48
  // Route: /api/plugin-blog (list/create) - empty path or 'list'
51
49
  // This handles both /api/plugin-blog and /api/plugin-blog?limit=3
52
50
  if (!route || route === 'list') {
53
51
  if (method === 'GET') {
54
- console.log('[BlogApiRouter] Routing to BlogListHandler');
55
52
  return await BlogListHandler(req, blogApiConfig);
56
53
  }
57
54
  if (method === 'POST') {
@@ -69,7 +69,7 @@ export function PostActionsMenu({
69
69
  const menuContent = isOpen && (
70
70
  <div
71
71
  ref={menuRef}
72
- className="fixed w-48 bg-dashboard-card border border-dashboard-border rounded-2xl shadow-xl z-[9999] overflow-hidden"
72
+ className="fixed w-48 bg-dashboard-card border border-dashboard-border rounded-2xl shadow-xl z-9999 overflow-hidden"
73
73
  style={{
74
74
  top: `${menuPosition.top}px`,
75
75
  right: `${menuPosition.right}px`,
@@ -25,6 +25,7 @@ type ViewMode = 'list' | 'cards';
25
25
 
26
26
  export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
27
27
  const [posts, setPosts] = useState<PostListItem[]>([]);
28
+ const [totalPosts, setTotalPosts] = useState<number>(0);
28
29
  const [loading, setLoading] = useState(true);
29
30
  const [search, setSearch] = useState('');
30
31
  const [statusFilter, setStatusFilter] = useState<PostStatus | 'all'>('all');
@@ -85,6 +86,7 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
85
86
  };
86
87
  });
87
88
  setPosts(postListItems);
89
+ setTotalPosts(data.total);
88
90
  }
89
91
  } catch (error) {
90
92
  console.error('Failed to fetch posts:', error);
@@ -173,6 +175,7 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
173
175
  if (response.ok) {
174
176
  // Remove from local state
175
177
  setPosts((prev) => prev.filter((p) => p.id !== postId));
178
+ setTotalPosts(prev => prev - 1);
176
179
  } else {
177
180
  const error = await response.json();
178
181
  alert(error.error || 'Failed to delete post');
@@ -210,7 +213,7 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
210
213
  </div>
211
214
 
212
215
  {/* Stats Summary */}
213
- <PostStats posts={posts} />
216
+ <PostStats total={totalPosts} posts={posts} />
214
217
 
215
218
  {/* Filters & Search Bar with View Toggle */}
216
219
  <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-6">
@@ -229,8 +232,8 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
229
232
  <button
230
233
  onClick={() => setViewMode('list')}
231
234
  className={`p-2 rounded-full transition-all ${viewMode === 'list'
232
- ? 'bg-white dark:bg-neutral-900 text-primary shadow-sm'
233
- : 'text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
235
+ ? 'bg-white dark:bg-neutral-900 text-primary shadow-sm'
236
+ : 'text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
234
237
  }`}
235
238
  title="List View"
236
239
  >
@@ -239,8 +242,8 @@ export function PostManagerView({ siteId, locale }: PostManagerViewProps) {
239
242
  <button
240
243
  onClick={() => setViewMode('cards')}
241
244
  className={`p-2 rounded-full transition-all ${viewMode === 'cards'
242
- ? 'bg-white dark:bg-neutral-900 text-primary shadow-sm'
243
- : 'text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
245
+ ? 'bg-white dark:bg-neutral-900 text-primary shadow-sm'
246
+ : 'text-neutral-500 dark:text-neutral-400 hover:text-neutral-950 dark:hover:text-white'
244
247
  }`}
245
248
  title="Card View"
246
249
  >
@@ -5,16 +5,16 @@
5
5
 
6
6
  'use client';
7
7
 
8
- import React from 'react';
9
8
  import { FileText, CheckCircle2, Clock, Archive } from 'lucide-react';
10
- import { PostListItem, PostStatus } from '../../types/post';
9
+ import { PostListItem } from '../../types/post';
10
+ import { ReactNode } from 'react';
11
11
 
12
12
  export interface PostStatsProps {
13
13
  posts: PostListItem[];
14
+ total: Number;
14
15
  }
15
16
 
16
- export function PostStats({ posts }: PostStatsProps) {
17
- const total = posts.length;
17
+ export function PostStats({ total, posts }: PostStatsProps) {
18
18
  const published = posts.filter(p => p.status === 'published').length;
19
19
  const drafts = posts.filter(p => p.status === 'draft').length;
20
20
  const scheduled = posts.filter(p => p.status === 'scheduled').length;
@@ -65,7 +65,7 @@ export function PostStats({ posts }: PostStatsProps) {
65
65
  </div>
66
66
  <div>
67
67
  <p className="text-2xl font-black text-neutral-950 dark:text-white">
68
- {stat.value}
68
+ {stat.value as ReactNode}
69
69
  </p>
70
70
  <p className="text-xs text-neutral-500 dark:text-neutral-400 uppercase tracking-wider">
71
71
  {stat.label}
@@ -56,7 +56,7 @@ export function PostTable({
56
56
  const { data: session, status: sessionStatus } = useSession();
57
57
  const currentUserId = (session?.user as any)?.id;
58
58
  const [userMap, setUserMap] = useState<Record<string, string>>({});
59
-
59
+
60
60
  // Helper function to check if user is the owner
61
61
  const isPostOwner = (post: PostListItem): boolean => {
62
62
  if (sessionStatus === 'loading') return false; // Don't show actions while loading
@@ -144,9 +144,11 @@ export function PostTable({
144
144
  </div>
145
145
  )}
146
146
  <div className="min-w-0 flex-1">
147
- <h3 className="font-bold text-neutral-950 dark:text-white mb-1 line-clamp-1">
148
- {post.title}
149
- </h3>
147
+ <button onClick={() => onEdit(post.id)} className='hover:cursor-pointer'>
148
+ <h3 className="font-bold hover:underline text-neutral-950 dark:text-white mb-1 line-clamp-1">
149
+ {post.title}
150
+ </h3>
151
+ </button>
150
152
  <p className="text-xs text-neutral-500 dark:text-neutral-400 font-mono">
151
153
  /{post.slug}
152
154
  </p>
package/src/api/README.md DELETED
@@ -1,224 +0,0 @@
1
- # Blog API
2
-
3
- RESTful API handlers for blog posts, included in the `@jhits/plugin-blog` package.
4
-
5
- ## Installation
6
-
7
- The API handlers are automatically available when you install `@jhits/plugin-blog`.
8
-
9
- ## Setup
10
-
11
- ### 1. Create API Route in Your App
12
-
13
- Create API routes in your Next.js app to mount the blog API handlers:
14
-
15
- **`src/app/api/blogs/route.ts`** - List and create blogs:
16
- ```typescript
17
- import { NextRequest } from 'next/server';
18
- import { GET, POST, createBlogApiConfig } from '@jhits/plugin-blog/api';
19
- import clientPromise from '@/lib/mongodb';
20
- import { cookies } from 'next/headers';
21
- import jwt from 'jsonwebtoken';
22
-
23
- const JWT_SECRET = process.env.JWT_SECRET || 'secret';
24
-
25
- async function getUserId(req: NextRequest): Promise<string | null> {
26
- try {
27
- const cookieStore = await cookies();
28
- const token = cookieStore.get('auth_token')?.value;
29
- if (!token) return null;
30
- const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
31
- return decoded.id;
32
- } catch {
33
- return null;
34
- }
35
- }
36
-
37
- const config = createBlogApiConfig({
38
- getDb: async () => clientPromise,
39
- getUserId,
40
- collectionName: 'blogs', // optional, defaults to 'blogs'
41
- });
42
-
43
- export async function GET(req: NextRequest) {
44
- return GET(req, undefined, config);
45
- }
46
-
47
- export async function POST(req: NextRequest) {
48
- return POST(req, undefined, config);
49
- }
50
- ```
51
-
52
- **`src/app/api/blogs/[slug]/route.ts`** - Get, update, and delete by slug:
53
- ```typescript
54
- import { NextRequest } from 'next/server';
55
- import { GET, PUT, DELETE, createBlogApiConfig } from '@jhits/plugin-blog/api';
56
- import clientPromise from '@/lib/mongodb';
57
- import { cookies } from 'next/headers';
58
- import jwt from 'jsonwebtoken';
59
-
60
- const JWT_SECRET = process.env.JWT_SECRET || 'secret';
61
-
62
- async function getUserId(req: NextRequest): Promise<string | null> {
63
- try {
64
- const cookieStore = await cookies();
65
- const token = cookieStore.get('auth_token')?.value;
66
- if (!token) return null;
67
- const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
68
- return decoded.id;
69
- } catch {
70
- return null;
71
- }
72
- }
73
-
74
- const config = createBlogApiConfig({
75
- getDb: async () => clientPromise,
76
- getUserId,
77
- collectionName: 'blogs',
78
- });
79
-
80
- export async function GET(
81
- req: NextRequest,
82
- { params }: { params: Promise<{ slug: string }> }
83
- ) {
84
- return GET(req, { params }, config);
85
- }
86
-
87
- export async function PUT(
88
- req: NextRequest,
89
- { params }: { params: Promise<{ slug: string }> }
90
- ) {
91
- return PUT(req, { params }, config);
92
- }
93
-
94
- export async function DELETE(
95
- req: NextRequest,
96
- { params }: { params: Promise<{ slug: string }> }
97
- ) {
98
- return DELETE(req, { params }, config);
99
- }
100
- ```
101
-
102
- ## API Endpoints
103
-
104
- ### GET /api/blogs
105
- List all blog posts.
106
-
107
- **Query Parameters:**
108
- - `limit` (number, default: 10) - Number of posts to return
109
- - `skip` (number, default: 0) - Number of posts to skip
110
- - `status` (string, optional) - Filter by status (`published`, `concept`, `draft`)
111
- - `admin` (boolean, default: false) - If true, returns all posts for authenticated user (including drafts)
112
-
113
- **Response:**
114
- ```json
115
- {
116
- "blogs": [...],
117
- "total": 100
118
- }
119
- ```
120
-
121
- ### POST /api/blogs
122
- Create a new blog post.
123
-
124
- **Request Body:**
125
- ```json
126
- {
127
- "title": "My Blog Post",
128
- "summary": "A brief summary",
129
- "contentBlocks": [...],
130
- "image": {
131
- "src": "/uploads/image.jpg",
132
- "alt": "Image alt text",
133
- "brightness": 100,
134
- "blur": 0
135
- },
136
- "categoryTags": {
137
- "category": "Technology",
138
- "tags": ["web", "development"]
139
- },
140
- "publicationData": {
141
- "status": "concept" | "published",
142
- "date": "2024-01-01T00:00:00.000Z"
143
- },
144
- "seo": {
145
- "title": "SEO Title",
146
- "description": "SEO Description",
147
- "keywords": ["keyword1", "keyword2"],
148
- "ogImage": "/uploads/og-image.jpg",
149
- "canonicalUrl": "https://example.com/blog/my-post"
150
- }
151
- }
152
- ```
153
-
154
- **Response:**
155
- ```json
156
- {
157
- "message": "Draft saved successfully",
158
- "blogId": "...",
159
- "slug": "my-blog-post-draft-1234"
160
- }
161
- ```
162
-
163
- ### GET /api/blogs/[slug]
164
- Get a single blog post by slug.
165
-
166
- **Response:**
167
- ```json
168
- {
169
- "_id": "...",
170
- "title": "My Blog Post",
171
- "slug": "my-blog-post",
172
- "contentBlocks": [...],
173
- ...
174
- }
175
- ```
176
-
177
- ### PUT /api/blogs/[slug]
178
- Update a blog post by slug.
179
-
180
- **Request Body:** Same as POST /api/blogs
181
-
182
- **Response:**
183
- ```json
184
- {
185
- "message": "Blog updated successfully",
186
- "slug": "updated-slug"
187
- }
188
- ```
189
-
190
- ### DELETE /api/blogs/[slug]
191
- Delete a blog post by slug.
192
-
193
- **Response:**
194
- ```json
195
- {
196
- "message": "Blog deleted successfully"
197
- }
198
- ```
199
-
200
- ## Security
201
-
202
- - All endpoints (except public GET) require authentication
203
- - Users can only access/modify their own posts
204
- - Published posts are visible to everyone
205
- - Drafts/concepts are only visible to the author
206
-
207
- ## Error Responses
208
-
209
- All endpoints return standard HTTP status codes:
210
- - `200` - Success
211
- - `400` - Bad Request (validation errors)
212
- - `401` - Unauthorized (not authenticated)
213
- - `403` - Forbidden (not authorized)
214
- - `404` - Not Found
215
- - `500` - Internal Server Error
216
-
217
- Error response format:
218
- ```json
219
- {
220
- "error": "Error message",
221
- "detail": "Detailed error information"
222
- }
223
- ```
224
-
@@ -1,91 +0,0 @@
1
- # Blog Plugin Hooks
2
-
3
- React hooks for fetching blog data in client applications.
4
-
5
- ## useBlogs
6
-
7
- Fetch a list of blog posts with automatic loading and error handling.
8
-
9
- ### Usage
10
-
11
- ```tsx
12
- import { useBlogs } from '@jhits/plugin-blog';
13
-
14
- function MyComponent() {
15
- const { blogs, loading, error, total, refetch } = useBlogs({
16
- limit: 10,
17
- skip: 0,
18
- status: 'published',
19
- admin: false,
20
- apiBaseUrl: '/api/blogs',
21
- });
22
-
23
- if (loading) return <div>Loading...</div>;
24
- if (error) return <div>Error: {error}</div>;
25
-
26
- return (
27
- <div>
28
- {blogs.map(blog => (
29
- <div key={blog.id}>
30
- <h2>{blog.title}</h2>
31
- <p>{blog.excerpt}</p>
32
- </div>
33
- ))}
34
- </div>
35
- );
36
- }
37
- ```
38
-
39
- ### Options
40
-
41
- - `limit` (number, default: 10): Maximum number of posts to fetch
42
- - `skip` (number, default: 0): Number of posts to skip (for pagination)
43
- - `status` (string, optional): Filter by status ('published', 'draft', 'concept')
44
- - `admin` (boolean, default: false): Whether to fetch all posts for admin (includes drafts)
45
- - `apiBaseUrl` (string, default: '/api/blogs'): API base URL
46
-
47
- ### Returns
48
-
49
- - `blogs` (PostListItem[]): Array of blog posts
50
- - `loading` (boolean): Whether data is currently loading
51
- - `error` (string | null): Error message if fetch failed
52
- - `total` (number): Total number of posts available
53
- - `refetch` (function): Function to manually refetch blogs
54
-
55
- ## useBlog
56
-
57
- Fetch a single blog post by slug.
58
-
59
- ### Usage
60
-
61
- ```tsx
62
- import { useBlog } from '@jhits/plugin-blog';
63
-
64
- function BlogPost({ slug }: { slug: string }) {
65
- const { blog, loading, error, refetch } = useBlog({ slug });
66
-
67
- if (loading) return <div>Loading...</div>;
68
- if (error) return <div>Error: {error}</div>;
69
- if (!blog) return <div>Post not found</div>;
70
-
71
- return (
72
- <article>
73
- <h1>{blog.title}</h1>
74
- <div>{/* Render blog content */}</div>
75
- </article>
76
- );
77
- }
78
- ```
79
-
80
- ### Options
81
-
82
- - `slug` (string, required): Blog post slug
83
- - `apiBaseUrl` (string, default: '/api/blogs'): API base URL
84
-
85
- ### Returns
86
-
87
- - `blog` (BlogPost | null): Blog post data
88
- - `loading` (boolean): Whether data is currently loading
89
- - `error` (string | null): Error message if fetch failed
90
- - `refetch` (function): Function to manually refetch the blog post
91
-
@@ -1,75 +0,0 @@
1
- # Blog Plugin Client Utilities
2
-
3
- Helper functions for fetching blog data in client applications (non-React).
4
-
5
- ## fetchBlogs
6
-
7
- Fetch blog posts from the API (returns a Promise).
8
-
9
- ### Usage
10
-
11
- ```ts
12
- import { fetchBlogs } from '@jhits/plugin-blog';
13
-
14
- async function loadBlogs() {
15
- try {
16
- const { blogs, total } = await fetchBlogs({
17
- limit: 10,
18
- skip: 0,
19
- status: 'published',
20
- admin: false,
21
- apiBaseUrl: '/api/blogs',
22
- });
23
-
24
- console.log(`Found ${total} posts`);
25
- blogs.forEach(blog => {
26
- console.log(blog.title);
27
- });
28
- } catch (error) {
29
- console.error('Failed to fetch blogs:', error);
30
- }
31
- }
32
- ```
33
-
34
- ### Options
35
-
36
- - `limit` (number, default: 10): Maximum number of posts to fetch
37
- - `skip` (number, default: 0): Number of posts to skip (for pagination)
38
- - `status` (string, optional): Filter by status ('published', 'draft', 'concept')
39
- - `admin` (boolean, default: false): Whether to fetch all posts for admin (includes drafts)
40
- - `apiBaseUrl` (string, default: '/api/blogs'): API base URL
41
-
42
- ### Returns
43
-
44
- Promise resolving to:
45
- - `blogs` (PostListItem[]): Array of blog posts
46
- - `total` (number): Total number of posts available
47
-
48
- ## fetchBlog
49
-
50
- Fetch a single blog post by slug (returns a Promise).
51
-
52
- ### Usage
53
-
54
- ```ts
55
- import { fetchBlog } from '@jhits/plugin-blog';
56
-
57
- async function loadBlogPost(slug: string) {
58
- try {
59
- const blog = await fetchBlog({ slug });
60
- console.log(blog.title);
61
- } catch (error) {
62
- console.error('Failed to fetch blog:', error);
63
- }
64
- }
65
- ```
66
-
67
- ### Options
68
-
69
- - `slug` (string, required): Blog post slug
70
- - `apiBaseUrl` (string, default: '/api/blogs'): API base URL
71
-
72
- ### Returns
73
-
74
- Promise resolving to a `BlogPost` object.
75
-
@@ -1,82 +0,0 @@
1
- # Blog Plugin Views
2
-
3
- ## Directory Structure
4
-
5
- ```
6
- src/views/
7
- ├── PostManager/
8
- │ ├── PostManagerView.tsx # Professional table layout for post management
9
- │ └── index.ts
10
- ├── CanvasEditor/
11
- │ ├── CanvasEditorView.tsx # Drag-and-drop block editor workspace
12
- │ └── index.ts
13
- ├── SlugSEO/
14
- │ ├── SlugSEOManagerView.tsx # URL slug and SEO metadata management
15
- │ └── index.ts
16
- └── Preview/
17
- ├── PreviewBridgeView.tsx # Live preview of blog posts
18
- └── index.ts
19
- ```
20
-
21
- ## Design System Compliance
22
-
23
- All views follow the dashboard's earth-tone design system:
24
-
25
- - **Colors**: Neutral palette (neutral-100 to neutral-900), primary (violet-700)
26
- - **Typography**:
27
- - Headings: `font-black uppercase tracking-tighter`
28
- - Labels: `text-[10px] font-black uppercase tracking-widest`
29
- - Body: `font-sans`
30
- - **Layout**:
31
- - Containers: `rounded-[2.5rem]`
32
- - Padding: `p-8`
33
- - Backgrounds: `bg-white dark:bg-neutral-900`
34
- - **Buttons**: `rounded-full`, uppercase, tracking-widest
35
- - **Borders**: `border-neutral-300 dark:border-neutral-700`
36
-
37
- ## PostManagerView
38
-
39
- Professional table layout with:
40
- - Search and filter functionality
41
- - Status badges (published, draft, scheduled, archived)
42
- - Empty state with "Create Your First Post" CTA
43
- - Action buttons (Edit, Preview, Delete)
44
- - Mock data for development
45
-
46
- ## CanvasEditorView
47
-
48
- Block editor workspace with:
49
- - Toolbar with Back, Preview, and Save buttons
50
- - Title input field
51
- - Block canvas area
52
- - Empty state for adding first block
53
- - Integrated with EditorProvider for state management
54
-
55
- ## SlugSEOManagerView
56
-
57
- SEO management interface with:
58
- - URL slug editor with collision detection placeholder
59
- - Google-style search result preview
60
- - Meta description editor with character counter
61
-
62
- ## PreviewBridgeView
63
-
64
- Live preview interface with:
65
- - Header with Refresh and Open in New Tab buttons
66
- - Preview container (ready for iframe or side-by-side panel)
67
- - Placeholder for preview rendering
68
-
69
- ## Usage
70
-
71
- Views are imported and used in the main `BlogPlugin` router:
72
-
73
- ```typescript
74
- import { PostManagerView } from './views/PostManager';
75
- import { CanvasEditorView } from './views/CanvasEditor';
76
- // ... etc
77
-
78
- // In router switch statement:
79
- case 'posts':
80
- return <PostManagerView siteId={siteId} locale={locale} />;
81
- ```
82
-