@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.
Files changed (291) hide show
  1. package/dist/api/categories.d.ts.map +1 -1
  2. package/dist/api/categories.js +42 -38
  3. package/dist/api/handler.d.ts +1 -26
  4. package/dist/api/handler.d.ts.map +1 -1
  5. package/dist/api/handler.js +81 -490
  6. package/dist/api/router.d.ts +0 -5
  7. package/dist/api/router.d.ts.map +1 -1
  8. package/dist/api/router.js +8 -35
  9. package/dist/api/service.d.ts +80 -0
  10. package/dist/api/service.d.ts.map +1 -0
  11. package/dist/api/service.js +219 -0
  12. package/dist/hooks/useAutoSave.d.ts +10 -0
  13. package/dist/hooks/useAutoSave.d.ts.map +1 -0
  14. package/dist/hooks/useAutoSave.js +57 -0
  15. package/dist/hooks/useCategories.d.ts +1 -1
  16. package/dist/hooks/useCategories.d.ts.map +1 -1
  17. package/dist/hooks/useCategories.js +15 -46
  18. package/dist/index.d.ts +24 -31
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +44 -201
  21. package/dist/init.d.ts +20 -7
  22. package/dist/init.d.ts.map +1 -1
  23. package/dist/init.js +8 -7
  24. package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
  25. package/dist/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -1
  26. package/dist/lib/layouts/blocks/ColumnsBlock.js +30 -113
  27. package/dist/lib/layouts/blocks/SectionBlock.d.ts.map +1 -1
  28. package/dist/lib/layouts/blocks/SectionBlock.js +9 -21
  29. package/dist/lib/layouts/index.d.ts +3 -3
  30. package/dist/lib/layouts/index.js +4 -4
  31. package/dist/lib/mappers/apiMapper.d.ts +10 -0
  32. package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
  33. package/dist/lib/mappers/apiMapper.js +47 -32
  34. package/dist/lib/rich-text/RichTextEditor.d.ts +4 -2
  35. package/dist/lib/rich-text/RichTextEditor.d.ts.map +1 -1
  36. package/dist/lib/rich-text/RichTextEditor.js +12 -9
  37. package/dist/lib/utils/config-resolver.d.ts +28 -0
  38. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  39. package/dist/lib/utils/config-resolver.js +46 -0
  40. package/dist/lib/utils/tree.d.ts +29 -0
  41. package/dist/lib/utils/tree.d.ts.map +1 -0
  42. package/dist/lib/utils/tree.js +129 -0
  43. package/dist/state/EditorContext.d.ts +3 -25
  44. package/dist/state/EditorContext.d.ts.map +1 -1
  45. package/dist/state/EditorContext.js +124 -174
  46. package/dist/state/reducer.d.ts +1 -5
  47. package/dist/state/reducer.d.ts.map +1 -1
  48. package/dist/state/reducer.js +128 -521
  49. package/dist/state/types.d.ts +12 -1
  50. package/dist/state/types.d.ts.map +1 -1
  51. package/dist/types/block.d.ts +9 -0
  52. package/dist/types/block.d.ts.map +1 -1
  53. package/dist/types/post.d.ts +17 -1
  54. package/dist/types/post.d.ts.map +1 -1
  55. package/dist/views/CanvasEditor/BlockWrapper.d.ts +5 -6
  56. package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
  57. package/dist/views/CanvasEditor/BlockWrapper.js +56 -264
  58. package/dist/views/CanvasEditor/CanvasEditorView.d.ts +5 -3
  59. package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
  60. package/dist/views/CanvasEditor/CanvasEditorView.js +55 -315
  61. package/dist/views/CanvasEditor/EditorBody.d.ts +6 -8
  62. package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
  63. package/dist/views/CanvasEditor/EditorBody.js +34 -482
  64. package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
  65. package/dist/views/CanvasEditor/EditorHeader.js +27 -63
  66. package/dist/views/CanvasEditor/LayoutContainer.d.ts.map +1 -1
  67. package/dist/views/CanvasEditor/LayoutContainer.js +49 -70
  68. package/dist/views/CanvasEditor/components/CustomBlockItem.js +1 -1
  69. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +15 -3
  70. package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
  71. package/dist/views/CanvasEditor/components/EditorCanvas.js +40 -18
  72. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +5 -1
  73. package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -1
  74. package/dist/views/CanvasEditor/components/EditorLibrary.js +11 -7
  75. package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
  76. package/dist/views/CanvasEditor/components/EditorSidebar.js +32 -14
  77. package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -6
  78. package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -1
  79. package/dist/views/CanvasEditor/components/FeaturedMediaSection.js +17 -128
  80. package/dist/views/CanvasEditor/components/JSONInspector.d.ts +9 -0
  81. package/dist/views/CanvasEditor/components/JSONInspector.d.ts.map +1 -0
  82. package/dist/views/CanvasEditor/components/JSONInspector.js +56 -0
  83. package/dist/views/CanvasEditor/components/LibraryItem.js +2 -2
  84. package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -4
  85. package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -1
  86. package/dist/views/CanvasEditor/components/PrivacySettingsSection.js +6 -28
  87. package/dist/views/CanvasEditor/components/index.d.ts +2 -0
  88. package/dist/views/CanvasEditor/components/index.d.ts.map +1 -1
  89. package/dist/views/CanvasEditor/components/index.js +1 -0
  90. package/dist/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -1
  91. package/dist/views/CanvasEditor/hooks/useHeroBlock.js +15 -18
  92. package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +3 -0
  93. package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
  94. package/dist/views/CanvasEditor/hooks/usePostLoader.js +12 -13
  95. package/dist/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -4
  96. package/dist/views/PostManager/EmptyState.d.ts +1 -1
  97. package/dist/views/PostManager/EmptyState.js +4 -4
  98. package/dist/views/PostManager/FilterDropdown.d.ts +21 -0
  99. package/dist/views/PostManager/FilterDropdown.d.ts.map +1 -0
  100. package/dist/views/PostManager/FilterDropdown.js +28 -0
  101. package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -1
  102. package/dist/views/PostManager/LanguageFlags.js +4 -1
  103. package/dist/views/PostManager/PostCards.d.ts.map +1 -1
  104. package/dist/views/PostManager/PostCards.js +23 -40
  105. package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
  106. package/dist/views/PostManager/PostFilters.js +34 -3
  107. package/dist/views/PostManager/PostManagerView.d.ts +1 -2
  108. package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
  109. package/dist/views/PostManager/PostManagerView.js +30 -96
  110. package/dist/views/PostManager/PostStats.d.ts.map +1 -1
  111. package/dist/views/PostManager/PostStats.js +10 -10
  112. package/dist/views/PostManager/PostTable.d.ts.map +1 -1
  113. package/dist/views/PostManager/PostTable.js +23 -40
  114. package/dist/views/Settings/SettingsView.d.ts +1 -1
  115. package/dist/views/Settings/SettingsView.d.ts.map +1 -1
  116. package/dist/views/Settings/SettingsView.js +12 -39
  117. package/dist/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -1
  118. package/dist/views/SlugSEO/SlugSEOManagerView.js +2 -2
  119. package/package.json +42 -6
  120. package/src/api/categories.ts +48 -52
  121. package/src/api/handler.ts +87 -594
  122. package/src/api/router.ts +15 -65
  123. package/src/api/service.ts +241 -0
  124. package/src/hooks/useAutoSave.ts +64 -0
  125. package/src/hooks/useCategories.ts +19 -47
  126. package/src/index.tsx +79 -293
  127. package/src/init.tsx +24 -11
  128. package/src/lib/blocks/BlockRenderer.tsx +1 -0
  129. package/src/lib/layouts/blocks/ColumnsBlock.tsx +60 -173
  130. package/src/lib/layouts/blocks/SectionBlock.tsx +22 -26
  131. package/src/lib/layouts/index.ts +4 -4
  132. package/src/lib/mappers/apiMapper.ts +63 -32
  133. package/src/lib/rich-text/RichTextEditor.tsx +16 -9
  134. package/src/lib/utils/config-resolver.ts +64 -0
  135. package/src/lib/utils/tree.ts +150 -0
  136. package/src/state/EditorContext.tsx +153 -232
  137. package/src/state/reducer.ts +141 -606
  138. package/src/state/types.ts +14 -1
  139. package/src/types/block.ts +10 -0
  140. package/src/types/post.ts +19 -1
  141. package/src/views/CanvasEditor/BlockWrapper.tsx +130 -460
  142. package/src/views/CanvasEditor/CanvasEditorView.tsx +145 -420
  143. package/src/views/CanvasEditor/EditorBody.tsx +98 -610
  144. package/src/views/CanvasEditor/EditorHeader.tsx +176 -196
  145. package/src/views/CanvasEditor/LayoutContainer.tsx +74 -89
  146. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +7 -8
  147. package/src/views/CanvasEditor/components/EditorCanvas.tsx +139 -84
  148. package/src/views/CanvasEditor/components/EditorLibrary.tsx +25 -10
  149. package/src/views/CanvasEditor/components/EditorSidebar.tsx +196 -127
  150. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +78 -210
  151. package/src/views/CanvasEditor/components/JSONInspector.tsx +125 -0
  152. package/src/views/CanvasEditor/components/LibraryItem.tsx +5 -6
  153. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +73 -124
  154. package/src/views/CanvasEditor/components/index.ts +2 -1
  155. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +15 -18
  156. package/src/views/CanvasEditor/hooks/usePostLoader.ts +21 -13
  157. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +4 -4
  158. package/src/views/PostManager/EmptyState.tsx +9 -10
  159. package/src/views/PostManager/FilterDropdown.tsx +95 -0
  160. package/src/views/PostManager/LanguageFlags.tsx +6 -2
  161. package/src/views/PostManager/PostCards.tsx +127 -133
  162. package/src/views/PostManager/PostFilters.tsx +73 -68
  163. package/src/views/PostManager/PostManagerView.tsx +132 -179
  164. package/src/views/PostManager/PostStats.tsx +21 -20
  165. package/src/views/PostManager/PostTable.tsx +137 -165
  166. package/src/views/Settings/SettingsView.tsx +64 -180
  167. package/src/views/SlugSEO/SlugSEOManagerView.tsx +59 -44
  168. package/src/hooks/index.d.ts +0 -8
  169. package/src/hooks/index.d.ts.map +0 -1
  170. package/src/hooks/useBlog.d.ts +0 -31
  171. package/src/hooks/useBlog.d.ts.map +0 -1
  172. package/src/hooks/useBlogs.d.ts +0 -39
  173. package/src/hooks/useBlogs.d.ts.map +0 -1
  174. package/src/hooks/useCategories.d.ts +0 -9
  175. package/src/hooks/useCategories.d.ts.map +0 -1
  176. package/src/lib/blocks/BlockRenderer.d.ts +0 -54
  177. package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
  178. package/src/lib/config-storage.d.ts +0 -30
  179. package/src/lib/config-storage.d.ts.map +0 -1
  180. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
  181. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
  182. package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
  183. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
  184. package/src/lib/layouts/index.d.ts +0 -23
  185. package/src/lib/layouts/index.d.ts.map +0 -1
  186. package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
  187. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
  188. package/src/lib/mappers/apiMapper.d.ts +0 -66
  189. package/src/lib/mappers/apiMapper.d.ts.map +0 -1
  190. package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
  191. package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
  192. package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
  193. package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
  194. package/src/lib/rich-text/index.d.ts +0 -9
  195. package/src/lib/rich-text/index.d.ts.map +0 -1
  196. package/src/lib/utils/blockHelpers.d.ts +0 -23
  197. package/src/lib/utils/blockHelpers.d.ts.map +0 -1
  198. package/src/lib/utils/configValidation.d.ts +0 -23
  199. package/src/lib/utils/configValidation.d.ts.map +0 -1
  200. package/src/registry/BlockRegistry.d.ts +0 -62
  201. package/src/registry/BlockRegistry.d.ts.map +0 -1
  202. package/src/registry/index.d.ts +0 -6
  203. package/src/registry/index.d.ts.map +0 -1
  204. package/src/state/EditorContext.d.ts +0 -45
  205. package/src/state/EditorContext.d.ts.map +0 -1
  206. package/src/state/index.d.ts +0 -7
  207. package/src/state/index.d.ts.map +0 -1
  208. package/src/state/reducer.d.ts +0 -11
  209. package/src/state/reducer.d.ts.map +0 -1
  210. package/src/state/types.d.ts +0 -162
  211. package/src/state/types.d.ts.map +0 -1
  212. package/src/types/block.d.ts +0 -221
  213. package/src/types/block.d.ts.map +0 -1
  214. package/src/types/index.d.ts +0 -8
  215. package/src/types/index.d.ts.map +0 -1
  216. package/src/types/post.d.ts +0 -136
  217. package/src/types/post.d.ts.map +0 -1
  218. package/src/utils/client.d.ts +0 -48
  219. package/src/utils/client.d.ts.map +0 -1
  220. package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
  221. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
  222. package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
  223. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
  224. package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
  225. package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
  226. package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
  227. package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
  228. package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
  229. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
  230. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
  231. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
  232. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
  233. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
  234. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
  235. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
  236. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
  237. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
  238. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
  239. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
  240. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
  241. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
  242. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
  243. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
  244. package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
  245. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
  246. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
  247. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
  248. package/src/views/CanvasEditor/components/index.d.ts +0 -21
  249. package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
  250. package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
  251. package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
  252. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
  253. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
  254. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
  255. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
  256. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
  257. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
  258. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
  259. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
  260. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
  261. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
  262. package/src/views/CanvasEditor/index.d.ts +0 -16
  263. package/src/views/CanvasEditor/index.d.ts.map +0 -1
  264. package/src/views/PostManager/EmptyState.d.ts +0 -10
  265. package/src/views/PostManager/EmptyState.d.ts.map +0 -1
  266. package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
  267. package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
  268. package/src/views/PostManager/PostCards.d.ts +0 -15
  269. package/src/views/PostManager/PostCards.d.ts.map +0 -1
  270. package/src/views/PostManager/PostFilters.d.ts +0 -16
  271. package/src/views/PostManager/PostFilters.d.ts.map +0 -1
  272. package/src/views/PostManager/PostManagerView.d.ts +0 -11
  273. package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
  274. package/src/views/PostManager/PostStats.d.ts +0 -11
  275. package/src/views/PostManager/PostStats.d.ts.map +0 -1
  276. package/src/views/PostManager/PostTable.d.ts +0 -15
  277. package/src/views/PostManager/PostTable.d.ts.map +0 -1
  278. package/src/views/PostManager/index.d.ts +0 -12
  279. package/src/views/PostManager/index.d.ts.map +0 -1
  280. package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
  281. package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
  282. package/src/views/Preview/index.d.ts +0 -6
  283. package/src/views/Preview/index.d.ts.map +0 -1
  284. package/src/views/Settings/SettingsView.d.ts +0 -10
  285. package/src/views/Settings/SettingsView.d.ts.map +0 -1
  286. package/src/views/Settings/index.d.ts +0 -6
  287. package/src/views/Settings/index.d.ts.map +0 -1
  288. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
  289. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
  290. package/src/views/SlugSEO/index.d.ts +0 -6
  291. package/src/views/SlugSEO/index.d.ts.map +0 -1
