@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
@@ -27,12 +27,13 @@ export function useHeroBlock(state, registeredBlocks) {
27
27
  return heroBlockFromContent;
28
28
  }
29
29
  // If no hero block in contentBlocks, initialize from defaults
30
- // Hero image and featured image are completely independent - no syncing
30
+ // We map metadata.excerpt to both 'summary' and 'description' to support different block implementations
31
31
  const initialData = {
32
32
  ...heroData,
33
33
  title: state.title || heroData.title || '',
34
34
  summary: state.metadata.excerpt || heroData.summary || '',
35
- image: heroData.image, // Use default image, not featured image
35
+ description: state.metadata.excerpt || heroData.description || '',
36
+ category: state.metadata.categories?.[0] || heroData.category || '',
36
37
  };
37
38
  return {
38
39
  id: generateBlockId(),
@@ -47,36 +48,32 @@ export function useHeroBlock(state, registeredBlocks) {
47
48
  else {
48
49
  setHeroBlock(null);
49
50
  }
50
- }, [heroBlockDefinition, state.blocks, state.title, state.metadata.excerpt]);
51
+ }, [heroBlockDefinition, state.blocks, state.title, state.metadata.excerpt, state.metadata.categories]);
51
52
  // Sync hero block with editor state when post is loaded (for existing posts)
