@jhits/plugin-blog 0.0.14 → 0.0.16
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 +5 -4
- package/src/api/categories.ts +43 -0
- package/src/api/check-title.ts +60 -0
- package/src/api/config-handler.ts +76 -0
- package/src/api/handler.ts +418 -0
- package/src/api/index.ts +33 -0
- package/src/api/route.ts +116 -0
- package/src/api/router.ts +128 -0
- package/src/api-server.ts +11 -0
- package/src/config.ts +161 -0
- package/src/hooks/index.d.ts +8 -0
- package/src/hooks/index.d.ts.map +1 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useBlog.d.ts +31 -0
- package/src/hooks/useBlog.d.ts.map +1 -0
- package/src/hooks/useBlog.ts +85 -0
- package/src/hooks/useBlogs.d.ts +39 -0
- package/src/hooks/useBlogs.d.ts.map +1 -0
- package/src/hooks/useBlogs.ts +123 -0
- package/src/hooks/useCategories.d.ts +9 -0
- package/src/hooks/useCategories.d.ts.map +1 -0
- package/src/hooks/useCategories.ts +76 -0
- package/src/index.server.ts +14 -0
- package/src/index.tsx +335 -0
- package/src/init.tsx +63 -0
- package/src/lib/blocks/BlockRenderer.d.ts +54 -0
- package/src/lib/blocks/BlockRenderer.d.ts.map +1 -0
- package/src/lib/blocks/BlockRenderer.tsx +141 -0
- package/src/lib/blocks/index.ts +6 -0
- package/src/lib/config-storage.d.ts +30 -0
- package/src/lib/config-storage.d.ts.map +1 -0
- package/src/lib/config-storage.ts +65 -0
- package/src/lib/index.ts +9 -0
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts +25 -0
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx +298 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
- package/src/lib/layouts/blocks/SectionBlock.d.ts +25 -0
- package/src/lib/layouts/blocks/SectionBlock.d.ts.map +1 -0
- package/src/lib/layouts/blocks/SectionBlock.tsx +104 -0
- package/src/lib/layouts/blocks/index.ts +8 -0
- package/src/lib/layouts/index.d.ts +23 -0
- package/src/lib/layouts/index.d.ts.map +1 -0
- package/src/lib/layouts/index.ts +52 -0
- package/src/lib/layouts/registerLayoutBlocks.d.ts +9 -0
- package/src/lib/layouts/registerLayoutBlocks.d.ts.map +1 -0
- package/src/lib/layouts/registerLayoutBlocks.ts +64 -0
- package/src/lib/mappers/apiMapper.d.ts +66 -0
- package/src/lib/mappers/apiMapper.d.ts.map +1 -0
- package/src/lib/mappers/apiMapper.ts +254 -0
- package/src/lib/migration/index.ts +6 -0
- package/src/lib/migration/mapper.ts +140 -0
- package/src/lib/rich-text/RichTextEditor.d.ts +45 -0
- package/src/lib/rich-text/RichTextEditor.d.ts.map +1 -0
- package/src/lib/rich-text/RichTextEditor.tsx +826 -0
- package/src/lib/rich-text/RichTextPreview.d.ts +16 -0
- package/src/lib/rich-text/RichTextPreview.d.ts.map +1 -0
- package/src/lib/rich-text/RichTextPreview.tsx +210 -0
- package/src/lib/rich-text/index.d.ts +9 -0
- package/src/lib/rich-text/index.d.ts.map +1 -0
- package/src/lib/rich-text/index.ts +10 -0
- package/src/lib/utils/blockHelpers.d.ts +23 -0
- package/src/lib/utils/blockHelpers.d.ts.map +1 -0
- package/src/lib/utils/blockHelpers.ts +72 -0
- package/src/lib/utils/configValidation.d.ts +23 -0
- package/src/lib/utils/configValidation.d.ts.map +1 -0
- package/src/lib/utils/configValidation.ts +137 -0
- package/src/lib/utils/index.ts +8 -0
- package/src/lib/utils/slugify.ts +79 -0
- package/src/registry/BlockRegistry.d.ts +62 -0
- package/src/registry/BlockRegistry.d.ts.map +1 -0
- package/src/registry/BlockRegistry.ts +139 -0
- package/src/registry/index.d.ts +6 -0
- package/src/registry/index.d.ts.map +1 -0
- package/src/registry/index.ts +11 -0
- package/src/state/EditorContext.d.ts +45 -0
- package/src/state/EditorContext.d.ts.map +1 -0
- package/src/state/EditorContext.tsx +283 -0
- package/src/state/index.d.ts +7 -0
- package/src/state/index.d.ts.map +1 -0
- package/src/state/index.ts +8 -0
- package/src/state/reducer.d.ts +11 -0
- package/src/state/reducer.d.ts.map +1 -0
- package/src/state/reducer.ts +694 -0
- package/src/state/types.d.ts +162 -0
- package/src/state/types.d.ts.map +1 -0
- package/src/state/types.ts +160 -0
- package/src/types/block.d.ts +221 -0
- package/src/types/block.d.ts.map +1 -0
- package/src/types/block.ts +269 -0
- package/src/types/index.d.ts +8 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/index.ts +17 -0
- package/src/types/post.d.ts +136 -0
- package/src/types/post.d.ts.map +1 -0
- package/src/types/post.ts +169 -0
- package/src/utils/client.d.ts +48 -0
- package/src/utils/client.d.ts.map +1 -0
- package/src/utils/client.ts +122 -0
- package/src/utils/index.ts +7 -0
- package/src/views/CanvasEditor/BlockWrapper.d.ts +16 -0
- package/src/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +522 -0
- package/src/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
- package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
- package/src/views/CanvasEditor/CanvasEditorView.tsx +337 -0
- package/src/views/CanvasEditor/EditorBody.d.ts +22 -0
- package/src/views/CanvasEditor/EditorBody.d.ts.map +1 -0
- package/src/views/CanvasEditor/EditorBody.tsx +665 -0
- package/src/views/CanvasEditor/EditorHeader.d.ts +18 -0
- package/src/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
- package/src/views/CanvasEditor/EditorHeader.tsx +268 -0
- package/src/views/CanvasEditor/LayoutContainer.d.ts +17 -0
- package/src/views/CanvasEditor/LayoutContainer.d.ts.map +1 -0
- package/src/views/CanvasEditor/LayoutContainer.tsx +322 -0
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +13 -0
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +1 -0
- package/src/views/CanvasEditor/SaveConfirmationModal.tsx +233 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +14 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +92 -0
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts +29 -0
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts +13 -0
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +25 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +341 -0
- package/src/views/CanvasEditor/components/LibraryItem.d.ts +14 -0
- package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +80 -0
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +15 -0
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +212 -0
- package/src/views/CanvasEditor/components/index.d.ts +21 -0
- package/src/views/CanvasEditor/components/index.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/index.ts +28 -0
- package/src/views/CanvasEditor/hooks/index.d.ts +10 -0
- package/src/views/CanvasEditor/hooks/index.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/index.ts +10 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +8 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +5 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +25 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
- package/src/views/CanvasEditor/index.d.ts +16 -0
- package/src/views/CanvasEditor/index.d.ts.map +1 -0
- package/src/views/CanvasEditor/index.ts +16 -0
- package/src/views/PostManager/EmptyState.d.ts +10 -0
- package/src/views/PostManager/EmptyState.d.ts.map +1 -0
- package/src/views/PostManager/EmptyState.tsx +42 -0
- package/src/views/PostManager/PostActionsMenu.d.ts +12 -0
- package/src/views/PostManager/PostActionsMenu.d.ts.map +1 -0
- package/src/views/PostManager/PostActionsMenu.tsx +112 -0
- package/src/views/PostManager/PostCards.d.ts +15 -0
- package/src/views/PostManager/PostCards.d.ts.map +1 -0
- package/src/views/PostManager/PostCards.tsx +197 -0
- package/src/views/PostManager/PostFilters.d.ts +16 -0
- package/src/views/PostManager/PostFilters.d.ts.map +1 -0
- package/src/views/PostManager/PostFilters.tsx +95 -0
- package/src/views/PostManager/PostManagerView.d.ts +11 -0
- package/src/views/PostManager/PostManagerView.d.ts.map +1 -0
- package/src/views/PostManager/PostManagerView.tsx +289 -0
- package/src/views/PostManager/PostStats.d.ts +11 -0
- package/src/views/PostManager/PostStats.d.ts.map +1 -0
- package/src/views/PostManager/PostStats.tsx +81 -0
- package/src/views/PostManager/PostTable.d.ts +15 -0
- package/src/views/PostManager/PostTable.d.ts.map +1 -0
- package/src/views/PostManager/PostTable.tsx +230 -0
- package/src/views/PostManager/index.d.ts +12 -0
- package/src/views/PostManager/index.d.ts.map +1 -0
- package/src/views/PostManager/index.ts +15 -0
- package/src/views/Preview/PreviewBridgeView.d.ts +12 -0
- package/src/views/Preview/PreviewBridgeView.d.ts.map +1 -0
- package/src/views/Preview/PreviewBridgeView.tsx +64 -0
- package/src/views/Preview/index.d.ts +6 -0
- package/src/views/Preview/index.d.ts.map +1 -0
- package/src/views/Preview/index.ts +7 -0
- package/src/views/Settings/SettingsView.d.ts +10 -0
- package/src/views/Settings/SettingsView.d.ts.map +1 -0
- package/src/views/Settings/SettingsView.tsx +298 -0
- package/src/views/Settings/index.d.ts +6 -0
- package/src/views/Settings/index.d.ts.map +1 -0
- package/src/views/Settings/index.ts +7 -0
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts +12 -0
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -0
- package/src/views/SlugSEO/SlugSEOManagerView.tsx +94 -0
- package/src/views/SlugSEO/index.d.ts +6 -0
- package/src/views/SlugSEO/index.d.ts.map +1 -0
- package/src/views/SlugSEO/index.ts +7 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section Block
|
|
3
|
+
* Full-width wrapper with configurable padding and background
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import { BlockEditProps, BlockPreviewProps } from '../../../types/block';
|
|
7
|
+
import { Block } from '../../../types/block';
|
|
8
|
+
/**
|
|
9
|
+
* Section Block Edit Component
|
|
10
|
+
*/
|
|
11
|
+
export declare const SectionEdit: React.FC<BlockEditProps & {
|
|
12
|
+
childBlocks: Block[];
|
|
13
|
+
onChildBlockAdd: (type: string, index: number, containerId: string) => void;
|
|
14
|
+
onChildBlockUpdate: (id: string, data: Partial<Block['data']>, containerId: string) => void;
|
|
15
|
+
onChildBlockDelete: (id: string, containerId: string) => void;
|
|
16
|
+
onChildBlockMove: (id: string, newIndex: number, containerId: string) => void;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Section Block Preview Component
|
|
20
|
+
*/
|
|
21
|
+
export declare const SectionPreview: React.FC<BlockPreviewProps & {
|
|
22
|
+
childBlocks?: Block[];
|
|
23
|
+
renderChild?: (block: Block) => React.ReactNode;
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=SectionBlock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SectionBlock.d.ts","sourceRoot":"","sources":["SectionBlock.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAGzE,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE7C;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,cAAc,GAAG;IAChD,WAAW,EAAE,KAAK,EAAE,CAAC;IACrB,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5E,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5F,kBAAkB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9D,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;CACjF,CA+CI,CAAC;AAEN;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,GAAG;IACtD,WAAW,CAAC,EAAE,KAAK,EAAE,CAAC;IACtB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAC;CACnD,CAyBA,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section Block
|
|
3
|
+
* Full-width wrapper with configurable padding and background
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use client';
|
|
7
|
+
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { BlockEditProps, BlockPreviewProps } from '../../../types/block';
|
|
10
|
+
import { LayoutContainer } from '../../../views/CanvasEditor/LayoutContainer';
|
|
11
|
+
import { LAYOUT_CONSTANTS, LAYOUT_BACKGROUNDS } from '../index';
|
|
12
|
+
import { Block } from '../../../types/block';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Section Block Edit Component
|
|
16
|
+
*/
|
|
17
|
+
export const SectionEdit: React.FC<BlockEditProps & {
|
|
18
|
+
childBlocks: Block[];
|
|
19
|
+
onChildBlockAdd: (type: string, index: number, containerId: string) => void;
|
|
20
|
+
onChildBlockUpdate: (id: string, data: Partial<Block['data']>, containerId: string) => void;
|
|
21
|
+
onChildBlockDelete: (id: string, containerId: string) => void;
|
|
22
|
+
onChildBlockMove: (id: string, newIndex: number, containerId: string) => void;
|
|
23
|
+
}> = ({
|
|
24
|
+
block,
|
|
25
|
+
onUpdate,
|
|
26
|
+
isSelected,
|
|
27
|
+
childBlocks = [],
|
|
28
|
+
onChildBlockAdd,
|
|
29
|
+
onChildBlockUpdate,
|
|
30
|
+
onChildBlockDelete,
|
|
31
|
+
onChildBlockMove,
|
|
32
|
+
}) => {
|
|
33
|
+
const background = (block.data.background as keyof typeof LAYOUT_BACKGROUNDS) || 'DEFAULT';
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
className={`rounded-xl transition-all ${isSelected
|
|
38
|
+
? 'bg-primary/5'
|
|
39
|
+
: ''
|
|
40
|
+
} ${LAYOUT_BACKGROUNDS[background]}`}
|
|
41
|
+
onDragStart={(e) => {
|
|
42
|
+
// Prevent section from being dragged when dragging nested blocks
|
|
43
|
+
// Check if the drag started on a nested block wrapper
|
|
44
|
+
const nestedBlockWrapper = (e.target as HTMLElement).closest('[data-block-wrapper]');
|
|
45
|
+
if (nestedBlockWrapper) {
|
|
46
|
+
const nestedBlockId = nestedBlockWrapper.getAttribute('data-block-id');
|
|
47
|
+
// If dragging a nested block, prevent the section's drag handler from firing
|
|
48
|
+
if (nestedBlockId && nestedBlockId !== block.id) {
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
console.log('[SectionBlock] Preventing section drag, nested block is being dragged:', nestedBlockId);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
{/* Nested Content */}
|
|
57
|
+
<div className={`px-8 py-4`}>
|
|
58
|
+
<LayoutContainer
|
|
59
|
+
blocks={childBlocks}
|
|
60
|
+
containerId={block.id}
|
|
61
|
+
onBlockAdd={onChildBlockAdd}
|
|
62
|
+
onBlockUpdate={onChildBlockUpdate}
|
|
63
|
+
onBlockDelete={onChildBlockDelete}
|
|
64
|
+
onBlockMove={onChildBlockMove}
|
|
65
|
+
emptyLabel="Drop blocks into section"
|
|
66
|
+
/>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Section Block Preview Component
|
|
74
|
+
*/
|
|
75
|
+
export const SectionPreview: React.FC<BlockPreviewProps & {
|
|
76
|
+
childBlocks?: Block[];
|
|
77
|
+
renderChild?: (block: Block) => React.ReactNode;
|
|
78
|
+
}> = ({ block, childBlocks = [], renderChild, context }) => {
|
|
79
|
+
const background = (block.data.background as keyof typeof LAYOUT_BACKGROUNDS) || 'DEFAULT';
|
|
80
|
+
|
|
81
|
+
// If childBlocks are provided, use them; otherwise get from block.children
|
|
82
|
+
const children = childBlocks.length > 0
|
|
83
|
+
? childBlocks
|
|
84
|
+
: (block.children && Array.isArray(block.children) && typeof block.children[0] === 'object'
|
|
85
|
+
? block.children as Block[]
|
|
86
|
+
: []);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<section className={`w-full ${LAYOUT_BACKGROUNDS[background]}`}>
|
|
90
|
+
<div className={`max-w-7xl mx-auto px-6 py-2`}>
|
|
91
|
+
{children.length > 0 && renderChild ? (
|
|
92
|
+
children.map((childBlock) => (
|
|
93
|
+
<React.Fragment key={childBlock.id}>
|
|
94
|
+
{renderChild(childBlock)}
|
|
95
|
+
</React.Fragment>
|
|
96
|
+
))
|
|
97
|
+
) : (
|
|
98
|
+
<div className="text-gray-400 text-sm italic">Empty section</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
</section>
|
|
102
|
+
);
|
|
103
|
+
};
|
|
104
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout System Constants
|
|
3
|
+
* Standardized spacing and styling for layout blocks
|
|
4
|
+
*/
|
|
5
|
+
export declare const LAYOUT_CONSTANTS: {
|
|
6
|
+
readonly GUTTER: "2rem";
|
|
7
|
+
readonly SPACING: "4rem";
|
|
8
|
+
readonly SPACING_SM: "2rem";
|
|
9
|
+
readonly SPACING_LG: "6rem";
|
|
10
|
+
readonly BORDER_RADIUS: "2rem";
|
|
11
|
+
};
|
|
12
|
+
export declare const LAYOUT_BACKGROUNDS: {
|
|
13
|
+
readonly DEFAULT: "bg-white";
|
|
14
|
+
readonly NEUTRAL: "bg-neutral-50";
|
|
15
|
+
readonly SAGE: "bg-primary/5";
|
|
16
|
+
readonly CREAM: "bg-amber-50/50";
|
|
17
|
+
};
|
|
18
|
+
export type ColumnLayout = '50-50' | '33-66' | '66-33' | '25-25-25-25' | '25-75' | '75-25';
|
|
19
|
+
export declare const COLUMN_LAYOUTS: Record<ColumnLayout, {
|
|
20
|
+
grid: string;
|
|
21
|
+
widths: number[];
|
|
22
|
+
}>;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,eAAO,MAAM,gBAAgB;;;;;;CAMnB,CAAC;AAGX,eAAO,MAAM,kBAAkB;;;;;CAKrB,CAAC;AAGX,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,GAAG,OAAO,CAAC;AAE3F,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,YAAY,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAyBnF,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout System Constants
|
|
3
|
+
* Standardized spacing and styling for layout blocks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Spacing Constants (Earth-tone aligned)
|
|
7
|
+
export const LAYOUT_CONSTANTS = {
|
|
8
|
+
GUTTER: '2rem', // 32px - Space between columns
|
|
9
|
+
SPACING: '4rem', // 64px - Vertical padding for sections
|
|
10
|
+
SPACING_SM: '2rem', // 32px - Smaller vertical padding
|
|
11
|
+
SPACING_LG: '6rem', // 96px - Larger vertical padding
|
|
12
|
+
BORDER_RADIUS: '2rem', // 32px - Consistent rounded corners
|
|
13
|
+
} as const;
|
|
14
|
+
|
|
15
|
+
// Background Colors (Light mode only - matches client website theme)
|
|
16
|
+
export const LAYOUT_BACKGROUNDS = {
|
|
17
|
+
DEFAULT: 'bg-white',
|
|
18
|
+
NEUTRAL: 'bg-neutral-50',
|
|
19
|
+
SAGE: 'bg-primary/5',
|
|
20
|
+
CREAM: 'bg-amber-50/50',
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
// Column Layout Presets
|
|
24
|
+
export type ColumnLayout = '50-50' | '33-66' | '66-33' | '25-25-25-25' | '25-75' | '75-25';
|
|
25
|
+
|
|
26
|
+
export const COLUMN_LAYOUTS: Record<ColumnLayout, { grid: string; widths: number[] }> = {
|
|
27
|
+
'50-50': {
|
|
28
|
+
grid: 'grid-cols-2',
|
|
29
|
+
widths: [50, 50],
|
|
30
|
+
},
|
|
31
|
+
'33-66': {
|
|
32
|
+
grid: 'grid-cols-3',
|
|
33
|
+
widths: [33, 66],
|
|
34
|
+
},
|
|
35
|
+
'66-33': {
|
|
36
|
+
grid: 'grid-cols-3',
|
|
37
|
+
widths: [66, 33],
|
|
38
|
+
},
|
|
39
|
+
'25-25-25-25': {
|
|
40
|
+
grid: 'grid-cols-4',
|
|
41
|
+
widths: [25, 25, 25, 25],
|
|
42
|
+
},
|
|
43
|
+
'25-75': {
|
|
44
|
+
grid: 'grid-cols-4',
|
|
45
|
+
widths: [25, 75],
|
|
46
|
+
},
|
|
47
|
+
'75-25': {
|
|
48
|
+
grid: 'grid-cols-4',
|
|
49
|
+
widths: [75, 25],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registerLayoutBlocks.d.ts","sourceRoot":"","sources":["registerLayoutBlocks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH;;GAEG;AACH,wBAAgB,oBAAoB,SAiDnC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Register Core Layout Blocks
|
|
3
|
+
* Registers Section and Columns blocks in the block registry
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { blockRegistry } from '../../registry/BlockRegistry';
|
|
7
|
+
import { SectionEdit, SectionPreview } from './blocks/SectionBlock';
|
|
8
|
+
import { ColumnsEdit, ColumnsPreview } from './blocks/ColumnsBlock';
|
|
9
|
+
import { Columns, Square } from 'lucide-react';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Register all core layout blocks
|
|
13
|
+
*/
|
|
14
|
+
export function registerLayoutBlocks() {
|
|
15
|
+
// Section Block
|
|
16
|
+
blockRegistry.register({
|
|
17
|
+
type: 'section',
|
|
18
|
+
name: 'Section',
|
|
19
|
+
description: 'Full-width wrapper with configurable padding and background',
|
|
20
|
+
icon: Square,
|
|
21
|
+
defaultData: {
|
|
22
|
+
padding: 'md',
|
|
23
|
+
background: 'DEFAULT',
|
|
24
|
+
},
|
|
25
|
+
category: 'layout',
|
|
26
|
+
isContainer: true,
|
|
27
|
+
validate: (data) => {
|
|
28
|
+
return ['sm', 'md', 'lg'].includes(data.padding as string) &&
|
|
29
|
+
['DEFAULT', 'NEUTRAL', 'SAGE', 'CREAM'].includes(data.background as string);
|
|
30
|
+
},
|
|
31
|
+
components: {
|
|
32
|
+
Edit: SectionEdit as any,
|
|
33
|
+
Preview: SectionPreview as any,
|
|
34
|
+
Icon: Square,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Columns Block
|
|
39
|
+
blockRegistry.register({
|
|
40
|
+
type: 'columns',
|
|
41
|
+
name: 'Columns',
|
|
42
|
+
description: 'Flex/grid container with configurable column layouts (50/50, 33/66, etc.)',
|
|
43
|
+
icon: Columns,
|
|
44
|
+
defaultData: {
|
|
45
|
+
columnCount: 2, // Start with 2 columns, can be dynamically added/removed
|
|
46
|
+
columnWidths: [50, 50], // Equal width columns by default
|
|
47
|
+
},
|
|
48
|
+
category: 'layout',
|
|
49
|
+
isContainer: true,
|
|
50
|
+
validate: (data) => {
|
|
51
|
+
// Support both new dynamic system (columnCount) and legacy layout system
|
|
52
|
+
if (data.columnCount !== undefined) {
|
|
53
|
+
return typeof data.columnCount === 'number' && data.columnCount > 0 && data.columnCount <= 6;
|
|
54
|
+
}
|
|
55
|
+
return ['50-50', '33-66', '66-33', '25-25-25-25', '25-75', '75-25'].includes(data.layout as string);
|
|
56
|
+
},
|
|
57
|
+
components: {
|
|
58
|
+
Edit: ColumnsEdit as any,
|
|
59
|
+
Preview: ColumnsPreview as any,
|
|
60
|
+
Icon: Columns,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Mapper
|
|
3
|
+
* Converts between API format (MongoDB) and BlogPost format
|
|
4
|
+
*/
|
|
5
|
+
import { BlogPost, PostStatus, SEOMetadata, PostMetadata } from '../../types/post';
|
|
6
|
+
import { Block } from '../../types/block';
|
|
7
|
+
/**
|
|
8
|
+
* API Blog Document Format (from MongoDB)
|
|
9
|
+
*/
|
|
10
|
+
export interface APIBlogDocument {
|
|
11
|
+
_id?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
title: string;
|
|
14
|
+
slug: string;
|
|
15
|
+
contentBlocks?: Block[];
|
|
16
|
+
content?: any[];
|
|
17
|
+
summary?: string;
|
|
18
|
+
image?: {
|
|
19
|
+
id?: string;
|
|
20
|
+
src?: string;
|
|
21
|
+
alt?: string;
|
|
22
|
+
isCustom?: boolean;
|
|
23
|
+
};
|
|
24
|
+
categoryTags?: {
|
|
25
|
+
category?: string;
|
|
26
|
+
tags?: string[];
|
|
27
|
+
};
|
|
28
|
+
publicationData?: {
|
|
29
|
+
status?: PostStatus | 'concept';
|
|
30
|
+
date?: string | Date;
|
|
31
|
+
};
|
|
32
|
+
seo?: {
|
|
33
|
+
title?: string;
|
|
34
|
+
description?: string;
|
|
35
|
+
keywords?: string[];
|
|
36
|
+
ogImage?: string;
|
|
37
|
+
canonicalUrl?: string;
|
|
38
|
+
};
|
|
39
|
+
authorId?: string;
|
|
40
|
+
createdAt?: string | Date;
|
|
41
|
+
updatedAt?: string | Date;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Convert API document to BlogPost format
|
|
45
|
+
*/
|
|
46
|
+
export declare function apiToBlogPost(doc: APIBlogDocument): BlogPost;
|
|
47
|
+
/**
|
|
48
|
+
* Convert BlogPost to API document format
|
|
49
|
+
*/
|
|
50
|
+
export declare function blogPostToAPI(post: BlogPost, authorId?: string): Partial<APIBlogDocument>;
|
|
51
|
+
/**
|
|
52
|
+
* Convert EditorState to API format for saving
|
|
53
|
+
* @param state - Editor state
|
|
54
|
+
* @param authorId - Optional author ID
|
|
55
|
+
* @param heroBlock - Optional hero block (stored separately from content blocks)
|
|
56
|
+
*/
|
|
57
|
+
export declare function editorStateToAPI(state: {
|
|
58
|
+
title: string;
|
|
59
|
+
slug: string;
|
|
60
|
+
blocks: Block[];
|
|
61
|
+
seo: SEOMetadata;
|
|
62
|
+
metadata: PostMetadata;
|
|
63
|
+
status: PostStatus;
|
|
64
|
+
postId?: string | null;
|
|
65
|
+
}, authorId?: string, heroBlock?: Block | null): Partial<APIBlogDocument>;
|
|
66
|
+
//# sourceMappingURL=apiMapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiMapper.d.ts","sourceRoot":"","sources":["apiMapper.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,KAAK,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE;QACJ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,OAAO,CAAC;KAGtB,CAAC;IACF,YAAY,CAAC,EAAE;QACX,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,eAAe,CAAC,EAAE;QACd,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;QAChC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACxB,CAAC;IACF,GAAG,CAAC,EAAE;QACF,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,CAAC,EAAE,MAAM,CAAC;KACzB,CAAC;IACF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,eAAe,GAAG,QAAQ,CAmE5D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAgCzF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,GAAG,EAAE,WAAW,CAAC;IACjB,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,OAAO,CAAC,eAAe,CAAC,CAkFxE"}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Mapper
|
|
3
|
+
* Converts between API format (MongoDB) and BlogPost format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { BlogPost, PostStatus, SEOMetadata, PostMetadata } from '../../types/post';
|
|
7
|
+
import { Block } from '../../types/block';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* API Blog Document Format (from MongoDB)
|
|
11
|
+
*/
|
|
12
|
+
export interface APIBlogDocument {
|
|
13
|
+
_id?: string;
|
|
14
|
+
id?: string;
|
|
15
|
+
title: string;
|
|
16
|
+
slug: string;
|
|
17
|
+
contentBlocks?: Block[]; // New block-based format
|
|
18
|
+
content?: any[]; // Legacy format
|
|
19
|
+
summary?: string;
|
|
20
|
+
image?: {
|
|
21
|
+
id?: string; // Semantic ID (preferred) - plugin-images handles everything else
|
|
22
|
+
src?: string; // Legacy support - will be converted to id when loading
|
|
23
|
+
alt?: string;
|
|
24
|
+
isCustom?: boolean;
|
|
25
|
+
// Transform fields (brightness, blur, scale, positionX, positionY) are NOT stored here
|
|
26
|
+
// They are handled by plugin-images API only
|
|
27
|
+
};
|
|
28
|
+
categoryTags?: {
|
|
29
|
+
category?: string;
|
|
30
|
+
tags?: string[];
|
|
31
|
+
};
|
|
32
|
+
publicationData?: {
|
|
33
|
+
status?: PostStatus | 'concept'; // API uses 'concept' instead of 'draft'
|
|
34
|
+
date?: string | Date;
|
|
35
|
+
};
|
|
36
|
+
seo?: {
|
|
37
|
+
title?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
keywords?: string[];
|
|
40
|
+
ogImage?: string;
|
|
41
|
+
canonicalUrl?: string;
|
|
42
|
+
};
|
|
43
|
+
authorId?: string;
|
|
44
|
+
createdAt?: string | Date;
|
|
45
|
+
updatedAt?: string | Date;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Convert API document to BlogPost format
|
|
50
|
+
*/
|
|
51
|
+
export function apiToBlogPost(doc: APIBlogDocument): BlogPost {
|
|
52
|
+
const id = doc._id?.toString() || doc.id || '';
|
|
53
|
+
|
|
54
|
+
// Use contentBlocks if available, otherwise fallback to content (legacy)
|
|
55
|
+
// Hero block is included in contentBlocks
|
|
56
|
+
const blocks = doc.contentBlocks || [];
|
|
57
|
+
|
|
58
|
+
// Convert publication data
|
|
59
|
+
const publicationDate = doc.publicationData?.date
|
|
60
|
+
? (typeof doc.publicationData.date === 'string'
|
|
61
|
+
? doc.publicationData.date
|
|
62
|
+
: doc.publicationData.date.toISOString())
|
|
63
|
+
: undefined;
|
|
64
|
+
|
|
65
|
+
// Convert SEO data
|
|
66
|
+
const seo: SEOMetadata = {
|
|
67
|
+
title: doc.seo?.title,
|
|
68
|
+
description: doc.seo?.description,
|
|
69
|
+
keywords: doc.seo?.keywords,
|
|
70
|
+
ogImage: doc.seo?.ogImage,
|
|
71
|
+
canonicalUrl: doc.seo?.canonicalUrl,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Convert metadata
|
|
75
|
+
// Only store semantic ID (id) and alt - plugin-images handles everything else
|
|
76
|
+
const metadata: PostMetadata = {
|
|
77
|
+
featuredImage: doc.image ? {
|
|
78
|
+
// Prefer id (semantic ID) over src (legacy)
|
|
79
|
+
id: doc.image.id || doc.image.src,
|
|
80
|
+
alt: doc.image.alt,
|
|
81
|
+
isCustom: doc.image.isCustom,
|
|
82
|
+
// Don't load transform fields - plugin-images handles those
|
|
83
|
+
} : undefined,
|
|
84
|
+
categories: doc.categoryTags?.category ? [doc.categoryTags.category] : [],
|
|
85
|
+
tags: doc.categoryTags?.tags || [],
|
|
86
|
+
excerpt: doc.summary,
|
|
87
|
+
privacy: undefined, // Privacy settings not in API yet
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Convert publication data - API uses 'concept' but we use 'draft'
|
|
91
|
+
const apiStatus = doc.publicationData?.status || 'concept';
|
|
92
|
+
const normalizedStatus = apiStatus === 'concept' ? 'draft' : apiStatus;
|
|
93
|
+
|
|
94
|
+
const publication = {
|
|
95
|
+
status: normalizedStatus as PostStatus,
|
|
96
|
+
date: publicationDate,
|
|
97
|
+
authorId: doc.authorId,
|
|
98
|
+
updatedAt: doc.updatedAt
|
|
99
|
+
? (typeof doc.updatedAt === 'string' ? doc.updatedAt : doc.updatedAt.toISOString())
|
|
100
|
+
: undefined,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
title: doc.title,
|
|
106
|
+
slug: doc.slug,
|
|
107
|
+
blocks,
|
|
108
|
+
seo,
|
|
109
|
+
publication,
|
|
110
|
+
metadata,
|
|
111
|
+
createdAt: doc.createdAt
|
|
112
|
+
? (typeof doc.createdAt === 'string' ? doc.createdAt : doc.createdAt.toISOString())
|
|
113
|
+
: new Date().toISOString(),
|
|
114
|
+
updatedAt: doc.updatedAt
|
|
115
|
+
? (typeof doc.updatedAt === 'string' ? doc.updatedAt : doc.updatedAt.toISOString())
|
|
116
|
+
: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Convert BlogPost to API document format
|
|
122
|
+
*/
|
|
123
|
+
export function blogPostToAPI(post: BlogPost, authorId?: string): Partial<APIBlogDocument> {
|
|
124
|
+
return {
|
|
125
|
+
title: post.title,
|
|
126
|
+
slug: post.slug,
|
|
127
|
+
contentBlocks: post.blocks, // Use new block format
|
|
128
|
+
summary: post.metadata.excerpt,
|
|
129
|
+
// Only save semantic ID (id) and alt - plugin-images handles transform data
|
|
130
|
+
image: post.metadata.featuredImage ? {
|
|
131
|
+
id: post.metadata.featuredImage.id,
|
|
132
|
+
alt: post.metadata.featuredImage.alt,
|
|
133
|
+
isCustom: post.metadata.featuredImage.isCustom,
|
|
134
|
+
// Don't save transform fields - plugin-images API handles those
|
|
135
|
+
} : undefined,
|
|
136
|
+
categoryTags: {
|
|
137
|
+
category: post.metadata.categories?.[0] || '',
|
|
138
|
+
tags: post.metadata.tags || [],
|
|
139
|
+
},
|
|
140
|
+
publicationData: {
|
|
141
|
+
// API uses 'concept' instead of 'draft'
|
|
142
|
+
status: post.publication.status === 'draft' ? 'concept' : post.publication.status,
|
|
143
|
+
date: post.publication.date ? new Date(post.publication.date) : new Date(),
|
|
144
|
+
},
|
|
145
|
+
seo: {
|
|
146
|
+
title: post.seo.title,
|
|
147
|
+
description: post.seo.description,
|
|
148
|
+
keywords: post.seo.keywords,
|
|
149
|
+
ogImage: post.seo.ogImage,
|
|
150
|
+
canonicalUrl: post.seo.canonicalUrl,
|
|
151
|
+
},
|
|
152
|
+
authorId: authorId || post.publication.authorId,
|
|
153
|
+
updatedAt: new Date(),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Convert EditorState to API format for saving
|
|
159
|
+
* @param state - Editor state
|
|
160
|
+
* @param authorId - Optional author ID
|
|
161
|
+
* @param heroBlock - Optional hero block (stored separately from content blocks)
|
|
162
|
+
*/
|
|
163
|
+
export function editorStateToAPI(state: {
|
|
164
|
+
title: string;
|
|
165
|
+
slug: string;
|
|
166
|
+
blocks: Block[];
|
|
167
|
+
seo: SEOMetadata;
|
|
168
|
+
metadata: PostMetadata;
|
|
169
|
+
status: PostStatus;
|
|
170
|
+
postId?: string | null;
|
|
171
|
+
}, authorId?: string, heroBlock?: Block | null): Partial<APIBlogDocument> {
|
|
172
|
+
// Map status: draft -> concept, published -> published, everything else stays as-is
|
|
173
|
+
const apiStatus = state.status === 'draft' ? 'concept' : state.status;
|
|
174
|
+
|
|
175
|
+
console.log('[editorStateToAPI] Mapping status:', {
|
|
176
|
+
editorStatus: state.status,
|
|
177
|
+
apiStatus: apiStatus,
|
|
178
|
+
willBePublished: apiStatus === 'published'
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Try to get category from metadata first, then check hero block
|
|
182
|
+
let category: string | undefined = undefined;
|
|
183
|
+
if (state.metadata.categories && state.metadata.categories.length > 0 && state.metadata.categories[0]?.trim()) {
|
|
184
|
+
category = state.metadata.categories[0].trim();
|
|
185
|
+
} else {
|
|
186
|
+
// Check hero block for category - use the passed heroBlock parameter first, then check state.blocks
|
|
187
|
+
const heroBlockToCheck = heroBlock || state.blocks.find(block => block.type === 'hero');
|
|
188
|
+
if (heroBlockToCheck && heroBlockToCheck.data && typeof heroBlockToCheck.data === 'object') {
|
|
189
|
+
const heroCategory = (heroBlockToCheck.data as any).category;
|
|
190
|
+
if (heroCategory && typeof heroCategory === 'string' && heroCategory.trim()) {
|
|
191
|
+
category = heroCategory.trim();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log('[editorStateToAPI] Category resolution:', {
|
|
197
|
+
fromMetadata: state.metadata.categories?.[0],
|
|
198
|
+
fromHeroBlock: (heroBlock || state.blocks.find(b => b.type === 'hero'))?.data ? ((heroBlock || state.blocks.find(b => b.type === 'hero'))!.data as any).category : undefined,
|
|
199
|
+
finalCategory: category,
|
|
200
|
+
hasHeroBlock: !!heroBlock,
|
|
201
|
+
heroBlockImage: heroBlock?.data ? (heroBlock.data as any)?.image : undefined,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Include hero block in contentBlocks if it exists
|
|
205
|
+
// Filter out any existing hero blocks from state.blocks first, then add the current hero block
|
|
206
|
+
const contentBlocksWithoutHero = state.blocks.filter(block => block.type !== 'hero');
|
|
207
|
+
const allBlocks = heroBlock
|
|
208
|
+
? [heroBlock, ...contentBlocksWithoutHero]
|
|
209
|
+
: contentBlocksWithoutHero;
|
|
210
|
+
|
|
211
|
+
console.log('[editorStateToAPI] Hero block details:', {
|
|
212
|
+
hasHeroBlock: !!heroBlock,
|
|
213
|
+
heroBlockType: heroBlock?.type,
|
|
214
|
+
heroBlockId: heroBlock?.id,
|
|
215
|
+
heroBlockImage: heroBlock?.data ? (heroBlock.data as any)?.image : undefined,
|
|
216
|
+
heroBlockImageSrc: heroBlock?.data ? (heroBlock.data as any)?.image?.src : undefined,
|
|
217
|
+
contentBlocksCount: allBlocks.length,
|
|
218
|
+
contentBlocksTypes: allBlocks.map(b => b.type),
|
|
219
|
+
heroBlockInContentBlocks: allBlocks.find(b => b.type === 'hero')?.data ? (allBlocks.find(b => b.type === 'hero')!.data as any)?.image : undefined,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
title: state.title,
|
|
224
|
+
slug: state.slug,
|
|
225
|
+
contentBlocks: allBlocks,
|
|
226
|
+
summary: state.metadata.excerpt,
|
|
227
|
+
// Only save semantic ID (id) and alt - plugin-images handles transform data
|
|
228
|
+
// Only create image object if id exists and is not empty
|
|
229
|
+
image: state.metadata.featuredImage?.id?.trim() ? {
|
|
230
|
+
id: state.metadata.featuredImage.id.trim(),
|
|
231
|
+
alt: state.metadata.featuredImage.alt || '',
|
|
232
|
+
isCustom: state.metadata.featuredImage.isCustom,
|
|
233
|
+
// Don't save transform fields - plugin-images API handles those
|
|
234
|
+
} : undefined,
|
|
235
|
+
categoryTags: {
|
|
236
|
+
category: category,
|
|
237
|
+
tags: state.metadata.tags || [],
|
|
238
|
+
},
|
|
239
|
+
publicationData: {
|
|
240
|
+
status: apiStatus,
|
|
241
|
+
date: new Date(),
|
|
242
|
+
},
|
|
243
|
+
seo: {
|
|
244
|
+
title: state.seo.title,
|
|
245
|
+
description: state.seo.description,
|
|
246
|
+
keywords: state.seo.keywords,
|
|
247
|
+
ogImage: state.seo.ogImage,
|
|
248
|
+
canonicalUrl: state.seo.canonicalUrl,
|
|
249
|
+
},
|
|
250
|
+
authorId,
|
|
251
|
+
updatedAt: new Date(),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|