@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.
- package/package.json +5 -4
- package/src/api/categories.ts +43 -0
- package/src/api/check-title.ts +60 -0
- package/src/api/config-handler.ts +76 -0
- package/src/api/handler.ts +418 -0
- package/src/api/index.ts +33 -0
- package/src/api/route.ts +116 -0
- package/src/api/router.ts +128 -0
- package/src/api-server.ts +11 -0
- package/src/config.ts +161 -0
- package/src/hooks/index.d.ts +8 -0
- package/src/hooks/index.d.ts.map +1 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useBlog.d.ts +31 -0
- package/src/hooks/useBlog.d.ts.map +1 -0
- package/src/hooks/useBlog.ts +85 -0
- package/src/hooks/useBlogs.d.ts +39 -0
- package/src/hooks/useBlogs.d.ts.map +1 -0
- package/src/hooks/useBlogs.ts +123 -0
- package/src/hooks/useCategories.d.ts +9 -0
- package/src/hooks/useCategories.d.ts.map +1 -0
- package/src/hooks/useCategories.ts +76 -0
- package/src/index.server.ts +14 -0
- package/src/index.tsx +335 -0
- package/src/init.tsx +63 -0
- package/src/lib/blocks/BlockRenderer.d.ts +54 -0
- package/src/lib/blocks/BlockRenderer.d.ts.map +1 -0
- package/src/lib/blocks/BlockRenderer.tsx +141 -0
- package/src/lib/blocks/index.ts +6 -0
- package/src/lib/config-storage.d.ts +30 -0
- package/src/lib/config-storage.d.ts.map +1 -0
- package/src/lib/config-storage.ts +65 -0
- package/src/lib/index.ts +9 -0
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts +25 -0
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx +298 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx.tmp +81 -0
- package/src/lib/layouts/blocks/SectionBlock.d.ts +25 -0
- package/src/lib/layouts/blocks/SectionBlock.d.ts.map +1 -0
- package/src/lib/layouts/blocks/SectionBlock.tsx +104 -0
- package/src/lib/layouts/blocks/index.ts +8 -0
- package/src/lib/layouts/index.d.ts +23 -0
- package/src/lib/layouts/index.d.ts.map +1 -0
- package/src/lib/layouts/index.ts +52 -0
- package/src/lib/layouts/registerLayoutBlocks.d.ts +9 -0
- package/src/lib/layouts/registerLayoutBlocks.d.ts.map +1 -0
- package/src/lib/layouts/registerLayoutBlocks.ts +64 -0
- package/src/lib/mappers/apiMapper.d.ts +66 -0
- package/src/lib/mappers/apiMapper.d.ts.map +1 -0
- package/src/lib/mappers/apiMapper.ts +254 -0
- package/src/lib/migration/index.ts +6 -0
- package/src/lib/migration/mapper.ts +140 -0
- package/src/lib/rich-text/RichTextEditor.d.ts +45 -0
- package/src/lib/rich-text/RichTextEditor.d.ts.map +1 -0
- package/src/lib/rich-text/RichTextEditor.tsx +826 -0
- package/src/lib/rich-text/RichTextPreview.d.ts +16 -0
- package/src/lib/rich-text/RichTextPreview.d.ts.map +1 -0
- package/src/lib/rich-text/RichTextPreview.tsx +210 -0
- package/src/lib/rich-text/index.d.ts +9 -0
- package/src/lib/rich-text/index.d.ts.map +1 -0
- package/src/lib/rich-text/index.ts +10 -0
- package/src/lib/utils/blockHelpers.d.ts +23 -0
- package/src/lib/utils/blockHelpers.d.ts.map +1 -0
- package/src/lib/utils/blockHelpers.ts +72 -0
- package/src/lib/utils/configValidation.d.ts +23 -0
- package/src/lib/utils/configValidation.d.ts.map +1 -0
- package/src/lib/utils/configValidation.ts +137 -0
- package/src/lib/utils/index.ts +8 -0
- package/src/lib/utils/slugify.ts +79 -0
- package/src/registry/BlockRegistry.d.ts +62 -0
- package/src/registry/BlockRegistry.d.ts.map +1 -0
- package/src/registry/BlockRegistry.ts +139 -0
- package/src/registry/index.d.ts +6 -0
- package/src/registry/index.d.ts.map +1 -0
- package/src/registry/index.ts +11 -0
- package/src/state/EditorContext.d.ts +45 -0
- package/src/state/EditorContext.d.ts.map +1 -0
- package/src/state/EditorContext.tsx +283 -0
- package/src/state/index.d.ts +7 -0
- package/src/state/index.d.ts.map +1 -0
- package/src/state/index.ts +8 -0
- package/src/state/reducer.d.ts +11 -0
- package/src/state/reducer.d.ts.map +1 -0
- package/src/state/reducer.ts +694 -0
- package/src/state/types.d.ts +162 -0
- package/src/state/types.d.ts.map +1 -0
- package/src/state/types.ts +160 -0
- package/src/types/block.d.ts +221 -0
- package/src/types/block.d.ts.map +1 -0
- package/src/types/block.ts +269 -0
- package/src/types/index.d.ts +8 -0
- package/src/types/index.d.ts.map +1 -0
- package/src/types/index.ts +17 -0
- package/src/types/post.d.ts +136 -0
- package/src/types/post.d.ts.map +1 -0
- package/src/types/post.ts +169 -0
- package/src/utils/client.d.ts +48 -0
- package/src/utils/client.d.ts.map +1 -0
- package/src/utils/client.ts +122 -0
- package/src/utils/index.ts +7 -0
- package/src/views/CanvasEditor/BlockWrapper.d.ts +16 -0
- package/src/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +522 -0
- package/src/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
- package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
- package/src/views/CanvasEditor/CanvasEditorView.tsx +337 -0
- package/src/views/CanvasEditor/EditorBody.d.ts +22 -0
- package/src/views/CanvasEditor/EditorBody.d.ts.map +1 -0
- package/src/views/CanvasEditor/EditorBody.tsx +665 -0
- package/src/views/CanvasEditor/EditorHeader.d.ts +18 -0
- package/src/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
- package/src/views/CanvasEditor/EditorHeader.tsx +268 -0
- package/src/views/CanvasEditor/LayoutContainer.d.ts +17 -0
- package/src/views/CanvasEditor/LayoutContainer.d.ts.map +1 -0
- package/src/views/CanvasEditor/LayoutContainer.tsx +322 -0
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +13 -0
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +1 -0
- package/src/views/CanvasEditor/SaveConfirmationModal.tsx +233 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +14 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +92 -0
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts +29 -0
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +160 -0
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +122 -0
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts +13 -0
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +181 -0
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +25 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +341 -0
- package/src/views/CanvasEditor/components/LibraryItem.d.ts +14 -0
- package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +80 -0
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +15 -0
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +212 -0
- package/src/views/CanvasEditor/components/index.d.ts +21 -0
- package/src/views/CanvasEditor/components/index.d.ts.map +1 -0
- package/src/views/CanvasEditor/components/index.ts +28 -0
- package/src/views/CanvasEditor/hooks/index.d.ts +10 -0
- package/src/views/CanvasEditor/hooks/index.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/index.ts +10 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +8 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useHeroBlock.ts +103 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +142 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +5 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +39 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +55 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +25 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +1 -0
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +339 -0
- package/src/views/CanvasEditor/index.d.ts +16 -0
- package/src/views/CanvasEditor/index.d.ts.map +1 -0
- package/src/views/CanvasEditor/index.ts +16 -0
- package/src/views/PostManager/EmptyState.d.ts +10 -0
- package/src/views/PostManager/EmptyState.d.ts.map +1 -0
- package/src/views/PostManager/EmptyState.tsx +42 -0
- package/src/views/PostManager/PostActionsMenu.d.ts +12 -0
- package/src/views/PostManager/PostActionsMenu.d.ts.map +1 -0
- package/src/views/PostManager/PostActionsMenu.tsx +112 -0
- package/src/views/PostManager/PostCards.d.ts +15 -0
- package/src/views/PostManager/PostCards.d.ts.map +1 -0
- package/src/views/PostManager/PostCards.tsx +197 -0
- package/src/views/PostManager/PostFilters.d.ts +16 -0
- package/src/views/PostManager/PostFilters.d.ts.map +1 -0
- package/src/views/PostManager/PostFilters.tsx +95 -0
- package/src/views/PostManager/PostManagerView.d.ts +11 -0
- package/src/views/PostManager/PostManagerView.d.ts.map +1 -0
- package/src/views/PostManager/PostManagerView.tsx +289 -0
- package/src/views/PostManager/PostStats.d.ts +11 -0
- package/src/views/PostManager/PostStats.d.ts.map +1 -0
- package/src/views/PostManager/PostStats.tsx +81 -0
- package/src/views/PostManager/PostTable.d.ts +15 -0
- package/src/views/PostManager/PostTable.d.ts.map +1 -0
- package/src/views/PostManager/PostTable.tsx +230 -0
- package/src/views/PostManager/index.d.ts +12 -0
- package/src/views/PostManager/index.d.ts.map +1 -0
- package/src/views/PostManager/index.ts +15 -0
- package/src/views/Preview/PreviewBridgeView.d.ts +12 -0
- package/src/views/Preview/PreviewBridgeView.d.ts.map +1 -0
- package/src/views/Preview/PreviewBridgeView.tsx +64 -0
- package/src/views/Preview/index.d.ts +6 -0
- package/src/views/Preview/index.d.ts.map +1 -0
- package/src/views/Preview/index.ts +7 -0
- package/src/views/Settings/SettingsView.d.ts +10 -0
- package/src/views/Settings/SettingsView.d.ts.map +1 -0
- package/src/views/Settings/SettingsView.tsx +298 -0
- package/src/views/Settings/index.d.ts +6 -0
- package/src/views/Settings/index.d.ts.map +1 -0
- package/src/views/Settings/index.ts +7 -0
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts +12 -0
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -0
- package/src/views/SlugSEO/SlugSEOManagerView.tsx +94 -0
- package/src/views/SlugSEO/index.d.ts +6 -0
- package/src/views/SlugSEO/index.d.ts.map +1 -0
- 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
|
+
}
|