@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
|
@@ -2,38 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useCallback, useEffect } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
-
import { Image as ImageIcon, Plus, X } from 'lucide-react';
|
|
5
|
+
import { Image as ImageIcon, Plus, X, Edit2, Trash2 } from 'lucide-react';
|
|
6
6
|
import { ImagePicker, Image } from '@jhits/plugin-images';
|
|
7
7
|
import type { ImageMetadata } from '@jhits/plugin-images';
|
|
8
8
|
import type { Block } from '../../../types/block';
|
|
9
9
|
|
|
10
10
|
export interface FeaturedImage {
|
|
11
|
-
// Store only the semantic ID - plugin-images handles everything else
|
|
12
11
|
id?: string;
|
|
13
12
|
alt?: string;
|
|
14
|
-
// Transform values (stored locally for UI, but plugin-images API is source of truth)
|
|
15
13
|
brightness?: number;
|
|
16
14
|
blur?: number;
|
|
17
15
|
scale?: number;
|
|
18
16
|
positionX?: number;
|
|
19
17
|
positionY?: number;
|
|
20
|
-
// Indicates if this is a custom featured image (not synced from hero)
|
|
21
18
|
isCustom?: boolean;
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
export interface FeaturedMediaSectionProps {
|
|
25
22
|
featuredImage?: FeaturedImage;
|
|
26
|
-
heroBlock?: Block | null;
|
|
27
|
-
slug?: string;
|
|
23
|
+
heroBlock?: Block | null;
|
|
24
|
+
slug?: string;
|
|
28
25
|
onUpdate: (image: FeaturedImage | undefined) => void;
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
/**
|
|
32
|
-
* Featured Media Section Component
|
|
33
|
-
* Handles featured image selection - completely independent from hero image
|
|
34
|
-
* Featured image is a thumbnail used for blog post cards
|
|
35
|
-
* Hero image is separate and managed in the hero block
|
|
36
|
-
*/
|
|
37
28
|
export function FeaturedMediaSection({
|
|
38
29
|
featuredImage,
|
|
39
30
|
heroBlock,
|
|
@@ -44,293 +35,171 @@ export function FeaturedMediaSection({
|
|
|
44
35
|
const [openEditorDirectly, setOpenEditorDirectly] = useState(false);
|
|
45
36
|
const [mounted, setMounted] = useState(false);
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
setMounted(true);
|
|
50
|
-
}, []);
|
|
38
|
+
useEffect(() => { setMounted(true); }, []);
|
|
51
39
|
|
|
52
|
-
// Create semantic ID for this featured image - plugin-images will handle everything
|
|
53
40
|
const semanticId = slug ? `blog-featured-${slug}` : `blog-featured-${Date.now()}`;
|
|
54
|
-
|
|
55
|
-
// Use semantic ID from featuredImage if it exists, otherwise use generated one
|
|
56
|
-
// IMPORTANT: Always use the actual id from featuredImage if available, otherwise the semanticId
|
|
57
|
-
// This ensures the id is stable and doesn't change on re-renders
|
|
58
41
|
const imageId = featuredImage?.id || semanticId;
|
|
59
42
|
|
|
60
|
-
// Ensure featuredImage always has an id when it exists
|
|
61
|
-
// This prevents the "missing featured image" issue on save
|
|
62
43
|
useEffect(() => {
|
|
63
44
|
if (featuredImage && !featuredImage.id) {
|
|
64
|
-
|
|
65
|
-
onUpdate({
|
|
66
|
-
...featuredImage,
|
|
67
|
-
id: semanticId,
|
|
68
|
-
});
|
|
45
|
+
onUpdate({ ...featuredImage, id: semanticId });
|
|
69
46
|
}
|
|
70
47
|
}, [featuredImage, semanticId, onUpdate]);
|
|
71
48
|
|
|
72
|
-
// Get transform values from featuredImage or use defaults
|
|
73
49
|
const brightness = featuredImage?.brightness ?? 100;
|
|
74
50
|
const blur = featuredImage?.blur ?? 0;
|
|
75
51
|
const scale = featuredImage?.scale ?? 1.0;
|
|
76
52
|
const positionX = featuredImage?.positionX ?? 0;
|
|
77
53
|
const positionY = featuredImage?.positionY ?? 0;
|
|
78
54
|
|
|
79
|
-
// Handle image selection - create initial mapping and update blog metadata with semantic ID
|
|
80
|
-
// Plugin-images Image component will automatically resolve the semantic ID when it renders
|
|
81
55
|
const handleImageChange = useCallback(async (image: ImageMetadata | null) => {
|
|
82
56
|
if (image) {
|
|
83
|
-
// Extract filename from image URL for reference
|
|
84
57
|
const isUploadedImage = image.url.startsWith('/api/uploads/');
|
|
85
58
|
let filename = image.filename;
|
|
59
|
+
if (!filename && isUploadedImage) filename = image.url.split('/api/uploads/')[1]?.split('?')[0] || image.id;
|
|
60
|
+
else if (!filename) filename = image.id || image.url.split('/').pop()?.split('?')[0] || `external-${Date.now()}`;
|
|
86
61
|
|
|
87
|
-
if (!filename && isUploadedImage) {
|
|
88
|
-
// Extract filename from URL if not provided
|
|
89
|
-
filename = image.url.split('/api/uploads/')[1]?.split('?')[0] || image.id;
|
|
90
|
-
} else if (!filename) {
|
|
91
|
-
// For external URLs, use the image ID or extract from URL
|
|
92
|
-
filename = image.id || image.url.split('/').pop()?.split('?')[0] || `external-${Date.now()}`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Create initial mapping in plugin-images API immediately
|
|
96
|
-
// This ensures the semantic ID resolves correctly
|
|
97
62
|
try {
|
|
98
|
-
const saveData = {
|
|
99
|
-
id: imageId,
|
|
100
|
-
filename: filename,
|
|
101
|
-
scale: 1.0,
|
|
102
|
-
positionX: 0,
|
|
103
|
-
positionY: 0,
|
|
104
|
-
brightness: 100,
|
|
105
|
-
blur: 0,
|
|
106
|
-
};
|
|
107
|
-
|
|
63
|
+
const saveData = { id: imageId, filename, scale: 1.0, positionX: 0, positionY: 0, brightness: 100, blur: 0 };
|
|
108
64
|
await fetch('/api/plugin-images/resolve', {
|
|
109
65
|
method: 'POST',
|
|
110
66
|
headers: { 'Content-Type': 'application/json' },
|
|
111
67
|
body: JSON.stringify(saveData),
|
|
112
68
|
});
|
|
113
|
-
} catch (error) {
|
|
114
|
-
console.error('[FeaturedMediaSection] Failed to create initial mapping:', error);
|
|
115
|
-
// Continue anyway - the mapping might be created later
|
|
116
|
-
}
|
|
69
|
+
} catch (error) { console.error(error); }
|
|
117
70
|
|
|
118
|
-
|
|
119
|
-
onUpdate({
|
|
120
|
-
id: imageId,
|
|
121
|
-
alt: image.alt || image.filename,
|
|
122
|
-
brightness: 100,
|
|
123
|
-
blur: 0,
|
|
124
|
-
scale: 1.0,
|
|
125
|
-
positionX: 0,
|
|
126
|
-
positionY: 0,
|
|
127
|
-
isCustom: true,
|
|
128
|
-
} as FeaturedImage);
|
|
71
|
+
onUpdate({ id: imageId, alt: image.alt || image.filename, brightness: 100, blur: 0, scale: 1.0, positionX: 0, positionY: 0, isCustom: true } as FeaturedImage);
|
|
129
72
|
} else {
|
|
130
|
-
// If removed, set to undefined
|
|
131
73
|
onUpdate(undefined);
|
|
132
74
|
}
|
|
133
75
|
setShowImagePicker(false);
|
|
134
76
|
}, [imageId, onUpdate]);
|
|
135
77
|
|
|
136
|
-
|
|
137
|
-
const handleEditorSave = useCallback(async (
|
|
138
|
-
finalScale: number,
|
|
139
|
-
finalPositionX: number,
|
|
140
|
-
finalPositionY: number,
|
|
141
|
-
finalBrightness?: number,
|
|
142
|
-
finalBlur?: number
|
|
143
|
-
) => {
|
|
78
|
+
const handleEditorSave = useCallback(async (finalScale: number, finalPositionX: number, finalPositionY: number, finalBrightness?: number, finalBlur?: number) => {
|
|
144
79
|
if (!featuredImage?.id) return;
|
|
145
|
-
|
|
146
|
-
// Reset the auto-open flag immediately to prevent reopening
|
|
147
80
|
setOpenEditorDirectly(false);
|
|
148
|
-
|
|
149
|
-
// Get the actual filename from the API (resolve the semantic ID)
|
|
150
|
-
let filename = imageId; // Fallback to semantic ID
|
|
81
|
+
let filename = imageId;
|
|
151
82
|
try {
|
|
152
83
|
const response = await fetch(`/api/plugin-images/resolve?id=${encodeURIComponent(imageId)}`);
|
|
153
84
|
if (response.ok) {
|
|
154
85
|
const data = await response.json();
|
|
155
86
|
filename = data.filename || imageId;
|
|
156
87
|
}
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error('Failed to resolve filename:', error);
|
|
159
|
-
}
|
|
88
|
+
} catch (error) { console.error(error); }
|
|
160
89
|
|
|
161
|
-
// Normalize position values
|
|
162
90
|
const normalizedPositionX = finalPositionX === -50 ? 0 : finalPositionX;
|
|
163
91
|
const normalizedPositionY = finalPositionY === -50 ? 0 : finalPositionY;
|
|
164
92
|
const finalBrightnessValue = finalBrightness ?? brightness;
|
|
165
93
|
const finalBlurValue = finalBlur ?? blur;
|
|
166
94
|
|
|
167
|
-
// Save to plugin-images API
|
|
168
95
|
try {
|
|
169
|
-
const saveData = {
|
|
170
|
-
id: imageId,
|
|
171
|
-
filename: filename,
|
|
172
|
-
scale: finalScale,
|
|
173
|
-
positionX: normalizedPositionX,
|
|
174
|
-
positionY: normalizedPositionY,
|
|
175
|
-
brightness: finalBrightnessValue,
|
|
176
|
-
blur: finalBlurValue,
|
|
177
|
-
};
|
|
178
|
-
|
|
96
|
+
const saveData = { id: imageId, filename, scale: finalScale, positionX: normalizedPositionX, positionY: normalizedPositionY, brightness: finalBrightnessValue, blur: finalBlurValue };
|
|
179
97
|
const response = await fetch('/api/plugin-images/resolve', {
|
|
180
98
|
method: 'POST',
|
|
181
99
|
headers: { 'Content-Type': 'application/json' },
|
|
182
100
|
body: JSON.stringify(saveData),
|
|
183
101
|
});
|
|
184
|
-
|
|
185
102
|
if (response.ok) {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
...featuredImage,
|
|
189
|
-
id: featuredImage.id || imageId, // Ensure id is always preserved
|
|
190
|
-
scale: finalScale,
|
|
191
|
-
positionX: normalizedPositionX,
|
|
192
|
-
positionY: normalizedPositionY,
|
|
193
|
-
brightness: finalBrightnessValue,
|
|
194
|
-
blur: finalBlurValue,
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
// Dispatch event to notify Image components
|
|
198
|
-
window.dispatchEvent(new CustomEvent('image-mapping-updated', {
|
|
199
|
-
detail: saveData
|
|
200
|
-
}));
|
|
103
|
+
onUpdate({ ...featuredImage, id: featuredImage.id || imageId, scale: finalScale, positionX: normalizedPositionX, positionY: normalizedPositionY, brightness: finalBrightnessValue, blur: finalBlurValue });
|
|
104
|
+
window.dispatchEvent(new CustomEvent('image-mapping-updated', { detail: saveData }));
|
|
201
105
|
}
|
|
202
|
-
} catch (error) {
|
|
203
|
-
console.error('Failed to save image transform:', error);
|
|
204
|
-
}
|
|
106
|
+
} catch (error) { console.error(error); }
|
|
205
107
|
}, [imageId, featuredImage, brightness, blur, onUpdate]);
|
|
206
108
|
|
|
207
109
|
return (
|
|
208
110
|
<section>
|
|
209
111
|
<div className="flex items-center gap-3 mb-6">
|
|
210
|
-
<
|
|
211
|
-
|
|
212
|
-
|
|
112
|
+
<div className="size-8 rounded-xl bg-primary/10 flex items-center justify-center text-primary border border-primary/20">
|
|
113
|
+
<ImageIcon size={14} />
|
|
114
|
+
</div>
|
|
115
|
+
<label className="text-[10px] font-black text-dashboard-text uppercase tracking-[0.3em]">
|
|
116
|
+
Visual Identity
|
|
213
117
|
</label>
|
|
214
118
|
</div>
|
|
119
|
+
|
|
215
120
|
{featuredImage?.id ? (
|
|
216
121
|
<div className="relative group">
|
|
217
|
-
|
|
218
|
-
{/* Blog component only handles the design/styling */}
|
|
219
|
-
<div className="relative aspect-[16/10] bg-dashboard-bg rounded-3xl overflow-hidden border border-dashboard-border group/image">
|
|
122
|
+
<div className="relative aspect-[16/10] bg-dashboard-bg rounded-3xl overflow-hidden border border-dashboard-border group/image shadow-inner">
|
|
220
123
|
<Image
|
|
221
124
|
id={imageId}
|
|
222
125
|
alt={featuredImage?.alt || 'Featured image'}
|
|
223
126
|
fill
|
|
224
|
-
className="object-cover w-full h-full"
|
|
225
|
-
editable={false}
|
|
226
|
-
{...({
|
|
227
|
-
brightness,
|
|
228
|
-
blur,
|
|
229
|
-
scale,
|
|
230
|
-
positionX,
|
|
231
|
-
positionY,
|
|
232
|
-
} as any)}
|
|
127
|
+
className="object-cover w-full h-full group-hover:scale-105 transition-transform duration-700"
|
|
128
|
+
editable={false}
|
|
129
|
+
{...({ brightness, blur, scale, positionX, positionY } as any)}
|
|
233
130
|
/>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<ImageIcon size={14} className="text-primary" />
|
|
244
|
-
<span className="text-[10px] font-bold uppercase tracking-widest">Edit</span>
|
|
245
|
-
</div>
|
|
246
|
-
</button>
|
|
131
|
+
<div className="absolute inset-0 z-30 flex items-center justify-center opacity-0 group-hover/image:opacity-100 transition-all duration-500 bg-black/20 backdrop-blur-[2px]">
|
|
132
|
+
<button
|
|
133
|
+
onClick={() => { setOpenEditorDirectly(true); setShowImagePicker(true); }}
|
|
134
|
+
className="flex items-center gap-2 px-5 py-2.5 bg-white text-black rounded-full shadow-2xl hover:scale-105 active:scale-95 transition-all"
|
|
135
|
+
>
|
|
136
|
+
<Edit2 size={12} className="fill-current" />
|
|
137
|
+
<span className="text-[10px] font-black uppercase tracking-widest">Adjust View</span>
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
247
140
|
</div>
|
|
248
|
-
<div className="mt-
|
|
141
|
+
<div className="mt-4 flex items-center justify-between px-1">
|
|
249
142
|
<button
|
|
250
143
|
onClick={() => setShowImagePicker(true)}
|
|
251
|
-
className="text-[
|
|
144
|
+
className="text-[9px] font-black uppercase tracking-[0.2em] text-primary hover:text-primary/80 transition-colors flex items-center gap-1.5"
|
|
252
145
|
>
|
|
253
|
-
|
|
146
|
+
<Plus size={10} strokeWidth={3} />
|
|
147
|
+
Replace
|
|
254
148
|
</button>
|
|
255
|
-
<span className="text-[10px] text-neutral-400">•</span>
|
|
256
149
|
<button
|
|
257
150
|
onClick={() => onUpdate(undefined)}
|
|
258
|
-
className="text-[
|
|
151
|
+
className="text-[9px] font-black uppercase tracking-[0.2em] text-red-500/60 hover:text-red-500 transition-colors flex items-center gap-1.5"
|
|
259
152
|
>
|
|
260
|
-
|
|
153
|
+
<Trash2 size={10} />
|
|
154
|
+
Detach
|
|
261
155
|
</button>
|
|
262
156
|
</div>
|
|
263
157
|
</div>
|
|
264
158
|
) : (
|
|
265
159
|
<div
|
|
266
|
-
className="group relative aspect-[16/10] bg-dashboard-bg rounded-
|
|
160
|
+
className="group relative aspect-[16/10] bg-dashboard-bg/50 rounded-[2.5rem] border-2 border-dashed border-dashboard-border/50 flex flex-col items-center justify-center text-dashboard-text-secondary/40 hover:bg-primary/[0.02] hover:border-primary/40 cursor-pointer transition-all duration-500 shadow-inner"
|
|
267
161
|
onClick={() => setShowImagePicker(true)}
|
|
268
162
|
>
|
|
269
|
-
<
|
|
270
|
-
|
|
163
|
+
<div className="size-14 rounded-full bg-dashboard-card flex items-center justify-center border border-dashboard-border group-hover:scale-110 group-hover:border-primary/20 transition-all duration-500 mb-4 shadow-sm">
|
|
164
|
+
<ImageIcon size={24} strokeWidth={1.5} className="group-hover:text-primary transition-colors" />
|
|
165
|
+
</div>
|
|
166
|
+
<span className="text-[10px] font-black uppercase tracking-[0.3em] group-hover:text-primary transition-colors">Assign Visual</span>
|
|
271
167
|
</div>
|
|
272
168
|
)}
|
|
273
169
|
|
|
274
|
-
{/* Image Picker Modal */}
|
|
275
170
|
{showImagePicker && mounted && createPortal(
|
|
276
|
-
<div className="fixed inset-0 z-[
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
setOpenEditorDirectly(false); // Reset flag when closing
|
|
289
|
-
}}
|
|
290
|
-
className="p-2 hover:bg-dashboard-bg dark:hover:bg-neutral-800 rounded-lg transition-colors text-neutral-700 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-white"
|
|
291
|
-
aria-label="Close"
|
|
292
|
-
>
|
|
293
|
-
<X size={20} className="transition-colors" />
|
|
171
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center p-6" onClick={() => { setShowImagePicker(false); setOpenEditorDirectly(false); }}>
|
|
172
|
+
<div className="absolute inset-0 bg-black/60 backdrop-blur-md" />
|
|
173
|
+
<div className="relative bg-dashboard-bg border border-dashboard-border rounded-[3rem] w-full max-w-3xl overflow-hidden shadow-2xl animate-in zoom-in-95 duration-300 flex flex-col max-h-full" onClick={(e) => e.stopPropagation()}>
|
|
174
|
+
<div className="p-8 border-b border-dashboard-border flex items-center justify-between">
|
|
175
|
+
<div>
|
|
176
|
+
<h3 className="text-2xl font-black text-dashboard-text uppercase tracking-tighter">
|
|
177
|
+
{openEditorDirectly ? 'Perfecting' : 'Curating'} Image
|
|
178
|
+
</h3>
|
|
179
|
+
<p className="text-xs text-dashboard-text-secondary italic mt-1">Select or adjust the featured visual for your article.</p>
|
|
180
|
+
</div>
|
|
181
|
+
<button onClick={() => { setShowImagePicker(false); setOpenEditorDirectly(false); }} className="size-12 flex items-center justify-center bg-dashboard-card border border-dashboard-border rounded-2xl hover:text-red-500 hover:border-red-500/20 transition-all">
|
|
182
|
+
<X size={20} />
|
|
294
183
|
</button>
|
|
295
184
|
</div>
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
scale,
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
}
|
|
315
|
-
}}
|
|
316
|
-
onBlurChange={(val) => {
|
|
317
|
-
// Update local state only - don't trigger save
|
|
318
|
-
if (featuredImage) {
|
|
319
|
-
onUpdate({
|
|
320
|
-
...featuredImage,
|
|
321
|
-
id: featuredImage.id || imageId, // Ensure id is preserved
|
|
322
|
-
blur: val,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
}}
|
|
326
|
-
onEditorSave={handleEditorSave}
|
|
327
|
-
darkMode={false}
|
|
328
|
-
showEffects={true} // Enable effects so editor can be used
|
|
329
|
-
aspectRatio="16/10" // Thumbnail aspect ratio for blog cards
|
|
330
|
-
borderRadius="rounded-3xl"
|
|
331
|
-
objectFit="cover" // Cover for thumbnails
|
|
332
|
-
objectPosition="center"
|
|
333
|
-
/>
|
|
185
|
+
<div className="flex-1 overflow-y-auto p-8 custom-scrollbar">
|
|
186
|
+
<ImagePicker
|
|
187
|
+
value={featuredImage?.id ? imageId : undefined}
|
|
188
|
+
onChange={handleImageChange}
|
|
189
|
+
brightness={brightness}
|
|
190
|
+
blur={blur}
|
|
191
|
+
{...({ scale, positionX, positionY } as any)}
|
|
192
|
+
onBrightnessChange={(val) => { if (featuredImage) onUpdate({ ...featuredImage, id: featuredImage.id || imageId, brightness: val }); }}
|
|
193
|
+
onBlurChange={(val) => { if (featuredImage) onUpdate({ ...featuredImage, id: featuredImage.id || imageId, blur: val }); }}
|
|
194
|
+
onEditorSave={handleEditorSave}
|
|
195
|
+
darkMode={true}
|
|
196
|
+
showEffects={true}
|
|
197
|
+
aspectRatio="16/10"
|
|
198
|
+
borderRadius="rounded-[2rem]"
|
|
199
|
+
objectFit="cover"
|
|
200
|
+
objectPosition="center"
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
334
203
|
</div>
|
|
335
204
|
</div>,
|
|
336
205
|
document.body
|
|
@@ -338,4 +207,3 @@ export function FeaturedMediaSection({
|
|
|
338
207
|
</section>
|
|
339
208
|
);
|
|
340
209
|
}
|
|
341
|
-
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useMemo } from 'react';
|
|
4
|
+
import { Code, X, Copy, Check } from 'lucide-react';
|
|
5
|
+
|
|
6
|
+
export interface JSONInspectorProps {
|
|
7
|
+
data: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Simple JSON Syntax Highlighter
|
|
12
|
+
*/
|
|
13
|
+
function highlightJSON(json: string) {
|
|
14
|
+
if (!json) return null;
|
|
15
|
+
|
|
16
|
+
// Convert to string and escape HTML
|
|
17
|
+
const escaped = json
|
|
18
|
+
.replace(/&/g, '&')
|
|
19
|
+
.replace(/</g, '<')
|
|
20
|
+
.replace(/>/g, '>');
|
|
21
|
+
|
|
22
|
+
return escaped.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) {
|
|
23
|
+
let cls = 'text-orange-400'; // number
|
|
24
|
+
if (/^"/.test(match)) {
|
|
25
|
+
if (/:$/.test(match)) {
|
|
26
|
+
cls = 'text-blue-400'; // key
|
|
27
|
+
} else {
|
|
28
|
+
cls = 'text-green-400'; // string
|
|
29
|
+
}
|
|
30
|
+
} else if (/true|false/.test(match)) {
|
|
31
|
+
cls = 'text-purple-400'; // boolean
|
|
32
|
+
} else if (/null/.test(match)) {
|
|
33
|
+
cls = 'text-neutral-500'; // null
|
|
34
|
+
}
|
|
35
|
+
return `<span class="${cls}">${match}</span>`;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Dev-only JSON Inspector
|
|
41
|
+
* Displays the current state of the blog post in a formatted JSON view
|
|
42
|
+
*/
|
|
43
|
+
export function JSONInspector({ data }: JSONInspectorProps) {
|
|
44
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
45
|
+
const [copied, setCopied] = useState(false);
|
|
46
|
+
|
|
47
|
+
// Memoize the highlighted HTML to prevent re-parsing on every render
|
|
48
|
+
const highlightedHTML = useMemo(() => {
|
|
49
|
+
const jsonString = JSON.stringify(data, null, 2);
|
|
50
|
+
return highlightJSON(jsonString);
|
|
51
|
+
}, [data]);
|
|
52
|
+
|
|
53
|
+
// Only visible in development mode
|
|
54
|
+
if (process.env.NODE_ENV !== 'development') return null;
|
|
55
|
+
|
|
56
|
+
const handleCopy = () => {
|
|
57
|
+
navigator.clipboard.writeText(JSON.stringify(data, null, 2));
|
|
58
|
+
setCopied(true);
|
|
59
|
+
setTimeout(() => setCopied(false), 2000);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<>
|
|
64
|
+
{/* Floating Toggle Button */}
|
|
65
|
+
<button
|
|
66
|
+
onClick={() => setIsOpen(true)}
|
|
67
|
+
className="fixed bottom-8 right-8 z-[100] bg-neutral-900 dark:bg-white text-white dark:text-neutral-900 px-5 py-3 rounded-2xl shadow-2xl hover:scale-105 active:scale-95 transition-all flex items-center gap-3 border border-white/10 dark:border-neutral-200"
|
|
68
|
+
title="Inspect JSON (Dev Only)"
|
|
69
|
+
>
|
|
70
|
+
<div className="size-2 rounded-full bg-green-500 animate-pulse" />
|
|
71
|
+
<Code size={18} strokeWidth={2.5} />
|
|
72
|
+
<span className="text-[10px] font-black uppercase tracking-[0.2em]">Inspect JSON</span>
|
|
73
|
+
</button>
|
|
74
|
+
|
|
75
|
+
{/* Modal Overlay */}
|
|
76
|
+
{isOpen && (
|
|
77
|
+
<div className="fixed inset-0 z-[110] bg-black/60 backdrop-blur-md flex items-center justify-center p-6 md:p-12 animate-in fade-in duration-300">
|
|
78
|
+
<div className="bg-white dark:bg-neutral-900 rounded-[2.5rem] w-full max-w-5xl h-[85vh] flex flex-col overflow-hidden shadow-[0_32px_64px_-12px_rgba(0,0,0,0.5)] border border-neutral-200 dark:border-neutral-800 animate-in zoom-in-95 duration-300">
|
|
79
|
+
|
|
80
|
+
{/* Header */}
|
|
81
|
+
<div className="px-8 py-6 border-b border-neutral-100 dark:border-neutral-800 flex justify-between items-center bg-neutral-50/50 dark:bg-neutral-800/50 shrink-0">
|
|
82
|
+
<div>
|
|
83
|
+
<div className="flex items-center gap-3 mb-1">
|
|
84
|
+
<h3 className="font-black uppercase tracking-tighter text-2xl text-neutral-900 dark:text-white">Blog Data</h3>
|
|
85
|
+
<span className="px-2 py-0.5 rounded-md bg-primary/10 text-primary text-[9px] font-black uppercase tracking-widest">Dev Tool</span>
|
|
86
|
+
</div>
|
|
87
|
+
<p className="text-[10px] text-neutral-500 font-bold uppercase tracking-widest">Raw state inspector for debugging purposes</p>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div className="flex items-center gap-3">
|
|
91
|
+
<button
|
|
92
|
+
onClick={handleCopy}
|
|
93
|
+
className="flex items-center gap-2 px-5 py-2.5 bg-neutral-100 dark:bg-neutral-800 hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all"
|
|
94
|
+
>
|
|
95
|
+
{copied ? <Check size={14} className="text-green-500" /> : <Copy size={14} />}
|
|
96
|
+
{copied ? 'Copied' : 'Copy JSON'}
|
|
97
|
+
</button>
|
|
98
|
+
<button
|
|
99
|
+
onClick={() => setIsOpen(false)}
|
|
100
|
+
className="p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-full transition-colors text-neutral-400 hover:text-neutral-900 dark:hover:text-white"
|
|
101
|
+
>
|
|
102
|
+
<X size={28} strokeWidth={1.5} />
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Content Area - Fixed scrolling and added syntax highlighting */}
|
|
108
|
+
<div className="flex-1 min-h-0 w-full bg-[#0d0d0d] overflow-y-auto overflow-x-hidden custom-scrollbar">
|
|
109
|
+
<pre
|
|
110
|
+
className="p-8 font-mono text-[13px] leading-relaxed text-neutral-300 whitespace-pre-wrap break-all"
|
|
111
|
+
dangerouslySetInnerHTML={{ __html: highlightedHTML || '' }}
|
|
112
|
+
/>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Footer */}
|
|
116
|
+
<div className="px-8 py-4 bg-neutral-50 dark:bg-neutral-800/30 border-t border-neutral-100 dark:border-neutral-800 flex justify-between items-center text-[9px] font-bold text-neutral-400 uppercase tracking-widest shrink-0">
|
|
117
|
+
<span>Blocks Count: {data.blocks?.length || 0}</span>
|
|
118
|
+
<span>Status: {data.status || 'Unknown'}</span>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -25,7 +25,7 @@ export function LibraryItem({
|
|
|
25
25
|
const mouseDownRef = useRef<{ x: number; y: number } | null>(null);
|
|
26
26
|
|
|
27
27
|
const handleDragStart = (e: React.DragEvent) => {
|
|
28
|
-
e.dataTransfer.setData('
|
|
28
|
+
e.dataTransfer.setData('blockType', blockType);
|
|
29
29
|
e.dataTransfer.effectAllowed = 'move';
|
|
30
30
|
setHasDragged(true); // Mark as dragged when drag starts
|
|
31
31
|
};
|
|
@@ -66,15 +66,14 @@ export function LibraryItem({
|
|
|
66
66
|
onMouseDown={handleMouseDown}
|
|
67
67
|
onMouseMove={handleMouseMove}
|
|
68
68
|
onClick={handleClick}
|
|
69
|
-
className="flex flex-col items-center justify-center p-
|
|
69
|
+
className="flex flex-col items-center justify-center p-6 rounded-[2rem] border border-dashboard-border/50 bg-dashboard-card/30 backdrop-blur-sm hover:border-primary/40 hover:bg-primary/[0.02] hover:shadow-2xl hover:shadow-primary/10 hover:scale-[1.05] transition-all duration-500 cursor-grab active:cursor-grabbing group"
|
|
70
70
|
>
|
|
71
|
-
<div className="text-
|
|
72
|
-
{React.cloneElement(icon as React.ReactElement, { strokeWidth: 1.5 } as any)}
|
|
71
|
+
<div className="text-dashboard-text-secondary group-hover:text-primary transition-all duration-500 group-hover:scale-110 mb-4">
|
|
72
|
+
{React.cloneElement(icon as React.ReactElement, { strokeWidth: 1.5, size: 24 } as any)}
|
|
73
73
|
</div>
|
|
74
|
-
<span className="text-[9px] font-black uppercase tracking-[0.
|
|
74
|
+
<span className="text-[9px] font-black uppercase tracking-[0.2em] text-dashboard-text-secondary group-hover:text-dashboard-text transition-colors text-center leading-tight">
|
|
75
75
|
{label}
|
|
76
76
|
</span>
|
|
77
77
|
</div>
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
|
-
|