@@ -10,655 +10,143 @@ import React, { useState, useRef, Fragment, useEffect } from 'react';
10
10
  import { Plus } from 'lucide-react';
11
11
  import { Block } from '../../types/block';
12
12
  import { BlockWrapper } from './BlockWrapper';
13
+ import { blockRegistry } from '../../registry/BlockRegistry';
14
+ import { useEditor } from '../../state/EditorContext';
13
15
 
14
16
  export interface EditorBodyProps {
15
17
  blocks: Block[];
16
- onBlockAdd: (type: string, index: number, containerId?: string) => void;
17
- onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
18
- onBlockDelete: (id: string) => void;
18
+ darkMode: boolean;
19
+ backgroundColors?: { light: string; dark?: string };
20
+ onBlockUpdate: (id: string, data: Partial<Block['data']>, containerId?: string) => void;
21
+ onBlockDelete: (id: string, containerId?: string) => void;
19
22
  onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
20
- /** Enable dark mode for content area and wrappers (default: true) */
21
- darkMode?: boolean;
22
- /** Background colors for the editor */
23
- backgroundColors?: {
24
- light: string;
25
- dark?: string;
26
- };
23
+ onBlockAdd: (type: string, index: number, containerId?: string) => void;
27
24
  }
28
25
 
29
26
  export function EditorBody({
30
27
  blocks,
31
- onBlockAdd,
28
+ darkMode,
29
+ backgroundColors,
32
30
  onBlockUpdate,
33
31
  onBlockDelete,
34
32
  onBlockMove,
35
- darkMode = true,
36
- backgroundColors,
33
+ onBlockAdd,
37
34
  }: EditorBodyProps) {
38
- // --- State ---
39
- const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
40
- const [isDragging, setIsDragging] = useState(false);
41
- const [draggedBlockId, setDraggedBlockId] = useState<string | null>(null);
42
- const [dropIndicatorPosition, setDropIndicatorPosition] = useState<{ top: number; left: number; width: number } | null>(null);
43
- const [calculatedInsertIndex, setCalculatedInsertIndex] = useState<number | null>(null);
44
-
45
- // --- Refs ---
46
- const containerRef = useRef<HTMLDivElement>(null);
47
- const blockRefs = useRef<Map<string, HTMLDivElement>>(new Map());
48
-
49
- // --- Cleanup & Event Listeners ---
50
- useEffect(() => {
51
- const handleClearIndicator = () => {
52
- setDropIndicatorPosition(null);
53
- setDragOverIndex(null);
54
- };
55
-
56
- const container = containerRef.current;
57
- if (container) {
58
- container.addEventListener('clear-drop-indicator', handleClearIndicator);
59
- return () => {
60
- container.removeEventListener('clear-drop-indicator', handleClearIndicator);
61
- };
62
- }
63
- }, []);
64
-
65
- useEffect(() => {
66
- const handleGlobalDragEnd = () => {
67
- setDropIndicatorPosition(null);
68
- setDragOverIndex(null);
69
- setIsDragging(false);
70
- setDraggedBlockId(null);
71
- setCalculatedInsertIndex(null);
72
- };
73
-
74
- document.addEventListener('dragend', handleGlobalDragEnd);
75
- return () => {
76
- document.removeEventListener('dragend', handleGlobalDragEnd);
77
- };
78
- }, []);
79
-
80
- // --- Helper Functions ---
81
- const setBlockRef = (blockId: string) => (el: HTMLDivElement | null) => {
82
- if (el) {
83
- blockRefs.current.set(blockId, el);
84
- } else {
85
- blockRefs.current.delete(blockId);
86
- }
87
- };
88
-
89
- // --- Drag & Drop Logic ---
90
-
91
- const handleDragStart = (e: React.DragEvent) => {
92
- // This is called when dragging starts from the container
93
- // The actual block drag start is handled in BlockWrapper
94
- setIsDragging(true);
95
- };
96
-
97
- const handleDragOver = (e: React.DragEvent, index: number, element?: HTMLElement) => {
98
- e.preventDefault();
99
- e.stopPropagation();
100
-
101
- // Check if we're dragging over a nested container - if so, clear our indicator
102
- const target = e.target as HTMLElement;
103
- const nestedContainer = target.closest('[data-layout-container]');
104
- if (nestedContainer && nestedContainer !== containerRef.current) {
105
- // We're over a nested container, clear our indicator
106
- setDropIndicatorPosition(null);
107
- setDragOverIndex(null);
108
- return;
109
- }
110
-
111
- setDragOverIndex(index);
112
-
113
- // Calculate position for absolute drop indicator - always show between blocks
114
- const container = containerRef.current;
115
- if (container && element) {
116
- requestAnimationFrame(() => {
117
- // Double-check refs are still valid inside RAF callback
118
- if (!containerRef.current) return;
119
-
120
- const containerRect = containerRef.current.getBoundingClientRect();
121
- const elementRect = element.getBoundingClientRect();
122
- const mouseY = e.clientY;
123
- const relativeTop = mouseY - containerRect.top;
124
-
125
- const elementTop = elementRect.top - containerRect.top;
126
- const elementBottom = elementRect.bottom - containerRect.top;
127
- const elementCenter = elementTop + elementRect.height / 2;
128
-
129
- // Calculate width - use container width minus padding (32px on each side)
130
- const padding = 32;
131
- const width = containerRect.width - (padding * 2);
132
-
133
- let finalTop: number;
134
-
135
- // Determine if we should show above or below the element
136
- if (relativeTop < elementCenter) {
137
- // Show above this block - position between previous block and current block
138
- if (index === 0) {
139
- // First block - show at top of container (before first block)
140
- finalTop = 0;
141
- } else {
142
- // Get previous block to find the gap
143
- const prevBlock = blocks[index - 1];
144
- const prevBlockEl = blockRefs.current.get(prevBlock.id);
145
- if (prevBlockEl) {
146
- const prevBlockRect = prevBlockEl.getBoundingClientRect();
147
- const prevBlockBottom = prevBlockRect.bottom - containerRect.top;
148
- // Position in the middle of the gap (mb-6 = 24px margin)
149
- finalTop = prevBlockBottom + 12; // Half of the 24px gap
150
- } else {
151
- finalTop = elementTop;
152
- }
153
- }
154
- } else {
155
- // Show below this block - position between current block and next block
156
- if (index === blocks.length - 1) {
157
- // Last block - show after it
158
- finalTop = elementBottom;
159
- } else {
160
- // Get next block to find the gap
161
- const nextBlock = blocks[index + 1];
162
- const nextBlockEl = blockRefs.current.get(nextBlock.id);
163
- if (nextBlockEl) {
164
- const nextBlockRect = nextBlockEl.getBoundingClientRect();
165
- const nextBlockTop = nextBlockRect.top - containerRect.top;
166
- // Position in the middle of the gap
167
- finalTop = elementBottom + (nextBlockTop - elementBottom) / 2;
168
- } else {
169
- finalTop = elementBottom;
170
- }
171
- }
172
- }
173
-
174
- setDropIndicatorPosition({
175
- top: finalTop,
176
- left: padding,
177
- width: width,
178
- });
179
- });
180
- }
181
- };
182
-
183
- const handleDragLeave = (e: React.DragEvent) => {
184
- // Only clear if we're actually leaving the container
185
- // Check if we're moving to a child element (nested container)
186
- const relatedTarget = e.relatedTarget as Node;
187
- if (!e.currentTarget.contains(relatedTarget)) {
188
- // Check if relatedTarget is inside a nested LayoutContainer
189
- const isMovingToNested = relatedTarget && (relatedTarget as HTMLElement).closest('[data-layout-container]');
190
- if (!isMovingToNested) {
191
- setDragOverIndex(null);
192
- setDropIndicatorPosition(null);
193
- setCalculatedInsertIndex(null);
194
- setIsDragging(false);
195
- }
196
- }
197
- };
198
-
199
- const handleDrop = (e: React.DragEvent, index: number, containerId?: string) => {
200
- e.preventDefault();
201
- e.stopPropagation();
202
-
203
- const dataTransferBlockId = e.dataTransfer.getData('block-id');
204
- const globalBlockId = typeof window !== 'undefined' ? (window as any).__DRAGGED_BLOCK_ID__ : null;
205
- const blockId = dataTransferBlockId || globalBlockId;
206
- const blockType = e.dataTransfer.getData('block-type');
207
-
208
- console.log('[EditorBody] Drop Event:', {
209
- index,
210
- containerId,
211
- dataTransferBlockId,
212
- globalBlockId,
213
- resolvedBlockId: blockId,
214
- blockType,
215
- currentBlocks: blocks.map(b => ({ id: b.id, type: b.type })),
216
- rootBlockIds: blocks.map(b => b.id),
217
- });
218
-
219
- // Clear the global dragged block ID
220
- if (typeof window !== 'undefined') {
221
- (window as any).__DRAGGED_BLOCK_ID__ = null;
222
- }
223
-
224
- if (blockId) {
225
- // Moving existing block - check if it's in root or nested
226
- const currentIndex = blocks.findIndex(b => b.id === blockId);
227
- console.log('[EditorBody] Block location check:', {
228
- blockId,
229
- currentIndex,
230
- isInRoot: currentIndex !== -1,
231
- targetIndex: index,
232
- containerId,
233
- });
234
-
235
- if (currentIndex !== -1) {
236
- // Block is in root level - move to container or within root
237
- if (containerId) {
238
- // Moving from root to container
239
- console.log('[EditorBody] Moving from root to container:', { blockId, index, containerId });
240
- onBlockMove(blockId, index, containerId);
241
- } else if (currentIndex !== index) {
242
- // Moving within root level
243
- const targetIndex = currentIndex < index ? index - 1 : index;
244
- console.log('[EditorBody] Moving within root:', { blockId, currentIndex, targetIndex });
245
- onBlockMove(blockId, targetIndex);
246
- } else {
247
- console.log('[EditorBody] Block already at target position, no move needed');
248
- }
249
- } else {
250
- // Block is nested somewhere - need to move it to this location
251
- console.log('[EditorBody] Moving nested block to root:', { blockId, index, containerId: containerId || 'root' });
252
- // Pass undefined containerId to move to root
253
- onBlockMove(blockId, index, undefined);
254
- }
255
- } else if (blockType) {
256
- // Adding new block from library
257
- console.log('[EditorBody] Adding new block from library:', { blockType, index, containerId });
258
- onBlockAdd(blockType, index, containerId);
259
- } else {
260
- console.warn('[EditorBody] Drop event with no block ID or type!');
261
- }
35
+ const { helpers } = useEditor();
36
+ const [hoverIndex, setHoverIndex] = useState<number | null>(null);
37
+ const bodyRef = useRef<HTMLDivElement>(null);
262
38
 
263
- setDragOverIndex(null);
264
- setIsDragging(false);
265
- setDraggedBlockId(null);
266
- setDropIndicatorPosition(null);
267
- setCalculatedInsertIndex(null);
268
- };
39
+ // Filter out hero block from the main body list - it's handled by EditorCanvas header slot
40
+ const mainBlocks = blocks.filter(b => b.type !== 'hero');
269
41
 
270
- const handleBodyDragOver = (e: React.DragEvent) => {
42
+ const handleDragOver = (e: React.DragEvent, index: number) => {
271
43
  e.preventDefault();
272
- e.stopPropagation();
273
-
274
- // Check if we're dragging over a nested container - if so, clear our indicator
275
- const target = e.target as HTMLElement;
276
- const nestedContainer = target.closest('[data-layout-container]');
277
- if (nestedContainer && nestedContainer !== containerRef.current) {
278
- // We're over a nested container, clear our indicator
279
- setDropIndicatorPosition(null);
280
- setDragOverIndex(null);
281
- setCalculatedInsertIndex(null);
282
- setIsDragging(false);
283
- return;
284
- }
285
-
286
- setIsDragging(true);
287
-
288
- // Calculate drop indicator position based on mouse Y position relative to blocks
289
- const container = containerRef.current;
290
- if (container && blocks.length > 0) {
291
- requestAnimationFrame(() => {
292
- if (!containerRef.current) return;
293
-
294
- const containerRect = containerRef.current.getBoundingClientRect();
295
- const mouseY = e.clientY;
296
- const relativeTop = mouseY - containerRect.top;
297
- const padding = 32;
298
- const width = containerRect.width - (padding * 2);
299
-
300
- // Find the closest insertion point based on block positions - always between blocks
301
- let insertIndex = blocks.length; // Default to end
302
- let indicatorTop = containerRect.height; // Default to bottom
303
-
304
- // Check each block to find where to insert
305
- for (let i = 0; i < blocks.length; i++) {
306
- const blockEl = blockRefs.current.get(blocks[i].id);
307
- if (blockEl) {
308
- const blockRect = blockEl.getBoundingClientRect();
309
- const blockTop = blockRect.top - containerRect.top;
310
- const blockBottom = blockRect.bottom - containerRect.top;
311
- const blockCenter = blockTop + blockRect.height / 2;
312
-
313
- // If mouse is above this block's center, insert before it
314
- if (relativeTop < blockCenter) {
315
- insertIndex = i;
316
- if (i === 0) {
317
- // First block - show at top
318
- indicatorTop = 0;
319
- } else {
320
- // Get previous block to find the gap
321
- const prevBlock = blocks[i - 1];
322
- const prevBlockEl = blockRefs.current.get(prevBlock.id);
323
- if (prevBlockEl) {
324
- const prevBlockRect = prevBlockEl.getBoundingClientRect();
325
- const prevBlockBottom = prevBlockRect.bottom - containerRect.top;
326
- // Position in the middle of the gap
327
- indicatorTop = prevBlockBottom + (blockTop - prevBlockBottom) / 2;
328
- } else {
329
- indicatorTop = blockTop;
330
- }
331
- }
332
- break;
333
- }
334
- // If mouse is below this block's center, check if we should insert after it
335
- else if (i === blocks.length - 1 || relativeTop < blockBottom) {
336
- insertIndex = i + 1;
337
- if (i === blocks.length - 1) {
338
- // Last block - show after it
339
- indicatorTop = blockBottom;
340
- } else {
341
- // Get next block to find the gap
342
- const nextBlock = blocks[i + 1];
343
- const nextBlockEl = blockRefs.current.get(nextBlock.id);
344
- if (nextBlockEl) {
345
- const nextBlockRect = nextBlockEl.getBoundingClientRect();
346
- const nextBlockTop = nextBlockRect.top - containerRect.top;
347
- // Position in the middle of the gap
348
- indicatorTop = blockBottom + (nextBlockTop - blockBottom) / 2;
349
- } else {
350
- indicatorTop = blockBottom;
351
- }
352
- }
353
- break;
354
- }
355
- }
356
- }
357
-
358
- // If we didn't find a position, use the last block's bottom
359
- if (insertIndex === blocks.length) {
360
- const lastBlockEl = blockRefs.current.get(blocks[blocks.length - 1].id);
361
- if (lastBlockEl) {
362
- const lastBlockRect = lastBlockEl.getBoundingClientRect();
363
- indicatorTop = lastBlockRect.bottom - containerRect.top;
364
- }
365
- }
366
-
367
- setCalculatedInsertIndex(insertIndex);
368
- setDropIndicatorPosition({
369
- top: indicatorTop,
370
- left: padding,
371
- width: width,
372
- });
373
- });
374
- } else if (container) {
375
- // No blocks, show indicator at top
376
- requestAnimationFrame(() => {
377
- if (!containerRef.current) return;
378
- const containerRect = containerRef.current.getBoundingClientRect();
379
- const padding = 32;
380
- const width = containerRect.width - (padding * 2);
381
- setCalculatedInsertIndex(0);
382
- setDropIndicatorPosition({
383
- top: 0,
384
- left: padding,
385
- width: width,
386
- });
387
- });
388
- }
44
+ setHoverIndex(index);
389
45
  };
390
46
 
391
- const handleBodyDrop = (e: React.DragEvent) => {
47
+ const handleDrop = (e: React.DragEvent, index: number) => {
392
48
  e.preventDefault();
393
- e.stopPropagation();
394
-
395
- const dataTransferBlockId = e.dataTransfer.getData('block-id');
396
- const globalBlockId = typeof window !== 'undefined' ? (window as any).__DRAGGED_BLOCK_ID__ : null;
397
- const blockId = dataTransferBlockId || globalBlockId;
398
- const blockType = e.dataTransfer.getData('block-type');
399
-
400
- // Use the calculated insert index if available, otherwise calculate it now
401
- let targetIndex = calculatedInsertIndex !== null ? calculatedInsertIndex : blocks.length;
49
+ setHoverIndex(null);
402
50
 
403
- // If we don't have a calculated index, calculate it based on mouse position
404
- if (calculatedInsertIndex === null && containerRef.current && blocks.length > 0) {
405
- const containerRect = containerRef.current.getBoundingClientRect();
406
- const mouseY = e.clientY;
407
- const relativeTop = mouseY - containerRect.top;
408
-
409
- for (let i = 0; i < blocks.length; i++) {
410
- const blockEl = blockRefs.current.get(blocks[i].id);
411
- if (blockEl) {
412
- const blockRect = blockEl.getBoundingClientRect();
413
- const blockTop = blockRect.top - containerRect.top;
414
- const blockCenter = blockTop + blockRect.height / 2;
415
-
416
- if (relativeTop < blockCenter) {
417
- targetIndex = i;
418
- break;
419
- } else if (i === blocks.length - 1) {
420
- targetIndex = blocks.length;
421
- break;
422
- }
423
- }
424
- }
425
- }
426
-
427
- console.log('[EditorBody] Body Drop Event:', {
428
- dataTransferBlockId,
429
- globalBlockId,
430
- resolvedBlockId: blockId,
431
- blockType,
432
- calculatedInsertIndex,
433
- targetIndex,
434
- currentBlocksCount: blocks.length,
435
- rootBlockIds: blocks.map(b => b.id),
436
- });
51
+ // SUPPORT BOTH: Adding new blocks and Moving existing blocks
52
+ const blockId = e.dataTransfer.getData('blockId') || (window as any).__DRAGGED_BLOCK_ID__;
53
+ const blockType = e.dataTransfer.getData('blockType');
437
54
 
438
- // Clear the global dragged block ID
55
+ // Clear global flag
439
56
  if (typeof window !== 'undefined') {
440
57
  (window as any).__DRAGGED_BLOCK_ID__ = null;
441
58
  }
442
59
 
443
60
  if (blockId) {
444
- // Moving existing block to calculated position
445
- const currentIndex = blocks.findIndex(b => b.id === blockId);
446
- console.log('[EditorBody] Body drop - block location:', {
447
- blockId,
448
- currentIndex,
449
- isInRoot: currentIndex !== -1,
450
- targetIndex,
451
- });
452
-
453
- if (currentIndex !== -1) {
454
- // Already in root, move to target position
455
- if (currentIndex !== targetIndex) {
456
- // Adjust for removal if moving forward
457
- const adjustedIndex = currentIndex < targetIndex ? targetIndex - 1 : targetIndex;
458
- console.log('[EditorBody] Moving within root:', { blockId, currentIndex, adjustedIndex });
459
- onBlockMove(blockId, Math.max(0, adjustedIndex));
460
- }
461
- } else {
462
- // Block is nested - move it to root level at target position
463
- console.log('[EditorBody] Moving nested block to root:', { blockId, targetIndex });
464
- onBlockMove(blockId, targetIndex, undefined);
61
+ // Moving existing block
62
+ const currentIndex = mainBlocks.findIndex(b => b.id === blockId);
63
+ let targetIndex = index;
64
+
65
+ if (currentIndex !== -1 && currentIndex < targetIndex) {
66
+ // Adjust for removal if moving forward in the same container
67
+ targetIndex = targetIndex - 1;
465
68
  }
69
+
70
+ onBlockMove(blockId, Math.max(0, targetIndex));
466
71
  } else if (blockType) {
467
- // Adding new block at calculated position
468
- console.log('[EditorBody] Adding new block:', { blockType, index: targetIndex });
469
- onBlockAdd(blockType, targetIndex);
470
- } else {
471
- console.warn('[EditorBody] Body drop with no block ID or type!');
72
+ // Adding new block from sidebar
73
+ onBlockAdd(blockType, index);
472
74
  }
473
-
474
- setIsDragging(false);
475
- setDragOverIndex(null);
476
- setDraggedBlockId(null);
477
- setDropIndicatorPosition(null);
478
- setCalculatedInsertIndex(null);
479
75
  };
480
76
 
481
- return (
77
+ const renderAddButton = (index: number) => (
482
78
  <div
483
- ref={containerRef}
484
- className={`relative w-full rounded-[2.5rem] border shadow-lg min-h-[400px] transition-all bg-transparent ${darkMode
485
- ? 'border-neutral-200 dark:border-neutral-800 shadow-neutral-200/50 dark:shadow-neutral-900/50'
486
- : 'border-neutral-200 shadow-neutral-200/50'
487
- }`}
488
- onDragOver={handleBodyDragOver}
489
- onDrop={handleBodyDrop}
490
- onDragLeave={handleDragLeave}
491
- onDragStart={handleDragStart}
492
- onDragEnd={() => {
493
- setDropIndicatorPosition(null);
494
- setDragOverIndex(null);
495
- setIsDragging(false);
496
- setDraggedBlockId(null);
497
- setCalculatedInsertIndex(null);
498
- }}
79
+ className={`relative h-4 group/add transition-all duration-300 ${
80
+ hoverIndex === index ? 'h-12' : 'h-4 opacity-0 hover:opacity-100'
81
+ }`}
82
+ onDragOver={(e) => handleDragOver(e, index)}
83
+ onDragLeave={() => setHoverIndex(null)}
84
+ onDrop={(e) => handleDrop(e, index)}
499
85
  >
500
- {/* 1. Visual Indicator Overlay */}
501
- {dropIndicatorPosition && isDragging && (
502
- <DropIndicator position={dropIndicatorPosition} darkMode={darkMode} />
503
- )}
504
-
505
- <div
506
- className="flex flex-col p-8"
507
- onDragOver={(e) => {
508
- // Only handle if not over a block or nested container
509
- const target = e.target as HTMLElement;
510
- const isOverBlock = target.closest('[data-block-wrapper]');
511
- const isOverNestedContainer = target.closest('[data-layout-container]') && target.closest('[data-layout-container]') !== containerRef.current;
512
-
513
- if (!isOverBlock && !isOverNestedContainer) {
514
- handleBodyDragOver(e);
515
- }
516
- }}
517
- onDrop={(e) => {
518
- // Only handle if not over a block or nested container
519
- const target = e.target as HTMLElement;
520
- const isOverBlock = target.closest('[data-block-wrapper]');
521
- const isOverNestedContainer = target.closest('[data-layout-container]') && target.closest('[data-layout-container]') !== containerRef.current;
522
-
523
- if (!isOverBlock && !isOverNestedContainer) {
524
- handleBodyDrop(e);
525
- }
526
- }}
527
- >
528
- {/* 2. Content Area */}
529
- {blocks.length === 0 ? (
530
- <EmptyState isDragging={isDragging} darkMode={darkMode} />
531
- ) : (
532
- blocks.map((block, index) => (
533
- <Fragment key={block.id}>
534
- {/* Visual Drop Zone - appears between blocks when dragging */}
535
- {index > 0 && (
536
- <div
537
- className={`h-6 transition-all duration-200 ${isDragging && dragOverIndex === index
538
- ? 'bg-primary/5 border-y border-dashed border-primary/20 rounded-lg'
539
- : 'bg-transparent'
540
- }`}
541
- />
542
- )}
543
-
544
- {/* Block Container */}
545
- <div
546
- ref={setBlockRef(block.id)}
547
- onDragOver={(e) => {
548
- const blockEl = blockRefs.current.get(block.id);
549
- if (blockEl) {
550
- handleDragOver(e, index, blockEl);
551
- }
552
- }}
553
- onDragLeave={handleDragLeave}
554
- onDrop={(e) => handleDrop(e, index)}
555
- className="relative mb-6 last:mb-0"
556
- >
557
- <BlockWrapper
558
- block={block}
559
- onUpdate={(data) => onBlockUpdate(block.id, data)}
560
- onDelete={() => onBlockDelete(block.id)}
561
- onMoveUp={index > 0 ? () => onBlockMove(block.id, index - 1) : undefined}
562
- onMoveDown={index < blocks.length - 1 ? () => onBlockMove(block.id, index + 1) : undefined}
563
- allBlocks={blocks}
564
- />
565
- </div>
566
- </Fragment>
567
- ))
568
- )}
86
+ <div className="absolute inset-0 flex items-center justify-center">
87
+ <div className={`w-full h-px ${darkMode ? 'bg-neutral-800' : 'bg-neutral-200'} absolute top-1/2 -translate-y-1/2`} />
88
+ <button
89
+ onClick={() => {
90
+ // Default to paragraph for quick add
91
+ onBlockAdd('paragraph', index);
92
+ }}
93
+ className="relative z-10 size-8 rounded-full bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 flex items-center justify-center text-neutral-400 hover:text-primary hover:border-primary hover:scale-110 transition-all shadow-sm"
94
+ >
95
+ <Plus size={16} />
96
+ </button>
569
97
  </div>
570
98
  </div>
571
99
  );
572
- }
573
-
574
- /**
575
- * Visual Line that shows where the block will land
576
- */
577
- function DropIndicator({ position, darkMode }: { position: { top: number; left: number; width: number }; darkMode: boolean }) {
578
- return (
579
- <div
580
- className="absolute z-50 pointer-events-none"
581
- style={{
582
- top: `${position.top - 12}px`,
583
- left: `${position.left}px`,
584
- width: `${position.width}px`,
585
- height: '24px',
586
- }}
587
- >
588
- {/* Drop zone background */}
589
- <div className={`absolute inset-0 rounded-lg border border-dashed backdrop-blur-sm ${darkMode
590
- ? 'bg-primary/10 dark:bg-primary/20 border-primary dark:border-primary/60'
591
- : 'bg-primary/10 border-primary'
592
- }`} />
593
-
594
- {/* Center line indicator */}
595
- <div className={`absolute top-1/2 left-0 right-0 h-0.5 rounded-full transform -translate-y-1/2 ${darkMode ? 'bg-primary dark:bg-primary/80' : 'bg-primary'
596
- }`} />
597
100
 
598
- {/* Drop icon indicator */}
599
- <div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
600
- <div className={`w-6 h-6 rounded-full flex items-center justify-center shadow-lg shadow-primary/30 ${darkMode ? 'bg-primary dark:bg-primary/90' : 'bg-primary'
601
- }`}>
602
- <div className={`w-2 h-2 rounded-full ${darkMode ? 'bg-white dark:bg-neutral-900' : 'bg-white'
603
- }`} />
604
- </div>
101
+ const renderBlocks = (blocksToRender: Block[], containerId?: string) => {
102
+ return (
103
+ <div className="space-y-4">
104
+ {renderAddButton(0)}
105
+ {blocksToRender.map((block, index) => (
106
+ <Fragment key={block.id}>
107
+ <BlockWrapper
108
+ block={block}
109
+ onUpdate={(data) => onBlockUpdate(block.id, data, containerId)}
110
+ onDelete={() => onBlockDelete(block.id, containerId)}
111
+ onDuplicate={() => helpers.duplicateBlock(block.id)}
112
+ onMoveUp={() => index > 0 && onBlockMove(block.id, index - 1, containerId)}
113
+ onMoveDown={() => index < blocksToRender.length - 1 && onBlockMove(block.id, index + 1, containerId)}
114
+
115
+ // Essential for nested rendering
116
+ childBlocks={Array.isArray(block.children) && typeof block.children[0] === 'object' ? (block.children as Block[]) : []}
117
+ onChildBlockAdd={(type, idx, cid) => onBlockAdd(type, idx ?? 0, cid)}
118
+ onChildBlockUpdate={onBlockUpdate}
119
+ onChildBlockDelete={onBlockDelete}
120
+ onChildBlockMove={onBlockMove}
121
+ />
122
+ {renderAddButton(index + 1)}
123
+ </Fragment>
124
+ ))}
605
125
  </div>
606
- </div>
607
- );
608
- }
126
+ );
127
+ };
609
128
 
610
- /**
611
- * Placeholder when the editor is empty
612
- */
613
- function EmptyState({ isDragging, darkMode }: { isDragging: boolean; darkMode: boolean }) {
614
129
  return (
615
- <div
616
- className={`flex flex-col items-center justify-center py-20 px-8 rounded-2xl border border-dashed transition-all ${darkMode
617
- ? isDragging
618
- ? 'border-primary/30 bg-primary/5 dark:bg-primary/10'
619
- : 'border-gray-200/50 dark:border-neutral-700/50 bg-neutral-50/50 dark:bg-neutral-800/30'
620
- : isDragging
621
- ? 'border-primary/30 bg-primary/5'
622
- : 'border-gray-200/50 bg-neutral-50/50'
623
- }`}
624
- >
625
- <div
626
- className={`p-4 rounded-full mb-4 transition-colors ${darkMode
627
- ? isDragging
628
- ? 'bg-primary/10 dark:bg-primary/20'
629
- : 'bg-neutral-100 dark:bg-neutral-800'
630
- : isDragging
631
- ? 'bg-primary/10'
632
- : 'bg-neutral-100'
633
- }`}
634
- >
635
- <Plus
636
- size={24}
637
- className={`transition-colors ${darkMode
638
- ? isDragging
639
- ? 'text-primary'
640
- : 'text-neutral-400 dark:text-neutral-500'
641
- : isDragging
642
- ? 'text-primary'
643
- : 'text-neutral-400'
644
- }`}
645
- />
646
- </div>
647
- <p
648
- className={`text-sm font-black uppercase tracking-wider transition-colors ${darkMode
649
- ? isDragging
650
- ? 'text-primary'
651
- : 'text-neutral-500 dark:text-neutral-400'
652
- : isDragging
653
- ? 'text-primary'
654
- : 'text-neutral-500'
655
- }`}
656
- >
657
- {isDragging ? 'Drop Block Here' : 'Drop a component here'}
658
- </p>
659
- <p className={`text-xs mt-2 ${darkMode ? 'text-neutral-400 dark:text-neutral-500' : 'text-neutral-400'
660
- }`}>
661
- Drag blocks from the library to get started
130
+ <div ref={bodyRef} className="relative min-h-[400px]">
131
+ {mainBlocks.length === 0 ? (
132
+ <div
133
+ className="flex flex-col items-center justify-center py-32 border-2 border-dashed border-neutral-200 dark:border-neutral-800 rounded-[2rem] text-neutral-400 hover:text-primary hover:border-primary transition-all cursor-pointer group"
134
+ onClick={() => onBlockAdd('paragraph', 0)}
135
+ onDragOver={(e) => e.preventDefault()}
136
+ onDrop={(e) => handleDrop(e, 0)}
137
+ >
138
+ <Plus size={48} strokeWidth={1} className="mb-4 opacity-20 group-hover:opacity-100 transition-opacity" />
139
+ <span className="text-sm font-bold uppercase tracking-widest">Add your first block</span>
140
+ </div>
141
+ ) : (
142
+ renderBlocks(mainBlocks)
143
+ )}
144
+
145
+ {/* Bottom spacer to allow dragging after last block */}
146
+ <div className="h-20" />
147
+
148
+ <p className={`text-xs mt-2 text-center ${darkMode ? 'text-neutral-500' : 'text-neutral-400'}`}>
149
+ Drag blocks from the library to build your story
662
150
  </p>
663
151
  </div>
664
152
  );