@jhits/plugin-blog 0.0.19 → 0.0.20
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/dist/api/categories.d.ts.map +1 -1
- package/dist/api/categories.js +42 -38
- package/dist/api/handler.d.ts +1 -26
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +81 -490
- package/dist/api/router.d.ts +0 -5
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +8 -35
- package/dist/api/service.d.ts +80 -0
- package/dist/api/service.d.ts.map +1 -0
- package/dist/api/service.js +219 -0
- package/dist/hooks/useAutoSave.d.ts +10 -0
- package/dist/hooks/useAutoSave.d.ts.map +1 -0
- package/dist/hooks/useAutoSave.js +57 -0
- package/dist/hooks/useCategories.d.ts +1 -1
- package/dist/hooks/useCategories.d.ts.map +1 -1
- package/dist/hooks/useCategories.js +15 -46
- package/dist/index.d.ts +24 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -201
- package/dist/init.d.ts +20 -7
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +8 -7
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/ColumnsBlock.js +30 -113
- package/dist/lib/layouts/blocks/SectionBlock.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/SectionBlock.js +9 -21
- package/dist/lib/layouts/index.d.ts +3 -3
- package/dist/lib/layouts/index.js +4 -4
- package/dist/lib/mappers/apiMapper.d.ts +10 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
- package/dist/lib/mappers/apiMapper.js +47 -32
- package/dist/lib/rich-text/RichTextEditor.d.ts +4 -2
- package/dist/lib/rich-text/RichTextEditor.d.ts.map +1 -1
- package/dist/lib/rich-text/RichTextEditor.js +12 -9
- package/dist/lib/utils/config-resolver.d.ts +28 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +46 -0
- package/dist/lib/utils/tree.d.ts +29 -0
- package/dist/lib/utils/tree.d.ts.map +1 -0
- package/dist/lib/utils/tree.js +129 -0
- package/dist/state/EditorContext.d.ts +3 -25
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +124 -174
- package/dist/state/reducer.d.ts +1 -5
- package/dist/state/reducer.d.ts.map +1 -1
- package/dist/state/reducer.js +128 -521
- package/dist/state/types.d.ts +12 -1
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/post.d.ts +17 -1
- package/dist/types/post.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts +5 -6
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +56 -264
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +5 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +55 -315
- package/dist/views/CanvasEditor/EditorBody.d.ts +6 -8
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +34 -482
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +27 -63
- package/dist/views/CanvasEditor/LayoutContainer.d.ts.map +1 -1
- package/dist/views/CanvasEditor/LayoutContainer.js +49 -70
- package/dist/views/CanvasEditor/components/CustomBlockItem.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +15 -3
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +40 -18
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +5 -1
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorLibrary.js +11 -7
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +32 -14
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -6
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.js +17 -128
- package/dist/views/CanvasEditor/components/JSONInspector.d.ts +9 -0
- package/dist/views/CanvasEditor/components/JSONInspector.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/JSONInspector.js +56 -0
- package/dist/views/CanvasEditor/components/LibraryItem.js +2 -2
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -4
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.js +6 -28
- package/dist/views/CanvasEditor/components/index.d.ts +2 -0
- package/dist/views/CanvasEditor/components/index.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/index.js +1 -0
- package/dist/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useHeroBlock.js +15 -18
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +3 -0
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.js +12 -13
- package/dist/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -4
- package/dist/views/PostManager/EmptyState.d.ts +1 -1
- package/dist/views/PostManager/EmptyState.js +4 -4
- package/dist/views/PostManager/FilterDropdown.d.ts +21 -0
- package/dist/views/PostManager/FilterDropdown.d.ts.map +1 -0
- package/dist/views/PostManager/FilterDropdown.js +28 -0
- package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -1
- package/dist/views/PostManager/LanguageFlags.js +4 -1
- package/dist/views/PostManager/PostCards.d.ts.map +1 -1
- package/dist/views/PostManager/PostCards.js +23 -40
- package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
- package/dist/views/PostManager/PostFilters.js +34 -3
- package/dist/views/PostManager/PostManagerView.d.ts +1 -2
- package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
- package/dist/views/PostManager/PostManagerView.js +30 -96
- package/dist/views/PostManager/PostStats.d.ts.map +1 -1
- package/dist/views/PostManager/PostStats.js +10 -10
- package/dist/views/PostManager/PostTable.d.ts.map +1 -1
- package/dist/views/PostManager/PostTable.js +23 -40
- package/dist/views/Settings/SettingsView.d.ts +1 -1
- package/dist/views/Settings/SettingsView.d.ts.map +1 -1
- package/dist/views/Settings/SettingsView.js +12 -39
- package/dist/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -1
- package/dist/views/SlugSEO/SlugSEOManagerView.js +2 -2
- package/package.json +42 -6
- package/src/api/categories.ts +48 -52
- package/src/api/handler.ts +87 -594
- package/src/api/router.ts +15 -65
- package/src/api/service.ts +241 -0
- package/src/hooks/useAutoSave.ts +64 -0
- package/src/hooks/useCategories.ts +19 -47
- package/src/index.tsx +79 -293
- package/src/init.tsx +24 -11
- package/src/lib/blocks/BlockRenderer.tsx +1 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx +60 -173
- package/src/lib/layouts/blocks/SectionBlock.tsx +22 -26
- package/src/lib/layouts/index.ts +4 -4
- package/src/lib/mappers/apiMapper.ts +63 -32
- package/src/lib/rich-text/RichTextEditor.tsx +16 -9
- package/src/lib/utils/config-resolver.ts +64 -0
- package/src/lib/utils/tree.ts +150 -0
- package/src/state/EditorContext.tsx +153 -232
- package/src/state/reducer.ts +141 -606
- package/src/state/types.ts +14 -1
- package/src/types/block.ts +10 -0
- package/src/types/post.ts +19 -1
- package/src/views/CanvasEditor/BlockWrapper.tsx +130 -460
- package/src/views/CanvasEditor/CanvasEditorView.tsx +145 -420
- package/src/views/CanvasEditor/EditorBody.tsx +98 -610
- package/src/views/CanvasEditor/EditorHeader.tsx +176 -196
- package/src/views/CanvasEditor/LayoutContainer.tsx +74 -89
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +7 -8
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +139 -84
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +25 -10
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +196 -127
- package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +78 -210
- package/src/views/CanvasEditor/components/JSONInspector.tsx +125 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +5 -6
- package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +73 -124
- package/src/views/CanvasEditor/components/index.ts +2 -1
- package/src/views/CanvasEditor/hooks/useHeroBlock.ts +15 -18
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +21 -13
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +4 -4
- package/src/views/PostManager/EmptyState.tsx +9 -10
- package/src/views/PostManager/FilterDropdown.tsx +95 -0
- package/src/views/PostManager/LanguageFlags.tsx +6 -2
- package/src/views/PostManager/PostCards.tsx +127 -133
- package/src/views/PostManager/PostFilters.tsx +73 -68
- package/src/views/PostManager/PostManagerView.tsx +132 -179
- package/src/views/PostManager/PostStats.tsx +21 -20
- package/src/views/PostManager/PostTable.tsx +137 -165
- package/src/views/Settings/SettingsView.tsx +64 -180
- package/src/views/SlugSEO/SlugSEOManagerView.tsx +59 -44
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.d.ts.map +0 -1
- package/src/hooks/useBlog.d.ts +0 -31
- package/src/hooks/useBlog.d.ts.map +0 -1
- package/src/hooks/useBlogs.d.ts +0 -39
- package/src/hooks/useBlogs.d.ts.map +0 -1
- package/src/hooks/useCategories.d.ts +0 -9
- package/src/hooks/useCategories.d.ts.map +0 -1
- package/src/lib/blocks/BlockRenderer.d.ts +0 -54
- package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
- package/src/lib/config-storage.d.ts +0 -30
- package/src/lib/config-storage.d.ts.map +0 -1
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
- package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
- package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
- package/src/lib/layouts/index.d.ts +0 -23
- package/src/lib/layouts/index.d.ts.map +0 -1
- package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
- package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
- package/src/lib/mappers/apiMapper.d.ts +0 -66
- package/src/lib/mappers/apiMapper.d.ts.map +0 -1
- package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
- package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
- package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
- package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
- package/src/lib/rich-text/index.d.ts +0 -9
- package/src/lib/rich-text/index.d.ts.map +0 -1
- package/src/lib/utils/blockHelpers.d.ts +0 -23
- package/src/lib/utils/blockHelpers.d.ts.map +0 -1
- package/src/lib/utils/configValidation.d.ts +0 -23
- package/src/lib/utils/configValidation.d.ts.map +0 -1
- package/src/registry/BlockRegistry.d.ts +0 -62
- package/src/registry/BlockRegistry.d.ts.map +0 -1
- package/src/registry/index.d.ts +0 -6
- package/src/registry/index.d.ts.map +0 -1
- package/src/state/EditorContext.d.ts +0 -45
- package/src/state/EditorContext.d.ts.map +0 -1
- package/src/state/index.d.ts +0 -7
- package/src/state/index.d.ts.map +0 -1
- package/src/state/reducer.d.ts +0 -11
- package/src/state/reducer.d.ts.map +0 -1
- package/src/state/types.d.ts +0 -162
- package/src/state/types.d.ts.map +0 -1
- package/src/types/block.d.ts +0 -221
- package/src/types/block.d.ts.map +0 -1
- package/src/types/index.d.ts +0 -8
- package/src/types/index.d.ts.map +0 -1
- package/src/types/post.d.ts +0 -136
- package/src/types/post.d.ts.map +0 -1
- package/src/utils/client.d.ts +0 -48
- package/src/utils/client.d.ts.map +0 -1
- package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
- package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
- package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
- package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
- package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
- package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
- package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
- package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
- package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
- package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
- package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/index.d.ts +0 -21
- package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
- package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
- package/src/views/CanvasEditor/index.d.ts +0 -16
- package/src/views/CanvasEditor/index.d.ts.map +0 -1
- package/src/views/PostManager/EmptyState.d.ts +0 -10
- package/src/views/PostManager/EmptyState.d.ts.map +0 -1
- package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
- package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
- package/src/views/PostManager/PostCards.d.ts +0 -15
- package/src/views/PostManager/PostCards.d.ts.map +0 -1
- package/src/views/PostManager/PostFilters.d.ts +0 -16
- package/src/views/PostManager/PostFilters.d.ts.map +0 -1
- package/src/views/PostManager/PostManagerView.d.ts +0 -11
- package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
- package/src/views/PostManager/PostStats.d.ts +0 -11
- package/src/views/PostManager/PostStats.d.ts.map +0 -1
- package/src/views/PostManager/PostTable.d.ts +0 -15
- package/src/views/PostManager/PostTable.d.ts.map +0 -1
- package/src/views/PostManager/index.d.ts +0 -12
- package/src/views/PostManager/index.d.ts.map +0 -1
- package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
- package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
- package/src/views/Preview/index.d.ts +0 -6
- package/src/views/Preview/index.d.ts.map +0 -1
- package/src/views/Settings/SettingsView.d.ts +0 -10
- package/src/views/Settings/SettingsView.d.ts.map +0 -1
- package/src/views/Settings/index.d.ts +0 -6
- package/src/views/Settings/index.d.ts.map +0 -1
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
- package/src/views/SlugSEO/index.d.ts +0 -6
- package/src/views/SlugSEO/index.d.ts.map +0 -1
package/dist/api/router.js
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Plugin Blog API Router
|
|
4
4
|
* Centralized API handler for all blog plugin routes
|
|
5
|
-
*
|
|
6
|
-
* This router handles requests to /api/plugin-blog/*
|
|
7
|
-
* and routes them to the appropriate handler
|
|
8
5
|
*/
|
|
9
6
|
import { NextResponse } from 'next/server';
|
|
10
7
|
import { GET as BlogListHandler, POST as BlogCreateHandler } from './handler';
|
|
@@ -12,83 +9,59 @@ import { GET as BlogGetHandler, PUT as BlogUpdateHandler, DELETE as BlogDeleteHa
|
|
|
12
9
|
import { GET as ConfigGetHandler, POST as ConfigPostHandler } from './config-handler';
|
|
13
10
|
/**
|
|
14
11
|
* Handle blog API requests
|
|
15
|
-
* Routes requests to appropriate handlers based on path
|
|
16
|
-
* Similar to plugin-dep, accepts config directly instead of requiring initialization
|
|
17
12
|
*/
|
|
18
13
|
export async function handleBlogApi(req, path, config) {
|
|
19
|
-
// Create the blog API config from the router config
|
|
20
14
|
const blogApiConfig = createBlogApiConfig({
|
|
21
15
|
getDb: config.getDb,
|
|
22
16
|
getUserId: config.getUserId,
|
|
23
17
|
collectionName: config.collectionName || 'blogs',
|
|
24
18
|
});
|
|
25
19
|
const method = req.method;
|
|
26
|
-
// Handle empty path array - means we're at /api/plugin-blog
|
|
27
|
-
// Ensure path is always an array
|
|
28
20
|
const safePath = Array.isArray(path) ? path : [];
|
|
29
21
|
const route = safePath.length > 0 ? safePath[0] : '';
|
|
30
22
|
try {
|
|
31
|
-
// Route: /api/plugin-blog (list/create) - empty path or 'list'
|
|
32
|
-
// This handles both /api/plugin-blog and /api/plugin-blog?limit=3
|
|
33
23
|
if (!route || route === 'list') {
|
|
34
|
-
if (method === 'GET')
|
|
24
|
+
if (method === 'GET')
|
|
35
25
|
return await BlogListHandler(req, blogApiConfig);
|
|
36
|
-
|
|
37
|
-
if (method === 'POST') {
|
|
26
|
+
if (method === 'POST')
|
|
38
27
|
return await BlogCreateHandler(req, blogApiConfig);
|
|
39
|
-
}
|
|
40
|
-
// Method not allowed for root route
|
|
41
|
-
return NextResponse.json({ error: `Method ${method} not allowed for route: /` }, { status: 405 });
|
|
42
28
|
}
|
|
43
|
-
// Route: /api/plugin-blog/new (create new)
|
|
44
29
|
else if (route === 'new') {
|
|
45
|
-
if (method === 'POST')
|
|
30
|
+
if (method === 'POST')
|
|
46
31
|
return await BlogCreateHandler(req, blogApiConfig);
|
|
47
|
-
}
|
|
48
32
|
}
|
|
49
|
-
// Route: /api/plugin-blog/categories (get categories)
|
|
50
33
|
else if (route === 'categories') {
|
|
51
34
|
if (method === 'GET') {
|
|
52
|
-
// Import categories handler
|
|
53
35
|
const categoriesModule = await import('./categories');
|
|
54
36
|
return await categoriesModule.GET(req, blogApiConfig);
|
|
55
37
|
}
|
|
56
38
|
}
|
|
57
|
-
// Route: /api/plugin-blog/check-title (check title duplicate)
|
|
58
39
|
else if (route === 'check-title') {
|
|
59
40
|
if (method === 'GET') {
|
|
60
41
|
const checkTitleModule = await import('./check-title');
|
|
61
42
|
return await checkTitleModule.GET(req, blogApiConfig);
|
|
62
43
|
}
|
|
63
44
|
}
|
|
64
|
-
// Route: /api/plugin-blog/config (get/save plugin config)
|
|
65
45
|
else if (route === 'config') {
|
|
66
46
|
const configApiConfig = {
|
|
67
47
|
getDb: config.getDb,
|
|
68
48
|
getUserId: config.getUserId,
|
|
69
49
|
siteId: config.siteId || 'default',
|
|
70
50
|
};
|
|
71
|
-
if (method === 'GET')
|
|
51
|
+
if (method === 'GET')
|
|
72
52
|
return await ConfigGetHandler(req, configApiConfig);
|
|
73
|
-
|
|
74
|
-
if (method === 'POST') {
|
|
53
|
+
if (method === 'POST')
|
|
75
54
|
return await ConfigPostHandler(req, configApiConfig);
|
|
76
|
-
}
|
|
77
55
|
}
|
|
78
|
-
// Route: /api/plugin-blog/[slug] (get/update/delete by slug)
|
|
79
56
|
else {
|
|
80
57
|
const slug = route;
|
|
81
|
-
if (method === 'GET')
|
|
58
|
+
if (method === 'GET')
|
|
82
59
|
return await BlogGetHandler(req, { params: Promise.resolve({ slug }) }, blogApiConfig);
|
|
83
|
-
|
|
84
|
-
if (method === 'PUT') {
|
|
60
|
+
if (method === 'PUT')
|
|
85
61
|
return await BlogUpdateHandler(req, { params: Promise.resolve({ slug }) }, blogApiConfig);
|
|
86
|
-
|
|
87
|
-
if (method === 'DELETE') {
|
|
62
|
+
if (method === 'DELETE')
|
|
88
63
|
return await BlogDeleteHandler(req, { params: Promise.resolve({ slug }) }, blogApiConfig);
|
|
89
|
-
}
|
|
90
64
|
}
|
|
91
|
-
// Method not allowed
|
|
92
65
|
return NextResponse.json({ error: `Method ${method} not allowed for route: ${route || '/'}` }, { status: 405 });
|
|
93
66
|
}
|
|
94
67
|
catch (error) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog API Service
|
|
3
|
+
* Shared server-side logic for MongoDB operations and language fallbacks
|
|
4
|
+
*/
|
|
5
|
+
import { BlogApiConfig } from './handler';
|
|
6
|
+
export declare class BlogService {
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: BlogApiConfig);
|
|
9
|
+
/**
|
|
10
|
+
* Common aggregation pipeline for including author data
|
|
11
|
+
*/
|
|
12
|
+
private getAuthorLookupPipeline;
|
|
13
|
+
/**
|
|
14
|
+
* List blogs with filters and language fallbacks
|
|
15
|
+
*/
|
|
16
|
+
listBlogs(options: {
|
|
17
|
+
limit: number;
|
|
18
|
+
skip: number;
|
|
19
|
+
status?: string;
|
|
20
|
+
isAdmin: boolean;
|
|
21
|
+
userId?: string | null;
|
|
22
|
+
requestedLanguage: string;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
blogs: any;
|
|
25
|
+
total: any;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* Get single blog by slug
|
|
29
|
+
*/
|
|
30
|
+
getBlogBySlug(slug: string, requestedLanguage: string, isAdmin?: boolean): Promise<{
|
|
31
|
+
id: any;
|
|
32
|
+
slug: any;
|
|
33
|
+
title: any;
|
|
34
|
+
summary: any;
|
|
35
|
+
contentBlocks: any;
|
|
36
|
+
image: any;
|
|
37
|
+
categoryTags: {
|
|
38
|
+
category: any;
|
|
39
|
+
tags: any;
|
|
40
|
+
};
|
|
41
|
+
lang: string;
|
|
42
|
+
isMissingTranslation: boolean;
|
|
43
|
+
requestedLanguage: string;
|
|
44
|
+
availableLanguages: string[];
|
|
45
|
+
updatedAt: any;
|
|
46
|
+
languages: any;
|
|
47
|
+
status: any;
|
|
48
|
+
publication: {
|
|
49
|
+
status: any;
|
|
50
|
+
date: any;
|
|
51
|
+
};
|
|
52
|
+
author: {
|
|
53
|
+
name: any;
|
|
54
|
+
image: any;
|
|
55
|
+
displayRole: any;
|
|
56
|
+
} | undefined;
|
|
57
|
+
} | null>;
|
|
58
|
+
/**
|
|
59
|
+
* Create or update the language-specific document structure
|
|
60
|
+
*/
|
|
61
|
+
prepareUpdateData(body: any, existingBlog: any | null, language: string): {
|
|
62
|
+
title: any;
|
|
63
|
+
slug: any;
|
|
64
|
+
summary: any;
|
|
65
|
+
contentBlocks: any;
|
|
66
|
+
image: any;
|
|
67
|
+
categoryTags: {
|
|
68
|
+
category: any;
|
|
69
|
+
tags: any;
|
|
70
|
+
};
|
|
71
|
+
publicationData: any;
|
|
72
|
+
languages: any;
|
|
73
|
+
updatedAt: Date;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Format a document for a specific language
|
|
77
|
+
*/
|
|
78
|
+
private formatWithLanguage;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/api/service.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C,qBAAa,WAAW;IACR,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,aAAa;IAEzC;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAiB/B;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,iBAAiB,EAAE,MAAM,CAAC;KAC7B;;;;IAiCD;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,OAAO,GAAE,OAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAarF;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,GAAG,EAAE,YAAY,EAAE,GAAG,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM;;;;;;;;;;;;;;IAqEvE;;OAEG;IACH,OAAO,CAAC,kBAAkB;CA2E7B"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog API Service
|
|
3
|
+
* Shared server-side logic for MongoDB operations and language fallbacks
|
|
4
|
+
*/
|
|
5
|
+
import { slugify } from '../lib/utils/slugify';
|
|
6
|
+
export class BlogService {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Common aggregation pipeline for including author data
|
|
12
|
+
*/
|
|
13
|
+
getAuthorLookupPipeline() {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
$lookup: {
|
|
17
|
+
from: 'users',
|
|
18
|
+
let: { authorId: '$authorId' },
|
|
19
|
+
pipeline: [
|
|
20
|
+
{ $match: { $expr: { $or: [{ $eq: ['$_id', '$$authorId'] }, { $eq: [{ $toString: '$_id' }, '$$authorId'] }] } } },
|
|
21
|
+
{ $project: { password: 0 } }
|
|
22
|
+
],
|
|
23
|
+
as: 'author'
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{ $addFields: { author: { $arrayElemAt: ['$author', 0] } } }
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* List blogs with filters and language fallbacks
|
|
31
|
+
*/
|
|
32
|
+
async listBlogs(options) {
|
|
33
|
+
const dbConn = await this.config.getDb();
|
|
34
|
+
const db = dbConn.db();
|
|
35
|
+
const collection = db.collection(this.config.collectionName || 'blogs');
|
|
36
|
+
let query = {};
|
|
37
|
+
if (options.isAdmin && options.userId) {
|
|
38
|
+
if (options.status)
|
|
39
|
+
query = { 'publicationData.status': options.status, authorId: options.userId };
|
|
40
|
+
else
|
|
41
|
+
query = { authorId: options.userId };
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
query = {
|
|
45
|
+
[`languages.${options.requestedLanguage}.status`]: 'published',
|
|
46
|
+
'publicationData.date': { $lte: new Date() },
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const pipeline = [
|
|
50
|
+
{ $match: query },
|
|
51
|
+
{ $sort: { 'publicationData.date': -1 } },
|
|
52
|
+
{ $skip: options.skip },
|
|
53
|
+
{ $limit: options.isAdmin ? 1000 : options.limit },
|
|
54
|
+
...this.getAuthorLookupPipeline()
|
|
55
|
+
];
|
|
56
|
+
const [data, totalCount] = await Promise.all([
|
|
57
|
+
collection.aggregate(pipeline).toArray(),
|
|
58
|
+
collection.countDocuments(query),
|
|
59
|
+
]);
|
|
60
|
+
const formatted = data.map((doc) => this.formatWithLanguage(doc, options.requestedLanguage, options.isAdmin)).filter(Boolean);
|
|
61
|
+
return { blogs: formatted, total: totalCount };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get single blog by slug
|
|
65
|
+
*/
|
|
66
|
+
async getBlogBySlug(slug, requestedLanguage, isAdmin = false) {
|
|
67
|
+
const dbConn = await this.config.getDb();
|
|
68
|
+
const db = dbConn.db();
|
|
69
|
+
const collection = db.collection(this.config.collectionName || 'blogs');
|
|
70
|
+
const pipeline = [{ $match: { slug } }, ...this.getAuthorLookupPipeline()];
|
|
71
|
+
const results = await collection.aggregate(pipeline).toArray();
|
|
72
|
+
const blog = results[0];
|
|
73
|
+
if (!blog)
|
|
74
|
+
return null;
|
|
75
|
+
return this.formatWithLanguage(blog, requestedLanguage, isAdmin);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create or update the language-specific document structure
|
|
79
|
+
*/
|
|
80
|
+
prepareUpdateData(body, existingBlog, language) {
|
|
81
|
+
const { title, summary, contentBlocks, image, categoryTags, publicationData, seo } = body;
|
|
82
|
+
const now = new Date();
|
|
83
|
+
const finalStatus = publicationData?.status || 'concept';
|
|
84
|
+
// Generate a localized slug for this specific language edition
|
|
85
|
+
// If it's not published, we add a draft suffix
|
|
86
|
+
const isPublishing = finalStatus === 'published';
|
|
87
|
+
let localizedSlug = slugify(title);
|
|
88
|
+
// Only append draft suffix if NOT publishing
|
|
89
|
+
if (!isPublishing) {
|
|
90
|
+
localizedSlug = `${localizedSlug}-draft-${Date.now().toString().slice(-4)}`;
|
|
91
|
+
}
|
|
92
|
+
const langData = {
|
|
93
|
+
blocks: contentBlocks || [],
|
|
94
|
+
metadata: {
|
|
95
|
+
title: title.trim(),
|
|
96
|
+
slug: localizedSlug, // Store the slug inside the language object
|
|
97
|
+
excerpt: (summary || '').trim(),
|
|
98
|
+
featuredImage: image,
|
|
99
|
+
categories: categoryTags?.category ? [categoryTags.category] : [],
|
|
100
|
+
tags: categoryTags?.tags || [],
|
|
101
|
+
seo: seo || {},
|
|
102
|
+
},
|
|
103
|
+
updatedAt: now.toISOString(),
|
|
104
|
+
status: finalStatus,
|
|
105
|
+
};
|
|
106
|
+
const updatedLanguages = {
|
|
107
|
+
...(existingBlog?.languages || {}),
|
|
108
|
+
[language]: langData,
|
|
109
|
+
};
|
|
110
|
+
// Determine global status based on ALL languages
|
|
111
|
+
// If at least ONE language is published, the global status is 'published'
|
|
112
|
+
// This ensures a new draft edition doesn't hide existing published editions
|
|
113
|
+
const anyPublished = Object.values(updatedLanguages).some((l) => l.status === 'published');
|
|
114
|
+
const globalStatus = anyPublished ? 'published' : 'draft';
|
|
115
|
+
// Update root slug if this is the primary language
|
|
116
|
+
const isPrimaryLanguage = !existingBlog || language === (existingBlog.metadata?.lang || 'nl');
|
|
117
|
+
const rootSlug = isPrimaryLanguage ? localizedSlug : (existingBlog?.slug || localizedSlug);
|
|
118
|
+
return {
|
|
119
|
+
// Keep the primary title/summary but allow them to be updated
|
|
120
|
+
title: title.trim(),
|
|
121
|
+
slug: rootSlug, // Include root slug in update
|
|
122
|
+
summary: (summary || '').trim(),
|
|
123
|
+
contentBlocks: contentBlocks || [],
|
|
124
|
+
image: image || {},
|
|
125
|
+
categoryTags: {
|
|
126
|
+
category: categoryTags?.category?.trim() || '',
|
|
127
|
+
tags: categoryTags?.tags || [],
|
|
128
|
+
},
|
|
129
|
+
publicationData: {
|
|
130
|
+
...(existingBlog?.publicationData || {}),
|
|
131
|
+
...publicationData,
|
|
132
|
+
status: globalStatus,
|
|
133
|
+
date: publicationData?.date ? new Date(publicationData.date) : (existingBlog?.publicationData?.date || now),
|
|
134
|
+
},
|
|
135
|
+
languages: updatedLanguages,
|
|
136
|
+
updatedAt: now,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Format a document for a specific language
|
|
141
|
+
*/
|
|
142
|
+
formatWithLanguage(doc, requestedLanguage, isAdmin) {
|
|
143
|
+
const languages = doc.languages || {};
|
|
144
|
+
let bestLanguage = requestedLanguage;
|
|
145
|
+
let isMissingTranslation = !languages[requestedLanguage];
|
|
146
|
+
if (isMissingTranslation && !isAdmin) {
|
|
147
|
+
const fallbacks = [doc.metadata?.lang || 'nl', 'nl', 'en'];
|
|
148
|
+
for (const lang of fallbacks) {
|
|
149
|
+
if (languages[lang]) {
|
|
150
|
+
bestLanguage = lang;
|
|
151
|
+
isMissingTranslation = false;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (isMissingTranslation)
|
|
156
|
+
return null; // Public view requires translation or fallback
|
|
157
|
+
}
|
|
158
|
+
const content = languages[bestLanguage] || {};
|
|
159
|
+
const meta = content.metadata || {};
|
|
160
|
+
const status = isMissingTranslation ? 'draft' : (content.status || 'draft');
|
|
161
|
+
// --- ROBUST SLUG RESOLUTION ---
|
|
162
|
+
let resolvedSlug = doc.slug;
|
|
163
|
+
if (!isMissingTranslation) {
|
|
164
|
+
if (meta.slug) {
|
|
165
|
+
resolvedSlug = meta.slug;
|
|
166
|
+
}
|
|
167
|
+
else if (meta.title) {
|
|
168
|
+
resolvedSlug = slugify(meta.title);
|
|
169
|
+
if (status !== 'published')
|
|
170
|
+
resolvedSlug += `-draft-${Date.now().toString().slice(-4)}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
id: doc.id || doc._id?.toString(),
|
|
175
|
+
// Prefer localized slug if available, fallback to root slug or generated one
|
|
176
|
+
slug: resolvedSlug,
|
|
177
|
+
// If missing translation, provide root content as template but keep status 'not-translated'
|
|
178
|
+
title: isMissingTranslation ? (doc.title || '') : (meta.title || doc.title || ''),
|
|
179
|
+
summary: isMissingTranslation ? (doc.summary || '') : (meta.excerpt || doc.summary || ''),
|
|
180
|
+
contentBlocks: isMissingTranslation ? (doc.contentBlocks || []) : (content.blocks || doc.contentBlocks || []),
|
|
181
|
+
image: isMissingTranslation ? doc.image : (meta.featuredImage || doc.image),
|
|
182
|
+
categoryTags: isMissingTranslation ? { category: '', tags: [] } : {
|
|
183
|
+
category: meta.categories?.[0] || '',
|
|
184
|
+
tags: meta.tags || []
|
|
185
|
+
},
|
|
186
|
+
lang: bestLanguage,
|
|
187
|
+
isMissingTranslation,
|
|
188
|
+
requestedLanguage,
|
|
189
|
+
availableLanguages: Object.keys(languages),
|
|
190
|
+
updatedAt: doc.updatedAt || doc.publicationData?.date || new Date().toISOString(),
|
|
191
|
+
// Include a lightweight summary of all languages for admin view (tooltips)
|
|
192
|
+
languages: isAdmin ? (() => {
|
|
193
|
+
const summary = {};
|
|
194
|
+
// Use Object.keys(languages) to ensure we iterate over all existing translations
|
|
195
|
+
Object.keys(languages).forEach(lang => {
|
|
196
|
+
const lData = languages[lang] || {};
|
|
197
|
+
// Normalize status: handle 'concept' -> 'draft'
|
|
198
|
+
let langStatus = lData.status || 'draft';
|
|
199
|
+
if (langStatus === 'concept')
|
|
200
|
+
langStatus = 'draft';
|
|
201
|
+
summary[lang] = {
|
|
202
|
+
status: langStatus,
|
|
203
|
+
metadata: {
|
|
204
|
+
title: lData.metadata?.title || doc.title || 'Untitled'
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
return summary;
|
|
209
|
+
})() : undefined,
|
|
210
|
+
status: status,
|
|
211
|
+
publication: {
|
|
212
|
+
status: status,
|
|
213
|
+
// FORCE undefined date for new editions so they don't look published or inherit old dates
|
|
214
|
+
date: isMissingTranslation ? undefined : (doc.publicationData?.date || doc.updatedAt)
|
|
215
|
+
},
|
|
216
|
+
author: doc.author ? { name: doc.author.name, image: doc.author.image, displayRole: doc.author.displayRole } : undefined
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook for automatic saving of editor state
|
|
3
|
+
*/
|
|
4
|
+
export declare function useAutoSave(postId: string | undefined, state: any, onSave: (state: any) => Promise<void>, delay?: number): {
|
|
5
|
+
autoSaveEnabled: boolean;
|
|
6
|
+
setAutoSaveEnabled: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
7
|
+
saveStatus: "error" | "idle" | "saving" | "saved";
|
|
8
|
+
countdown: number | null;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=useAutoSave.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useAutoSave.d.ts","sourceRoot":"","sources":["../../src/hooks/useAutoSave.ts"],"names":[],"mappings":"AAIA;;GAEG;AACH,wBAAgB,WAAW,CACvB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,EACrC,KAAK,SAAQ;;;;;EAoDhB"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Hook for automatic saving of editor state
|
|
5
|
+
*/
|
|
6
|
+
export function useAutoSave(postId, state, onSave, delay = 10000) {
|
|
7
|
+
const [autoSaveEnabled, setAutoSaveEnabled] = useState(true);
|
|
8
|
+
const [saveStatus, setSaveStatus] = useState('idle');
|
|
9
|
+
const [countdown, setCountdown] = useState(null);
|
|
10
|
+
const timerRef = useRef(null);
|
|
11
|
+
const countdownIntervalRef = useRef(null);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (!autoSaveEnabled || !state.isDirty || !postId) {
|
|
14
|
+
setCountdown(null);
|
|
15
|
+
if (timerRef.current)
|
|
16
|
+
clearTimeout(timerRef.current);
|
|
17
|
+
if (countdownIntervalRef.current)
|
|
18
|
+
clearInterval(countdownIntervalRef.current);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Reset timer on state change
|
|
22
|
+
if (timerRef.current)
|
|
23
|
+
clearTimeout(timerRef.current);
|
|
24
|
+
if (countdownIntervalRef.current)
|
|
25
|
+
clearInterval(countdownIntervalRef.current);
|
|
26
|
+
const startTime = Date.now();
|
|
27
|
+
setCountdown(Math.ceil(delay / 1000));
|
|
28
|
+
countdownIntervalRef.current = setInterval(() => {
|
|
29
|
+
const remaining = Math.max(0, delay - (Date.now() - startTime));
|
|
30
|
+
setCountdown(Math.ceil(remaining / 1000));
|
|
31
|
+
}, 1000);
|
|
32
|
+
timerRef.current = setTimeout(async () => {
|
|
33
|
+
setSaveStatus('saving');
|
|
34
|
+
try {
|
|
35
|
+
await onSave(state);
|
|
36
|
+
setSaveStatus('saved');
|
|
37
|
+
setTimeout(() => setSaveStatus('idle'), 2000);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
setSaveStatus('error');
|
|
41
|
+
setTimeout(() => setSaveStatus('idle'), 3000);
|
|
42
|
+
}
|
|
43
|
+
}, delay);
|
|
44
|
+
return () => {
|
|
45
|
+
if (timerRef.current)
|
|
46
|
+
clearTimeout(timerRef.current);
|
|
47
|
+
if (countdownIntervalRef.current)
|
|
48
|
+
clearInterval(countdownIntervalRef.current);
|
|
49
|
+
};
|
|
50
|
+
}, [state, autoSaveEnabled, postId, delay, onSave]);
|
|
51
|
+
return {
|
|
52
|
+
autoSaveEnabled,
|
|
53
|
+
setAutoSaveEnabled,
|
|
54
|
+
saveStatus,
|
|
55
|
+
countdown
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Hook to fetch categories from existing blog posts
|
|
3
3
|
* Extracts categories from Hero blocks in all posts
|
|
4
4
|
*/
|
|
5
|
-
export declare function useCategories(): {
|
|
5
|
+
export declare function useCategories(language?: string): {
|
|
6
6
|
categories: string[];
|
|
7
7
|
loading: boolean;
|
|
8
8
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCategories.d.ts","sourceRoot":"","sources":["../../src/hooks/useCategories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,aAAa;;;
|
|
1
|
+
{"version":3,"file":"useCategories.d.ts","sourceRoot":"","sources":["../../src/hooks/useCategories.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,wBAAgB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM;;;EAsC9C"}
|
|
@@ -4,60 +4,29 @@
|
|
|
4
4
|
*/
|
|
5
5
|
'use client';
|
|
6
6
|
import { useState, useEffect } from 'react';
|
|
7
|
-
export function useCategories() {
|
|
7
|
+
export function useCategories(language) {
|
|
8
8
|
const [categories, setCategories] = useState([]);
|
|
9
9
|
const [loading, setLoading] = useState(true);
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
const fetchCategories = async () => {
|
|
12
12
|
try {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
categorySet.add(cat.trim());
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
}
|
|
13
|
+
// We rely on the specialized categories endpoint which is strictly filtered by the Orchestrator
|
|
14
|
+
const url = language
|
|
15
|
+
? `/api/plugin-blog/categories?language=${language}`
|
|
16
|
+
: '/api/plugin-blog/categories';
|
|
17
|
+
const response = await fetch(url);
|
|
18
|
+
if (response.ok) {
|
|
19
|
+
const data = await response.json();
|
|
20
|
+
const list = Array.isArray(data.categories) ? data.categories : [];
|
|
21
|
+
setCategories(list);
|
|
27
22
|
}
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
else {
|
|
24
|
+
console.error(`[useCategories] API_ERROR: ${response.status}`);
|
|
25
|
+
setCategories([]);
|
|
30
26
|
}
|
|
31
|
-
// 2. Fetch all blog posts and extract categories from Hero blocks
|
|
32
|
-
try {
|
|
33
|
-
const response = await fetch('/api/plugin-blog?admin=true&limit=1000');
|
|
34
|
-
if (response.ok) {
|
|
35
|
-
const data = await response.json();
|
|
36
|
-
const posts = Array.isArray(data.blogs) ? data.blogs : (Array.isArray(data) ? data : []);
|
|
37
|
-
posts.forEach((post) => {
|
|
38
|
-
if (post.blocks && Array.isArray(post.blocks)) {
|
|
39
|
-
// Find Hero block
|
|
40
|
-
const heroBlock = post.blocks.find((block) => block.type === 'hero');
|
|
41
|
-
if (heroBlock && heroBlock.data && heroBlock.data.category) {
|
|
42
|
-
const category = heroBlock.data.category.trim();
|
|
43
|
-
if (category) {
|
|
44
|
-
categorySet.add(category);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch (e) {
|
|
52
|
-
console.error('Failed to fetch posts for categories:', e);
|
|
53
|
-
}
|
|
54
|
-
// Convert to sorted array
|
|
55
|
-
const sortedCategories = Array.from(categorySet).sort();
|
|
56
|
-
setCategories(sortedCategories);
|
|
57
27
|
}
|
|
58
28
|
catch (error) {
|
|
59
|
-
console.error('
|
|
60
|
-
// Fallback to empty array
|
|
29
|
+
console.error('[useCategories] CRITICAL_SYNC_FAILURE:', error);
|
|
61
30
|
setCategories([]);
|
|
62
31
|
}
|
|
63
32
|
finally {
|
|
@@ -65,6 +34,6 @@ export function useCategories() {
|
|
|
65
34
|
}
|
|
66
35
|
};
|
|
67
36
|
fetchCategories();
|
|
68
|
-
}, []);
|
|
37
|
+
}, [language]);
|
|
69
38
|
return { categories, loading };
|
|
70
39
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,55 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Plugin Blog - Main Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Multi-Tenant Architecture: Accepts custom blocks from client applications
|
|
3
|
+
* Simplified with Configuration Resolver
|
|
5
4
|
*/
|
|
5
|
+
import React from 'react';
|
|
6
6
|
import { ClientBlockDefinition } from './types/block';
|
|
7
|
-
/**
|
|
8
|
-
* Plugin Props Interface
|
|
9
|
-
* Matches the PluginProps from @jhits/jhits-dashboard
|
|
10
|
-
*/
|
|
11
7
|
export interface PluginProps {
|
|
12
8
|
subPath: string[];
|
|
13
9
|
siteId: string;
|
|
14
10
|
locale: string;
|
|
15
|
-
/** Custom blocks from client application (optional, can also come from window.__JHITS_PLUGIN_PROPS__) */
|
|
16
11
|
customBlocks?: ClientBlockDefinition[];
|
|
17
|
-
/** Enable dark mode for content area and wrappers (default: true) */
|
|
18
12
|
darkMode?: boolean;
|
|
19
|
-
/** Background colors for the editor */
|
|
20
13
|
backgroundColors?: {
|
|
21
|
-
/** Background color for light mode (REQUIRED) */
|
|
22
14
|
light: string;
|
|
23
|
-
/** Background color for dark mode (optional) */
|
|
24
15
|
dark?: string;
|
|
25
16
|
};
|
|
17
|
+
LayoutWrapper?: React.ComponentType<any>;
|
|
18
|
+
translations?: Record<string, any>;
|
|
26
19
|
}
|
|
27
20
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* Client Handshake:
|
|
32
|
-
* - Client apps can pass customBlocks via props
|
|
33
|
-
* - Or via window.__JHITS_PLUGIN_PROPS__['plugin-blog'].customBlocks
|
|
34
|
-
* - The EditorProvider will automatically register these blocks
|
|
21
|
+
* Client-facing configuration type
|
|
22
|
+
* Allows partial overrides of plugin behavior
|
|
35
23
|
*/
|
|
24
|
+
export type BlogPluginConfig = Partial<Omit<PluginProps, 'subPath' | 'siteId' | 'locale'>> & {
|
|
25
|
+
customBlocks?: ClientBlockDefinition[];
|
|
26
|
+
darkMode?: boolean;
|
|
27
|
+
backgroundColors?: {
|
|
28
|
+
light: string;
|
|
29
|
+
dark?: string;
|
|
30
|
+
};
|
|
31
|
+
LayoutWrapper?: React.ComponentType<any>;
|
|
32
|
+
translations?: Record<string, any>;
|
|
33
|
+
};
|
|
36
34
|
export default function BlogPlugin(props: PluginProps): import("react/jsx-runtime").JSX.Element;
|
|
37
35
|
export { BlogPlugin as Index };
|
|
38
|
-
export type { Block,
|
|
39
|
-
export type { SEOMetadata, PublicationData,
|
|
36
|
+
export type { Block, ClientBlockDefinition, BlockEditProps, BlockPreviewProps, BlockTypeDefinition, IBlockComponent } from './types';
|
|
37
|
+
export type { SEOMetadata, PublicationData, BlogPost, PostListItem, PostStatus } from './types';
|
|
40
38
|
export { initBlogPlugin } from './init';
|
|
41
|
-
export type { BlogPluginConfig } from './init';
|
|
42
39
|
export { RichTextEditor, RichTextPreview } from './lib/rich-text';
|
|
43
|
-
export
|
|
44
|
-
export { useBlogs, useBlog } from './hooks';
|
|
45
|
-
export type { UseBlogsOptions, UseBlogsResult, UseBlogOptions, UseBlogResult } from './hooks';
|
|
46
|
-
export { fetchBlogs, fetchBlog } from './utils/client';
|
|
47
|
-
export type { FetchBlogsOptions, FetchBlogsResult, FetchBlogOptions } from './utils/client';
|
|
48
|
-
export { BlockRenderer, BlocksRenderer } from './lib/blocks/BlockRenderer';
|
|
40
|
+
export { useBlogs, useBlog, useCategories } from './hooks';
|
|
49
41
|
export { blockRegistry } from './registry';
|
|
50
|
-
export { registerLayoutBlocks } from './lib/layouts/registerLayoutBlocks';
|
|
51
|
-
export { getPluginConfig, savePluginConfig } from './lib/config-storage';
|
|
52
42
|
export { EditorProvider, useEditor } from './state/EditorContext';
|
|
53
|
-
export type { EditorProviderProps
|
|
54
|
-
export {
|
|
43
|
+
export type { EditorProviderProps } from './state/EditorContext';
|
|
44
|
+
export type { EditorState, EditorContextValue } from './state/types';
|
|
45
|
+
export { BlockRenderer, BlocksRenderer } from './lib/blocks/BlockRenderer';
|
|
46
|
+
export { ColumnsPreview } from './lib/layouts/blocks/ColumnsBlock';
|
|
47
|
+
export { registerLayoutBlocks } from './lib/layouts/registerLayoutBlocks';
|
|
55
48
|
//# sourceMappingURL=index.d.ts.map
|