@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 CHANGED
@@ -1,60 +1,57 @@
1
1
  {
2
- "name": "@jhits/plugin-blog",
3
- "version": "0.0.4",
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/**/*.{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
+ }
@@ -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') {
@@ -4,5 +4,6 @@
4
4
  */
5
5
 
6
6
  export * from './client';
7
- export * from '../lib/utils/blockHelpers';
7
+ export * from './blockHelpers';
8
+ export * from './slugify';
8
9
 
@@ -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>