@jhits/plugin-blog 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/package.json +5 -4
  2. package/src/api/categories.ts +43 -0
  3. package/src/api/check-title.ts +60 -0
  4. package/src/api/config-handler.ts +76 -0
  5. package/src/api/handler.ts +418 -0
  6. package/src/api/index.ts +33 -0
  7. package/src/api/route.ts +116 -0
  8. package/src/api/router.ts +128 -0
  9. package/src/api-server.ts +11 -0
  10. package/src/config.ts +161 -0
  11. package/src/hooks/index.d.ts +8 -0
  12. package/src/hooks/index.d.ts.map +1 -0
  13. package/src/hooks/index.ts +9 -0
  14. package/src/hooks/useBlog.d.ts +31 -0
  15. package/src/hooks/useBlog.d.ts.map +1 -0
  16. package/src/hooks/useBlog.ts +85 -0
  17. package/src/hooks/useBlogs.d.ts +39 -0
  18. package/src/hooks/useBlogs.d.ts.map +1 -0
  19. package/src/hooks/useBlogs.ts +123 -0
  20. package/src/hooks/useCategories.d.ts +9 -0
  21. package/src/hooks/useCategories.d.ts.map +1 -0
  22. package/src/hooks/useCategories.ts +76 -0
  23. package/src/index.server.ts +14 -0
  24. package/src/index.tsx +335 -0
  25. package/src/init.tsx +63 -0
  26. package/src/lib/blocks/BlockRenderer.d.ts +54 -0
  27. package/src/lib/blocks/BlockRenderer.d.ts.map +1 -0
  28. package/src/lib/blocks/BlockRenderer.tsx +141 -0
  29. package/src/lib/blocks/index.ts +6 -0
  30. package/src/lib/config-storage.d.ts +30 -0
  31. package/src/lib/config-storage.d.ts.map +1 -0
  32. package/src/lib/config-storage.ts +65 -0
  33. package/src/lib/index.ts +9 -0
  34. package/src/lib/layouts/blocks/ColumnsBlock.d.ts +25 -0
  35. package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -0
  36. package/src/lib/layouts/blocks/ColumnsBlock.tsx +298 -0
  37. package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
  38. package/src/lib/layouts/blocks/SectionBlock.d.ts +25 -0
  39. package/src/lib/layouts/blocks/SectionBlock.d.ts.map +1 -0
  40. package/src/lib/layouts/blocks/SectionBlock.tsx +104 -0
  41. package/src/lib/layouts/blocks/index.ts +8 -0
  42. package/src/lib/layouts/index.d.ts +23 -0
  43. package/src/lib/layouts/index.d.ts.map +1 -0
  44. package/src/lib/layouts/index.ts +52 -0
  45. package/src/lib/layouts/registerLayoutBlocks.d.ts +9 -0
  46. package/src/lib/layouts/registerLayoutBlocks.d.ts.map +1 -0
  47. package/src/lib/layouts/registerLayoutBlocks.ts +64 -0
  48. package/src/lib/mappers/apiMapper.d.ts +66 -0
  49. package/src/lib/mappers/apiMapper.d.ts.map +1 -0
  50. package/src/lib/mappers/apiMapper.ts +254 -0
  51. package/src/lib/migration/index.ts +6 -0
  52. package/src/lib/migration/mapper.ts +140 -0
  53. package/src/lib/rich-text/RichTextEditor.d.ts +45 -0
  54. package/src/lib/rich-text/RichTextEditor.d.ts.map +1 -0
  55. package/src/lib/rich-text/RichTextEditor.tsx +826 -0
  56. package/src/lib/rich-text/RichTextPreview.d.ts +16 -0
  57. package/src/lib/rich-text/RichTextPreview.d.ts.map +1 -0
  58. package/src/lib/rich-text/RichTextPreview.tsx +210 -0
  59. package/src/lib/rich-text/index.d.ts +9 -0
  60. package/src/lib/rich-text/index.d.ts.map +1 -0
  61. package/src/lib/rich-text/index.ts +10 -0
  62. package/src/lib/utils/blockHelpers.d.ts +23 -0
  63. package/src/lib/utils/blockHelpers.d.ts.map +1 -0
  64. package/src/lib/utils/blockHelpers.ts +72 -0
  65. package/src/lib/utils/configValidation.d.ts +23 -0
  66. package/src/lib/utils/configValidation.d.ts.map +1 -0
  67. package/src/lib/utils/configValidation.ts +137 -0
  68. package/src/lib/utils/index.ts +8 -0
  69. package/src/lib/utils/slugify.ts +79 -0
  70. package/src/registry/BlockRegistry.d.ts +62 -0
  71. package/src/registry/BlockRegistry.d.ts.map +1 -0
  72. package/src/registry/BlockRegistry.ts +139 -0
  73. package/src/registry/index.d.ts +6 -0
  74. package/src/registry/index.d.ts.map +1 -0
  75. package/src/registry/index.ts +11 -0
  76. package/src/state/EditorContext.d.ts +45 -0
  77. package/src/state/EditorContext.d.ts.map +1 -0
  78. package/src/state/EditorContext.tsx +283 -0
  79. package/src/state/index.d.ts +7 -0
  80. package/src/state/index.d.ts.map +1 -0
  81. package/src/state/index.ts +8 -0
  82. package/src/state/reducer.d.ts +11 -0
  83. package/src/state/reducer.d.ts.map +1 -0
  84. package/src/state/reducer.ts +694 -0
  85. package/src/state/types.d.ts +162 -0
  86. package/src/state/types.d.ts.map +1 -0
  87. package/src/state/types.ts +160 -0
  88. package/src/types/block.d.ts +221 -0
  89. package/src/types/block.d.ts.map +1 -0
  90. package/src/types/block.ts +269 -0
  91. package/src/types/index.d.ts +8 -0
  92. package/src/types/index.d.ts.map +1 -0
  93. package/src/types/index.ts +17 -0
  94. package/src/types/post.d.ts +136 -0
  95. package/src/types/post.d.ts.map +1 -0
  96. package/src/types/post.ts +169 -0
  97. package/src/utils/client.d.ts +48 -0
  98. package/src/utils/client.d.ts.map +1 -0
  99. package/src/utils/client.ts +122 -0
  100. package/src/utils/index.ts +7 -0
  101. package/src/views/CanvasEditor/BlockWrapper.d.ts +16 -0
  102. package/src/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
  103. package/src/views/CanvasEditor/BlockWrapper.tsx +522 -0
  104. package/src/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
  105. package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
  106. package/src/views/CanvasEditor/CanvasEditorView.tsx +337 -0
  107. package/src/views/CanvasEditor/EditorBody.d.ts +22 -0
  108. package/src/views/CanvasEditor/EditorBody.d.ts.map +1 -0
  109. package/src/views/CanvasEditor/EditorBody.tsx +665 -0
  110. package/src/views/CanvasEditor/EditorHeader.d.ts +18 -0
  111. package/src/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
  112. package/src/views/CanvasEditor/EditorHeader.tsx +268 -0
  113. package/src/views/CanvasEditor/LayoutContainer.d.ts +17 -0
  114. package/src/views/CanvasEditor/LayoutContainer.d.ts.map +1 -0
  115. package/src/views/CanvasEditor/LayoutContainer.tsx +322 -0
  116. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +13 -0
  117. package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +1 -0
  118. package/src/views/CanvasEditor/SaveConfirmationModal.tsx +233 -0
  119. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +14 -0
  120. package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
  121. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +92 -0
  122. package/src/views/CanvasEditor/components/EditorCanvas.d.ts +29 -0
  123. package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
  124. package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
  125. package/src/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
  126. package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
  127. package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
  128. package/src/views/CanvasEditor/components/EditorSidebar.d.ts +13 -0
  129. package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
  130. package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
  131. package/src/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
  132. package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
  133. package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
  134. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +25 -0
  135. package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -0
  136. package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +341 -0
  137. package/src/views/CanvasEditor/components/LibraryItem.d.ts +14 -0
  138. package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
  139. package/src/views/CanvasEditor/components/LibraryItem.tsx +80 -0
  140. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +15 -0
  141. package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -0
  142. package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +212 -0
  143. package/src/views/CanvasEditor/components/index.d.ts +21 -0
  144. package/src/views/CanvasEditor/components/index.d.ts.map +1 -0
  145. package/src/views/CanvasEditor/components/index.ts +28 -0
  146. package/src/views/CanvasEditor/hooks/index.d.ts +10 -0
  147. package/src/views/CanvasEditor/hooks/index.d.ts.map +1 -0
  148. package/src/views/CanvasEditor/hooks/index.ts +10 -0
  149. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +8 -0
  150. package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -0
  151. package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
  152. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
  153. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
  154. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
  155. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +5 -0
  156. package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -0
  157. package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
  158. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
  159. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
  160. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
  161. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +25 -0
  162. package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +1 -0
  163. package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
  164. package/src/views/CanvasEditor/index.d.ts +16 -0
  165. package/src/views/CanvasEditor/index.d.ts.map +1 -0
  166. package/src/views/CanvasEditor/index.ts +16 -0
  167. package/src/views/PostManager/EmptyState.d.ts +10 -0
  168. package/src/views/PostManager/EmptyState.d.ts.map +1 -0
  169. package/src/views/PostManager/EmptyState.tsx +42 -0
  170. package/src/views/PostManager/PostActionsMenu.d.ts +12 -0
  171. package/src/views/PostManager/PostActionsMenu.d.ts.map +1 -0
  172. package/src/views/PostManager/PostActionsMenu.tsx +112 -0
  173. package/src/views/PostManager/PostCards.d.ts +15 -0
  174. package/src/views/PostManager/PostCards.d.ts.map +1 -0
  175. package/src/views/PostManager/PostCards.tsx +197 -0
  176. package/src/views/PostManager/PostFilters.d.ts +16 -0
  177. package/src/views/PostManager/PostFilters.d.ts.map +1 -0
  178. package/src/views/PostManager/PostFilters.tsx +95 -0
  179. package/src/views/PostManager/PostManagerView.d.ts +11 -0
  180. package/src/views/PostManager/PostManagerView.d.ts.map +1 -0
  181. package/src/views/PostManager/PostManagerView.tsx +289 -0
  182. package/src/views/PostManager/PostStats.d.ts +11 -0
  183. package/src/views/PostManager/PostStats.d.ts.map +1 -0
  184. package/src/views/PostManager/PostStats.tsx +81 -0
  185. package/src/views/PostManager/PostTable.d.ts +15 -0
  186. package/src/views/PostManager/PostTable.d.ts.map +1 -0
  187. package/src/views/PostManager/PostTable.tsx +230 -0
  188. package/src/views/PostManager/index.d.ts +12 -0
  189. package/src/views/PostManager/index.d.ts.map +1 -0
  190. package/src/views/PostManager/index.ts +15 -0
  191. package/src/views/Preview/PreviewBridgeView.d.ts +12 -0
  192. package/src/views/Preview/PreviewBridgeView.d.ts.map +1 -0
  193. package/src/views/Preview/PreviewBridgeView.tsx +64 -0
  194. package/src/views/Preview/index.d.ts +6 -0
  195. package/src/views/Preview/index.d.ts.map +1 -0
  196. package/src/views/Preview/index.ts +7 -0
  197. package/src/views/Settings/SettingsView.d.ts +10 -0
  198. package/src/views/Settings/SettingsView.d.ts.map +1 -0
  199. package/src/views/Settings/SettingsView.tsx +298 -0
  200. package/src/views/Settings/index.d.ts +6 -0
  201. package/src/views/Settings/index.d.ts.map +1 -0
  202. package/src/views/Settings/index.ts +7 -0
  203. package/src/views/SlugSEO/SlugSEOManagerView.d.ts +12 -0
  204. package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -0
  205. package/src/views/SlugSEO/SlugSEOManagerView.tsx +94 -0
  206. package/src/views/SlugSEO/index.d.ts +6 -0
  207. package/src/views/SlugSEO/index.d.ts.map +1 -0
  208. package/src/views/SlugSEO/index.ts +7 -0
