@jhits/plugin-blog 0.0.5 → 0.0.7
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 +16 -16
- package/src/api/config-handler.ts +76 -0
- package/src/api/handler.ts +4 -4
- package/src/api/router.ts +17 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useCategories.ts +76 -0
- package/src/index.tsx +8 -27
- package/src/init.tsx +0 -9
- package/src/lib/config-storage.ts +65 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx +177 -13
- package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
- package/src/lib/layouts/registerLayoutBlocks.ts +6 -1
- package/src/lib/mappers/apiMapper.ts +53 -22
- package/src/registry/BlockRegistry.ts +1 -4
- package/src/state/EditorContext.tsx +39 -33
- package/src/state/types.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/post.ts +4 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +87 -24
- package/src/views/CanvasEditor/CanvasEditorView.tsx +214 -794
- package/src/views/CanvasEditor/EditorBody.tsx +317 -127
- package/src/views/CanvasEditor/EditorHeader.tsx +106 -17
- package/src/views/CanvasEditor/LayoutContainer.tsx +208 -380
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
- package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +260 -49
- package/src/views/CanvasEditor/components/index.ts +11 -0
- package/src/views/CanvasEditor/hooks/index.ts +10 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
- package/src/views/PostManager/PostCards.tsx +18 -13
- package/src/views/PostManager/PostFilters.tsx +15 -0
- package/src/views/PostManager/PostManagerView.tsx +21 -15
- package/src/views/PostManager/PostTable.tsx +7 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jhits/plugin-blog",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Professional blog management system for the JHITS ecosystem",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@jhits/plugin-core": "^0.0.1",
|
|
22
|
-
"@jhits/plugin-content": "^0.0.1",
|
|
23
|
-
"@jhits/plugin-images": "^0.0.1",
|
|
24
21
|
"bcrypt": "^6.0.0",
|
|
25
|
-
"framer-motion": "^12.
|
|
22
|
+
"framer-motion": "^12.27.5",
|
|
26
23
|
"lucide-react": "^0.562.0",
|
|
27
24
|
"mongodb": "^7.0.0",
|
|
28
|
-
"next-auth": "^4.24.13"
|
|
25
|
+
"next-auth": "^4.24.13",
|
|
26
|
+
"@jhits/plugin-content": "0.0.3",
|
|
27
|
+
"@jhits/plugin-core": "0.0.1",
|
|
28
|
+
"@jhits/plugin-images": "0.0.5"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"next": ">=15.0.0",
|
|
@@ -35,20 +35,20 @@
|
|
|
35
35
|
"react-dom": ">=18.0.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@tailwindcss/postcss": "^4",
|
|
38
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
39
39
|
"@types/bcrypt": "^6.0.0",
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"@types/react": "^19",
|
|
42
|
-
"@types/react-dom": "^19",
|
|
43
|
-
"eslint": "^9",
|
|
44
|
-
"eslint-config-next": "16.1.
|
|
45
|
-
"next": "16.1.
|
|
46
|
-
"next-intl": "4.
|
|
40
|
+
"@types/node": "^25.0.9",
|
|
41
|
+
"@types/react": "^19.2.9",
|
|
42
|
+
"@types/react-dom": "^19.2.3",
|
|
43
|
+
"eslint": "^9.39.2",
|
|
44
|
+
"eslint-config-next": "16.1.4",
|
|
45
|
+
"next": "16.1.4",
|
|
46
|
+
"next-intl": "4.7.0",
|
|
47
47
|
"next-themes": "0.4.6",
|
|
48
48
|
"react": "19.2.3",
|
|
49
49
|
"react-dom": "19.2.3",
|
|
50
|
-
"tailwindcss": "^4",
|
|
51
|
-
"typescript": "^5"
|
|
50
|
+
"tailwindcss": "^4.1.18",
|
|
51
|
+
"typescript": "^5.9.3"
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
|
54
54
|
"src",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog Config API Handler
|
|
3
|
+
* Handles saving/loading plugin configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
7
|
+
import { getPluginConfig, savePluginConfig } from '../lib/config-storage';
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export interface ConfigApiConfig {
|
|
11
|
+
getDb: () => Promise<{ db: any }>;
|
|
12
|
+
getUserId: (req: NextRequest) => Promise<string | null>;
|
|
13
|
+
siteId?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* GET /api/plugin-blog/config - Get plugin config
|
|
18
|
+
*/
|
|
19
|
+
export async function GET(req: NextRequest, config: ConfigApiConfig): Promise<NextResponse> {
|
|
20
|
+
try {
|
|
21
|
+
const siteId = config.siteId || 'default';
|
|
22
|
+
|
|
23
|
+
const pluginConfig = await getPluginConfig(
|
|
24
|
+
config.getDb,
|
|
25
|
+
'plugin-blog',
|
|
26
|
+
siteId
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!pluginConfig) {
|
|
30
|
+
return NextResponse.json({ config: null });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return NextResponse.json({ config: pluginConfig.config });
|
|
34
|
+
} catch (err: any) {
|
|
35
|
+
console.error('[BlogConfigAPI] GET error:', err);
|
|
36
|
+
return NextResponse.json(
|
|
37
|
+
{ error: 'Failed to get config', detail: err.message },
|
|
38
|
+
{ status: 500 }
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* POST /api/plugin-blog/config - Save plugin config
|
|
45
|
+
*/
|
|
46
|
+
export async function POST(req: NextRequest, config: ConfigApiConfig): Promise<NextResponse> {
|
|
47
|
+
try {
|
|
48
|
+
const userId = await config.getUserId(req);
|
|
49
|
+
if (!userId) {
|
|
50
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const body = await req.json();
|
|
54
|
+
const siteId = body.siteId || config.siteId || 'default';
|
|
55
|
+
const pluginConfig = body.config;
|
|
56
|
+
|
|
57
|
+
if (!pluginConfig) {
|
|
58
|
+
return NextResponse.json({ error: 'Config is required' }, { status: 400 });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
await savePluginConfig(
|
|
62
|
+
config.getDb,
|
|
63
|
+
'plugin-blog',
|
|
64
|
+
siteId,
|
|
65
|
+
pluginConfig
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
return NextResponse.json({ message: 'Config saved successfully' });
|
|
69
|
+
} catch (err: any) {
|
|
70
|
+
console.error('[BlogConfigAPI] POST error:', err);
|
|
71
|
+
return NextResponse.json(
|
|
72
|
+
{ error: 'Failed to save config', detail: err.message },
|
|
73
|
+
{ status: 500 }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/api/handler.ts
CHANGED
|
@@ -124,7 +124,7 @@ export async function POST(req: NextRequest, config: BlogApiConfig): Promise<Nex
|
|
|
124
124
|
if (isPublishing) {
|
|
125
125
|
// Publishing requires all fields
|
|
126
126
|
if (!summary?.trim()) errors.push('Summary is required for publishing');
|
|
127
|
-
if (!image?.
|
|
127
|
+
if (!image?.id?.trim()) errors.push('Featured image is required for publishing');
|
|
128
128
|
// Only require category if it's explicitly provided and empty
|
|
129
129
|
// If categoryTags is undefined or category is undefined, that's also missing
|
|
130
130
|
if (!categoryTags || !categoryTags.category || !categoryTags.category.trim()) {
|
|
@@ -168,7 +168,7 @@ export async function POST(req: NextRequest, config: BlogApiConfig): Promise<Nex
|
|
|
168
168
|
summary: (summary || '').trim(),
|
|
169
169
|
contentBlocks: contentBlocks || [],
|
|
170
170
|
content: content || [],
|
|
171
|
-
image: image || {
|
|
171
|
+
image: image || { id: '', alt: '' }, // Only store id and alt - plugin-images handles transforms
|
|
172
172
|
categoryTags: {
|
|
173
173
|
category: categoryTags?.category?.trim() || '',
|
|
174
174
|
tags: categoryTags?.tags || [],
|
|
@@ -295,7 +295,7 @@ export async function PUT_BY_SLUG(
|
|
|
295
295
|
// Collect all missing fields for better error messages
|
|
296
296
|
const missingFields: string[] = [];
|
|
297
297
|
if (!summary?.trim()) missingFields.push('summary');
|
|
298
|
-
if (!image?.
|
|
298
|
+
if (!image?.id?.trim()) missingFields.push('featured image');
|
|
299
299
|
// Only require category if it's explicitly provided and empty
|
|
300
300
|
// If categoryTags is undefined or category is undefined, that's also missing
|
|
301
301
|
if (!categoryTags || !categoryTags.category || !categoryTags.category.trim()) {
|
|
@@ -308,7 +308,7 @@ export async function PUT_BY_SLUG(
|
|
|
308
308
|
isPublishing,
|
|
309
309
|
missingFields,
|
|
310
310
|
summary: summary?.trim() || 'missing',
|
|
311
|
-
|
|
311
|
+
imageId: image?.id?.trim() || 'missing',
|
|
312
312
|
category: categoryTags?.category?.trim() || 'missing',
|
|
313
313
|
hasContent,
|
|
314
314
|
contentBlocksLength: contentBlocks?.length || 0,
|
package/src/api/router.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { NextRequest, NextResponse } from 'next/server';
|
|
12
12
|
import { GET as BlogListHandler, POST as BlogCreateHandler } from './handler';
|
|
13
13
|
import { GET as BlogGetHandler, PUT as BlogUpdateHandler, DELETE as BlogDeleteHandler, createBlogApiConfig } from './route';
|
|
14
|
+
import { GET as ConfigGetHandler, POST as ConfigPostHandler } from './config-handler';
|
|
14
15
|
|
|
15
16
|
export interface BlogApiRouterConfig {
|
|
16
17
|
/** MongoDB client promise - should return { db: () => Database } */
|
|
@@ -19,6 +20,8 @@ export interface BlogApiRouterConfig {
|
|
|
19
20
|
getUserId: (req: NextRequest) => Promise<string | null>;
|
|
20
21
|
/** Collection name (default: 'blogs') */
|
|
21
22
|
collectionName?: string;
|
|
23
|
+
/** Site ID for multi-site setups */
|
|
24
|
+
siteId?: string;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
/**
|
|
@@ -81,6 +84,20 @@ export async function handleBlogApi(
|
|
|
81
84
|
return await checkTitleModule.GET(req, blogApiConfig);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
87
|
+
// Route: /api/plugin-blog/config (get/save plugin config)
|
|
88
|
+
else if (route === 'config') {
|
|
89
|
+
const configApiConfig = {
|
|
90
|
+
getDb: config.getDb,
|
|
91
|
+
getUserId: config.getUserId,
|
|
92
|
+
siteId: config.siteId || 'default',
|
|
93
|
+
};
|
|
94
|
+
if (method === 'GET') {
|
|
95
|
+
return await ConfigGetHandler(req, configApiConfig);
|
|
96
|
+
}
|
|
97
|
+
if (method === 'POST') {
|
|
98
|
+
return await ConfigPostHandler(req, configApiConfig);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
84
101
|
// Route: /api/plugin-blog/[slug] (get/update/delete by slug)
|
|
85
102
|
else {
|
|
86
103
|
const slug = route;
|
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to fetch categories from existing blog posts
|
|
3
|
+
* Extracts categories from Hero blocks in all posts
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import { useState, useEffect } from 'react';
|
|
9
|
+
|
|
10
|
+
export function useCategories() {
|
|
11
|
+
const [categories, setCategories] = useState<string[]>([]);
|
|
12
|
+
const [loading, setLoading] = useState(true);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const fetchCategories = async () => {
|
|
16
|
+
try {
|
|
17
|
+
const categorySet = new Set<string>();
|
|
18
|
+
|
|
19
|
+
// 1. Fetch from categories endpoint (legacy categoryTags.category)
|
|
20
|
+
try {
|
|
21
|
+
const categoriesResponse = await fetch('/api/plugin-blog/categories');
|
|
22
|
+
if (categoriesResponse.ok) {
|
|
23
|
+
const categoriesData = await categoriesResponse.json();
|
|
24
|
+
if (Array.isArray(categoriesData.categories)) {
|
|
25
|
+
categoriesData.categories.forEach((cat: string) => {
|
|
26
|
+
if (cat && cat.trim()) {
|
|
27
|
+
categorySet.add(cat.trim());
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch (e) {
|
|
33
|
+
// Categories endpoint not available, continue
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Fetch all blog posts and extract categories from Hero blocks
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetch('/api/plugin-blog?admin=true&limit=1000');
|
|
39
|
+
if (response.ok) {
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
const posts = Array.isArray(data.blogs) ? data.blogs : (Array.isArray(data) ? data : []);
|
|
42
|
+
|
|
43
|
+
posts.forEach((post: any) => {
|
|
44
|
+
if (post.blocks && Array.isArray(post.blocks)) {
|
|
45
|
+
// Find Hero block
|
|
46
|
+
const heroBlock = post.blocks.find((block: any) => block.type === 'hero');
|
|
47
|
+
if (heroBlock && heroBlock.data && heroBlock.data.category) {
|
|
48
|
+
const category = heroBlock.data.category.trim();
|
|
49
|
+
if (category) {
|
|
50
|
+
categorySet.add(category);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error('Failed to fetch posts for categories:', e);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Convert to sorted array
|
|
61
|
+
const sortedCategories = Array.from(categorySet).sort();
|
|
62
|
+
setCategories(sortedCategories);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Failed to fetch categories:', error);
|
|
65
|
+
// Fallback to empty array
|
|
66
|
+
setCategories([]);
|
|
67
|
+
} finally {
|
|
68
|
+
setLoading(false);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
fetchCategories();
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
return { categories, loading };
|
|
76
|
+
}
|
package/src/index.tsx
CHANGED
|
@@ -49,7 +49,6 @@ export interface PluginProps {
|
|
|
49
49
|
*/
|
|
50
50
|
export default function BlogPlugin(props: PluginProps) {
|
|
51
51
|
const { subPath, siteId, locale, customBlocks: propsCustomBlocks, darkMode: propsDarkMode, backgroundColors: propsBackgroundColors } = props;
|
|
52
|
-
console.log('[BlogPlugin] Component rendering, propsDarkMode:', propsDarkMode);
|
|
53
52
|
|
|
54
53
|
// Get custom blocks from props or window global (client app injection point)
|
|
55
54
|
const customBlocks = useMemo(() => {
|
|
@@ -79,7 +78,6 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
79
78
|
if (saved) {
|
|
80
79
|
const config = JSON.parse(saved);
|
|
81
80
|
if (config.darkMode !== undefined) {
|
|
82
|
-
console.log('[BlogPlugin] Using darkMode from localStorage (dev settings):', config.darkMode);
|
|
83
81
|
return config.darkMode as boolean;
|
|
84
82
|
}
|
|
85
83
|
}
|
|
@@ -90,7 +88,6 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
90
88
|
|
|
91
89
|
// Then try props
|
|
92
90
|
if (propsDarkMode !== undefined) {
|
|
93
|
-
console.log('[BlogPlugin] Using darkMode from props:', propsDarkMode);
|
|
94
91
|
return propsDarkMode;
|
|
95
92
|
}
|
|
96
93
|
|
|
@@ -98,12 +95,10 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
98
95
|
if (typeof window !== 'undefined' && (window as any).__JHITS_PLUGIN_PROPS__) {
|
|
99
96
|
const pluginProps = (window as any).__JHITS_PLUGIN_PROPS__['plugin-blog'];
|
|
100
97
|
if (pluginProps?.darkMode !== undefined) {
|
|
101
|
-
console.log('[BlogPlugin] Using darkMode from window global (fallback):', pluginProps.darkMode);
|
|
102
98
|
return pluginProps.darkMode as boolean;
|
|
103
99
|
}
|
|
104
100
|
}
|
|
105
101
|
|
|
106
|
-
console.log('[BlogPlugin] darkMode not found, defaulting to true');
|
|
107
102
|
return true; // Default to dark mode enabled
|
|
108
103
|
}, [propsDarkMode]);
|
|
109
104
|
|
|
@@ -167,8 +162,6 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
167
162
|
};
|
|
168
163
|
}, []);
|
|
169
164
|
|
|
170
|
-
console.log('[BlogPlugin] Final darkMode value:', darkMode);
|
|
171
|
-
console.log('[BlogPlugin] Background colors:', backgroundColors);
|
|
172
165
|
if (heroBlockDefinition !== undefined) {
|
|
173
166
|
console.log('[BlogPlugin] Hero block definition:', heroBlockDefinition ? 'found' : 'not found (REQUIRED)');
|
|
174
167
|
}
|
|
@@ -180,13 +173,12 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
180
173
|
|
|
181
174
|
case 'editor':
|
|
182
175
|
const postId = subPath[1]; // This is actually the slug, not the ID
|
|
183
|
-
console.log('[BlogPlugin] Rendering editor route with postId (slug):', postId, 'darkMode:', darkMode);
|
|
184
176
|
return (
|
|
185
177
|
<EditorProvider
|
|
186
178
|
customBlocks={customBlocks}
|
|
187
179
|
darkMode={darkMode}
|
|
188
180
|
backgroundColors={backgroundColors}
|
|
189
|
-
onSave={async (state) => {
|
|
181
|
+
onSave={async (state, heroBlock) => {
|
|
190
182
|
// Save to API - update existing post
|
|
191
183
|
// Use the route postId (original slug) to identify which blog to update
|
|
192
184
|
// If route postId is missing, use state.slug or state.postId as fallback
|
|
@@ -195,22 +187,7 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
195
187
|
throw new Error('Cannot save: no post identifier available. Please reload the page.');
|
|
196
188
|
}
|
|
197
189
|
console.log('[BlogPlugin] Saving post with slug:', originalSlug);
|
|
198
|
-
|
|
199
|
-
title: state.title,
|
|
200
|
-
status: state.status,
|
|
201
|
-
blocksCount: state.blocks.length,
|
|
202
|
-
blocks: state.blocks.map(b => ({ id: b.id, type: b.type, hasData: !!b.data })),
|
|
203
|
-
hasFeaturedImage: !!state.metadata.featuredImage,
|
|
204
|
-
});
|
|
205
|
-
const apiData = editorStateToAPI(state);
|
|
206
|
-
console.log('[BlogPlugin] API data being sent:', {
|
|
207
|
-
title: apiData.title,
|
|
208
|
-
status: apiData.publicationData?.status,
|
|
209
|
-
contentBlocksCount: apiData.contentBlocks?.length || 0,
|
|
210
|
-
contentBlocks: apiData.contentBlocks?.map((b: any) => ({ id: b.id, type: b.type, hasData: !!b.data })) || [],
|
|
211
|
-
hasImage: !!apiData.image,
|
|
212
|
-
fullApiData: JSON.stringify(apiData, null, 2),
|
|
213
|
-
});
|
|
190
|
+
const apiData = editorStateToAPI(state, undefined, heroBlock);
|
|
214
191
|
const response = await fetch(`/api/plugin-blog/${originalSlug}`, {
|
|
215
192
|
method: 'PUT',
|
|
216
193
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -233,7 +210,6 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
233
210
|
throw new Error(errorMessage + missingFieldsMsg);
|
|
234
211
|
}
|
|
235
212
|
const result = await response.json();
|
|
236
|
-
console.log('[BlogPlugin] Save successful:', result);
|
|
237
213
|
// If the slug changed, update the URL
|
|
238
214
|
if (result.slug && result.slug !== originalSlug) {
|
|
239
215
|
window.history.replaceState(null, '', `/dashboard/blog/editor/${result.slug}`);
|
|
@@ -246,7 +222,6 @@ export default function BlogPlugin(props: PluginProps) {
|
|
|
246
222
|
);
|
|
247
223
|
|
|
248
224
|
case 'new':
|
|
249
|
-
console.log('[BlogPlugin] Rendering new route with darkMode:', darkMode);
|
|
250
225
|
return (
|
|
251
226
|
<EditorProvider
|
|
252
227
|
customBlocks={customBlocks}
|
|
@@ -345,10 +320,16 @@ export { blockRegistry } from './registry';
|
|
|
345
320
|
// Export layout block registration
|
|
346
321
|
export { registerLayoutBlocks } from './lib/layouts/registerLayoutBlocks';
|
|
347
322
|
|
|
323
|
+
// Export config storage utilities
|
|
324
|
+
export { getPluginConfig, savePluginConfig } from './lib/config-storage';
|
|
325
|
+
|
|
348
326
|
// Export editor state management
|
|
349
327
|
export { EditorProvider, useEditor } from './state/EditorContext';
|
|
350
328
|
export type { EditorProviderProps, EditorState, EditorContextValue } from './state';
|
|
351
329
|
|
|
330
|
+
// Export hooks
|
|
331
|
+
export { useCategories } from './hooks/useCategories';
|
|
332
|
+
|
|
352
333
|
// Note: API handlers are server-only and exported from ./index.ts (server entry point)
|
|
353
334
|
// They are NOT exported here to prevent client/server context mixing
|
|
354
335
|
|
package/src/init.tsx
CHANGED
|
@@ -59,14 +59,5 @@ export function initBlogPlugin(config: BlogPluginConfig): void {
|
|
|
59
59
|
darkMode: config.darkMode !== undefined ? config.darkMode : true, // Default to true
|
|
60
60
|
backgroundColors: config.backgroundColors || undefined,
|
|
61
61
|
};
|
|
62
|
-
|
|
63
|
-
console.log('[BlogPlugin] Initialized with config:', {
|
|
64
|
-
customBlocks: config.customBlocks?.length || 0,
|
|
65
|
-
darkMode: config.darkMode !== undefined ? config.darkMode : true,
|
|
66
|
-
backgroundColors: config.backgroundColors ? {
|
|
67
|
-
light: config.backgroundColors.light,
|
|
68
|
-
dark: config.backgroundColors.dark || 'not set',
|
|
69
|
-
} : 'not set',
|
|
70
|
-
});
|
|
71
62
|
}
|
|
72
63
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Configuration Storage
|
|
3
|
+
* Stores plugin-specific configuration in MongoDB
|
|
4
|
+
* Used by both client apps and dashboard
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface PluginConfigDocument {
|
|
8
|
+
_id?: string;
|
|
9
|
+
pluginId: string;
|
|
10
|
+
siteId: string;
|
|
11
|
+
config: {
|
|
12
|
+
customBlocks?: unknown[];
|
|
13
|
+
darkMode?: boolean;
|
|
14
|
+
backgroundColors?: {
|
|
15
|
+
light: string;
|
|
16
|
+
dark?: string;
|
|
17
|
+
};
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
};
|
|
20
|
+
updatedAt: Date;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const COLLECTION_NAME = 'pluginConfigs';
|
|
24
|
+
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
export async function getPluginConfigCollection(getDb: () => Promise<{ db: any }>) {
|
|
27
|
+
const { db } = await getDb();
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
return (db as any).collection(COLLECTION_NAME);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
export async function getPluginConfig(
|
|
34
|
+
getDb: () => Promise<{ db: any }>,
|
|
35
|
+
pluginId: string,
|
|
36
|
+
siteId: string = 'default'
|
|
37
|
+
): Promise<PluginConfigDocument | null> {
|
|
38
|
+
const collection = await getPluginConfigCollection(getDb);
|
|
39
|
+
return collection.findOne({ pluginId, siteId }) as Promise<PluginConfigDocument | null>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
export async function savePluginConfig(
|
|
44
|
+
getDb: () => Promise<{ db: any }>,
|
|
45
|
+
pluginId: string,
|
|
46
|
+
siteId: string,
|
|
47
|
+
config: PluginConfigDocument['config']
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
const collection = await getPluginConfigCollection(getDb);
|
|
50
|
+
|
|
51
|
+
await collection.updateOne(
|
|
52
|
+
{ pluginId, siteId },
|
|
53
|
+
{
|
|
54
|
+
$set: {
|
|
55
|
+
config,
|
|
56
|
+
updatedAt: new Date()
|
|
57
|
+
},
|
|
58
|
+
$setOnInsert: {
|
|
59
|
+
pluginId,
|
|
60
|
+
siteId
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
{ upsert: true }
|
|
64
|
+
);
|
|
65
|
+
}
|