@jhits/plugin-blog 0.0.15 → 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.
Files changed (208) hide show
  1. package/package.json +5 -4
  2. package/src/api/categories.ts +43 -0
  3. package/src/api/check-title.ts +60 -0
  4. package/src/api/config-handler.ts +76 -0
  5. package/src/api/handler.ts +418 -0
  6. package/src/api/index.ts +33 -0
  7. package/src/api/route.ts +116 -0
  8. package/src/api/router.ts +128 -0
  9. package/src/api-server.ts +11 -0
  10. package/src/config.ts +161 -0
  11. package/src/hooks/index.d.ts +8 -0
  12. package/src/hooks/index.d.ts.map +1 -0
  13. package/src/hooks/index.ts +9 -0
  14. package/src/hooks/useBlog.d.ts +31 -0
  15. package/src/hooks/useBlog.d.ts.map +1 -0
  16. package/src/hooks/useBlog.ts +85 -0
  17. package/src/hooks/useBlogs.d.ts +39 -0
  18. package/src/hooks/useBlogs.d.ts.map +1 -0
  19. package/src/hooks/useBlogs.ts +123 -0
  20. package/src/hooks/useCategories.d.ts +9 -0
  21. package/src/hooks/useCategories.d.ts.map +1 -0
  22. package/src/hooks/useCategories.ts +76 -0
  23. package/src/index.server.ts +14 -0
  24. package/src/index.tsx +335 -0
  25. package/src/init.tsx +63 -0
  26. package/src/lib/blocks/BlockRenderer.d.ts +54 -0
  27. package/src/lib/blocks/BlockRenderer.d.ts.map +1 -0
  28. package/src/lib/blocks/BlockRenderer.tsx +141 -0
  29. package/src/lib/blocks/index.ts +6 -0
  30. package/src/lib/config-storage.d.ts +30 -0
  31. package/src/lib/config-storage.d.ts.map +1 -0
  32. package/src/lib/config-storage.ts +65 -0
  33. package/src/lib/index.ts +9 -0
  34. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +25 -0
  35. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -0
  36. package/src/lib/layouts/blocks/ColumnsBlock.tsx +298 -0
  37. package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
  38. package/src/lib/layouts/blocks/SectionBlock.d.ts +25 -0
  39. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +1 -0
  40. package/src/lib/layouts/blocks/SectionBlock.tsx +104 -0
  41. package/src/lib/layouts/blocks/index.ts +8 -0
  42. package/src/lib/layouts/index.d.ts +23 -0
  43. package/src/lib/layouts/index.d.ts.map +1 -0
  44. package/src/lib/layouts/index.ts +52 -0
  45. package/src/lib/layouts/registerLayoutBlocks.d.ts +9 -0
  46. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +1 -0
  47. package/src/lib/layouts/registerLayoutBlocks.ts +64 -0
  48. package/src/lib/mappers/apiMapper.d.ts +66 -0
  49. package/src/lib/mappers/apiMapper.d.ts.map +1 -0
  50. package/src/lib/mappers/apiMapper.ts +254 -0
  51. package/src/lib/migration/index.ts +6 -0
  52. package/src/lib/migration/mapper.ts +140 -0
  53. package/src/lib/rich-text/RichTextEditor.d.ts +45 -0
  54. package/src/lib/rich-text/RichTextEditor.d.ts.map +1 -0
  55. package/src/lib/rich-text/RichTextEditor.tsx +826 -0
  56. package/src/lib/rich-text/RichTextPreview.d.ts +16 -0
  57. package/src/lib/rich-text/RichTextPreview.d.ts.map +1 -0
  58. package/src/lib/rich-text/RichTextPreview.tsx +210 -0
  59. package/src/lib/rich-text/index.d.ts +9 -0
  60. package/src/lib/rich-text/index.d.ts.map +1 -0
  61. package/src/lib/rich-text/index.ts +10 -0
  62. package/src/lib/utils/blockHelpers.d.ts +23 -0
  63. package/src/lib/utils/blockHelpers.d.ts.map +1 -0
  64. package/src/lib/utils/blockHelpers.ts +72 -0
  65. package/src/lib/utils/configValidation.d.ts +23 -0
  66. package/src/lib/utils/configValidation.d.ts.map +1 -0
  67. package/src/lib/utils/configValidation.ts +137 -0
  68. package/src/lib/utils/index.ts +8 -0
  69. package/src/lib/utils/slugify.ts +79 -0
  70. package/src/registry/BlockRegistry.d.ts +62 -0
  71. package/src/registry/BlockRegistry.d.ts.map +1 -0
  72. package/src/registry/BlockRegistry.ts +139 -0
  73. package/src/registry/index.d.ts +6 -0
  74. package/src/registry/index.d.ts.map +1 -0
  75. package/src/registry/index.ts +11 -0
  76. package/src/state/EditorContext.d.ts +45 -0
  77. package/src/state/EditorContext.d.ts.map +1 -0
  78. package/src/state/EditorContext.tsx +283 -0
  79. package/src/state/index.d.ts +7 -0
  80. package/src/state/index.d.ts.map +1 -0
  81. package/src/state/index.ts +8 -0
  82. package/src/state/reducer.d.ts +11 -0
  83. package/src/state/reducer.d.ts.map +1 -0
  84. package/src/state/reducer.ts +694 -0
  85. package/src/state/types.d.ts +162 -0
  86. package/src/state/types.d.ts.map +1 -0
  87. package/src/state/types.ts +160 -0
  88. package/src/types/block.d.ts +221 -0
  89. package/src/types/block.d.ts.map +1 -0
  90. package/src/types/block.ts +269 -0
  91. package/src/types/index.d.ts +8 -0
  92. package/src/types/index.d.ts.map +1 -0
  93. package/src/types/index.ts +17 -0
  94. package/src/types/post.d.ts +136 -0
  95. package/src/types/post.d.ts.map +1 -0
  96. package/src/types/post.ts +169 -0
  97. package/src/utils/client.d.ts +48 -0
  98. package/src/utils/client.d.ts.map +1 -0
  99. package/src/utils/client.ts +122 -0
  100. package/src/utils/index.ts +7 -0
  101. package/src/views/CanvasEditor/BlockWrapper.d.ts +16 -0
  102. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
  103. package/src/views/CanvasEditor/BlockWrapper.tsx +522 -0
  104. package/src/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
  105. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
  106. package/src/views/CanvasEditor/CanvasEditorView.tsx +337 -0
  107. package/src/views/CanvasEditor/EditorBody.d.ts +22 -0
  108. package/src/views/CanvasEditor/EditorBody.d.ts.map +1 -0
  109. package/src/views/CanvasEditor/EditorBody.tsx +665 -0
  110. package/src/views/CanvasEditor/EditorHeader.d.ts +18 -0
  111. package/src/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
  112. package/src/views/CanvasEditor/EditorHeader.tsx +268 -0
  113. package/src/views/CanvasEditor/LayoutContainer.d.ts +17 -0
  114. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +1 -0
  115. package/src/views/CanvasEditor/LayoutContainer.tsx +322 -0
  116. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +13 -0
  117. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +1 -0
  118. package/src/views/CanvasEditor/SaveConfirmationModal.tsx +233 -0
  119. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +14 -0
  120. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
  121. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +92 -0
  122. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +29 -0
  123. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
  124. package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
  125. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
  126. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
  127. package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
  128. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +13 -0
  129. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
  130. package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
  131. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
  132. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
  133. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  134. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +25 -0
  135. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -0
  136. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +341 -0
  137. package/src/views/CanvasEditor/components/LibraryItem.d.ts +14 -0
  138. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
  139. package/src/views/CanvasEditor/components/LibraryItem.tsx +80 -0
  140. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +15 -0
  141. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -0
  142. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +212 -0
  143. package/src/views/CanvasEditor/components/index.d.ts +21 -0
  144. package/src/views/CanvasEditor/components/index.d.ts.map +1 -0
  145. package/src/views/CanvasEditor/components/index.ts +28 -0
  146. package/src/views/CanvasEditor/hooks/index.d.ts +10 -0
  147. package/src/views/CanvasEditor/hooks/index.d.ts.map +1 -0
  148. package/src/views/CanvasEditor/hooks/index.ts +10 -0
  149. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +8 -0
  150. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -0
  151. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
  152. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
  153. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  154. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
  155. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +5 -0
  156. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -0
  157. package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
  158. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
  159. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
  160. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
  161. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +25 -0
  162. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +1 -0
  163. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
  164. package/src/views/CanvasEditor/index.d.ts +16 -0
  165. package/src/views/CanvasEditor/index.d.ts.map +1 -0
  166. package/src/views/CanvasEditor/index.ts +16 -0
  167. package/src/views/PostManager/EmptyState.d.ts +10 -0
  168. package/src/views/PostManager/EmptyState.d.ts.map +1 -0
  169. package/src/views/PostManager/EmptyState.tsx +42 -0
  170. package/src/views/PostManager/PostActionsMenu.d.ts +12 -0
  171. package/src/views/PostManager/PostActionsMenu.d.ts.map +1 -0
  172. package/src/views/PostManager/PostActionsMenu.tsx +112 -0
  173. package/src/views/PostManager/PostCards.d.ts +15 -0
  174. package/src/views/PostManager/PostCards.d.ts.map +1 -0
  175. package/src/views/PostManager/PostCards.tsx +197 -0
  176. package/src/views/PostManager/PostFilters.d.ts +16 -0
  177. package/src/views/PostManager/PostFilters.d.ts.map +1 -0
  178. package/src/views/PostManager/PostFilters.tsx +95 -0
  179. package/src/views/PostManager/PostManagerView.d.ts +11 -0
  180. package/src/views/PostManager/PostManagerView.d.ts.map +1 -0
  181. package/src/views/PostManager/PostManagerView.tsx +289 -0
  182. package/src/views/PostManager/PostStats.d.ts +11 -0
  183. package/src/views/PostManager/PostStats.d.ts.map +1 -0
  184. package/src/views/PostManager/PostStats.tsx +81 -0
  185. package/src/views/PostManager/PostTable.d.ts +15 -0
  186. package/src/views/PostManager/PostTable.d.ts.map +1 -0
  187. package/src/views/PostManager/PostTable.tsx +230 -0
  188. package/src/views/PostManager/index.d.ts +12 -0
  189. package/src/views/PostManager/index.d.ts.map +1 -0
  190. package/src/views/PostManager/index.ts +15 -0
  191. package/src/views/Preview/PreviewBridgeView.d.ts +12 -0
  192. package/src/views/Preview/PreviewBridgeView.d.ts.map +1 -0
  193. package/src/views/Preview/PreviewBridgeView.tsx +64 -0
  194. package/src/views/Preview/index.d.ts +6 -0
  195. package/src/views/Preview/index.d.ts.map +1 -0
  196. package/src/views/Preview/index.ts +7 -0
  197. package/src/views/Settings/SettingsView.d.ts +10 -0
  198. package/src/views/Settings/SettingsView.d.ts.map +1 -0
  199. package/src/views/Settings/SettingsView.tsx +298 -0
  200. package/src/views/Settings/index.d.ts +6 -0
  201. package/src/views/Settings/index.d.ts.map +1 -0
  202. package/src/views/Settings/index.ts +7 -0
  203. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +12 -0
  204. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -0
  205. package/src/views/SlugSEO/SlugSEOManagerView.tsx +94 -0
  206. package/src/views/SlugSEO/index.d.ts +6 -0
  207. package/src/views/SlugSEO/index.d.ts.map +1 -0
  208. 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,8 @@
1
+ /**
2
+ * Core Layout Blocks
3
+ * Section and Columns blocks for the Universal Layout System
4
+ */
5
+
6
+ export { SectionEdit, SectionPreview } from './SectionBlock';
7
+ export { ColumnsEdit, ColumnsPreview } from './ColumnsBlock';
8
+
@@ -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,9 @@
1
+ /**
2
+ * Register Core Layout Blocks
3
+ * Registers Section and Columns blocks in the block registry
4
+ */
5
+ /**
6
+ * Register all core layout blocks
7
+ */
8
+ export declare function registerLayoutBlocks(): void;
9
+ //# sourceMappingURL=registerLayoutBlocks.d.ts.map
@@ -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
+
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Migration utilities exports
3
+ */
4
+
5
+ export * from './mapper';
6
+