@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,233 @@
1
+ 'use client';
2
+
3
+ import React, { useEffect, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { X, AlertTriangle, CheckCircle2 } from 'lucide-react';
6
+ import { motion, AnimatePresence } from 'framer-motion';
7
+
8
+ export interface SaveConfirmationModalProps {
9
+ isOpen: boolean;
10
+ onClose: () => void;
11
+ onConfirm: () => Promise<void>;
12
+ isSaving: boolean;
13
+ postTitle?: string;
14
+ isPublished?: boolean;
15
+ saveAsDraft?: boolean;
16
+ error?: string | null;
17
+ }
18
+
19
+ export function SaveConfirmationModal({
20
+ isOpen,
21
+ onClose,
22
+ onConfirm,
23
+ isSaving,
24
+ postTitle,
25
+ isPublished,
26
+ saveAsDraft = false,
27
+ error,
28
+ }: SaveConfirmationModalProps) {
29
+ const [mounted, setMounted] = useState(false);
30
+ const [showSuccess, setShowSuccess] = useState(false);
31
+ const [saveError, setSaveError] = useState<string | null>(null);
32
+
33
+ useEffect(() => {
34
+ setMounted(true);
35
+ }, []);
36
+
37
+ // Reset states when modal opens
38
+ useEffect(() => {
39
+ if (isOpen) {
40
+ setShowSuccess(false);
41
+ setSaveError(null);
42
+ }
43
+ }, [isOpen]);
44
+
45
+ // Update error state when error prop changes
46
+ useEffect(() => {
47
+ if (error) {
48
+ setSaveError(error);
49
+ setShowSuccess(false);
50
+ } else {
51
+ setSaveError(null);
52
+ }
53
+ }, [error]);
54
+
55
+ if (!mounted) return null;
56
+
57
+ const handleConfirm = async () => {
58
+ // Clear any previous errors
59
+ setSaveError(null);
60
+ try {
61
+ await onConfirm();
62
+ // Only show success if onConfirm completes without error
63
+ setShowSuccess(true);
64
+ // Close modal after showing success for 1.5 seconds
65
+ setTimeout(() => {
66
+ onClose();
67
+ setShowSuccess(false);
68
+ setSaveError(null);
69
+ }, 1500);
70
+ } catch (error: any) {
71
+ // Display error in modal
72
+ const errorMessage = error.message || 'Failed to save post. Please try again.';
73
+ setSaveError(errorMessage);
74
+ console.error('[SaveConfirmationModal] Save failed:', error);
75
+ }
76
+ };
77
+
78
+ const modalContent = (
79
+ <AnimatePresence>
80
+ {isOpen && (
81
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
82
+ {/* Backdrop */}
83
+ <motion.div
84
+ initial={{ opacity: 0 }}
85
+ animate={{ opacity: 1 }}
86
+ exit={{ opacity: 0 }}
87
+ onClick={onClose}
88
+ className="absolute inset-0 bg-black/60 backdrop-blur-md"
89
+ />
90
+
91
+ {/* Modal */}
92
+ <motion.div
93
+ initial={{ scale: 0.9, opacity: 0, y: 20 }}
94
+ animate={{ scale: 1, opacity: 1, y: 0 }}
95
+ exit={{ scale: 0.9, opacity: 0, y: 20 }}
96
+ onClick={(e) => e.stopPropagation()}
97
+ className="relative w-full font-sans max-w-md bg-dashboard-card rounded-[2.5rem] p-8 shadow-2xl border border-dashboard-border"
98
+ >
99
+ {/* Close Button */}
100
+ <button
101
+ onClick={onClose}
102
+ disabled={isSaving}
103
+ className="absolute top-6 right-6 text-neutral-400 hover:text-neutral-900 dark:hover:text-white transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
104
+ >
105
+ <X size={24} />
106
+ </button>
107
+
108
+ {/* Icon */}
109
+ <div className={`w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-6 transition-all ${
110
+ saveError
111
+ ? 'bg-red-100 dark:bg-red-900/20'
112
+ : showSuccess
113
+ ? 'bg-green-100 dark:bg-green-900/20'
114
+ : 'bg-amber-100 dark:bg-amber-900/20'
115
+ }`}>
116
+ {saveError ? (
117
+ <AlertTriangle size={32} className="text-red-600 dark:text-red-400" />
118
+ ) : showSuccess ? (
119
+ <CheckCircle2 size={32} className="text-green-600 dark:text-green-400" />
120
+ ) : (
121
+ <AlertTriangle size={32} className="text-amber-600 dark:text-amber-400" />
122
+ )}
123
+ </div>
124
+
125
+ {/* Title */}
126
+ <h3 className="text-2xl font-black text-center mb-4 text-neutral-950 dark:text-white">
127
+ {saveError
128
+ ? 'Error'
129
+ : showSuccess
130
+ ? 'Success!'
131
+ : saveAsDraft
132
+ ? 'Save Draft'
133
+ : `Confirm ${isPublished ? 'Update' : 'Publish'}`
134
+ }
135
+ </h3>
136
+
137
+ {/* Message */}
138
+ <div className="text-center mb-8">
139
+ {saveError ? (
140
+ <div className="space-y-3">
141
+ <p className="text-red-700 dark:text-red-300 font-semibold">
142
+ {saveError}
143
+ </p>
144
+ <p className="text-sm text-neutral-600 dark:text-neutral-400">
145
+ Please fix the issues above and try again.
146
+ </p>
147
+ </div>
148
+ ) : showSuccess ? (
149
+ <p className="text-green-700 dark:text-green-300 font-semibold">
150
+ {saveAsDraft ? 'Draft saved successfully!' : 'Post saved successfully!'}
151
+ </p>
152
+ ) : (
153
+ <>
154
+ <p className="text-neutral-700 dark:text-neutral-300 mb-2">
155
+ {saveAsDraft
156
+ ? 'Save this post as a draft? You can continue editing and publish it later.'
157
+ : isPublished
158
+ ? 'You are about to update this post. Changes cannot be undone.'
159
+ : 'You are about to publish this post. This action cannot be undone.'
160
+ }
161
+ </p>
162
+ {postTitle && (
163
+ <p className="text-sm text-neutral-500 dark:text-neutral-400 italic">
164
+ "{postTitle}"
165
+ </p>
166
+ )}
167
+ </>
168
+ )}
169
+ </div>
170
+
171
+ {/* Actions */}
172
+ {showSuccess ? (
173
+ <div className="flex justify-center">
174
+ <button
175
+ onClick={onClose}
176
+ className="px-6 py-3 rounded-full bg-green-600 text-white font-bold uppercase text-[10px] tracking-widest transition-all hover:bg-green-700 shadow-lg shadow-green-600/20"
177
+ >
178
+ Close
179
+ </button>
180
+ </div>
181
+ ) : saveError ? (
182
+ <div className="flex gap-4">
183
+ <button
184
+ onClick={onClose}
185
+ className="flex-1 px-6 py-3 rounded-full border-2 border-dashboard-border text-dashboard-text font-bold uppercase text-[10px] tracking-widest transition-all hover:bg-dashboard-bg"
186
+ >
187
+ Close
188
+ </button>
189
+ <button
190
+ onClick={handleConfirm}
191
+ disabled={isSaving}
192
+ className="flex-1 px-6 py-3 rounded-full bg-primary text-white font-bold uppercase text-[10px] tracking-widest transition-all hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-primary/20"
193
+ >
194
+ {isSaving
195
+ ? 'Retrying...'
196
+ : 'Try Again'
197
+ }
198
+ </button>
199
+ </div>
200
+ ) : (
201
+ <div className="flex gap-4">
202
+ <button
203
+ onClick={onClose}
204
+ disabled={isSaving}
205
+ className="flex-1 px-6 py-3 rounded-full border-2 border-dashboard-border text-dashboard-text font-bold uppercase text-[10px] tracking-widest transition-all hover:bg-dashboard-bg disabled:opacity-50 disabled:cursor-not-allowed"
206
+ >
207
+ Cancel
208
+ </button>
209
+ <button
210
+ onClick={handleConfirm}
211
+ disabled={isSaving}
212
+ className="flex-1 px-6 py-3 rounded-full bg-primary text-white font-bold uppercase text-[10px] tracking-widest transition-all hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg shadow-primary/20"
213
+ >
214
+ {isSaving
215
+ ? 'Saving...'
216
+ : saveAsDraft
217
+ ? 'Save Draft'
218
+ : isPublished
219
+ ? 'Update Post'
220
+ : 'Publish Post'
221
+ }
222
+ </button>
223
+ </div>
224
+ )}
225
+ </motion.div>
226
+ </div>
227
+ )}
228
+ </AnimatePresence>
229
+ );
230
+
231
+ return createPortal(modalContent, document.body);
232
+ }
233
+
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export interface CustomBlockItemProps {
3
+ blockType: string;
4
+ name: string;
5
+ description?: string;
6
+ icon: React.ReactNode;
7
+ onAddBlock?: (blockType: string) => void;
8
+ }
9
+ /**
10
+ * Custom Block Item Component
11
+ * Draggable custom block from client registry and clickable to add at bottom
12
+ */
13
+ export declare function CustomBlockItem({ blockType, name, description, icon, onAddBlock }: CustomBlockItemProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=CustomBlockItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CustomBlockItem.d.ts","sourceRoot":"","sources":["CustomBlockItem.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA2B,MAAM,OAAO,CAAC;AAGhD,MAAM,WAAW,oBAAoB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAC5B,SAAS,EACT,IAAI,EACJ,WAAW,EACX,IAAI,EACJ,UAAU,EACb,EAAE,oBAAoB,2CAmEtB"}
@@ -0,0 +1,92 @@
1
+ 'use client';
2
+
3
+ import React, { useState, useRef } from 'react';
4
+ import { GripVertical } from 'lucide-react';
5
+
6
+ export interface CustomBlockItemProps {
7
+ blockType: string;
8
+ name: string;
9
+ description?: string;
10
+ icon: React.ReactNode;
11
+ onAddBlock?: (blockType: string) => void;
12
+ }
13
+
14
+ /**
15
+ * Custom Block Item Component
16
+ * Draggable custom block from client registry and clickable to add at bottom
17
+ */
18
+ export function CustomBlockItem({
19
+ blockType,
20
+ name,
21
+ description,
22
+ icon,
23
+ onAddBlock
24
+ }: CustomBlockItemProps) {
25
+ const [hasDragged, setHasDragged] = useState(false);
26
+ const mouseDownRef = useRef<{ x: number; y: number } | null>(null);
27
+
28
+ const handleDragStart = (e: React.DragEvent) => {
29
+ e.dataTransfer.setData('block-type', blockType);
30
+ e.dataTransfer.effectAllowed = 'move';
31
+ setHasDragged(true); // Mark as dragged when drag starts
32
+ };
33
+
34
+ const handleMouseDown = (e: React.MouseEvent) => {
35
+ // Track mouse position on mousedown
36
+ mouseDownRef.current = { x: e.clientX, y: e.clientY };
37
+ setHasDragged(false); // Reset drag state
38
+ };
39
+
40
+ const handleMouseMove = (e: React.MouseEvent) => {
41
+ // If mouse moved more than 5px, consider it a drag
42
+ if (mouseDownRef.current) {
43
+ const dx = Math.abs(e.clientX - mouseDownRef.current.x);
44
+ const dy = Math.abs(e.clientY - mouseDownRef.current.y);
45
+ if (dx > 5 || dy > 5) {
46
+ setHasDragged(true);
47
+ }
48
+ }
49
+ };
50
+
51
+ const handleClick = (e: React.MouseEvent) => {
52
+ // Only add block if we didn't drag
53
+ if (!hasDragged && onAddBlock) {
54
+ e.preventDefault();
55
+ e.stopPropagation();
56
+ onAddBlock(blockType);
57
+ }
58
+ // Reset state
59
+ mouseDownRef.current = null;
60
+ setTimeout(() => setHasDragged(false), 100);
61
+ };
62
+
63
+ return (
64
+ <div
65
+ draggable
66
+ onDragStart={handleDragStart}
67
+ onMouseDown={handleMouseDown}
68
+ onMouseMove={handleMouseMove}
69
+ onClick={handleClick}
70
+ className="p-4 rounded-xl border border-dashboard-border bg-dashboard-bg hover:border-primary cursor-pointer transition-all group"
71
+ title={description}
72
+ >
73
+ <div className="flex items-center justify-between mb-2">
74
+ <div className="flex items-center gap-2">
75
+ <div className="text-neutral-500 dark:text-neutral-400 group-hover:text-primary dark:group-hover:text-primary transition-colors">
76
+ {icon}
77
+ </div>
78
+ <span className="text-[10px] font-bold uppercase tracking-wider text-neutral-700 dark:text-neutral-300 group-hover:text-neutral-950 dark:group-hover:text-white transition-colors">
79
+ {name}
80
+ </span>
81
+ </div>
82
+ <GripVertical size={12} className="text-neutral-400 dark:text-neutral-500 group-hover:text-neutral-600 dark:group-hover:text-neutral-400" />
83
+ </div>
84
+ {description && (
85
+ <p className="text-[9px] text-neutral-500 dark:text-neutral-400 leading-relaxed line-clamp-2">
86
+ {description}
87
+ </p>
88
+ )}
89
+ </div>
90
+ );
91
+ }
92
+
@@ -0,0 +1,29 @@
1
+ import type { Block } from '../../../types/block';
2
+ import type { BlockTypeDefinition } from '../../../types/block';
3
+ export interface EditorCanvasProps {
4
+ isPreviewMode: boolean;
5
+ heroBlock: Block | null;
6
+ heroBlockDefinition: BlockTypeDefinition | undefined;
7
+ contentBlocks: Block[];
8
+ title: string;
9
+ siteId: string;
10
+ locale: string;
11
+ darkMode: boolean;
12
+ backgroundColors?: {
13
+ light: string;
14
+ dark?: string;
15
+ };
16
+ featuredImage?: {
17
+ id?: string;
18
+ alt?: string;
19
+ };
20
+ onTitleChange: (title: string) => void;
21
+ onHeroBlockUpdate: (data: Partial<Block['data']>) => void;
22
+ onHeroBlockDelete: () => void;
23
+ onBlockAdd: (type: string, index: number, containerId?: string) => void;
24
+ onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
25
+ onBlockDelete: (id: string) => void;
26
+ onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
27
+ }
28
+ export declare function EditorCanvas({ isPreviewMode, heroBlock, heroBlockDefinition, contentBlocks, title, siteId, locale, darkMode, backgroundColors, featuredImage, onTitleChange, onHeroBlockUpdate, onHeroBlockDelete, onBlockAdd, onBlockUpdate, onBlockDelete, onBlockMove, }: EditorCanvasProps): import("react/jsx-runtime").JSX.Element;
29
+ //# sourceMappingURL=EditorCanvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorCanvas.d.ts","sourceRoot":"","sources":["EditorCanvas.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,KAAK,GAAG,IAAI,CAAC;IACxB,mBAAmB,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACrD,aAAa,EAAE,KAAK,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,aAAa,CAAC,EAAE;QACZ,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,GAAG,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,iBAAiB,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IAC1D,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,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;CAC7E;AAED,wBAAgB,YAAY,CAAC,EACzB,aAAa,EACb,SAAS,EACT,mBAAmB,EACnB,aAAa,EACb,KAAK,EACL,MAAM,EACN,MAAM,EACN,QAAQ,EACR,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,aAAa,EACb,aAAa,EACb,WAAW,GACd,EAAE,iBAAiB,2CA0GnB"}
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+
3
+ import React, { useRef, useEffect } from 'react';
4
+ import { BlockWrapper } from '../BlockWrapper';
5
+ import { EditorBody } from '../EditorBody';
6
+ import { BlockRenderer } from '../../../lib/blocks/BlockRenderer';
7
+ import type { Block } from '../../../types/block';
8
+ import type { BlockTypeDefinition } from '../../../types/block';
9
+
10
+ export interface EditorCanvasProps {
11
+ isPreviewMode: boolean;
12
+ heroBlock: Block | null;
13
+ heroBlockDefinition: BlockTypeDefinition | undefined;
14
+ contentBlocks: Block[];
15
+ title: string;
16
+ siteId: string;
17
+ locale: string;
18
+ darkMode: boolean;
19
+ backgroundColors?: {
20
+ light: string;
21
+ dark?: string;
22
+ };
23
+ featuredImage?: {
24
+ id?: string; // Semantic ID - plugin-images handles transforms
25
+ alt?: string;
26
+ };
27
+ onTitleChange: (title: string) => void;
28
+ onHeroBlockUpdate: (data: Partial<Block['data']>) => void;
29
+ onHeroBlockDelete: () => void;
30
+ onBlockAdd: (type: string, index: number, containerId?: string) => void;
31
+ onBlockUpdate: (id: string, data: Partial<Block['data']>) => void;
32
+ onBlockDelete: (id: string) => void;
33
+ onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
34
+ }
35
+
36
+ export function EditorCanvas({
37
+ isPreviewMode,
38
+ heroBlock,
39
+ heroBlockDefinition,
40
+ contentBlocks,
41
+ title,
42
+ siteId,
43
+ locale,
44
+ darkMode,
45
+ backgroundColors,
46
+ featuredImage,
47
+ onTitleChange,
48
+ onHeroBlockUpdate,
49
+ onHeroBlockDelete,
50
+ onBlockAdd,
51
+ onBlockUpdate,
52
+ onBlockDelete,
53
+ onBlockMove,
54
+ }: EditorCanvasProps) {
55
+ const titleRef = useRef<HTMLTextAreaElement>(null);
56
+
57
+ // Handle Title Auto-resize
58
+ useEffect(() => {
59
+ if (titleRef.current) {
60
+ titleRef.current.style.height = 'auto';
61
+ titleRef.current.style.height = `${titleRef.current.scrollHeight}px`;
62
+ }
63
+ }, [title]);
64
+
65
+ return (
66
+ <div
67
+ className="flex-1 overflow-y-auto overflow-x-hidden pb-40 px-6 custom-scrollbar selection:bg-primary/20 dark:selection:bg-primary/30 min-h-0"
68
+ style={{
69
+ backgroundColor: backgroundColors
70
+ ? (darkMode && backgroundColors.dark
71
+ ? backgroundColors.dark
72
+ : backgroundColors.light)
73
+ : undefined,
74
+ }}
75
+ >
76
+ <div className={`mx-auto transition-all duration-500 max-w-7xl w-full`}>
77
+ {/* Hero Block - Only show editable version when NOT in preview mode */}
78
+ {!isPreviewMode && heroBlockDefinition && heroBlock && (
79
+ <div className="mb-12">
80
+ <BlockWrapper
81
+ block={heroBlock}
82
+ onUpdate={onHeroBlockUpdate}
83
+ onDelete={onHeroBlockDelete}
84
+ onMoveUp={() => { }}
85
+ onMoveDown={() => { }}
86
+ allBlocks={[heroBlock]}
87
+ />
88
+ </div>
89
+ )}
90
+
91
+ {isPreviewMode ? (
92
+ <div className="space-y-8">
93
+ {heroBlockDefinition && heroBlock && (
94
+ <BlockRenderer
95
+ block={heroBlock}
96
+ context={{
97
+ siteId,
98
+ locale,
99
+ // Pass featured image as fallback for hero block
100
+ // Only pass id and alt - plugin-images handles transforms
101
+ fallbackImage: featuredImage ? {
102
+ id: featuredImage.id,
103
+ alt: featuredImage.alt,
104
+ } : undefined,
105
+ }}
106
+ />
107
+ )}
108
+
109
+ {!heroBlockDefinition && title && (
110
+ <h1 className="text-5xl font-serif font-medium text-neutral-950 dark:text-white leading-tight mb-12">
111
+ {title}
112
+ </h1>
113
+ )}
114
+
115
+ {contentBlocks.length > 0 ? (
116
+ <div className="space-y-8">
117
+ {contentBlocks.map((block) => (
118
+ <BlockRenderer
119
+ key={block.id}
120
+ block={block}
121
+ context={{ siteId, locale }}
122
+ />
123
+ ))}
124
+ </div>
125
+ ) : (
126
+ <div className="text-center py-20 text-neutral-400 dark:text-neutral-500">
127
+ <p className="text-sm">No content blocks yet. Switch to Edit mode to add blocks.</p>
128
+ </div>
129
+ )}
130
+ </div>
131
+ ) : (
132
+ <>
133
+ {!heroBlockDefinition && (
134
+ <div className="mb-12">
135
+ <textarea
136
+ ref={titleRef}
137
+ rows={1}
138
+ value={title}
139
+ onChange={(e) => onTitleChange(e.target.value)}
140
+ placeholder="The title of your story..."
141
+ className="w-full bg-transparent border-none outline-none text-5xl font-serif font-medium placeholder:text-neutral-500 dark:placeholder:text-neutral-500 resize-none leading-tight transition-colors duration-300 text-neutral-950 dark:text-white"
142
+ />
143
+ </div>
144
+ )}
145
+
146
+ <EditorBody
147
+ blocks={contentBlocks}
148
+ darkMode={darkMode}
149
+ backgroundColors={backgroundColors}
150
+ onBlockAdd={onBlockAdd}
151
+ onBlockUpdate={onBlockUpdate}
152
+ onBlockDelete={onBlockDelete}
153
+ onBlockMove={onBlockMove}
154
+ />
155
+ </>
156
+ )}
157
+ </div>
158
+ </div>
159
+ );
160
+ }
@@ -0,0 +1,7 @@
1
+ import type { BlockTypeDefinition } from '../../../types/block';
2
+ export interface EditorLibraryProps {
3
+ registeredBlocks: BlockTypeDefinition[];
4
+ onAddBlock: (type: string) => void;
5
+ }
6
+ export declare function EditorLibrary({ registeredBlocks, onAddBlock }: EditorLibraryProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=EditorLibrary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EditorLibrary.d.ts","sourceRoot":"","sources":["EditorLibrary.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,MAAM,WAAW,kBAAkB;IAC/B,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;IACxC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,EAAE,gBAAgB,EAAE,UAAU,EAAE,EAAE,kBAAkB,2CA6GjF"}
@@ -0,0 +1,122 @@
1
+ 'use client';
2
+
3
+ import React from 'react';
4
+ import { Library, Image as ImageIcon, LayoutTemplate, Type, Box } from 'lucide-react';
5
+ import { LibraryItem, CustomBlockItem } from './index';
6
+ import type { BlockTypeDefinition } from '../../../types/block';
7
+
8
+ export interface EditorLibraryProps {
9
+ registeredBlocks: BlockTypeDefinition[];
10
+ onAddBlock: (type: string) => void;
11
+ }
12
+
13
+ export function EditorLibrary({ registeredBlocks, onAddBlock }: EditorLibraryProps) {
14
+ // Get all registered blocks from state (excluding Hero block from sidebar)
15
+ const allBlocks = registeredBlocks.filter(block => block.type !== 'hero');
16
+ const textBlocks = allBlocks.filter(block => block.category === 'text');
17
+ const customBlocks = allBlocks.filter(block => block.category === 'custom');
18
+ const mediaBlocks = allBlocks.filter(block => block.category === 'media');
19
+ const layoutBlocks = allBlocks.filter(block => block.category === 'layout');
20
+
21
+ return (
22
+ <div className="p-6 w-72 min-w-0 max-w-full">
23
+ {/* Text Blocks */}
24
+ {textBlocks.length > 0 && (
25
+ <div className="mb-10">
26
+ <h3 className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6">Text</h3>
27
+ <div className="grid grid-cols-2 gap-3">
28
+ {textBlocks.map((block) => {
29
+ const IconComponent = block.icon || block.components.Icon || Type;
30
+ return (
31
+ <LibraryItem
32
+ key={block.type}
33
+ icon={<IconComponent size={16} />}
34
+ label={block.name}
35
+ blockType={block.type}
36
+ description={block.description}
37
+ onAddBlock={onAddBlock}
38
+ />
39
+ );
40
+ })}
41
+ </div>
42
+ </div>
43
+ )}
44
+
45
+ {/* Media Blocks */}
46
+ {mediaBlocks.length > 0 && (
47
+ <div className="mb-10">
48
+ <h3 className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6">Media</h3>
49
+ <div className="grid grid-cols-2 gap-3">
50
+ {mediaBlocks.map((block) => {
51
+ const IconComponent = block.icon || block.components.Icon || ImageIcon;
52
+ return (
53
+ <LibraryItem
54
+ key={block.type}
55
+ icon={<IconComponent size={16} />}
56
+ label={block.name}
57
+ blockType={block.type}
58
+ description={block.description}
59
+ onAddBlock={onAddBlock}
60
+ />
61
+ );
62
+ })}
63
+ </div>
64
+ </div>
65
+ )}
66
+
67
+ {/* Layout Blocks */}
68
+ {layoutBlocks.length > 0 && (
69
+ <div className="mb-10">
70
+ <h3 className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6">Layout</h3>
71
+ <div className="grid grid-cols-2 gap-3">
72
+ {layoutBlocks.map((block) => {
73
+ const IconComponent = block.icon || block.components.Icon || LayoutTemplate;
74
+ return (
75
+ <LibraryItem
76
+ key={block.type}
77
+ icon={<IconComponent size={16} />}
78
+ label={block.name}
79
+ blockType={block.type}
80
+ description={block.description}
81
+ onAddBlock={onAddBlock}
82
+ />
83
+ );
84
+ })}
85
+ </div>
86
+ </div>
87
+ )}
88
+
89
+ {/* Custom Blocks */}
90
+ {customBlocks.length > 0 && (
91
+ <div>
92
+ <h3 className="text-[10px] uppercase tracking-[0.2em] text-neutral-500 dark:text-neutral-400 font-black mb-6">Custom Blocks</h3>
93
+ <div className="space-y-3">
94
+ {customBlocks.map((block) => {
95
+ const IconComponent = block.icon || block.components.Icon || Box;
96
+ return (
97
+ <CustomBlockItem
98
+ key={block.type}
99
+ blockType={block.type}
100
+ name={block.name}
101
+ description={block.description}
102
+ icon={<IconComponent size={14} />}
103
+ onAddBlock={onAddBlock}
104
+ />
105
+ );
106
+ })}
107
+ </div>
108
+ </div>
109
+ )}
110
+
111
+ {/* Empty State */}
112
+ {allBlocks.length === 0 && (
113
+ <div className="text-center py-12">
114
+ <Library size={32} className="mx-auto text-neutral-300 dark:text-neutral-700 mb-4" />
115
+ <p className="text-xs text-neutral-500 dark:text-neutral-400">
116
+ No blocks registered yet.
117
+ </p>
118
+ </div>
119
+ )}
120
+ </div>
121
+ );
122
+ }