52
- // BUT: Never sync image from featured image to hero - they are independent
53
- // Only sync title, summary, and category
54
53
  useEffect(() => {
55
54
  if (heroBlock && heroBlockDefinition && state.postId) {
56
- // Only update if the hero block data doesn't match the editor state
57
- // This prevents overwriting user edits with stale data
58
55
  const currentTitle = heroBlock.data?.title || '';
59
56
  const currentSummary = heroBlock.data?.summary || '';
57
+ const currentDescription = heroBlock.data?.description || '';
60
58
  const currentCategory = heroBlock.data?.category || '';
61
59
  const stateTitle = state.title || '';
62
- const stateSummary = state.metadata.excerpt || '';
60
+ const stateExcerpt = state.metadata.excerpt || '';
63
61
  const stateCategory = state.metadata.categories?.[0] || '';
64
- // Check if hero block is out of sync with editor state
65
- // NOTE: We do NOT sync image anymore - hero and featured image are independent
66
62
  const titleMismatch = currentTitle !== stateTitle;
67
- const summaryMismatch = currentSummary !== stateSummary;
63
+ // Check both summary and description for mismatches
64
+ const summaryMismatch = currentSummary !== stateExcerpt;
65
+ const descriptionMismatch = currentDescription !== stateExcerpt;
68
66
  const categoryMismatch = currentCategory !== stateCategory;
69
- // Only update title, summary, and category - NEVER update image
70
- // The hero block image should come from contentBlocks, not from featured image
71
- if ((titleMismatch || summaryMismatch || categoryMismatch) && (stateTitle || stateSummary || stateCategory)) {
67
+ // Sync if state has values and there's a mismatch
68
+ if ((titleMismatch || summaryMismatch || descriptionMismatch || categoryMismatch) && (stateTitle || stateExcerpt || stateCategory)) {
72
69
  setHeroBlock({
73
70
  ...heroBlock,
74
71
  data: {
75
72
  ...heroBlock.data,
76
- title: stateTitle || heroBlock.data?.title || '',
77
- summary: stateSummary || heroBlock.data?.summary || '',
78
- // DO NOT sync image - keep hero block's own image
79
- category: stateCategory || heroBlock.data?.category || '',
73
+ title: stateTitle || currentTitle,
74
+ summary: stateExcerpt || currentSummary,
75
+ description: stateExcerpt || currentDescription,
76
+ category: stateCategory || currentCategory,
80
77
  },
81
78
  });
82
79
  }
@@ -1,4 +1,7 @@
1
1
  import type { BlogPost } from '../../../types/post';
2
+ /**
3
+ * Technical hook for loading post data into the Orchestrator
4
+ */
2
5
  export declare function usePostLoader(postId: string | undefined, currentPostId: string | null, loadPost: (post: BlogPost) => void, resetHeroBlock: () => void, language?: string): {
3
6
  isLoadingPost: boolean;
4
7
  };
@@ -1 +1 @@
1
- {"version":3,"file":"usePostLoader.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/usePostLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,wBAAgB,aAAa,CACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,EAClC,cAAc,EAAE,MAAM,IAAI,EAC1B,QAAQ,CAAC,EAAE,MAAM;;EAwCpB"}
1
+ {"version":3,"file":"usePostLoader.d.ts","sourceRoot":"","sources":["../../../../src/views/CanvasEditor/hooks/usePostLoader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD;;GAEG;AACH,wBAAgB,aAAa,CACzB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,EAC5B,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,EAClC,cAAc,EAAE,MAAM,IAAI,EAC1B,QAAQ,CAAC,EAAE,MAAM;;EA2CpB"}
@@ -1,32 +1,34 @@
1
+ 'use client';
1
2
  import { useEffect, useState, useRef } from 'react';
2
3
  import { apiToBlogPost } from '../../../lib/mappers/apiMapper';
4
+ /**
5
+ * Technical hook for loading post data into the Orchestrator
6
+ */
3
7
  export function usePostLoader(postId, currentPostId, loadPost, resetHeroBlock, language) {
4
8
  const [isLoadingPost, setIsLoadingPost] = useState(false);
5
- // Use a ref to track language so changes don't re-trigger initial load
6
9
  const languageRef = useRef(language);
7
10
  languageRef.current = language;
8
11
  useEffect(() => {
12
+ // Only trigger if we have a target ID but no active post in state
13
+ // This prevents infinite loops while allowing initial synchronization
9
14
  if (postId && !currentPostId) {
10
15
  const loadPostData = async () => {
11
16
  try {
12
17
  setIsLoadingPost(true);
13
- // Reset hero block before loading new post so it gets re-initialized from the new post's blocks
14
18
  resetHeroBlock();
15
- const lang = languageRef.current;
16
- const url = lang
17
- ? `/api/plugin-blog/${postId}?language=${lang}`
18
- : `/api/plugin-blog/${postId}`;
19
+ const lang = languageRef.current || 'nl';
20
+ // We use the relative URL which will hit the Dashboard API
21
+ const url = `/api/plugin-blog/${postId}?language=${lang}&admin=true`;
19
22
  const response = await fetch(url);
20
23
  if (!response.ok) {
21
- throw new Error('Failed to load post');
24
+ throw new Error(`NODE_HANDSHAKE_FAILED: ${response.status}`);
22
25
  }
23
26
  const apiDoc = await response.json();
24
27
  const blogPost = apiToBlogPost(apiDoc);
25
28
  loadPost(blogPost);
26
29
  }
27
30
  catch (error) {
28
- console.error('Failed to load post:', error);
29
- alert('Failed to load post. Please try again.');
31
+ console.error('[usePostLoader] CRITICAL_SYNC_ERROR:', error);
30
32
  }
31
33
  finally {
32
34
  setIsLoadingPost(false);
@@ -34,9 +36,6 @@ export function usePostLoader(postId, currentPostId, loadPost, resetHeroBlock, l
34
36
  };
35
37
  loadPostData();
36
38
  }
37
- // Only re-run on initial load (postId / currentPostId change), NOT on language change
38
- // Language switching is handled separately by handleLanguageChange
39
- // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [postId, currentPostId]);
39
+ }, [postId, currentPostId, loadPost, resetHeroBlock]);
41
40
  return { isLoadingPost };
42
41
  }
@@ -63,7 +63,6 @@ export function useUnsavedChanges({ state, isDirty, onSave, heroBlock, autoSaveE
63
63
  try {
64
64
  localStorage.setItem(AUTO_SAVE_STORAGE_KEY, enabled.toString());
65
65
  setAutoSaveEnabledState(enabled);
66
- console.log('[useUnsavedChanges] Auto-save preference updated:', enabled);
67
66
  }
68
67
  catch (error) {
69
68
  console.error('[useUnsavedChanges] Failed to save auto-save preference:', error);
@@ -89,7 +88,6 @@ export function useUnsavedChanges({ state, isDirty, onSave, heroBlock, autoSaveE
89
88
  // When a post is loaded (postId exists) and isDirty is false, update the saved state reference
90
89
  if (postId && !isDirty && lastSavedStateRef.current === '') {
91
90
  lastSavedStateRef.current = getStateSnapshot();
92
- console.log('[useUnsavedChanges] Initialized saved state reference after post load');
93
91
  }
94
92
  // Also update if isDirty becomes false after being true (e.g., after save or MARK_CLEAN)
95
93
  if (!isDirty && lastSavedStateRef.current !== getStateSnapshot()) {
@@ -106,11 +104,9 @@ export function useUnsavedChanges({ state, isDirty, onSave, heroBlock, autoSaveE
106
104
  setSaveStatus('saving');
107
105
  setCountdown(null);
108
106
  countdownStartTimeRef.current = null;
109
- console.log('[useUnsavedChanges] Auto-saving...');
110
107
  await onSave(heroBlock);
111
108
  lastSavedStateRef.current = getStateSnapshot();
112
109
  setSaveStatus('saved');
113
- console.log('[useUnsavedChanges] Auto-save completed');
114
110
  // Clear save status after 2 seconds
115
111
  if (saveStatusTimeoutRef.current) {
116
112
  clearTimeout(saveStatusTimeoutRef.current);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Empty State Component
3
- * Botanical-themed empty state for when no posts are found
3
+ * Generic empty state for when no posts are found
4
4
  */
5
5
  export interface EmptyStateProps {
6
6
  hasFilters: boolean;
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Empty State Component
3
- * Botanical-themed empty state for when no posts are found
3
+ * Generic empty state for when no posts are found
4
4
  */
5
5
  'use client';
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
- import { Sprout, Plus } from 'lucide-react';
7
+ import { PenTool, Plus } from 'lucide-react';
8
8
  export function EmptyState({ hasFilters, onCreatePost }) {
9
- return (_jsxs("div", { className: "flex flex-col items-center justify-center py-20 px-8 bg-neutral-100 dark:bg-neutral-800/50 rounded-[2.5rem] border-2 border-dashed border-neutral-300 dark:border-neutral-700", children: [_jsx("div", { className: "w-24 h-24 rounded-full bg-green-500/10 dark:bg-green-500/20 flex items-center justify-center mb-6", children: _jsx(Sprout, { className: "text-green-600 dark:text-green-400 size-12" }) }), _jsx("h3", { className: "text-xl font-black text-neutral-950 dark:text-white uppercase tracking-tight mb-2", children: hasFilters ? 'No Posts Found' : 'No Posts Yet' }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400 text-center mb-6 max-w-md", children: hasFilters
9
+ return (_jsxs("div", { className: "flex flex-col items-center justify-center py-20 px-8 bg-neutral-100 dark:bg-neutral-800/50 rounded-[2.5rem] border-2 border-dashed border-neutral-300 dark:border-neutral-700", children: [_jsx("div", { className: "w-24 h-24 rounded-full bg-primary/10 dark:bg-primary/20 flex items-center justify-center mb-6", children: _jsx(PenTool, { className: "text-primary size-12" }) }), _jsx("h3", { className: "text-xl font-black text-neutral-950 dark:text-white uppercase tracking-tight mb-2", children: hasFilters ? 'No Results Found' : 'Your Journal is Empty' }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400 text-center mb-6 max-w-md leading-relaxed", children: hasFilters
10
10
  ? 'Try adjusting your search or filter criteria to find what you\'re looking for.'
11
- : 'Start growing your content garden. Create your first blog post to share your botanical knowledge with the world.' }), !hasFilters && (_jsxs("button", { onClick: onCreatePost, className: "inline-flex items-center gap-2 px-6 py-3 bg-primary text-white rounded-full text-[10px] font-black uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg shadow-primary/20", children: [_jsx(Plus, { size: 16 }), "Create Your First Post"] }))] }));
11
+ : 'It looks like you haven\'t published any stories yet. Start writing to share your insights with the world.' }), !hasFilters && (_jsxs("button", { onClick: onCreatePost, className: "inline-flex items-center gap-2 px-8 py-4 bg-primary text-white rounded-full text-[10px] font-black uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg shadow-primary/20", children: [_jsx(Plus, { size: 16 }), "Start Writing"] }))] }));
12
12
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Filter Dropdown Component
3
+ * Custom styled dropdown matching the dashboard design
4
+ */
5
+ import React from 'react';
6
+ interface Option {
7
+ label: string;
8
+ value: string;
9
+ icon?: React.ReactNode;
10
+ }
11
+ interface FilterDropdownProps {
12
+ options: Option[];
13
+ value: string;
14
+ onChange: (value: string) => void;
15
+ icon: React.ReactNode;
16
+ label: string;
17
+ minWidth?: string;
18
+ }
19
+ export declare function FilterDropdown({ options, value, onChange, icon, label, minWidth }: FilterDropdownProps): import("react/jsx-runtime").JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=FilterDropdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilterDropdown.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/FilterDropdown.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAG3D,UAAU,MAAM;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC1B;AAED,UAAU,mBAAmB;IACzB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,cAAc,CAAC,EAC3B,OAAO,EACP,KAAK,EACL,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,QAAkB,EACrB,EAAE,mBAAmB,2CA8DrB"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Filter Dropdown Component
3
+ * Custom styled dropdown matching the dashboard design
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { useState, useRef, useEffect } from 'react';
8
+ import { ChevronDown } from 'lucide-react';
9
+ export function FilterDropdown({ options, value, onChange, icon, label, minWidth = '160px' }) {
10
+ const [isOpen, setIsOpen] = useState(false);
11
+ const dropdownRef = useRef(null);
12
+ const selectedOption = options.find(opt => opt.value === value) || options[0];
13
+ useEffect(() => {
14
+ const handleClickOutside = (event) => {
15
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
16
+ setIsOpen(false);
17
+ }
18
+ };
19
+ document.addEventListener('mousedown', handleClickOutside);
20
+ return () => document.removeEventListener('mousedown', handleClickOutside);
21
+ }, []);
22
+ return (_jsxs("div", { className: "relative", ref: dropdownRef, children: [_jsxs("button", { onClick: () => setIsOpen(!isOpen), className: `flex items-center gap-3 px-4 py-2.5 rounded-xl transition-all hover:bg-white/5 group ${isOpen ? 'text-primary' : 'text-dashboard-text-secondary'}`, style: { minWidth }, children: [_jsx("div", { className: `${isOpen ? 'text-primary' : 'text-dashboard-text-secondary group-hover:text-primary'} transition-colors`, children: selectedOption.icon || icon }), _jsxs("div", { className: "flex flex-col items-start flex-1 overflow-hidden text-left", children: [_jsx("span", { className: "text-[8px] font-black uppercase tracking-[0.2em] opacity-40", children: label }), _jsx("span", { className: "text-[10px] font-black uppercase tracking-widest truncate w-full", children: selectedOption.label })] }), _jsx(ChevronDown, { className: `size-3 transition-transform duration-300 ${isOpen ? 'rotate-180 text-primary' : 'opacity-30'}` })] }), isOpen && (_jsx("div", { className: "absolute top-full left-0 mt-3 p-2 bg-dashboard-bg/95 backdrop-blur-2xl border border-dashboard-border rounded-2xl shadow-2xl z-[100] min-w-[200px] animate-in fade-in zoom-in-95 duration-200", children: options.map((option) => (_jsxs("button", { onClick: () => {
23
+ onChange(option.value);
24
+ setIsOpen(false);
25
+ }, className: `w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-all ${option.value === value
26
+ ? 'bg-primary/10 text-primary font-bold'
27
+ : 'text-dashboard-text-secondary hover:bg-white/5 hover:text-dashboard-text'}`, children: [_jsx("div", { className: option.value === value ? 'text-primary' : 'opacity-50', children: option.icon || icon }), _jsx("span", { className: "text-[10px] font-black uppercase tracking-widest", children: option.label })] }, option.value))) }))] }));
28
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"LanguageFlags.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/LanguageFlags.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,kBAAkB;IACxB,IAAI,EAAE,YAAY,CAAC;CACtB;AAwBD,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,EAAE,kBAAkB,kDAiGzD"}
1
+ {"version":3,"file":"LanguageFlags.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/LanguageFlags.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEhD,UAAU,kBAAkB;IACxB,IAAI,EAAE,YAAY,CAAC;CACtB;AAwBD,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,EAAE,kBAAkB,kDAqGzD"}
@@ -46,9 +46,12 @@ export function LanguageFlags({ post }) {
46
46
  return (_jsx("div", { className: "flex gap-2", children: post.availableLanguages.map((lang) => {
47
47
  const langData = post.languages?.[lang];
48
48
  const status = langData?.status || 'draft';
49
+ // Try to get title from metadata, fallback to root title
49
50
  const langTitle = langData?.metadata?.title || post.title;
50
51
  const isHovered = hoveredLang === lang;
51
- return (_jsxs("div", { ref: (el) => { flagRefs.current[lang] = el; }, className: "relative", onMouseEnter: () => setHoveredLang(lang), onMouseLeave: () => setHoveredLang(null), children: [_jsxs(motion.div, { whileHover: { scale: 1.15 }, className: `relative flex items-center justify-center w-7 h-4.5 rounded shadow-sm overflow-hidden border cursor-help transition-colors ${status === 'published'
52
+ return (_jsxs("div", { ref: (el) => { flagRefs.current[lang] = el; }, className: "relative", onMouseEnter: () => {
53
+ setHoveredLang(lang);
54
+ }, onMouseLeave: () => setHoveredLang(null), children: [_jsxs(motion.div, { whileHover: { scale: 1.15 }, className: `relative flex items-center justify-center w-7 h-4.5 rounded shadow-sm overflow-hidden border cursor-help transition-colors ${status === 'published'
52
55
  ? 'border-green-500/40 bg-white'
53
56
  : 'border-amber-500/40 bg-white opacity-90'}`, children: [_jsx("img", { src: getFlagUrl(lang), alt: lang, className: "w-full h-full object-cover" }), status !== 'published' && (_jsx("div", { className: "absolute inset-0 bg-amber-500/5" }))] }), typeof document !== 'undefined' && createPortal(_jsx(AnimatePresence, { children: isHovered && tooltipCoords && (_jsx(motion.div, { initial: { opacity: 0, y: -90, x: '-50%', scale: 0.95 }, animate: { opacity: 1, y: -100, x: '-50%', scale: 1 }, exit: { opacity: 0, y: -90, x: '-50%', scale: 0.95 }, className: "fixed z-[9999] pointer-events-none", style: {
54
57
  top: tooltipCoords.top - 12,
@@ -1 +1 @@
1
- {"version":3,"file":"PostCards.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/PostCards.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,YAAY,EAAc,MAAM,kBAAkB,CAAC;AAK5D,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA4BD,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,MAAM,EACN,MAAM,EACN,SAAS,EACT,WAAW,EACX,QAAQ,GACX,EAAE,cAAc,2CAoJhB"}
1
+ {"version":3,"file":"PostCards.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/PostCards.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,EAAE,YAAY,EAAc,MAAM,kBAAkB,CAAC;AAK5D,MAAM,WAAW,cAAc;IAC3B,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AA4BD,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,MAAM,EACN,MAAM,EACN,SAAS,EACT,WAAW,EACX,QAAQ,GACX,EAAE,cAAc,2CA+IhB"}
@@ -4,26 +4,25 @@
4
4
  */
5
5
  'use client';
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
- import { useState, useEffect } from 'react';
8
- import { Calendar, User, UserCheck } from 'lucide-react';
9
- import { Image } from '@jhits/plugin-images';
7
+ import { FileText, Tag, Activity } from 'lucide-react';
8
+ import { Image as PluginImage } from '@jhits/plugin-images';
10
9
  import { PostActionsMenu } from './PostActionsMenu';
11
10
  import { LanguageFlags } from './LanguageFlags';
12
11
  import { useSession } from 'next-auth/react';
13
12
  function getStatusBadgeColor(status) {
14
13
  switch (status) {
15
14
  case 'published':
16
- return 'bg-green-500/10 text-green-700 dark:text-green-400 border-green-500/20';
15
+ return 'bg-emerald-500 text-white border-emerald-400 shadow-lg shadow-emerald-500/20';
17
16
  case 'draft':
18
- return 'bg-amber-500/10 text-amber-700 dark:text-amber-400 border-amber-500/20';
17
+ return 'bg-amber-500 text-white border-amber-400 shadow-lg shadow-amber-500/20';
19
18
  case 'scheduled':
20
- return 'bg-blue-500/10 text-blue-700 dark:text-blue-400 border-blue-500/20';
19
+ return 'bg-blue-500 text-white border-blue-400 shadow-lg shadow-blue-500/20';
21
20
  case 'archived':
22
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
21
+ return 'bg-neutral-500 text-white border-neutral-400';
23
22
  case 'not-translated':
24
- return 'bg-red-500/10 text-red-700 dark:text-red-400 border-red-500/20 italic';
23
+ return 'bg-red-500/10 text-red-500 border-red-500/20 italic';
25
24
  default:
26
- return 'bg-neutral-500/10 text-neutral-700 dark:text-neutral-400 border-neutral-500/20';
25
+ return 'bg-neutral-500/10 text-neutral-500 border-neutral-500/20';
27
26
  }
28
27
  }
29
28
  function formatDate(dateString, locale) {
@@ -38,43 +37,27 @@ function formatDate(dateString, locale) {
38
37
  export function PostCards({ posts, locale, onEdit, onPreview, onDuplicate, onDelete, }) {
39
38
  const { data: session, status: sessionStatus } = useSession();
40
39
  const currentUserId = session?.user?.id;
41
- const [userMap, setUserMap] = useState({});
42
40
  // Helper function to check if user is the owner
43
41
  const isPostOwner = (post) => {
44
42
  if (sessionStatus === 'loading')
45
- return false; // Don't show actions while loading
43
+ return false;
46
44
  if (!currentUserId || !post.authorId)
47
45
  return false;
48
- // Convert both to strings for comparison to handle ObjectId vs string
49
46
  return String(currentUserId) === String(post.authorId);
50
47
  };
51
- // Fetch users to map IDs to names
52
- useEffect(() => {
53
- const fetchUsers = async () => {
54
- try {
55
- const response = await fetch('/api/users');
56
- const users = await response.json();
57
- if (Array.isArray(users)) {
58
- const map = {};
59
- users.forEach((user) => {
60
- const id = user._id?.toString();
61
- if (id) {
62
- map[id] = user.name || user.email || 'Unknown';
63
- }
64
- });
65
- setUserMap(map);
66
- }
67
- }
68
- catch (error) {
69
- console.error('Failed to fetch users:', error);
70
- }
71
- };
72
- fetchUsers();
73
- }, []);
74
- const getAuthorName = (authorId) => {
75
- if (!authorId)
76
- return 'Unknown';
77
- return userMap[authorId] || authorId;
48
+ const getAuthorData = (post) => {
49
+ if (post.author) {
50
+ return {
51
+ name: post.author.name || 'Unknown Author',
52
+ image: post.author.image
53
+ };
54
+ }
55
+ return { name: 'Unknown Author' };
78
56
  };
79
- return (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6", children: posts.map((post) => (_jsxs("div", { className: "flex flex-col bg-dashboard-card rounded-2xl border border-dashboard-border overflow-hidden hover:shadow-xl transition-all duration-300 group h-full", children: [_jsxs("div", { className: "relative w-full h-48 bg-neutral-200 dark:bg-neutral-800 overflow-hidden flex-shrink-0", children: [post.featuredImage ? (_jsx(Image, { id: post.featuredImage, alt: post.title, fill: true, editable: false, className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-300" })) : (_jsx("div", { className: "w-full h-full flex items-center justify-center", children: _jsx("span", { className: "text-sm text-neutral-400", children: "No Image" }) })), isPostOwner(post) && (_jsx("div", { className: "absolute top-4 left-4 z-10", children: _jsx("div", { className: "bg-white/90 dark:bg-neutral-900/90 backdrop-blur-sm rounded-full p-1 shadow-lg border border-neutral-200 dark:border-neutral-700", children: _jsx(PostActionsMenu, { onEdit: () => onEdit(post.id), onPreview: () => onPreview(post.id), onDuplicate: () => onDuplicate(post.id), onDelete: () => onDelete(post.id) }) }) })), _jsx("div", { className: "absolute top-4 right-4", children: _jsx("span", { className: `inline-flex items-center px-3 py-1 rounded-full text-[10px] font-black uppercase tracking-wider border backdrop-blur-sm ${getStatusBadgeColor(post.status)}`, children: post.status }) })] }), _jsxs("div", { className: "p-6 flex flex-col flex-1", children: [_jsxs("div", { className: "mb-4 min-h-[72px]", children: [_jsx("button", { onClick: () => onEdit(post.id), className: "text-left w-full hover:cursor-pointer", children: _jsx("h3", { className: "font-bold text-lg text-neutral-950 dark:text-white mb-2 line-clamp-2 group-hover:text-primary transition-colors hover:underline", children: post.title }) }), _jsxs("p", { className: "text-xs text-neutral-500 dark:text-neutral-400 font-mono line-clamp-1", children: ["/", post.slug] })] }), _jsx("div", { className: "flex-1 mb-4 min-h-[40px]", children: post.excerpt && (_jsx("p", { className: "text-sm text-neutral-600 dark:text-neutral-400 line-clamp-2", children: post.excerpt })) }), _jsx("div", { className: "mb-6", children: _jsx(LanguageFlags, { post: post }) }), _jsxs("div", { className: "space-y-3 pt-4 border-t border-neutral-200 dark:border-neutral-700 mt-auto", children: [_jsxs("div", { className: "flex items-center gap-2", children: [isPostOwner(post) ? (_jsx(UserCheck, { size: 14, className: "text-primary" })) : (_jsx(User, { size: 14, className: "text-neutral-400" })), _jsxs("span", { className: `text-xs ${isPostOwner(post) ? 'text-primary font-semibold' : 'text-neutral-600 dark:text-neutral-400'}`, children: [getAuthorName(post.authorId), isPostOwner(post) && (_jsx("span", { className: "ml-1", children: "(You)" }))] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 14, className: "text-neutral-400" }), _jsx("span", { className: "text-xs text-neutral-600 dark:text-neutral-400", children: formatDate(post.updatedAt, locale) })] })] })] })] }, post.id))) }));
57
+ return (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6", children: posts.map((post) => {
58
+ const author = getAuthorData(post);
59
+ const owner = isPostOwner(post);
60
+ const initials = author.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
61
+ return (_jsxs("div", { className: "group relative flex flex-col h-full animate-in fade-in slide-in-from-bottom-2 duration-500", children: [_jsx("div", { className: "absolute inset-0 bg-dashboard-card/40 backdrop-blur-md rounded-[2.5rem] border border-dashboard-border/40 transition-all duration-500 group-hover:border-primary/30 group-hover:bg-primary/5 group-hover:shadow-xl group-hover:shadow-primary/5" }), _jsxs("div", { className: "relative p-3.5 flex flex-col h-full space-y-5", children: [_jsxs("div", { className: "aspect-[16/9.5] relative bg-dashboard-bg/50 rounded-[2rem] overflow-hidden border border-dashboard-border/40 group-hover:border-primary/20 transition-all shadow-sm", children: [post.featuredImage ? (_jsx(PluginImage, { id: post.featuredImage, alt: post.title, fill: true, editable: false, className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-700" })) : (_jsx("div", { className: "w-full h-full flex flex-col items-center justify-center bg-dashboard-bg/50 opacity-20", children: _jsx(FileText, { size: 40 }) })), _jsx("div", { className: "absolute top-3.5 right-3.5", children: _jsx("span", { className: `inline-flex items-center px-3 py-1 rounded-full text-[9px] font-bold uppercase tracking-wider border backdrop-blur-md shadow-sm ${getStatusBadgeColor(post.status)}`, children: post.status === 'not-translated' ? 'Edition Missing' : post.status }) }), owner && (_jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/10 transition-all duration-500 flex items-center justify-center opacity-0 group-hover:opacity-100 z-20", children: _jsx("div", { className: "bg-white/10 backdrop-blur-md p-1.5 rounded-2xl border border-white/20 shadow-2xl flex items-center gap-1 scale-90 group-hover:scale-100 transition-transform duration-500", children: _jsx(PostActionsMenu, { onEdit: () => onEdit(post.id), onPreview: () => onPreview(post.id), onDuplicate: () => onDuplicate(post.id), onDelete: () => onDelete(post.id) }) }) }))] }), _jsxs("div", { className: "px-3.5 pb-2.5 flex-1 flex flex-col", children: [_jsxs("div", { className: "flex items-center gap-3 mb-3.5", children: [_jsx(LanguageFlags, { post: post }), _jsx("div", { className: "h-4 w-px bg-dashboard-border/30" }), _jsxs("span", { className: "text-[10px] font-bold text-primary uppercase tracking-widest flex items-center gap-1.5 opacity-80", children: [_jsx(Tag, { size: 12 }), post.category || 'Article'] })] }), _jsx("button", { onClick: () => onEdit(post.id), className: "text-left w-full group/title mb-2.5", children: _jsx("h3", { className: "text-xl font-bold text-dashboard-text tracking-tight leading-tight line-clamp-2 group-hover/title:text-primary transition-colors", children: post.title }) }), _jsx("p", { className: "text-sm text-dashboard-text-secondary font-medium line-clamp-2 opacity-60 mb-6", children: post.excerpt || 'No summary available for this publication.' }), _jsxs("div", { className: "pt-4 border-t border-dashboard-border/30 mt-auto space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [_jsx("div", { className: `size-9 rounded-xl flex items-center justify-center text-xs font-bold border transition-all overflow-hidden shrink-0 ${owner ? 'bg-primary text-white border-primary/20 shadow-lg shadow-primary/20' : 'bg-dashboard-bg/50 text-dashboard-text-secondary border-dashboard-border/60'}`, children: author.image ? (_jsx("img", { src: author.image, alt: author.name, className: "size-full object-cover", crossOrigin: "anonymous" })) : initials }), _jsxs("div", { className: "flex flex-col min-w-0", children: [_jsxs("span", { className: "text-[10px] font-bold text-dashboard-text uppercase tracking-tight truncate", children: [author.name, " ", owner && _jsx("span", { className: "text-primary ml-0.5", children: "(YOU)" })] }), _jsx("span", { className: "text-[9px] font-semibold text-dashboard-text-secondary uppercase tracking-widest opacity-60", children: formatDate(post.updatedAt, locale) })] })] }), post.status === 'published' && (_jsxs("div", { className: "flex items-center gap-1.5 px-2.5 py-0.5 bg-emerald-500/10 text-emerald-500 rounded-full border border-emerald-500/20", children: [_jsx(Activity, { size: 10, className: "animate-pulse" }), _jsx("span", { className: "text-[8px] font-bold uppercase tracking-widest", children: "Live" })] }))] }), _jsx("div", { className: "bg-dashboard-bg/40 px-3 py-1.5 rounded-xl border border-dashboard-border/30", children: _jsxs("p", { className: "text-[9px] font-mono text-dashboard-text-secondary/50 uppercase tracking-tight truncate", children: ["Path: /", post.slug] }) })] })] })] })] }, post.id));
62
+ }) }));
80
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PostFilters.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/PostFilters.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,UAAU,GAAG,KAAK,CAAC;IACjC,oBAAoB,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,KAAK,IAAI,CAAC;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACrB,EAAE,gBAAgB,2CA+FlB"}
1
+ {"version":3,"file":"PostFilters.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/PostFilters.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,YAAY,EAAE,UAAU,GAAG,KAAK,CAAC;IACjC,oBAAoB,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,KAAK,IAAI,CAAC;IAC1D,cAAc,EAAE,MAAM,CAAC;IACvB,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,wBAAgB,WAAW,CAAC,EACxB,MAAM,EACN,cAAc,EACd,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,UAAU,EACV,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACrB,EAAE,gBAAgB,2CAoGlB"}
@@ -5,16 +5,47 @@
5
5
  'use client';
6
6
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  import { Search, Filter, Tag, Globe } from 'lucide-react';
8
+ import { FilterDropdown } from './FilterDropdown';
8
9
  export function PostFilters({ search, onSearchChange, statusFilter, onStatusFilterChange, categoryFilter, onCategoryFilterChange, categories, language, onLanguageChange, availableLanguages, }) {
9
10
  const langNames = {
10
11
  nl: 'Nederlands (NL)',
11
- en: 'English (EN)',
12
- sv: 'Svenska (SV)',
12
+ en: 'English (GB)',
13
+ sv: 'Svenska (SE)',
13
14
  de: 'Deutsch (DE)',
14
15
  fr: 'Français (FR)',
15
16
  es: 'Español (ES)',
16
17
  it: 'Italiano (IT)',
17
18
  pt: 'Português (PT)',
18
19
  };
19
- return (_jsxs("div", { className: "flex flex-col sm:flex-row gap-4 mb-6", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx("label", { htmlFor: "blog-post-search", className: "absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0", style: { clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }, children: "Search posts by title or content" }), _jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4" }), _jsx("input", { id: "blog-post-search", name: "blog-post-search", type: "text", value: search, onChange: (e) => onSearchChange(e.target.value), placeholder: "Search posts by title or content...", className: "w-full pl-11 pr-4 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary outline-none transition-all" })] }), _jsxs("div", { className: "relative", children: [_jsx("label", { htmlFor: "blog-post-status-filter", className: "absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0", style: { clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }, children: "Filter by status" }), _jsx(Filter, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" }), _jsxs("select", { id: "blog-post-status-filter", name: "blog-post-status-filter", value: statusFilter, onChange: (e) => onStatusFilterChange(e.target.value), className: "pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[160px]", children: [_jsx("option", { value: "all", children: "All Statuses" }), _jsx("option", { value: "published", children: "Published" }), _jsx("option", { value: "draft", children: "Draft" }), _jsx("option", { value: "scheduled", children: "Scheduled" }), _jsx("option", { value: "archived", children: "Archived" })] })] }), _jsxs("div", { className: "relative", children: [_jsx("label", { htmlFor: "blog-post-category-filter", className: "absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0", style: { clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }, children: "Filter by category" }), _jsx(Tag, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" }), _jsxs("select", { id: "blog-post-category-filter", name: "blog-post-category-filter", value: categoryFilter, onChange: (e) => onCategoryFilterChange(e.target.value), className: "pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[160px]", children: [_jsx("option", { value: "all", children: "All Categories" }), categories.map((category) => (_jsx("option", { value: category, children: category }, category)))] })] }), _jsxs("div", { className: "relative", children: [_jsx("label", { htmlFor: "blog-post-language-filter", className: "absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0", style: { clip: 'rect(0, 0, 0, 0)', clipPath: 'inset(50%)' }, children: "Primary Language" }), _jsx(Globe, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4 pointer-events-none" }), _jsx("select", { id: "blog-post-language-filter", name: "blog-post-language-filter", value: language, onChange: (e) => onLanguageChange(e.target.value), className: "pl-11 pr-8 py-3 bg-neutral-100 dark:bg-neutral-800 border border-neutral-300 dark:border-neutral-700 rounded-2xl text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary appearance-none outline-none cursor-pointer min-w-[140px]", children: availableLanguages.map(lang => (_jsx("option", { value: lang, children: langNames[lang] || lang.toUpperCase() }, lang))) })] })] }));
20
+ const getFlagUrl = (lang) => {
21
+ const mapping = {
22
+ en: 'gb',
23
+ nl: 'nl',
24
+ sv: 'se',
25
+ de: 'de',
26
+ fr: 'fr',
27
+ es: 'es',
28
+ it: 'it',
29
+ pt: 'pt'
30
+ };
31
+ const countryCode = mapping[lang] || lang;
32
+ return `https://flagcdn.com/w40/${countryCode.toLowerCase()}.png`;
33
+ };
34
+ const statusOptions = [
35
+ { label: 'All Statuses', value: 'all' },
36
+ { label: 'Published', value: 'published' },
37
+ { label: 'Draft', value: 'draft' },
38
+ { label: 'Scheduled', value: 'scheduled' },
39
+ { label: 'Archived', value: 'archived' },
40
+ ];
41
+ const categoryOptions = [
42
+ { label: 'All Categories', value: 'all' },
43
+ ...categories.map(cat => ({ label: cat, value: cat }))
44
+ ];
45
+ const languageOptions = availableLanguages.map(lang => ({
46
+ label: langNames[lang] || lang.toUpperCase(),
47
+ value: lang,
48
+ icon: _jsx("img", { src: getFlagUrl(lang), alt: "", className: "w-4 h-2.5 rounded-sm object-cover shadow-sm border border-white/10" })
49
+ }));
50
+ return (_jsxs("div", { className: "flex-1 flex flex-col md:flex-row items-center gap-4", children: [_jsxs("div", { className: "relative flex-1 group w-full", children: [_jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-dashboard-text-secondary/40 group-focus-within:text-primary transition-colors duration-300", size: 18 }), _jsx("input", { id: "blog-post-search", name: "blog-post-search", type: "text", value: search, onChange: (e) => onSearchChange(e.target.value), placeholder: "Search articles by title...", className: "w-full pl-12 pr-6 py-3.5 bg-dashboard-card/40 border border-dashboard-border/40 rounded-2xl text-sm font-semibold text-dashboard-text placeholder:text-dashboard-text-secondary/30 outline-none focus:border-primary/30 transition-all" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-1.5 p-1.5 bg-dashboard-card/50 rounded-xl border border-dashboard-border/40 w-full md:w-auto", children: [_jsx(FilterDropdown, { label: "Status", icon: _jsx(Filter, { size: 14, className: "text-primary/60" }), value: statusFilter, options: statusOptions, onChange: (val) => onStatusFilterChange(val), minWidth: "140px" }), _jsx("div", { className: "h-6 w-px bg-dashboard-border/30 mx-1 hidden md:block" }), _jsx(FilterDropdown, { label: "Category", icon: _jsx(Tag, { size: 14, className: "text-primary/60" }), value: categoryFilter, options: categoryOptions, onChange: onCategoryFilterChange, minWidth: "150px" }), _jsx("div", { className: "h-6 w-px bg-dashboard-border/30 mx-1 hidden md:block" }), _jsx(FilterDropdown, { label: "Language", icon: _jsx(Globe, { size: 14, className: "text-primary/60" }), value: language, options: languageOptions, onChange: onLanguageChange, minWidth: "160px" })] })] }));
20
51
  }
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Post Manager View
3
- * Production-ready listing page for managing blog posts
4
- * Follows dashboard earth-tone design system
3
+ * Natural scroll layout with sticky header synchronization
5
4
  */
6
5
  export interface PostManagerViewProps {
7
6
  siteId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"PostManagerView.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/PostManagerView.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAcH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,2CA4RvE"}
1
+ {"version":3,"file":"PostManagerView.d.ts","sourceRoot":"","sources":["../../../src/views/PostManager/PostManagerView.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAcH,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,oBAAoB,2CA8OvE"}