@jhits/plugin-blog 0.0.18 → 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 -500
  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 -604
  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
@@ -1,13 +1,15 @@
1
1
  /**
2
2
  * Editor Reducer
3
- * Pure function that handles state transitions
3
+ * Pure function that handles state transitions using tree utilities
4
4
  */
5
5
  import { initialEditorState } from './types';
6
+ import { findNode, mapTree, filterTree, removeNode, addNodeToContainer } from '../lib/utils/tree';
7
+ import { blockRegistry } from '../registry/BlockRegistry';
8
+ import { slugify } from '../lib/utils/slugify';
6
9
  /**
7
10
  * Generate a unique block ID
8
11
  */
9
12
  function generateBlockId() {
10
- // Use crypto.randomUUID if available, otherwise fallback to timestamp-based
11
13
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
12
14
  return crypto.randomUUID();
13
15
  }
@@ -22,543 +24,172 @@ function cloneBlock(block) {
22
24
  id: generateBlockId(),
23
25
  data: { ...block.data },
24
26
  meta: block.meta ? { ...block.meta } : undefined,
25
- children: block.children ? (Array.isArray(block.children[0])
27
+ children: block.children && typeof block.children[0] === 'object'
26
28
  ? block.children.map(cloneBlock)
27
- : [...block.children]) : undefined,
29
+ : block.children ? [...block.children] : undefined,
28
30
  };
29
31
  }
