@jhits/plugin-blog 0.0.4 → 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 +55 -58
- package/src/api/handler.ts +6 -7
- package/src/api/router.ts +1 -4
- package/src/utils/index.ts +2 -1
- package/src/views/PostManager/PostActionsMenu.tsx +1 -1
- package/src/views/PostManager/PostManagerView.tsx +8 -5
- package/src/views/PostManager/PostStats.tsx +5 -5
- package/src/views/PostManager/PostTable.tsx +6 -4
package/package.json
CHANGED
|
@@ -1,60 +1,57 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"typescript": "^5"
|
|
52
|
-
},
|
|
53
|
-
"files": [
|
|
54
|
-
"src/**/*.{ts,tsx,json}",
|
|
55
|
-
"!src/**/*.md",
|
|
56
|
-
"!src/**/README.md",
|
|
57
|
-
"!README.md",
|
|
58
|
-
"package.json"
|
|
59
|
-
]
|
|
60
|
-
}
|
|
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
|
+
}
|
package/src/api/handler.ts
CHANGED
|
@@ -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') {
|
package/src/utils/index.ts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
233
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
|
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
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
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>
|