@@ -0,0 +1,14 @@
1
+ export interface CanvasEditorViewProps {
2
+ postId?: string;
3
+ siteId: string;
4
+ locale: string;
5
+ /** Enable dark mode for content area and wrappers (default: true) */
6
+ darkMode?: boolean;
7
+ /** Background colors for the editor */
8
+ backgroundColors?: {
9
+ light: string;
10
+ dark?: string;
11
+ };
12
+ }
13
+ export declare function CanvasEditorView({ postId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale }: CanvasEditorViewProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=CanvasEditorView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasEditorView.d.ts","sourceRoot":"","sources":["CanvasEditorView.tsx"],"names":[],"mappings":"AAaA,MAAM,WAAW,qBAAqB;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,qBAAqB,2CAsTpI"}
@@ -0,0 +1,337 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useEffect, useRef } from 'react';
4
+ import { useEditor } from '../../state/EditorContext';
5
+ import { EditorHeader } from './EditorHeader';
6
+ import { ErrorBanner } from './components/ErrorBanner';
7
+ import { EditorLibrary } from './components/EditorLibrary';
8
+ import { EditorCanvas } from './components/EditorCanvas';
9
+ import { EditorSidebar } from './components/EditorSidebar';
10
+ import { usePostLoader, useHeroBlock, useRegisteredBlocks, useKeyboardShortcuts, useUnsavedChanges } from './hooks';
11
+ import type { Block } from '../../types/block';
12
+ import type { SEOMetadata, PostMetadata } from '../../types/post';
13
+
14
+ export interface CanvasEditorViewProps {
15
+ postId?: string;
16
+ siteId: string;
17
+ locale: string;
18
+ /** Enable dark mode for content area and wrappers (default: true) */
19
+ darkMode?: boolean;
20
+ /** Background colors for the editor */
21
+ backgroundColors?: {
22
+ light: string;
23
+ dark?: string;
24
+ };
25
+ }
26
+
27
+ export function CanvasEditorView({ postId, darkMode, backgroundColors: propsBackgroundColors, siteId, locale }: CanvasEditorViewProps) {
28
+ const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
29
+ const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
30
+ const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
31
+ const [isSidebarOpen, setSidebarOpen] = useState(true);
32
+ const [isLibraryOpen, setLibraryOpen] = useState(true);
33
+ const [isPreviewMode, setIsPreviewMode] = useState(false);
34
+ const [isSaving, setIsSaving] = useState(false);
35
+ const [saveError, setSaveError] = useState<string | null>(null);
36
+
37
+ // Get registered blocks
38
+ const registeredBlocks = useRegisteredBlocks();
39
+
40
+ // Hero block management
41
+ const { heroBlock, setHeroBlock, heroBlockDefinition } = useHeroBlock(state, registeredBlocks);
42
+
43
+ // Post loading
44
+ const { isLoadingPost } = usePostLoader(
45
+ postId,
46
+ state.postId,
47
+ (post) => {
48
+ helpers.loadPost(post);
49
+ // After loading, ensure we're marked as clean
50
+ // Use setTimeout to ensure this runs after the reducer has processed LOAD_POST
51
+ setTimeout(() => {
52
+ dispatch({ type: 'MARK_CLEAN' });
53
+ }, 0);
54
+ },
55
+ () => setHeroBlock(null)
56
+ );
57
+
58
+ // Track if we just loaded a post to prevent marking as dirty during cleanup
59
+ const justLoadedRef = useRef(false);
60
+ const previousIsLoadingRef = useRef<boolean>(false);
61
+ const loadingCleanupTimerRef = useRef<NodeJS.Timeout | null>(null);
62
+
63
+ // Mark when post loading completes and ensure it stays clean after all effects
64
+ useEffect(() => {
65
+ // Detect when loading just finished (was loading, now not loading, and we have a postId)
66
+ const loadingJustFinished = previousIsLoadingRef.current && !isLoadingPost && state.postId;
67
+
68
+ if (loadingJustFinished) {
69
+ justLoadedRef.current = true;
70
+
71
+ // Clear any existing cleanup timer
72
+ if (loadingCleanupTimerRef.current) {
73
+ clearTimeout(loadingCleanupTimerRef.current);
74
+ }
75
+
76
+ // Wait for all effects to complete, then ensure we're marked as clean
77
+ // Use multiple animation frames + setTimeout to ensure all effects have run
78
+ requestAnimationFrame(() => {
79
+ requestAnimationFrame(() => {
80
+ loadingCleanupTimerRef.current = setTimeout(() => {
81
+ // Force mark as clean after loading - this ensures cleanup effects don't leave us dirty
82
+ console.log('[CanvasEditorView] Post loading complete - ensuring clean state');
83
+ dispatch({ type: 'MARK_CLEAN' });
84
+ justLoadedRef.current = false;
85
+ loadingCleanupTimerRef.current = null;
86
+ }, 500); // Delay to ensure all effects complete
87
+ });
88
+ });
89
+ }
90
+
91
+ // Update ref
92
+ previousIsLoadingRef.current = isLoadingPost;
93
+
94
+ return () => {
95
+ if (loadingCleanupTimerRef.current) {
96
+ clearTimeout(loadingCleanupTimerRef.current);
97
+ loadingCleanupTimerRef.current = null;
98
+ }
99
+ };
100
+ }, [isLoadingPost, state.postId, dispatch]);
101
+
102
+ // Keyboard shortcuts
103
+ useKeyboardShortcuts(state, dispatch, canUndo, canRedo, helpers.undo, helpers.redo);
104
+
105
+ // Unsaved changes warning and auto-save
106
+ const { autoSaveEnabled, setAutoSaveEnabled, countdown, saveStatus } = useUnsavedChanges({
107
+ state,
108
+ isDirty: state.isDirty,
109
+ onSave: async () => {
110
+ // Preserve current status: if already published, keep it published
111
+ // Otherwise save as draft
112
+ const shouldPublish = state.status === 'published';
113
+ await handleSave(shouldPublish);
114
+ },
115
+ heroBlock,
116
+ postId: state.postId,
117
+ });
118
+
119
+ // Listen for hero title updates from HeroBlock (if it dispatches events)
120
+ useEffect(() => {
121
+ const handleHeroTitleUpdate = (e: CustomEvent) => {
122
+ dispatch({ type: 'SET_TITLE', payload: e.detail });
123
+ };
124
+ window.addEventListener('hero-title-update', handleHeroTitleUpdate as EventListener);
125
+ return () => window.removeEventListener('hero-title-update', handleHeroTitleUpdate as EventListener);
126
+ }, [dispatch]);
127
+
128
+ // Remove any hero blocks from the content blocks array
129
+ // Note: This effect will mark as dirty, but the loading cleanup effect will fix it
130
+ useEffect(() => {
131
+ const heroBlocksInContent = state.blocks.filter(b => b.type === 'hero');
132
+ if (heroBlocksInContent.length > 0) {
133
+ heroBlocksInContent.forEach(block => {
134
+ dispatch({ type: 'DELETE_BLOCK', payload: { id: block.id } });
135
+ });
136
+ // Don't mark as clean here - let the loading cleanup effect handle it
137
+ // This ensures we wait for all effects to complete before marking clean
138
+ }
139
+ }, [state.blocks, dispatch]);
140
+
141
+ // Filter out hero blocks from content blocks
142
+ const contentBlocks = state.blocks.filter(b => b.type !== 'hero');
143
+
144
+ // Handler to add block at the bottom when clicking (not dragging)
145
+ const handleAddBlockAtBottom = (blockType: string) => {
146
+ // Add at the end of content blocks (excluding hero)
147
+ helpers.addBlock(blockType, contentBlocks.length, undefined);
148
+ };
149
+
150
+ // Handle save
151
+ const handleSave = async (publish?: boolean) => {
152
+ setIsSaving(true);
153
+ setSaveError(null);
154
+ try {
155
+ // Status should already be set in EditorHeader, but verify and log
156
+ console.log('[CanvasEditorView] onSave called with publish:', publish, 'current status:', state.status);
157
+
158
+ // Only change status if explicitly requested (publish is true or false)
159
+ // If publish is undefined, preserve the current status (used for autosave)
160
+ if (publish === true && state.status !== 'published') {
161
+ console.warn('[CanvasEditorView] Status mismatch! Setting to published...');
162
+ dispatch({ type: 'SET_STATUS', payload: 'published' });
163
+ await new Promise(resolve => setTimeout(resolve, 100));
164
+ } else if (publish === false && state.status !== 'draft' && state.status !== 'published') {
165
+ // Only set to draft if not already published (preserve published status)
166
+ // This prevents autosave from changing published posts back to draft
167
+ console.warn('[CanvasEditorView] Status mismatch! Setting to draft...');
168
+ dispatch({ type: 'SET_STATUS', payload: 'draft' });
169
+ await new Promise(resolve => setTimeout(resolve, 100));
170
+ }
171
+
172
+ console.log('[CanvasEditorView] Final status before save:', state.status);
173
+
174
+ // Pass hero block to save function so it can be included in the saved data
175
+ await helpers.save(heroBlock);
176
+ setIsSaving(false);
177
+ } catch (error: any) {
178
+ console.error('[CanvasEditorView] Save error:', error);
179
+ // Extract and format user-friendly error message
180
+ let errorMessage = error.message || 'Failed to save post';
181
+
182
+ // Make error messages more user-friendly
183
+ if (errorMessage.includes('Missing required fields')) {
184
+ errorMessage = errorMessage.replace('Missing required fields for publishing:', 'To publish, please fill in:');
185
+ } else if (errorMessage.includes('All required fields')) {
186
+ errorMessage = 'To publish, please fill in all required fields: summary, featured image, category, and content.';
187
+ } else if (errorMessage.includes('Unauthorized')) {
188
+ errorMessage = 'You are not authorized to save this post. Please log in again.';
189
+ } else if (errorMessage.includes('Failed to save')) {
190
+ errorMessage = 'Unable to save the post. Please check your connection and try again.';
191
+ }
192
+
193
+ setSaveError(errorMessage);
194
+ setIsSaving(false); // Always reset saving state on error
195
+ throw error; // Re-throw so EditorHeader can handle it
196
+ }
197
+ };
198
+
199
+ // Handle hero block update
200
+ const handleHeroBlockUpdate = (data: Partial<Block['data']>) => {
201
+ if (!heroBlock) return;
202
+
203
+ setHeroBlock({
204
+ ...heroBlock,
205
+ data: { ...heroBlock.data, ...data },
206
+ });
207
+
208
+ // Sync title to editor state
209
+ if (data.title !== undefined && typeof data.title === 'string') {
210
+ dispatch({ type: 'SET_TITLE', payload: data.title });
211
+ }
212
+
213
+ // Sync summary to editor state metadata
214
+ if (data.summary !== undefined && typeof data.summary === 'string') {
215
+ dispatch({
216
+ type: 'SET_METADATA',
217
+ payload: { excerpt: data.summary }
218
+ });
219
+ }
220
+
221
+ // Hero image and featured image are completely independent
222
+ // Do NOT sync hero image to featured image
223
+ // The featured image is a separate thumbnail that the client adjusts independently
224
+
225
+ // Sync category to editor state metadata
226
+ if (data.category !== undefined && typeof data.category === 'string') {
227
+ dispatch({
228
+ type: 'SET_METADATA',
229
+ payload: {
230
+ categories: data.category.trim() ? [data.category.trim()] : []
231
+ }
232
+ });
233
+ }
234
+ };
235
+
236
+ // Handle hero block delete/reset
237
+ const handleHeroBlockDelete = () => {
238
+ if (!heroBlock || !heroBlockDefinition) return;
239
+ const defaultData = heroBlockDefinition.defaultData || {};
240
+ setHeroBlock({
241
+ ...heroBlock,
242
+ data: { ...defaultData },
243
+ });
244
+ };
245
+
246
+ return (
247
+ <div className="h-full rounded-[2.5rem] w-full bg-dashboard-card text-dashboard-text flex flex-col font-sans transition-colors duration-300 overflow-hidden relative">
248
+ <main className="flex flex-1 flex-col relative min-h-0">
249
+ {/* Error Banner */}
250
+ <ErrorBanner error={saveError} onDismiss={() => setSaveError(null)} />
251
+
252
+ <EditorHeader
253
+ isLibraryOpen={isLibraryOpen}
254
+ onLibraryToggle={() => setLibraryOpen(!isLibraryOpen)}
255
+ isPreviewMode={isPreviewMode}
256
+ onPreviewToggle={() => setIsPreviewMode(!isPreviewMode)}
257
+ isSidebarOpen={isSidebarOpen}
258
+ onSidebarToggle={() => setSidebarOpen(!isSidebarOpen)}
259
+ isSaving={isSaving}
260
+ onSave={handleSave}
261
+ onSaveError={(error) => {
262
+ // Format error message for display
263
+ if (error) {
264
+ let formattedError = error;
265
+ if (formattedError.includes('Missing required fields')) {
266
+ formattedError = formattedError.replace('Missing required fields for publishing:', 'To publish, please fill in:');
267
+ }
268
+ setSaveError(formattedError);
269
+ } else {
270
+ setSaveError(null);
271
+ }
272
+ }}
273
+ autoSaveEnabled={autoSaveEnabled}
274
+ onAutoSaveToggle={setAutoSaveEnabled}
275
+ isDirty={state.isDirty}
276
+ autoSaveCountdown={countdown}
277
+ autoSaveStatus={saveStatus}
278
+ />
279
+
280
+ {/* Editor Content Wrapper */}
281
+ <div className="flex flex-1 relative overflow-hidden min-h-0 flex-nowrap">
282
+ {/* LEFT SIDEBAR: COMPONENT LIBRARY */}
283
+ {!isPreviewMode && (
284
+ <aside
285
+ className={`transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-r border-dashboard-border bg-dashboard-sidebar overflow-y-auto overflow-x-hidden h-full ${isLibraryOpen ? 'w-72' : 'w-0 opacity-0 pointer-events-none'
286
+ }`}
287
+ >
288
+ <EditorLibrary
289
+ registeredBlocks={registeredBlocks}
290
+ onAddBlock={handleAddBlockAtBottom}
291
+ />
292
+ </aside>
293
+ )}
294
+
295
+ {/* CENTER: THE WRITING CANVAS */}
296
+ <EditorCanvas
297
+ isPreviewMode={isPreviewMode}
298
+ heroBlock={heroBlock}
299
+ heroBlockDefinition={heroBlockDefinition}
300
+ contentBlocks={contentBlocks}
301
+ title={state.title}
302
+ siteId={siteId}
303
+ locale={locale}
304
+ darkMode={effectiveDarkMode}
305
+ backgroundColors={effectiveBackgroundColors}
306
+ featuredImage={state.metadata.featuredImage}
307
+ onTitleChange={(title: string) => dispatch({ type: 'SET_TITLE', payload: title })}
308
+ onHeroBlockUpdate={handleHeroBlockUpdate}
309
+ onHeroBlockDelete={handleHeroBlockDelete}
310
+ onBlockAdd={(type: string, index: number, containerId?: string) => helpers.addBlock(type, index, containerId)}
311
+ onBlockUpdate={(id: string, data: Partial<Block['data']>) => helpers.updateBlock(id, data)}
312
+ onBlockDelete={(id: string) => helpers.deleteBlock(id)}
313
+ onBlockMove={(id: string, newIndex: number, containerId?: string) => helpers.moveBlock(id, newIndex, containerId)}
314
+ />
315
+
316
+ {/* RIGHT SIDEBAR: THE "DESK" (SETTINGS) */}
317
+ {!isPreviewMode && (
318
+ <aside
319
+ className={`transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto overflow-x-hidden h-full ${isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none'
320
+ }`}
321
+ >
322
+ <EditorSidebar
323
+ slug={state.slug}
324
+ seo={state.seo}
325
+ metadata={state.metadata}
326
+ heroBlock={heroBlock}
327
+ status={state.status}
328
+ onSEOUpdate={(seo: Partial<SEOMetadata>) => dispatch({ type: 'SET_SEO', payload: seo })}
329
+ onMetadataUpdate={(metadata: Partial<PostMetadata>) => dispatch({ type: 'SET_METADATA', payload: metadata })}
330
+ />
331
+ </aside>
332
+ )}
333
+ </div>
334
+ </main>
335
+ </div>
336
+ );
337
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Editor Body Component
3
+ * Primary root container for the editor canvas
4
+ * Acts as the main drop zone for blocks
5
+ */
6
+ import { Block } from '../../types/block';
7
+ export interface EditorBodyProps {
8
+ blocks: Block[];
9
+ onBlockAdd: (type: string, index: number, containerId?: string) => void;
10
+ onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
11
+ onBlockDelete: (id: string) => void;
12
+ onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
13
+ /** Enable dark mode for content area and wrappers (default: true) */
14
+ darkMode?: boolean;
15
+ /** Background colors for the editor */
16
+ backgroundColors?: {
17
+ light: string;
18
+ dark?: string;
19
+ };
20
+ }
21
+ export declare function EditorBody({ blocks, onBlockAdd, onBlockUpdate, onBlockDelete, onBlockMove, darkMode, backgroundColors, }: EditorBodyProps): import("react/jsx-runtime").JSX.Element;
22
+ //# sourceMappingURL=EditorBody.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorBody.d.ts","sourceRoot":"","sources":["EditorBody.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAG1C,MAAM,WAAW,eAAe;IAC5B,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACxE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAClE,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1E,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED,wBAAgB,UAAU,CAAC,EACvB,MAAM,EACN,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,EACX,QAAe,EACf,gBAAgB,GACnB,EAAE,eAAe,2CAuhBjB"}