30
- /**
31
- * Find a block by ID recursively (including nested blocks)
32
- */
33
- function findBlockById(blocks, id) {
34
- for (const block of blocks) {
35
- if (block.id === id) {
36
- return block;
37
- }
38
- if (block.children && Array.isArray(block.children) && block.children.length > 0) {
39
- // Check if children are Block objects or IDs
40
- if (typeof block.children[0] === 'object') {
41
- const found = findBlockById(block.children, id);
42
- if (found)
43
- return found;
44
- }
45
- }
46
- }
47
- return null;
48
- }
49
- /**
50
- * Update blocks recursively to add a block to a container
51
- */
52
- function addBlockToContainer(blocks, containerId, newBlock, index) {
53
- return blocks.map(block => {
54
- // Check if this is the container (exact match or column container like "block-123-col-0")
55
- const isContainer = block.id === containerId;
56
- const isColumnContainer = containerId.startsWith(`${block.id}-col-`);
57
- if (isContainer) {
58
- // Direct container match
59
- const currentChildren = Array.isArray(block.children)
60
- ? (typeof block.children[0] === 'object'
61
- ? block.children
62
- : [])
63
- : [];
64
- const updatedChildren = [...currentChildren];
65
- if (index !== undefined && index >= 0 && index <= updatedChildren.length) {
66
- updatedChildren.splice(index, 0, newBlock);
67
- }
68
- else {
69
- updatedChildren.push(newBlock);
70
- }
71
- return {
72
- ...block,
73
- children: updatedChildren,
74
- };
75
- }
76
- else if (isColumnContainer) {
77
- // Column container - extract column index and store in block meta
78
- const columnIndex = parseInt(containerId.split('-col-')[1] || '0', 10);
79
- newBlock.meta = {
80
- ...newBlock.meta,
81
- columnIndex,
82
- };
83
- const currentChildren = Array.isArray(block.children)
84
- ? (typeof block.children[0] === 'object'
85
- ? block.children
86
- : [])
87
- : [];
88
- const updatedChildren = [...currentChildren];
89
- if (index !== undefined && index >= 0 && index <= updatedChildren.length) {
90
- updatedChildren.splice(index, 0, newBlock);
91
- }
92
- else {
93
- updatedChildren.push(newBlock);
94
- }
95
- return {
96
- ...block,
97
- children: updatedChildren,
98
- };
99
- }
100
- // Recursively search nested blocks
101
- if (block.children && Array.isArray(block.children) && block.children.length > 0) {
102
- if (typeof block.children[0] === 'object') {
103
- return {
104
- ...block,
105
- children: addBlockToContainer(block.children, containerId, newBlock, index),
106
- };
107
- }
108
- }
109
- return block;
110
- });
111
- }
112
- /**
113
- * Update blocks recursively to update a nested block
114
- */
115
- function updateNestedBlock(blocks, id, data) {
116
- return blocks.map(block => {
117
- if (block.id === id) {
118
- return {
119
- ...block,
120
- data: { ...block.data, ...data },
121
- };
122
- }
123
- if (block.children && Array.isArray(block.children) && block.children.length > 0) {
124
- if (typeof block.children[0] === 'object') {
125
- return {
126
- ...block,
127
- children: updateNestedBlock(block.children, id, data),
128
- };
129
- }
130
- }
131
- return block;
132
- });
133
- }
134
- /**
135
- * Update blocks recursively to delete a nested block
136
- */
137
- function deleteNestedBlock(blocks, id) {
138
- return blocks
139
- .filter(block => block.id !== id)
140
- .map(block => {
141
- if (block.children && Array.isArray(block.children) && block.children.length > 0) {
142
- if (typeof block.children[0] === 'object') {
143
- return {
144
- ...block,
145
- children: deleteNestedBlock(block.children, id),
146
- };
147
- }
148
- }
149
- return block;
150
- });
151
- }
152
- /**
153
- * Find and remove a block from wherever it is (root or nested)
154
- */
155
- function removeBlockFromTree(blocks, blockId) {
156
- let removedBlock = null;
157
- console.log('[removeBlockFromTree] Searching for block:', {
158
- blockId,
159
- rootBlocks: blocks.map(b => ({ id: b.id, type: b.type })),
160
- });
161
- // First check root level
162
- const rootIndex = blocks.findIndex(b => b.id === blockId);
163
- if (rootIndex !== -1) {
164
- removedBlock = blocks[rootIndex];
165
- console.log('[removeBlockFromTree] Found at root level:', {
166
- blockId,
167
- index: rootIndex,
168
- block: { id: removedBlock.id, type: removedBlock.type },
169
- });
170
- return {
171
- updatedBlocks: blocks.filter((_, i) => i !== rootIndex),
172
- removedBlock,
173
- };
174
- }
175
- // Then check nested blocks
176
- const updatedBlocks = blocks.map(block => {
177
- if (block.children && Array.isArray(block.children) && block.children.length > 0) {
178
- if (typeof block.children[0] === 'object') {
179
- const children = block.children;
180
- const childIndex = children.findIndex(b => b.id === blockId);
181
- if (childIndex !== -1) {
182
- removedBlock = children[childIndex];
183
- console.log('[removeBlockFromTree] Found in nested container:', {
184
- blockId,
185
- containerId: block.id,
186
- containerType: block.type,
187
- childIndex,
188
- block: { id: removedBlock.id, type: removedBlock.type },
189
- });
190
- return {
191
- ...block,
192
- children: children.filter((_, i) => i !== childIndex),
193
- };
194
- }
195
- // Recursively search nested children
196
- const { updatedBlocks: updatedChildren, removedBlock: foundBlock } = removeBlockFromTree(children, blockId);
197
- if (foundBlock) {
198
- removedBlock = foundBlock;
199
- console.log('[removeBlockFromTree] Found in deeper nesting:', {
200
- blockId,
201
- containerId: block.id,
202
- block: { id: removedBlock.id, type: removedBlock.type },
203
- });
204
- return {
205
- ...block,
206
- children: updatedChildren,
207
- };
208
- }
209
- }
210
- }
211
- return block;
212
- });
213
- if (!removedBlock) {
214
- console.warn('[removeBlockFromTree] Block not found in tree:', { blockId });
215
- }
216
- return { updatedBlocks, removedBlock };
217
- }
218
- /**
219
- * Update blocks recursively to move a nested block within the same container
220
- */
221
- function moveNestedBlock(blocks, containerId, blockId, newIndex) {
222
- return blocks.map(block => {
223
- if (block.id === containerId && block.children && Array.isArray(block.children)) {
224
- const children = typeof block.children[0] === 'object'
225
- ? block.children
226
- : [];
227
- const currentIndex = children.findIndex(b => b.id === blockId);
228
- if (currentIndex !== -1 && newIndex >= 0 && newIndex < children.length) {
229
- const updatedChildren = [...children];
230
- const [movedBlock] = updatedChildren.splice(currentIndex, 1);
231
- updatedChildren.splice(newIndex, 0, movedBlock);
232
- return {
233
- ...block,
234
- children: updatedChildren,
235
- };
236
- }
237
- }
238
- if (block.children && Array.isArray(block.children) && block.children.length > 0) {
239
- if (typeof block.children[0] === 'object') {
240
- return {
241
- ...block,
242
- children: moveNestedBlock(block.children, containerId, blockId, newIndex),
243
- };
244
- }
245
- }
246
- return block;
247
- });
248
- }
249
- /**
250
- * Move a block to a container (handles cross-container moves)
251
- */
252
- function moveBlockToContainer(blocks, blockId, containerId, newIndex) {
253
- console.log('[moveBlockToContainer] Starting move:', {
254
- blockId,
255
- containerId,
256
- newIndex,
257
- });
258
- // First, find and remove the block from wherever it is
259
- const { updatedBlocks, removedBlock } = removeBlockFromTree(blocks, blockId);
260
- if (!removedBlock) {
261
- // Block not found, return unchanged
262
- console.warn('[moveBlockToContainer] Block not found, cannot move');
263
- return blocks;
264
- }
265
- console.log('[moveBlockToContainer] Block removed, now adding to container:', {
266
- removedBlock: { id: removedBlock.id, type: removedBlock.type },
267
- containerId,
268
- newIndex,
269
- });
270
- // Handle column containers
271
- const isColumnContainer = containerId.includes('-col-');
272
- if (isColumnContainer) {
273
- const [parentId, columnPart] = containerId.split('-col-');
274
- const columnIndex = parseInt(columnPart || '0', 10);
275
- removedBlock.meta = {
276
- ...removedBlock.meta,
277
- columnIndex,
278
- };
279
- console.log('[moveBlockToContainer] Setting column index:', { columnIndex });
280
- }
281
- // Now add the block to the target container
282
- const result = addBlockToContainer(updatedBlocks, containerId, removedBlock, newIndex);
283
- console.log('[moveBlockToContainer] Move complete:', {
284
- resultBlocks: result.map(b => ({ id: b.id, type: b.type })),
285
- });
286
- return result;
287
- }
288
- /**
289
- * Editor Reducer
290
- * Handles all state transitions for the editor
291
- */
292
32
  export function editorReducer(state, action) {
293
33
  switch (action.type) {
294
34
  case 'SET_BLOCKS':
295
- return {
296
- ...state,
297
- blocks: action.payload,
298
- isDirty: true,
299
- };
35
+ return { ...state, blocks: action.payload, isDirty: true };
300
36
  case 'ADD_BLOCK': {
301
37
  const { block, index, containerId } = action.payload;
38
+ const definition = blockRegistry.get(block.type);
302
39
  const newBlock = {
303
40
  ...block,
304
41
  id: block.id || generateBlockId(),
305
- };
306
- // If containerId is provided, add to container's children
307
- if (containerId) {
308
- const updatedBlocks = addBlockToContainer(state.blocks, containerId, newBlock, index);
309
- return {
310
- ...state,
311
- blocks: updatedBlocks,
312
- selectedBlockId: newBlock.id,
313
- isDirty: true,
314
- };
315
- }
316
- // Otherwise, add to root level
317
- const newBlocks = [...state.blocks];
318
- if (index !== undefined && index >= 0 && index <= newBlocks.length) {
319
- newBlocks.splice(index, 0, newBlock);
320
- }
321
- else {
322
- newBlocks.push(newBlock);
323
- }
324
- return {
325
- ...state,
326
- blocks: newBlocks,
327
- selectedBlockId: newBlock.id,
328
- isDirty: true,
329
- };
42
+ // Initialize children as empty array if it's a container block
43
+ children: (definition?.isContainer && !block.children) ? [] : block.children
44
+ };
45
+ const updatedBlocks = containerId
46
+ ? addNodeToContainer(state.blocks, containerId, newBlock, index)
47
+ : (() => {
48
+ const next = [...state.blocks];
49
+ if (index !== undefined && index >= 0 && index <= next.length)
50
+ next.splice(index, 0, newBlock);
51
+ else
52
+ next.push(newBlock);
53
+ return next;
54
+ })();
55
+ return { ...state, blocks: updatedBlocks, selectedBlockId: newBlock.id, isDirty: true };
330
56
  }
331
57
  case 'UPDATE_BLOCK': {
332
58
  const { id, data } = action.payload;
333
- // Check if block is at root level
334
- const rootBlock = state.blocks.find(block => block.id === id);
335
- if (rootBlock) {
336
- const newBlocks = state.blocks.map(block => block.id === id
337
- ? {
338
- ...block,
339
- data: { ...block.data, ...data },
340
- }
341
- : block);
342
- return {
343
- ...state,
344
- blocks: newBlocks,
345
- isDirty: true,
346
- };
347
- }
348
- // Otherwise, update nested block
349
- const newBlocks = updateNestedBlock(state.blocks, id, data);
350
59
  return {
351
60
  ...state,
352
- blocks: newBlocks,
61
+ blocks: mapTree(state.blocks, node => node.id === id ? { ...node, data: { ...node.data, ...data } } : node),
353
62
  isDirty: true,
354
63
  };
355
64
  }
356
65
  case 'DELETE_BLOCK': {
357
66
  const { id } = action.payload;
358
- // Check if block is at root level
359
- const rootBlock = state.blocks.find(block => block.id === id);
360
- if (rootBlock) {
361
- const newBlocks = state.blocks.filter(block => block.id !== id);
362
- const wasSelected = state.selectedBlockId === id;
363
- return {
364
- ...state,
365
- blocks: newBlocks,
366
- selectedBlockId: wasSelected ? null : state.selectedBlockId,
367
- isDirty: true,
368
- };
369
- }
370
- // Otherwise, delete nested block
371
- const newBlocks = deleteNestedBlock(state.blocks, id);
372
- const wasSelected = state.selectedBlockId === id;
373
67
  return {
374
68
  ...state,
375
- blocks: newBlocks,
376
- selectedBlockId: wasSelected ? null : state.selectedBlockId,
69
+ blocks: filterTree(state.blocks, node => node.id !== id),
70
+ selectedBlockId: state.selectedBlockId === id ? null : state.selectedBlockId,
377
71
  isDirty: true,
378
72
  };
379
73
  }
380
74
  case 'DUPLICATE_BLOCK': {
381
75
  const { id } = action.payload;
382
- const blockIndex = state.blocks.findIndex(block => block.id === id);
383
- if (blockIndex === -1) {
76
+ const blockToDup = findNode(state.blocks, id);
77
+ if (!blockToDup)
384
78
  return state;
79
+ const duplicated = cloneBlock(blockToDup);
80
+ // 1. First find the parent container and current index
81
+ let targetContainerId = undefined;
82
+ let targetIndex = -1;
83
+ // Check root level
84
+ const rootIndex = state.blocks.findIndex(b => b.id === id);
85
+ if (rootIndex !== -1) {
86
+ targetIndex = rootIndex + 1;
385
87
  }
386
- const blockToDuplicate = state.blocks[blockIndex];
387
- const duplicatedBlock = cloneBlock(blockToDuplicate);
388
- const newBlocks = [...state.blocks];
389
- newBlocks.splice(blockIndex + 1, 0, duplicatedBlock);
390
- return {
391
- ...state,
392
- blocks: newBlocks,
393
- selectedBlockId: duplicatedBlock.id,
394
- isDirty: true,
395
- };
88
+ else {
89
+ // Check nested levels to find parent and index
90
+ const findParentInfo = (nodes, parentId) => {
91
+ const idx = nodes.findIndex(n => n.id === id);
92
+ if (idx !== -1) {
93
+ targetContainerId = parentId;
94
+ targetIndex = idx + 1;
95
+ return true;
96
+ }
97
+ for (const node of nodes) {
98
+ if (node.children && Array.isArray(node.children) && typeof node.children[0] === 'object') {
99
+ if (findParentInfo(node.children, node.id))
100
+ return true;
101
+ }
102
+ }
103
+ return false;
104
+ };
105
+ findParentInfo(state.blocks);
106
+ }
107
+ if (targetIndex === -1)
108
+ return state;
109
+ const nextBlocks = targetContainerId
110
+ ? addNodeToContainer(state.blocks, targetContainerId, duplicated, targetIndex)
111
+ : (() => {
112
+ const next = [...state.blocks];
113
+ next.splice(targetIndex, 0, duplicated);
114
+ return next;
115
+ })();
116
+ return { ...state, blocks: nextBlocks, selectedBlockId: duplicated.id, isDirty: true };
396
117
  }
397
118
  case 'MOVE_BLOCK': {
398
119
  const { id, newIndex, containerId: rawContainerId } = action.payload;
399
- // Normalize 'root' string to undefined
400
- const containerId = rawContainerId === 'root' || rawContainerId === undefined ? undefined : rawContainerId;
401
- console.log('[Reducer] MOVE_BLOCK action:', {
402
- blockId: id,
403
- newIndex,
404
- rawContainerId,
405
- normalizedContainerId: containerId || 'root',
406
- currentRootBlocks: state.blocks.map(b => ({ id: b.id, type: b.type, hasChildren: !!b.children })),
407
- });
408
- // If containerId is provided (and not 'root'), move to/within container
409
- if (containerId) {
410
- // First check if block is already in this container
411
- const containerBlock = findBlockById(state.blocks, containerId);
412
- console.log('[Reducer] Container lookup:', {
413
- containerId,
414
- found: !!containerBlock,
415
- hasChildren: containerBlock?.children ? Array.isArray(containerBlock.children) : false,
416
- });
417
- if (containerBlock && containerBlock.children && Array.isArray(containerBlock.children)) {
418
- const children = typeof containerBlock.children[0] === 'object'
419
- ? containerBlock.children
420
- : [];
421
- const currentIndex = children.findIndex(b => b.id === id);
422
- console.log('[Reducer] Block in container check:', {
423
- blockId: id,
424
- currentIndex,
425
- containerChildren: children.map(b => ({ id: b.id, type: b.type })),
426
- });
427
- if (currentIndex !== -1) {
428
- // Block is already in this container - move within container
429
- console.log('[Reducer] Moving within container');
430
- const newBlocks = moveNestedBlock(state.blocks, containerId, id, newIndex);
431
- console.log('[Reducer] After move within container:', {
432
- newRootBlocks: newBlocks.map(b => ({ id: b.id, type: b.type })),
433
- });
434
- return {
435
- ...state,
436
- blocks: newBlocks,
437
- isDirty: true,
438
- };
439
- }
440
- }
441
- // Block is not in this container - move it from wherever it is (root or nested)
442
- console.log('[Reducer] Moving block to container from elsewhere');
443
- const newBlocks = moveBlockToContainer(state.blocks, id, containerId, newIndex);
444
- console.log('[Reducer] After move to container:', {
445
- newRootBlocks: newBlocks.map(b => ({ id: b.id, type: b.type })),
446
- });
447
- return {
448
- ...state,
449
- blocks: newBlocks,
450
- isDirty: true,
451
- };
120
+ const containerId = rawContainerId === 'root' ? undefined : rawContainerId;
121
+ // CRITICAL: Prevent moving a block into itself!
122
+ if (id === containerId) {
123
+ console.warn('[Reducer] MOVE_BLOCK aborted: Cannot move a block into itself', id);
124
+ return state;
125
+ }
126
+ // 1. Remove from current position
127
+ const { updatedNodes: afterRemove, removedNode } = removeNode(state.blocks, id);
128
+ if (!removedNode) {
129
+ console.error('[Reducer] MOVE_BLOCK failed: Node not found', id);
130
+ return state;
452
131
  }
453
- // Moving to root level (containerId is undefined)
454
- const currentIndex = state.blocks.findIndex(block => block.id === id);
455
- console.log('[Reducer] Moving to root level:', {
456
- blockId: id,
457
- currentIndex,
458
- isInRoot: currentIndex !== -1,
459
- newIndex,
460
- rootBlocksCount: state.blocks.length,
461
- });
462
- if (currentIndex !== -1) {
463
- // Block is already at root level - move within root
464
- if (newIndex < 0 || newIndex >= state.blocks.length) {
465
- console.warn('[Reducer] Invalid newIndex for root move:', { newIndex, blocksLength: state.blocks.length });
466
- return state;
132
+ // 2. Clear meta if moving to root
133
+ if (!containerId) {
134
+ if (removedNode.meta) {
135
+ const newMeta = { ...removedNode.meta };
136
+ delete newMeta.columnIndex;
137
+ removedNode.meta = newMeta;
467
138
  }
468
- console.log('[Reducer] Moving within root level');
469
- const newBlocks = [...state.blocks];
470
- const [movedBlock] = newBlocks.splice(currentIndex, 1);
471
- newBlocks.splice(newIndex, 0, movedBlock);
472
- console.log('[Reducer] After move within root:', {
473
- newRootBlocks: newBlocks.map(b => ({ id: b.id, type: b.type })),
474
- });
475
- return {
476
- ...state,
477
- blocks: newBlocks,
478
- isDirty: true,
479
- };
480
139
  }
481
- // Block is nested somewhere - move it to root level
482
- console.log('[Reducer] Block is nested, removing from tree and adding to root');
483
- const { updatedBlocks, removedBlock } = removeBlockFromTree(state.blocks, id);
484
- console.log('[Reducer] Block removal result:', {
485
- removedBlock: removedBlock ? { id: removedBlock.id, type: removedBlock.type } : null,
486
- updatedBlocksCount: updatedBlocks.length,
487
- });
488
- if (removedBlock) {
489
- // Clear any nested metadata (like columnIndex)
490
- const cleanedBlock = {
491
- ...removedBlock,
492
- meta: removedBlock.meta ? {
493
- ...removedBlock.meta,
494
- columnIndex: undefined,
495
- } : undefined,
496
- };
497
- const newBlocks = [...updatedBlocks];
498
- if (newIndex >= 0 && newIndex <= newBlocks.length) {
499
- newBlocks.splice(newIndex, 0, cleanedBlock);
140
+ // 3. Add to new position with fallback
141
+ try {
142
+ const finalBlocks = containerId
143
+ ? addNodeToContainer(afterRemove, containerId, removedNode, newIndex)
144
+ : (() => {
145
+ const next = [...afterRemove];
146
+ const safeIndex = Math.max(0, Math.min(newIndex, next.length));
147
+ next.splice(safeIndex, 0, removedNode);
148
+ return next;
149
+ })();
150
+ return { ...state, blocks: finalBlocks, isDirty: true };
151
+ }
152
+ catch (error) {
153
+ console.error('[Reducer] MOVE_BLOCK failed to add to container:', error);
154
+ // CRITICAL FALLBACK: Re-insert at root so it doesn't disappear
155
+ const fallbackBlocks = [...afterRemove];
156
+ fallbackBlocks.push(removedNode);
157
+ return { ...state, blocks: fallbackBlocks, isDirty: true };
158
+ }
159
+ }
160
+ case 'SET_TITLE': {
161
+ const title = action.payload;
162
+ const status = state.status;
163
+ let slug = slugify(title);
164
+ // Add draft suffix if not published to match API behavior
165
+ if (status !== 'published' && slug) {
166
+ // If it already has a draft suffix, keep it or generate new one
167
+ if (!state.slug.includes('-draft-')) {
168
+ slug = `${slug}-draft-${Date.now().toString().slice(-4)}`;
500
169
  }
501
170
  else {
502
- newBlocks.push(cleanedBlock);
171
+ // Just update the base part of the draft slug
172
+ const suffix = state.slug.split('-draft-')[1];
173
+ slug = `${slug}-draft-${suffix}`;
503
174
  }
504
- console.log('[Reducer] After move nested to root:', {
505
- newRootBlocks: newBlocks.map(b => ({ id: b.id, type: b.type })),
506
- insertedAt: newIndex >= 0 && newIndex <= updatedBlocks.length ? newIndex : newBlocks.length - 1,
507
- });
508
- return {
509
- ...state,
510
- blocks: newBlocks,
511
- isDirty: true,
512
- };
513
175
  }
514
- console.warn('[Reducer] Block not found in tree:', { blockId: id });
515
- return state;
176
+ return { ...state, title, slug, isDirty: true };
516
177
  }
517
- case 'SET_TITLE':
178
+ case 'SET_SLUG': return { ...state, slug: action.payload, isDirty: true };
179
+ case 'SET_SEO': return { ...state, seo: { ...state.seo, ...action.payload }, isDirty: true };
180
+ case 'SET_METADATA': return { ...state, metadata: { ...state.metadata, ...action.payload }, isDirty: true };
181
+ case 'SET_STATUS': return { ...state, status: action.payload, isDirty: true };
182
+ case 'SET_FOCUS_MODE': return { ...state, focusMode: action.payload };
183
+ case 'SELECT_BLOCK': return { ...state, selectedBlockId: action.payload };
184
+ case 'DESELECT_BLOCK':
518
185
  return {
519
186
  ...state,
520
- title: action.payload,
521
- isDirty: true,
522
- };
523
- case 'SET_SLUG':
524
- return {
525
- ...state,
526
- slug: action.payload,
527
- isDirty: true,
528
- };
529
- case 'SET_SEO':
530
- return {
531
- ...state,
532
- seo: { ...state.seo, ...action.payload },
533
- isDirty: true,
534
- };
535
- case 'SET_METADATA':
536
- return {
537
- ...state,
538
- metadata: { ...state.metadata, ...action.payload },
539
- isDirty: true,
540
- };
541
- case 'SET_STATUS':
542
- return {
543
- ...state,
544
- status: action.payload,
545
- isDirty: true,
546
- };
547
- case 'SET_FOCUS_MODE':
548
- return {
549
- ...state,
550
- focusMode: action.payload,
551
- };
552
- case 'SELECT_BLOCK':
553
- return {
554
- ...state,
555
- selectedBlockId: action.payload,
556
- };
557
- case 'SET_DRAGGED_BLOCK':
558
- return {
559
- ...state,
560
- draggedBlockId: action.payload,
187
+ selectedBlockId: state.selectedBlockId === action.payload ? null : state.selectedBlockId
561
188
  };
189
+ case 'SET_DRAGGED_BLOCK': return { ...state, draggedBlockId: action.payload };
190
+ case 'MARK_CLEAN': return { ...state, isDirty: false };
191
+ case 'MARK_DIRTY': return { ...state, isDirty: true };
192
+ case 'SET_CURRENT_LANGUAGE': return { ...state, currentLanguage: action.payload };
562
193
  case 'LOAD_POST': {
563
194
  const post = action.payload;
564
195
  return {
@@ -574,31 +205,7 @@ export function editorReducer(state, action) {
574
205
  selectedBlockId: null,
575
206
  };
576
207
  }
577
- case 'RESET_EDITOR':
578
- return {
579
- ...initialEditorState,
580
- };
581
- case 'MARK_CLEAN':
582
- return {
583
- ...state,
584
- isDirty: false,
585
- };
586
- case 'MARK_DIRTY':
587
- return {
588
- ...state,
589
- isDirty: true,
590
- };
591
- case 'SET_CURRENT_LANGUAGE':
592
- return {
593
- ...state,
594
- currentLanguage: action.payload,
595
- };
596
- case 'UNDO':
597
- case 'REDO':
598
- case 'SAVE_HISTORY':
599
- // These are handled by the context, not the reducer
600
- return state;
601
- default:
602
- return state;
208
+ case 'RESET_EDITOR': return { ...initialEditorState };
209
+ default: return state;
603
210
  }
604
211
  }