@jhits/plugin-blog 0.0.19 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/categories.d.ts.map +1 -1
- package/dist/api/categories.js +42 -38
- package/dist/api/handler.d.ts +1 -26
- package/dist/api/handler.d.ts.map +1 -1
- package/dist/api/handler.js +81 -490
- package/dist/api/router.d.ts +0 -5
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +8 -35
- package/dist/api/service.d.ts +80 -0
- package/dist/api/service.d.ts.map +1 -0
- package/dist/api/service.js +219 -0
- package/dist/hooks/useAutoSave.d.ts +10 -0
- package/dist/hooks/useAutoSave.d.ts.map +1 -0
- package/dist/hooks/useAutoSave.js +57 -0
- package/dist/hooks/useCategories.d.ts +1 -1
- package/dist/hooks/useCategories.d.ts.map +1 -1
- package/dist/hooks/useCategories.js +15 -46
- package/dist/index.d.ts +24 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -201
- package/dist/init.d.ts +20 -7
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +8 -7
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/ColumnsBlock.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/ColumnsBlock.js +30 -113
- package/dist/lib/layouts/blocks/SectionBlock.d.ts.map +1 -1
- package/dist/lib/layouts/blocks/SectionBlock.js +9 -21
- package/dist/lib/layouts/index.d.ts +3 -3
- package/dist/lib/layouts/index.js +4 -4
- package/dist/lib/mappers/apiMapper.d.ts +10 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -1
- package/dist/lib/mappers/apiMapper.js +47 -32
- package/dist/lib/rich-text/RichTextEditor.d.ts +4 -2
- package/dist/lib/rich-text/RichTextEditor.d.ts.map +1 -1
- package/dist/lib/rich-text/RichTextEditor.js +12 -9
- package/dist/lib/utils/config-resolver.d.ts +28 -0
- package/dist/lib/utils/config-resolver.d.ts.map +1 -0
- package/dist/lib/utils/config-resolver.js +46 -0
- package/dist/lib/utils/tree.d.ts +29 -0
- package/dist/lib/utils/tree.d.ts.map +1 -0
- package/dist/lib/utils/tree.js +129 -0
- package/dist/state/EditorContext.d.ts +3 -25
- package/dist/state/EditorContext.d.ts.map +1 -1
- package/dist/state/EditorContext.js +124 -174
- package/dist/state/reducer.d.ts +1 -5
- package/dist/state/reducer.d.ts.map +1 -1
- package/dist/state/reducer.js +128 -521
- package/dist/state/types.d.ts +12 -1
- package/dist/state/types.d.ts.map +1 -1
- package/dist/types/block.d.ts +9 -0
- package/dist/types/block.d.ts.map +1 -1
- package/dist/types/post.d.ts +17 -1
- package/dist/types/post.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.d.ts +5 -6
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -1
- package/dist/views/CanvasEditor/BlockWrapper.js +56 -264
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +5 -3
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -1
- package/dist/views/CanvasEditor/CanvasEditorView.js +55 -315
- package/dist/views/CanvasEditor/EditorBody.d.ts +6 -8
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorBody.js +34 -482
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/EditorHeader.js +27 -63
- package/dist/views/CanvasEditor/LayoutContainer.d.ts.map +1 -1
- package/dist/views/CanvasEditor/LayoutContainer.js +49 -70
- package/dist/views/CanvasEditor/components/CustomBlockItem.js +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +15 -3
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorCanvas.js +40 -18
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +5 -1
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorLibrary.js +11 -7
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/EditorSidebar.js +32 -14
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -6
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/FeaturedMediaSection.js +17 -128
- package/dist/views/CanvasEditor/components/JSONInspector.d.ts +9 -0
- package/dist/views/CanvasEditor/components/JSONInspector.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/JSONInspector.js +56 -0
- package/dist/views/CanvasEditor/components/LibraryItem.js +2 -2
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -4
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/PrivacySettingsSection.js +6 -28
- package/dist/views/CanvasEditor/components/index.d.ts +2 -0
- package/dist/views/CanvasEditor/components/index.d.ts.map +1 -1
- package/dist/views/CanvasEditor/components/index.js +1 -0
- package/dist/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/useHeroBlock.js +15 -18
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts +3 -0
- package/dist/views/CanvasEditor/hooks/usePostLoader.d.ts.map +1 -1
- package/dist/views/CanvasEditor/hooks/usePostLoader.js +12 -13
- package/dist/views/CanvasEditor/hooks/useUnsavedChanges.js +0 -4
- package/dist/views/PostManager/EmptyState.d.ts +1 -1
- package/dist/views/PostManager/EmptyState.js +4 -4
- package/dist/views/PostManager/FilterDropdown.d.ts +21 -0
- package/dist/views/PostManager/FilterDropdown.d.ts.map +1 -0
- package/dist/views/PostManager/FilterDropdown.js +28 -0
- package/dist/views/PostManager/LanguageFlags.d.ts.map +1 -1
- package/dist/views/PostManager/LanguageFlags.js +4 -1
- package/dist/views/PostManager/PostCards.d.ts.map +1 -1
- package/dist/views/PostManager/PostCards.js +23 -40
- package/dist/views/PostManager/PostFilters.d.ts.map +1 -1
- package/dist/views/PostManager/PostFilters.js +34 -3
- package/dist/views/PostManager/PostManagerView.d.ts +1 -2
- package/dist/views/PostManager/PostManagerView.d.ts.map +1 -1
- package/dist/views/PostManager/PostManagerView.js +30 -96
- package/dist/views/PostManager/PostStats.d.ts.map +1 -1
- package/dist/views/PostManager/PostStats.js +10 -10
- package/dist/views/PostManager/PostTable.d.ts.map +1 -1
- package/dist/views/PostManager/PostTable.js +23 -40
- package/dist/views/Settings/SettingsView.d.ts +1 -1
- package/dist/views/Settings/SettingsView.d.ts.map +1 -1
- package/dist/views/Settings/SettingsView.js +12 -39
- package/dist/views/SlugSEO/SlugSEOManagerView.d.ts.map +1 -1
- package/dist/views/SlugSEO/SlugSEOManagerView.js +2 -2
- package/package.json +42 -6
- package/src/api/categories.ts +48 -52
- package/src/api/handler.ts +87 -594
- package/src/api/router.ts +15 -65
- package/src/api/service.ts +241 -0
- package/src/hooks/useAutoSave.ts +64 -0
- package/src/hooks/useCategories.ts +19 -47
- package/src/index.tsx +79 -293
- package/src/init.tsx +24 -11
- package/src/lib/blocks/BlockRenderer.tsx +1 -0
- package/src/lib/layouts/blocks/ColumnsBlock.tsx +60 -173
- package/src/lib/layouts/blocks/SectionBlock.tsx +22 -26
- package/src/lib/layouts/index.ts +4 -4
- package/src/lib/mappers/apiMapper.ts +63 -32
- package/src/lib/rich-text/RichTextEditor.tsx +16 -9
- package/src/lib/utils/config-resolver.ts +64 -0
- package/src/lib/utils/tree.ts +150 -0
- package/src/state/EditorContext.tsx +153 -232
- package/src/state/reducer.ts +141 -606
- package/src/state/types.ts +14 -1
- package/src/types/block.ts +10 -0
- package/src/types/post.ts +19 -1
- package/src/views/CanvasEditor/BlockWrapper.tsx +130 -460
- package/src/views/CanvasEditor/CanvasEditorView.tsx +145 -420
- package/src/views/CanvasEditor/EditorBody.tsx +98 -610
- package/src/views/CanvasEditor/EditorHeader.tsx +176 -196
- package/src/views/CanvasEditor/LayoutContainer.tsx +74 -89
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +7 -8
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +139 -84
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +25 -10
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +196 -127
- package/src/views/CanvasEditor/components/FeaturedMediaSection.tsx +78 -210
- package/src/views/CanvasEditor/components/JSONInspector.tsx +125 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +5 -6
- package/src/views/CanvasEditor/components/PrivacySettingsSection.tsx +73 -124
- package/src/views/CanvasEditor/components/index.ts +2 -1
- package/src/views/CanvasEditor/hooks/useHeroBlock.ts +15 -18
- package/src/views/CanvasEditor/hooks/usePostLoader.ts +21 -13
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.ts +4 -4
- package/src/views/PostManager/EmptyState.tsx +9 -10
- package/src/views/PostManager/FilterDropdown.tsx +95 -0
- package/src/views/PostManager/LanguageFlags.tsx +6 -2
- package/src/views/PostManager/PostCards.tsx +127 -133
- package/src/views/PostManager/PostFilters.tsx +73 -68
- package/src/views/PostManager/PostManagerView.tsx +132 -179
- package/src/views/PostManager/PostStats.tsx +21 -20
- package/src/views/PostManager/PostTable.tsx +137 -165
- package/src/views/Settings/SettingsView.tsx +64 -180
- package/src/views/SlugSEO/SlugSEOManagerView.tsx +59 -44
- package/src/hooks/index.d.ts +0 -8
- package/src/hooks/index.d.ts.map +0 -1
- package/src/hooks/useBlog.d.ts +0 -31
- package/src/hooks/useBlog.d.ts.map +0 -1
- package/src/hooks/useBlogs.d.ts +0 -39
- package/src/hooks/useBlogs.d.ts.map +0 -1
- package/src/hooks/useCategories.d.ts +0 -9
- package/src/hooks/useCategories.d.ts.map +0 -1
- package/src/lib/blocks/BlockRenderer.d.ts +0 -54
- package/src/lib/blocks/BlockRenderer.d.ts.map +0 -1
- package/src/lib/config-storage.d.ts +0 -30
- package/src/lib/config-storage.d.ts.map +0 -1
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts +0 -25
- package/src/lib/layouts/blocks/ColumnsBlock.d.ts.map +0 -1
- package/src/lib/layouts/blocks/SectionBlock.d.ts +0 -25
- package/src/lib/layouts/blocks/SectionBlock.d.ts.map +0 -1
- package/src/lib/layouts/index.d.ts +0 -23
- package/src/lib/layouts/index.d.ts.map +0 -1
- package/src/lib/layouts/registerLayoutBlocks.d.ts +0 -9
- package/src/lib/layouts/registerLayoutBlocks.d.ts.map +0 -1
- package/src/lib/mappers/apiMapper.d.ts +0 -66
- package/src/lib/mappers/apiMapper.d.ts.map +0 -1
- package/src/lib/rich-text/RichTextEditor.d.ts +0 -45
- package/src/lib/rich-text/RichTextEditor.d.ts.map +0 -1
- package/src/lib/rich-text/RichTextPreview.d.ts +0 -16
- package/src/lib/rich-text/RichTextPreview.d.ts.map +0 -1
- package/src/lib/rich-text/index.d.ts +0 -9
- package/src/lib/rich-text/index.d.ts.map +0 -1
- package/src/lib/utils/blockHelpers.d.ts +0 -23
- package/src/lib/utils/blockHelpers.d.ts.map +0 -1
- package/src/lib/utils/configValidation.d.ts +0 -23
- package/src/lib/utils/configValidation.d.ts.map +0 -1
- package/src/registry/BlockRegistry.d.ts +0 -62
- package/src/registry/BlockRegistry.d.ts.map +0 -1
- package/src/registry/index.d.ts +0 -6
- package/src/registry/index.d.ts.map +0 -1
- package/src/state/EditorContext.d.ts +0 -45
- package/src/state/EditorContext.d.ts.map +0 -1
- package/src/state/index.d.ts +0 -7
- package/src/state/index.d.ts.map +0 -1
- package/src/state/reducer.d.ts +0 -11
- package/src/state/reducer.d.ts.map +0 -1
- package/src/state/types.d.ts +0 -162
- package/src/state/types.d.ts.map +0 -1
- package/src/types/block.d.ts +0 -221
- package/src/types/block.d.ts.map +0 -1
- package/src/types/index.d.ts +0 -8
- package/src/types/index.d.ts.map +0 -1
- package/src/types/post.d.ts +0 -136
- package/src/types/post.d.ts.map +0 -1
- package/src/utils/client.d.ts +0 -48
- package/src/utils/client.d.ts.map +0 -1
- package/src/views/CanvasEditor/BlockWrapper.d.ts +0 -16
- package/src/views/CanvasEditor/BlockWrapper.d.ts.map +0 -1
- package/src/views/CanvasEditor/CanvasEditorView.d.ts +0 -14
- package/src/views/CanvasEditor/CanvasEditorView.d.ts.map +0 -1
- package/src/views/CanvasEditor/EditorBody.d.ts +0 -22
- package/src/views/CanvasEditor/EditorBody.d.ts.map +0 -1
- package/src/views/CanvasEditor/EditorHeader.d.ts +0 -18
- package/src/views/CanvasEditor/EditorHeader.d.ts.map +0 -1
- package/src/views/CanvasEditor/LayoutContainer.d.ts +0 -17
- package/src/views/CanvasEditor/LayoutContainer.d.ts.map +0 -1
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts +0 -13
- package/src/views/CanvasEditor/SaveConfirmationModal.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts +0 -14
- package/src/views/CanvasEditor/components/CustomBlockItem.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts +0 -29
- package/src/views/CanvasEditor/components/EditorCanvas.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts +0 -7
- package/src/views/CanvasEditor/components/EditorLibrary.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts +0 -13
- package/src/views/CanvasEditor/components/EditorSidebar.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts +0 -6
- package/src/views/CanvasEditor/components/ErrorBanner.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts +0 -25
- package/src/views/CanvasEditor/components/FeaturedMediaSection.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/LibraryItem.d.ts +0 -14
- package/src/views/CanvasEditor/components/LibraryItem.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts +0 -15
- package/src/views/CanvasEditor/components/PrivacySettingsSection.d.ts.map +0 -1
- package/src/views/CanvasEditor/components/index.d.ts +0 -21
- package/src/views/CanvasEditor/components/index.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/index.d.ts +0 -10
- package/src/views/CanvasEditor/hooks/index.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts +0 -8
- package/src/views/CanvasEditor/hooks/useHeroBlock.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +0 -3
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts +0 -5
- package/src/views/CanvasEditor/hooks/usePostLoader.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +0 -2
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +0 -1
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts +0 -25
- package/src/views/CanvasEditor/hooks/useUnsavedChanges.d.ts.map +0 -1
- package/src/views/CanvasEditor/index.d.ts +0 -16
- package/src/views/CanvasEditor/index.d.ts.map +0 -1
- package/src/views/PostManager/EmptyState.d.ts +0 -10
- package/src/views/PostManager/EmptyState.d.ts.map +0 -1
- package/src/views/PostManager/PostActionsMenu.d.ts +0 -12
- package/src/views/PostManager/PostActionsMenu.d.ts.map +0 -1
- package/src/views/PostManager/PostCards.d.ts +0 -15
- package/src/views/PostManager/PostCards.d.ts.map +0 -1
- package/src/views/PostManager/PostFilters.d.ts +0 -16
- package/src/views/PostManager/PostFilters.d.ts.map +0 -1
- package/src/views/PostManager/PostManagerView.d.ts +0 -11
- package/src/views/PostManager/PostManagerView.d.ts.map +0 -1
- package/src/views/PostManager/PostStats.d.ts +0 -11
- package/src/views/PostManager/PostStats.d.ts.map +0 -1
- package/src/views/PostManager/PostTable.d.ts +0 -15
- package/src/views/PostManager/PostTable.d.ts.map +0 -1
- package/src/views/PostManager/index.d.ts +0 -12
- package/src/views/PostManager/index.d.ts.map +0 -1
- package/src/views/Preview/PreviewBridgeView.d.ts +0 -12
- package/src/views/Preview/PreviewBridgeView.d.ts.map +0 -1
- package/src/views/Preview/index.d.ts +0 -6
- package/src/views/Preview/index.d.ts.map +0 -1
- package/src/views/Settings/SettingsView.d.ts +0 -10
- package/src/views/Settings/SettingsView.d.ts.map +0 -1
- package/src/views/Settings/index.d.ts +0 -6
- package/src/views/Settings/index.d.ts.map +0 -1
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts +0 -12
- package/src/views/SlugSEO/SlugSEOManagerView.d.ts.map +0 -1
- package/src/views/SlugSEO/index.d.ts +0 -6
- package/src/views/SlugSEO/index.d.ts.map +0 -1
|
@@ -10,655 +10,143 @@ import React, { useState, useRef, Fragment, useEffect } from 'react';
|
|
|
10
10
|
import { Plus } from 'lucide-react';
|
|
11
11
|
import { Block } from '../../types/block';
|
|
12
12
|
import { BlockWrapper } from './BlockWrapper';
|
|
13
|
+
import { blockRegistry } from '../../registry/BlockRegistry';
|
|
14
|
+
import { useEditor } from '../../state/EditorContext';
|
|
13
15
|
|
|
14
16
|
export interface EditorBodyProps {
|
|
15
17
|
blocks: Block[];
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
darkMode: boolean;
|
|
19
|
+
backgroundColors?: { light: string; dark?: string };
|
|
20
|
+
onBlockUpdate: (id: string, data: Partial<Block['data']>, containerId?: string) => void;
|
|
21
|
+
onBlockDelete: (id: string, containerId?: string) => void;
|
|
19
22
|
onBlockMove: (id: string, newIndex: number, containerId?: string) => void;
|
|
20
|
-
|
|
21
|
-
darkMode?: boolean;
|
|
22
|
-
/** Background colors for the editor */
|
|
23
|
-
backgroundColors?: {
|
|
24
|
-
light: string;
|
|
25
|
-
dark?: string;
|
|
26
|
-
};
|
|
23
|
+
onBlockAdd: (type: string, index: number, containerId?: string) => void;
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export function EditorBody({
|
|
30
27
|
blocks,
|
|
31
|
-
|
|
28
|
+
darkMode,
|
|
29
|
+
backgroundColors,
|
|
32
30
|
onBlockUpdate,
|
|
33
31
|
onBlockDelete,
|
|
34
32
|
onBlockMove,
|
|
35
|
-
|
|
36
|
-
backgroundColors,
|
|
33
|
+
onBlockAdd,
|
|
37
34
|
}: EditorBodyProps) {
|
|
38
|
-
|
|
39
|
-
const [
|
|
40
|
-
const
|
|
41
|
-
const [draggedBlockId, setDraggedBlockId] = useState<string | null>(null);
|
|
42
|
-
const [dropIndicatorPosition, setDropIndicatorPosition] = useState<{ top: number; left: number; width: number } | null>(null);
|
|
43
|
-
const [calculatedInsertIndex, setCalculatedInsertIndex] = useState<number | null>(null);
|
|
44
|
-
|
|
45
|
-
// --- Refs ---
|
|
46
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
47
|
-
const blockRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
|
48
|
-
|
|
49
|
-
// --- Cleanup & Event Listeners ---
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
const handleClearIndicator = () => {
|
|
52
|
-
setDropIndicatorPosition(null);
|
|
53
|
-
setDragOverIndex(null);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const container = containerRef.current;
|
|
57
|
-
if (container) {
|
|
58
|
-
container.addEventListener('clear-drop-indicator', handleClearIndicator);
|
|
59
|
-
return () => {
|
|
60
|
-
container.removeEventListener('clear-drop-indicator', handleClearIndicator);
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
}, []);
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
const handleGlobalDragEnd = () => {
|
|
67
|
-
setDropIndicatorPosition(null);
|
|
68
|
-
setDragOverIndex(null);
|
|
69
|
-
setIsDragging(false);
|
|
70
|
-
setDraggedBlockId(null);
|
|
71
|
-
setCalculatedInsertIndex(null);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
document.addEventListener('dragend', handleGlobalDragEnd);
|
|
75
|
-
return () => {
|
|
76
|
-
document.removeEventListener('dragend', handleGlobalDragEnd);
|
|
77
|
-
};
|
|
78
|
-
}, []);
|
|
79
|
-
|
|
80
|
-
// --- Helper Functions ---
|
|
81
|
-
const setBlockRef = (blockId: string) => (el: HTMLDivElement | null) => {
|
|
82
|
-
if (el) {
|
|
83
|
-
blockRefs.current.set(blockId, el);
|
|
84
|
-
} else {
|
|
85
|
-
blockRefs.current.delete(blockId);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// --- Drag & Drop Logic ---
|
|
90
|
-
|
|
91
|
-
const handleDragStart = (e: React.DragEvent) => {
|
|
92
|
-
// This is called when dragging starts from the container
|
|
93
|
-
// The actual block drag start is handled in BlockWrapper
|
|
94
|
-
setIsDragging(true);
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const handleDragOver = (e: React.DragEvent, index: number, element?: HTMLElement) => {
|
|
98
|
-
e.preventDefault();
|
|
99
|
-
e.stopPropagation();
|
|
100
|
-
|
|
101
|
-
// Check if we're dragging over a nested container - if so, clear our indicator
|
|
102
|
-
const target = e.target as HTMLElement;
|
|
103
|
-
const nestedContainer = target.closest('[data-layout-container]');
|
|
104
|
-
if (nestedContainer && nestedContainer !== containerRef.current) {
|
|
105
|
-
// We're over a nested container, clear our indicator
|
|
106
|
-
setDropIndicatorPosition(null);
|
|
107
|
-
setDragOverIndex(null);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
setDragOverIndex(index);
|
|
112
|
-
|
|
113
|
-
// Calculate position for absolute drop indicator - always show between blocks
|
|
114
|
-
const container = containerRef.current;
|
|
115
|
-
if (container && element) {
|
|
116
|
-
requestAnimationFrame(() => {
|
|
117
|
-
// Double-check refs are still valid inside RAF callback
|
|
118
|
-
if (!containerRef.current) return;
|
|
119
|
-
|
|
120
|
-
const containerRect = containerRef.current.getBoundingClientRect();
|
|
121
|
-
const elementRect = element.getBoundingClientRect();
|
|
122
|
-
const mouseY = e.clientY;
|
|
123
|
-
const relativeTop = mouseY - containerRect.top;
|
|
124
|
-
|
|
125
|
-
const elementTop = elementRect.top - containerRect.top;
|
|
126
|
-
const elementBottom = elementRect.bottom - containerRect.top;
|
|
127
|
-
const elementCenter = elementTop + elementRect.height / 2;
|
|
128
|
-
|
|
129
|
-
// Calculate width - use container width minus padding (32px on each side)
|
|
130
|
-
const padding = 32;
|
|
131
|
-
const width = containerRect.width - (padding * 2);
|
|
132
|
-
|
|
133
|
-
let finalTop: number;
|
|
134
|
-
|
|
135
|
-
// Determine if we should show above or below the element
|
|
136
|
-
if (relativeTop < elementCenter) {
|
|
137
|
-
// Show above this block - position between previous block and current block
|
|
138
|
-
if (index === 0) {
|
|
139
|
-
// First block - show at top of container (before first block)
|
|
140
|
-
finalTop = 0;
|
|
141
|
-
} else {
|
|
142
|
-
// Get previous block to find the gap
|
|
143
|
-
const prevBlock = blocks[index - 1];
|
|
144
|
-
const prevBlockEl = blockRefs.current.get(prevBlock.id);
|
|
145
|
-
if (prevBlockEl) {
|
|
146
|
-
const prevBlockRect = prevBlockEl.getBoundingClientRect();
|
|
147
|
-
const prevBlockBottom = prevBlockRect.bottom - containerRect.top;
|
|
148
|
-
// Position in the middle of the gap (mb-6 = 24px margin)
|
|
149
|
-
finalTop = prevBlockBottom + 12; // Half of the 24px gap
|
|
150
|
-
} else {
|
|
151
|
-
finalTop = elementTop;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
} else {
|
|
155
|
-
// Show below this block - position between current block and next block
|
|
156
|
-
if (index === blocks.length - 1) {
|
|
157
|
-
// Last block - show after it
|
|
158
|
-
finalTop = elementBottom;
|
|
159
|
-
} else {
|
|
160
|
-
// Get next block to find the gap
|
|
161
|
-
const nextBlock = blocks[index + 1];
|
|
162
|
-
const nextBlockEl = blockRefs.current.get(nextBlock.id);
|
|
163
|
-
if (nextBlockEl) {
|
|
164
|
-
const nextBlockRect = nextBlockEl.getBoundingClientRect();
|
|
165
|
-
const nextBlockTop = nextBlockRect.top - containerRect.top;
|
|
166
|
-
// Position in the middle of the gap
|
|
167
|
-
finalTop = elementBottom + (nextBlockTop - elementBottom) / 2;
|
|
168
|
-
} else {
|
|
169
|
-
finalTop = elementBottom;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
setDropIndicatorPosition({
|
|
175
|
-
top: finalTop,
|
|
176
|
-
left: padding,
|
|
177
|
-
width: width,
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const handleDragLeave = (e: React.DragEvent) => {
|
|
184
|
-
// Only clear if we're actually leaving the container
|
|
185
|
-
// Check if we're moving to a child element (nested container)
|
|
186
|
-
const relatedTarget = e.relatedTarget as Node;
|
|
187
|
-
if (!e.currentTarget.contains(relatedTarget)) {
|
|
188
|
-
// Check if relatedTarget is inside a nested LayoutContainer
|
|
189
|
-
const isMovingToNested = relatedTarget && (relatedTarget as HTMLElement).closest('[data-layout-container]');
|
|
190
|
-
if (!isMovingToNested) {
|
|
191
|
-
setDragOverIndex(null);
|
|
192
|
-
setDropIndicatorPosition(null);
|
|
193
|
-
setCalculatedInsertIndex(null);
|
|
194
|
-
setIsDragging(false);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
const handleDrop = (e: React.DragEvent, index: number, containerId?: string) => {
|
|
200
|
-
e.preventDefault();
|
|
201
|
-
e.stopPropagation();
|
|
202
|
-
|
|
203
|
-
const dataTransferBlockId = e.dataTransfer.getData('block-id');
|
|
204
|
-
const globalBlockId = typeof window !== 'undefined' ? (window as any).__DRAGGED_BLOCK_ID__ : null;
|
|
205
|
-
const blockId = dataTransferBlockId || globalBlockId;
|
|
206
|
-
const blockType = e.dataTransfer.getData('block-type');
|
|
207
|
-
|
|
208
|
-
console.log('[EditorBody] Drop Event:', {
|
|
209
|
-
index,
|
|
210
|
-
containerId,
|
|
211
|
-
dataTransferBlockId,
|
|
212
|
-
globalBlockId,
|
|
213
|
-
resolvedBlockId: blockId,
|
|
214
|
-
blockType,
|
|
215
|
-
currentBlocks: blocks.map(b => ({ id: b.id, type: b.type })),
|
|
216
|
-
rootBlockIds: blocks.map(b => b.id),
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
// Clear the global dragged block ID
|
|
220
|
-
if (typeof window !== 'undefined') {
|
|
221
|
-
(window as any).__DRAGGED_BLOCK_ID__ = null;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (blockId) {
|
|
225
|
-
// Moving existing block - check if it's in root or nested
|
|
226
|
-
const currentIndex = blocks.findIndex(b => b.id === blockId);
|
|
227
|
-
console.log('[EditorBody] Block location check:', {
|
|
228
|
-
blockId,
|
|
229
|
-
currentIndex,
|
|
230
|
-
isInRoot: currentIndex !== -1,
|
|
231
|
-
targetIndex: index,
|
|
232
|
-
containerId,
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
if (currentIndex !== -1) {
|
|
236
|
-
// Block is in root level - move to container or within root
|
|
237
|
-
if (containerId) {
|
|
238
|
-
// Moving from root to container
|
|
239
|
-
console.log('[EditorBody] Moving from root to container:', { blockId, index, containerId });
|
|
240
|
-
onBlockMove(blockId, index, containerId);
|
|
241
|
-
} else if (currentIndex !== index) {
|
|
242
|
-
// Moving within root level
|
|
243
|
-
const targetIndex = currentIndex < index ? index - 1 : index;
|
|
244
|
-
console.log('[EditorBody] Moving within root:', { blockId, currentIndex, targetIndex });
|
|
245
|
-
onBlockMove(blockId, targetIndex);
|
|
246
|
-
} else {
|
|
247
|
-
console.log('[EditorBody] Block already at target position, no move needed');
|
|
248
|
-
}
|
|
249
|
-
} else {
|
|
250
|
-
// Block is nested somewhere - need to move it to this location
|
|
251
|
-
console.log('[EditorBody] Moving nested block to root:', { blockId, index, containerId: containerId || 'root' });
|
|
252
|
-
// Pass undefined containerId to move to root
|
|
253
|
-
onBlockMove(blockId, index, undefined);
|
|
254
|
-
}
|
|
255
|
-
} else if (blockType) {
|
|
256
|
-
// Adding new block from library
|
|
257
|
-
console.log('[EditorBody] Adding new block from library:', { blockType, index, containerId });
|
|
258
|
-
onBlockAdd(blockType, index, containerId);
|
|
259
|
-
} else {
|
|
260
|
-
console.warn('[EditorBody] Drop event with no block ID or type!');
|
|
261
|
-
}
|
|
35
|
+
const { helpers } = useEditor();
|
|
36
|
+
const [hoverIndex, setHoverIndex] = useState<number | null>(null);
|
|
37
|
+
const bodyRef = useRef<HTMLDivElement>(null);
|
|
262
38
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
setDraggedBlockId(null);
|
|
266
|
-
setDropIndicatorPosition(null);
|
|
267
|
-
setCalculatedInsertIndex(null);
|
|
268
|
-
};
|
|
39
|
+
// Filter out hero block from the main body list - it's handled by EditorCanvas header slot
|
|
40
|
+
const mainBlocks = blocks.filter(b => b.type !== 'hero');
|
|
269
41
|
|
|
270
|
-
const
|
|
42
|
+
const handleDragOver = (e: React.DragEvent, index: number) => {
|
|
271
43
|
e.preventDefault();
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
// Check if we're dragging over a nested container - if so, clear our indicator
|
|
275
|
-
const target = e.target as HTMLElement;
|
|
276
|
-
const nestedContainer = target.closest('[data-layout-container]');
|
|
277
|
-
if (nestedContainer && nestedContainer !== containerRef.current) {
|
|
278
|
-
// We're over a nested container, clear our indicator
|
|
279
|
-
setDropIndicatorPosition(null);
|
|
280
|
-
setDragOverIndex(null);
|
|
281
|
-
setCalculatedInsertIndex(null);
|
|
282
|
-
setIsDragging(false);
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
setIsDragging(true);
|
|
287
|
-
|
|
288
|
-
// Calculate drop indicator position based on mouse Y position relative to blocks
|
|
289
|
-
const container = containerRef.current;
|
|
290
|
-
if (container && blocks.length > 0) {
|
|
291
|
-
requestAnimationFrame(() => {
|
|
292
|
-
if (!containerRef.current) return;
|
|
293
|
-
|
|
294
|
-
const containerRect = containerRef.current.getBoundingClientRect();
|
|
295
|
-
const mouseY = e.clientY;
|
|
296
|
-
const relativeTop = mouseY - containerRect.top;
|
|
297
|
-
const padding = 32;
|
|
298
|
-
const width = containerRect.width - (padding * 2);
|
|
299
|
-
|
|
300
|
-
// Find the closest insertion point based on block positions - always between blocks
|
|
301
|
-
let insertIndex = blocks.length; // Default to end
|
|
302
|
-
let indicatorTop = containerRect.height; // Default to bottom
|
|
303
|
-
|
|
304
|
-
// Check each block to find where to insert
|
|
305
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
306
|
-
const blockEl = blockRefs.current.get(blocks[i].id);
|
|
307
|
-
if (blockEl) {
|
|
308
|
-
const blockRect = blockEl.getBoundingClientRect();
|
|
309
|
-
const blockTop = blockRect.top - containerRect.top;
|
|
310
|
-
const blockBottom = blockRect.bottom - containerRect.top;
|
|
311
|
-
const blockCenter = blockTop + blockRect.height / 2;
|
|
312
|
-
|
|
313
|
-
// If mouse is above this block's center, insert before it
|
|
314
|
-
if (relativeTop < blockCenter) {
|
|
315
|
-
insertIndex = i;
|
|
316
|
-
if (i === 0) {
|
|
317
|
-
// First block - show at top
|
|
318
|
-
indicatorTop = 0;
|
|
319
|
-
} else {
|
|
320
|
-
// Get previous block to find the gap
|
|
321
|
-
const prevBlock = blocks[i - 1];
|
|
322
|
-
const prevBlockEl = blockRefs.current.get(prevBlock.id);
|
|
323
|
-
if (prevBlockEl) {
|
|
324
|
-
const prevBlockRect = prevBlockEl.getBoundingClientRect();
|
|
325
|
-
const prevBlockBottom = prevBlockRect.bottom - containerRect.top;
|
|
326
|
-
// Position in the middle of the gap
|
|
327
|
-
indicatorTop = prevBlockBottom + (blockTop - prevBlockBottom) / 2;
|
|
328
|
-
} else {
|
|
329
|
-
indicatorTop = blockTop;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
// If mouse is below this block's center, check if we should insert after it
|
|
335
|
-
else if (i === blocks.length - 1 || relativeTop < blockBottom) {
|
|
336
|
-
insertIndex = i + 1;
|
|
337
|
-
if (i === blocks.length - 1) {
|
|
338
|
-
// Last block - show after it
|
|
339
|
-
indicatorTop = blockBottom;
|
|
340
|
-
} else {
|
|
341
|
-
// Get next block to find the gap
|
|
342
|
-
const nextBlock = blocks[i + 1];
|
|
343
|
-
const nextBlockEl = blockRefs.current.get(nextBlock.id);
|
|
344
|
-
if (nextBlockEl) {
|
|
345
|
-
const nextBlockRect = nextBlockEl.getBoundingClientRect();
|
|
346
|
-
const nextBlockTop = nextBlockRect.top - containerRect.top;
|
|
347
|
-
// Position in the middle of the gap
|
|
348
|
-
indicatorTop = blockBottom + (nextBlockTop - blockBottom) / 2;
|
|
349
|
-
} else {
|
|
350
|
-
indicatorTop = blockBottom;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// If we didn't find a position, use the last block's bottom
|
|
359
|
-
if (insertIndex === blocks.length) {
|
|
360
|
-
const lastBlockEl = blockRefs.current.get(blocks[blocks.length - 1].id);
|
|
361
|
-
if (lastBlockEl) {
|
|
362
|
-
const lastBlockRect = lastBlockEl.getBoundingClientRect();
|
|
363
|
-
indicatorTop = lastBlockRect.bottom - containerRect.top;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
setCalculatedInsertIndex(insertIndex);
|
|
368
|
-
setDropIndicatorPosition({
|
|
369
|
-
top: indicatorTop,
|
|
370
|
-
left: padding,
|
|
371
|
-
width: width,
|
|
372
|
-
});
|
|
373
|
-
});
|
|
374
|
-
} else if (container) {
|
|
375
|
-
// No blocks, show indicator at top
|
|
376
|
-
requestAnimationFrame(() => {
|
|
377
|
-
if (!containerRef.current) return;
|
|
378
|
-
const containerRect = containerRef.current.getBoundingClientRect();
|
|
379
|
-
const padding = 32;
|
|
380
|
-
const width = containerRect.width - (padding * 2);
|
|
381
|
-
setCalculatedInsertIndex(0);
|
|
382
|
-
setDropIndicatorPosition({
|
|
383
|
-
top: 0,
|
|
384
|
-
left: padding,
|
|
385
|
-
width: width,
|
|
386
|
-
});
|
|
387
|
-
});
|
|
388
|
-
}
|
|
44
|
+
setHoverIndex(index);
|
|
389
45
|
};
|
|
390
46
|
|
|
391
|
-
const
|
|
47
|
+
const handleDrop = (e: React.DragEvent, index: number) => {
|
|
392
48
|
e.preventDefault();
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
const dataTransferBlockId = e.dataTransfer.getData('block-id');
|
|
396
|
-
const globalBlockId = typeof window !== 'undefined' ? (window as any).__DRAGGED_BLOCK_ID__ : null;
|
|
397
|
-
const blockId = dataTransferBlockId || globalBlockId;
|
|
398
|
-
const blockType = e.dataTransfer.getData('block-type');
|
|
399
|
-
|
|
400
|
-
// Use the calculated insert index if available, otherwise calculate it now
|
|
401
|
-
let targetIndex = calculatedInsertIndex !== null ? calculatedInsertIndex : blocks.length;
|
|
49
|
+
setHoverIndex(null);
|
|
402
50
|
|
|
403
|
-
//
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const mouseY = e.clientY;
|
|
407
|
-
const relativeTop = mouseY - containerRect.top;
|
|
408
|
-
|
|
409
|
-
for (let i = 0; i < blocks.length; i++) {
|
|
410
|
-
const blockEl = blockRefs.current.get(blocks[i].id);
|
|
411
|
-
if (blockEl) {
|
|
412
|
-
const blockRect = blockEl.getBoundingClientRect();
|
|
413
|
-
const blockTop = blockRect.top - containerRect.top;
|
|
414
|
-
const blockCenter = blockTop + blockRect.height / 2;
|
|
415
|
-
|
|
416
|
-
if (relativeTop < blockCenter) {
|
|
417
|
-
targetIndex = i;
|
|
418
|
-
break;
|
|
419
|
-
} else if (i === blocks.length - 1) {
|
|
420
|
-
targetIndex = blocks.length;
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
console.log('[EditorBody] Body Drop Event:', {
|
|
428
|
-
dataTransferBlockId,
|
|
429
|
-
globalBlockId,
|
|
430
|
-
resolvedBlockId: blockId,
|
|
431
|
-
blockType,
|
|
432
|
-
calculatedInsertIndex,
|
|
433
|
-
targetIndex,
|
|
434
|
-
currentBlocksCount: blocks.length,
|
|
435
|
-
rootBlockIds: blocks.map(b => b.id),
|
|
436
|
-
});
|
|
51
|
+
// SUPPORT BOTH: Adding new blocks and Moving existing blocks
|
|
52
|
+
const blockId = e.dataTransfer.getData('blockId') || (window as any).__DRAGGED_BLOCK_ID__;
|
|
53
|
+
const blockType = e.dataTransfer.getData('blockType');
|
|
437
54
|
|
|
438
|
-
// Clear
|
|
55
|
+
// Clear global flag
|
|
439
56
|
if (typeof window !== 'undefined') {
|
|
440
57
|
(window as any).__DRAGGED_BLOCK_ID__ = null;
|
|
441
58
|
}
|
|
442
59
|
|
|
443
60
|
if (blockId) {
|
|
444
|
-
// Moving existing block
|
|
445
|
-
const currentIndex =
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
targetIndex
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
if (currentIndex !== -1) {
|
|
454
|
-
// Already in root, move to target position
|
|
455
|
-
if (currentIndex !== targetIndex) {
|
|
456
|
-
// Adjust for removal if moving forward
|
|
457
|
-
const adjustedIndex = currentIndex < targetIndex ? targetIndex - 1 : targetIndex;
|
|
458
|
-
console.log('[EditorBody] Moving within root:', { blockId, currentIndex, adjustedIndex });
|
|
459
|
-
onBlockMove(blockId, Math.max(0, adjustedIndex));
|
|
460
|
-
}
|
|
461
|
-
} else {
|
|
462
|
-
// Block is nested - move it to root level at target position
|
|
463
|
-
console.log('[EditorBody] Moving nested block to root:', { blockId, targetIndex });
|
|
464
|
-
onBlockMove(blockId, targetIndex, undefined);
|
|
61
|
+
// Moving existing block
|
|
62
|
+
const currentIndex = mainBlocks.findIndex(b => b.id === blockId);
|
|
63
|
+
let targetIndex = index;
|
|
64
|
+
|
|
65
|
+
if (currentIndex !== -1 && currentIndex < targetIndex) {
|
|
66
|
+
// Adjust for removal if moving forward in the same container
|
|
67
|
+
targetIndex = targetIndex - 1;
|
|
465
68
|
}
|
|
69
|
+
|
|
70
|
+
onBlockMove(blockId, Math.max(0, targetIndex));
|
|
466
71
|
} else if (blockType) {
|
|
467
|
-
// Adding new block
|
|
468
|
-
|
|
469
|
-
onBlockAdd(blockType, targetIndex);
|
|
470
|
-
} else {
|
|
471
|
-
console.warn('[EditorBody] Body drop with no block ID or type!');
|
|
72
|
+
// Adding new block from sidebar
|
|
73
|
+
onBlockAdd(blockType, index);
|
|
472
74
|
}
|
|
473
|
-
|
|
474
|
-
setIsDragging(false);
|
|
475
|
-
setDragOverIndex(null);
|
|
476
|
-
setDraggedBlockId(null);
|
|
477
|
-
setDropIndicatorPosition(null);
|
|
478
|
-
setCalculatedInsertIndex(null);
|
|
479
75
|
};
|
|
480
76
|
|
|
481
|
-
|
|
77
|
+
const renderAddButton = (index: number) => (
|
|
482
78
|
<div
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
onDrop={handleBodyDrop}
|
|
490
|
-
onDragLeave={handleDragLeave}
|
|
491
|
-
onDragStart={handleDragStart}
|
|
492
|
-
onDragEnd={() => {
|
|
493
|
-
setDropIndicatorPosition(null);
|
|
494
|
-
setDragOverIndex(null);
|
|
495
|
-
setIsDragging(false);
|
|
496
|
-
setDraggedBlockId(null);
|
|
497
|
-
setCalculatedInsertIndex(null);
|
|
498
|
-
}}
|
|
79
|
+
className={`relative h-4 group/add transition-all duration-300 ${
|
|
80
|
+
hoverIndex === index ? 'h-12' : 'h-4 opacity-0 hover:opacity-100'
|
|
81
|
+
}`}
|
|
82
|
+
onDragOver={(e) => handleDragOver(e, index)}
|
|
83
|
+
onDragLeave={() => setHoverIndex(null)}
|
|
84
|
+
onDrop={(e) => handleDrop(e, index)}
|
|
499
85
|
>
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const isOverNestedContainer = target.closest('[data-layout-container]') && target.closest('[data-layout-container]') !== containerRef.current;
|
|
512
|
-
|
|
513
|
-
if (!isOverBlock && !isOverNestedContainer) {
|
|
514
|
-
handleBodyDragOver(e);
|
|
515
|
-
}
|
|
516
|
-
}}
|
|
517
|
-
onDrop={(e) => {
|
|
518
|
-
// Only handle if not over a block or nested container
|
|
519
|
-
const target = e.target as HTMLElement;
|
|
520
|
-
const isOverBlock = target.closest('[data-block-wrapper]');
|
|
521
|
-
const isOverNestedContainer = target.closest('[data-layout-container]') && target.closest('[data-layout-container]') !== containerRef.current;
|
|
522
|
-
|
|
523
|
-
if (!isOverBlock && !isOverNestedContainer) {
|
|
524
|
-
handleBodyDrop(e);
|
|
525
|
-
}
|
|
526
|
-
}}
|
|
527
|
-
>
|
|
528
|
-
{/* 2. Content Area */}
|
|
529
|
-
{blocks.length === 0 ? (
|
|
530
|
-
<EmptyState isDragging={isDragging} darkMode={darkMode} />
|
|
531
|
-
) : (
|
|
532
|
-
blocks.map((block, index) => (
|
|
533
|
-
<Fragment key={block.id}>
|
|
534
|
-
{/* Visual Drop Zone - appears between blocks when dragging */}
|
|
535
|
-
{index > 0 && (
|
|
536
|
-
<div
|
|
537
|
-
className={`h-6 transition-all duration-200 ${isDragging && dragOverIndex === index
|
|
538
|
-
? 'bg-primary/5 border-y border-dashed border-primary/20 rounded-lg'
|
|
539
|
-
: 'bg-transparent'
|
|
540
|
-
}`}
|
|
541
|
-
/>
|
|
542
|
-
)}
|
|
543
|
-
|
|
544
|
-
{/* Block Container */}
|
|
545
|
-
<div
|
|
546
|
-
ref={setBlockRef(block.id)}
|
|
547
|
-
onDragOver={(e) => {
|
|
548
|
-
const blockEl = blockRefs.current.get(block.id);
|
|
549
|
-
if (blockEl) {
|
|
550
|
-
handleDragOver(e, index, blockEl);
|
|
551
|
-
}
|
|
552
|
-
}}
|
|
553
|
-
onDragLeave={handleDragLeave}
|
|
554
|
-
onDrop={(e) => handleDrop(e, index)}
|
|
555
|
-
className="relative mb-6 last:mb-0"
|
|
556
|
-
>
|
|
557
|
-
<BlockWrapper
|
|
558
|
-
block={block}
|
|
559
|
-
onUpdate={(data) => onBlockUpdate(block.id, data)}
|
|
560
|
-
onDelete={() => onBlockDelete(block.id)}
|
|
561
|
-
onMoveUp={index > 0 ? () => onBlockMove(block.id, index - 1) : undefined}
|
|
562
|
-
onMoveDown={index < blocks.length - 1 ? () => onBlockMove(block.id, index + 1) : undefined}
|
|
563
|
-
allBlocks={blocks}
|
|
564
|
-
/>
|
|
565
|
-
</div>
|
|
566
|
-
</Fragment>
|
|
567
|
-
))
|
|
568
|
-
)}
|
|
86
|
+
<div className="absolute inset-0 flex items-center justify-center">
|
|
87
|
+
<div className={`w-full h-px ${darkMode ? 'bg-neutral-800' : 'bg-neutral-200'} absolute top-1/2 -translate-y-1/2`} />
|
|
88
|
+
<button
|
|
89
|
+
onClick={() => {
|
|
90
|
+
// Default to paragraph for quick add
|
|
91
|
+
onBlockAdd('paragraph', index);
|
|
92
|
+
}}
|
|
93
|
+
className="relative z-10 size-8 rounded-full bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 flex items-center justify-center text-neutral-400 hover:text-primary hover:border-primary hover:scale-110 transition-all shadow-sm"
|
|
94
|
+
>
|
|
95
|
+
<Plus size={16} />
|
|
96
|
+
</button>
|
|
569
97
|
</div>
|
|
570
98
|
</div>
|
|
571
99
|
);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
/**
|
|
575
|
-
* Visual Line that shows where the block will land
|
|
576
|
-
*/
|
|
577
|
-
function DropIndicator({ position, darkMode }: { position: { top: number; left: number; width: number }; darkMode: boolean }) {
|
|
578
|
-
return (
|
|
579
|
-
<div
|
|
580
|
-
className="absolute z-50 pointer-events-none"
|
|
581
|
-
style={{
|
|
582
|
-
top: `${position.top - 12}px`,
|
|
583
|
-
left: `${position.left}px`,
|
|
584
|
-
width: `${position.width}px`,
|
|
585
|
-
height: '24px',
|
|
586
|
-
}}
|
|
587
|
-
>
|
|
588
|
-
{/* Drop zone background */}
|
|
589
|
-
<div className={`absolute inset-0 rounded-lg border border-dashed backdrop-blur-sm ${darkMode
|
|
590
|
-
? 'bg-primary/10 dark:bg-primary/20 border-primary dark:border-primary/60'
|
|
591
|
-
: 'bg-primary/10 border-primary'
|
|
592
|
-
}`} />
|
|
593
|
-
|
|
594
|
-
{/* Center line indicator */}
|
|
595
|
-
<div className={`absolute top-1/2 left-0 right-0 h-0.5 rounded-full transform -translate-y-1/2 ${darkMode ? 'bg-primary dark:bg-primary/80' : 'bg-primary'
|
|
596
|
-
}`} />
|
|
597
100
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
101
|
+
const renderBlocks = (blocksToRender: Block[], containerId?: string) => {
|
|
102
|
+
return (
|
|
103
|
+
<div className="space-y-4">
|
|
104
|
+
{renderAddButton(0)}
|
|
105
|
+
{blocksToRender.map((block, index) => (
|
|
106
|
+
<Fragment key={block.id}>
|
|
107
|
+
<BlockWrapper
|
|
108
|
+
block={block}
|
|
109
|
+
onUpdate={(data) => onBlockUpdate(block.id, data, containerId)}
|
|
110
|
+
onDelete={() => onBlockDelete(block.id, containerId)}
|
|
111
|
+
onDuplicate={() => helpers.duplicateBlock(block.id)}
|
|
112
|
+
onMoveUp={() => index > 0 && onBlockMove(block.id, index - 1, containerId)}
|
|
113
|
+
onMoveDown={() => index < blocksToRender.length - 1 && onBlockMove(block.id, index + 1, containerId)}
|
|
114
|
+
|
|
115
|
+
// Essential for nested rendering
|
|
116
|
+
childBlocks={Array.isArray(block.children) && typeof block.children[0] === 'object' ? (block.children as Block[]) : []}
|
|
117
|
+
onChildBlockAdd={(type, idx, cid) => onBlockAdd(type, idx ?? 0, cid)}
|
|
118
|
+
onChildBlockUpdate={onBlockUpdate}
|
|
119
|
+
onChildBlockDelete={onBlockDelete}
|
|
120
|
+
onChildBlockMove={onBlockMove}
|
|
121
|
+
/>
|
|
122
|
+
{renderAddButton(index + 1)}
|
|
123
|
+
</Fragment>
|
|
124
|
+
))}
|
|
605
125
|
</div>
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
126
|
+
);
|
|
127
|
+
};
|
|
609
128
|
|
|
610
|
-
/**
|
|
611
|
-
* Placeholder when the editor is empty
|
|
612
|
-
*/
|
|
613
|
-
function EmptyState({ isDragging, darkMode }: { isDragging: boolean; darkMode: boolean }) {
|
|
614
129
|
return (
|
|
615
|
-
<div
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
<Plus
|
|
636
|
-
size={24}
|
|
637
|
-
className={`transition-colors ${darkMode
|
|
638
|
-
? isDragging
|
|
639
|
-
? 'text-primary'
|
|
640
|
-
: 'text-neutral-400 dark:text-neutral-500'
|
|
641
|
-
: isDragging
|
|
642
|
-
? 'text-primary'
|
|
643
|
-
: 'text-neutral-400'
|
|
644
|
-
}`}
|
|
645
|
-
/>
|
|
646
|
-
</div>
|
|
647
|
-
<p
|
|
648
|
-
className={`text-sm font-black uppercase tracking-wider transition-colors ${darkMode
|
|
649
|
-
? isDragging
|
|
650
|
-
? 'text-primary'
|
|
651
|
-
: 'text-neutral-500 dark:text-neutral-400'
|
|
652
|
-
: isDragging
|
|
653
|
-
? 'text-primary'
|
|
654
|
-
: 'text-neutral-500'
|
|
655
|
-
}`}
|
|
656
|
-
>
|
|
657
|
-
{isDragging ? 'Drop Block Here' : 'Drop a component here'}
|
|
658
|
-
</p>
|
|
659
|
-
<p className={`text-xs mt-2 ${darkMode ? 'text-neutral-400 dark:text-neutral-500' : 'text-neutral-400'
|
|
660
|
-
}`}>
|
|
661
|
-
Drag blocks from the library to get started
|
|
130
|
+
<div ref={bodyRef} className="relative min-h-[400px]">
|
|
131
|
+
{mainBlocks.length === 0 ? (
|
|
132
|
+
<div
|
|
133
|
+
className="flex flex-col items-center justify-center py-32 border-2 border-dashed border-neutral-200 dark:border-neutral-800 rounded-[2rem] text-neutral-400 hover:text-primary hover:border-primary transition-all cursor-pointer group"
|
|
134
|
+
onClick={() => onBlockAdd('paragraph', 0)}
|
|
135
|
+
onDragOver={(e) => e.preventDefault()}
|
|
136
|
+
onDrop={(e) => handleDrop(e, 0)}
|
|
137
|
+
>
|
|
138
|
+
<Plus size={48} strokeWidth={1} className="mb-4 opacity-20 group-hover:opacity-100 transition-opacity" />
|
|
139
|
+
<span className="text-sm font-bold uppercase tracking-widest">Add your first block</span>
|
|
140
|
+
</div>
|
|
141
|
+
) : (
|
|
142
|
+
renderBlocks(mainBlocks)
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
{/* Bottom spacer to allow dragging after last block */}
|
|
146
|
+
<div className="h-20" />
|
|
147
|
+
|
|
148
|
+
<p className={`text-xs mt-2 text-center ${darkMode ? 'text-neutral-500' : 'text-neutral-400'}`}>
|
|
149
|
+
Drag blocks from the library to build your story
|
|
662
150
|
</p>
|
|
663
151
|
</div>
|
|
664
152
|
);
|