create-nextblock 0.0.1
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/bin/create-nextblock.js +997 -0
- package/package.json +25 -0
- package/scripts/sync-template.js +284 -0
- package/templates/nextblock-template/.env.example +37 -0
- package/templates/nextblock-template/.swcrc +30 -0
- package/templates/nextblock-template/README.md +194 -0
- package/templates/nextblock-template/app/(auth-pages)/forgot-password/page.tsx +57 -0
- package/templates/nextblock-template/app/(auth-pages)/layout.tsx +9 -0
- package/templates/nextblock-template/app/(auth-pages)/post-sign-in/page.tsx +28 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-in/page.tsx +67 -0
- package/templates/nextblock-template/app/(auth-pages)/sign-up/page.tsx +70 -0
- package/templates/nextblock-template/app/ToasterProvider.tsx +17 -0
- package/templates/nextblock-template/app/[slug]/PageClientContent.tsx +147 -0
- package/templates/nextblock-template/app/[slug]/page.tsx +145 -0
- package/templates/nextblock-template/app/[slug]/page.utils.ts +183 -0
- package/templates/nextblock-template/app/actions/email.ts +31 -0
- package/templates/nextblock-template/app/actions/formActions.ts +65 -0
- package/templates/nextblock-template/app/actions/languageActions.ts +130 -0
- package/templates/nextblock-template/app/actions/postActions.ts +80 -0
- package/templates/nextblock-template/app/actions.ts +146 -0
- package/templates/nextblock-template/app/api/process-image/route.ts +210 -0
- package/templates/nextblock-template/app/api/revalidate/route.ts +86 -0
- package/templates/nextblock-template/app/api/revalidate-log/route.ts +23 -0
- package/templates/nextblock-template/app/api/upload/presigned-url/route.ts +106 -0
- package/templates/nextblock-template/app/api/upload/proxy/route.ts +84 -0
- package/templates/nextblock-template/app/auth/callback/route.ts +58 -0
- package/templates/nextblock-template/app/blog/[slug]/PostClientContent.tsx +169 -0
- package/templates/nextblock-template/app/blog/[slug]/page.tsx +177 -0
- package/templates/nextblock-template/app/blog/[slug]/page.utils.ts +136 -0
- package/templates/nextblock-template/app/blog/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/CmsClientLayout.tsx +321 -0
- package/templates/nextblock-template/app/cms/blocks/actions.ts +434 -0
- package/templates/nextblock-template/app/cms/blocks/components/BackgroundSelector.tsx +348 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorArea.tsx +567 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockEditorModal.tsx +98 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeCard.tsx +58 -0
- package/templates/nextblock-template/app/cms/blocks/components/BlockTypeSelector.tsx +62 -0
- package/templates/nextblock-template/app/cms/blocks/components/ColumnEditor.tsx +276 -0
- package/templates/nextblock-template/app/cms/blocks/components/DeleteBlockButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/blocks/components/EditableBlock.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/components/MediaLibraryModal.tsx +120 -0
- package/templates/nextblock-template/app/cms/blocks/components/SectionConfigPanel.tsx +133 -0
- package/templates/nextblock-template/app/cms/blocks/components/SortableBlockItem.tsx +46 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ButtonBlockEditor.tsx +85 -0
- package/templates/nextblock-template/app/cms/blocks/editors/FormBlockEditor.tsx +182 -0
- package/templates/nextblock-template/app/cms/blocks/editors/HeadingBlockEditor.tsx +111 -0
- package/templates/nextblock-template/app/cms/blocks/editors/ImageBlockEditor.tsx +150 -0
- package/templates/nextblock-template/app/cms/blocks/editors/PostsGridBlockEditor.tsx +79 -0
- package/templates/nextblock-template/app/cms/blocks/editors/SectionBlockEditor.tsx +337 -0
- package/templates/nextblock-template/app/cms/blocks/editors/TextBlockEditor.tsx +81 -0
- package/templates/nextblock-template/app/cms/blocks/editors/VideoEmbedBlockEditor.tsx +64 -0
- package/templates/nextblock-template/app/cms/components/ConfirmationModal.tsx +51 -0
- package/templates/nextblock-template/app/cms/components/ContentLanguageSwitcher.tsx +145 -0
- package/templates/nextblock-template/app/cms/components/CopyContentFromLanguage.tsx +203 -0
- package/templates/nextblock-template/app/cms/components/LanguageFilterSelect.tsx +69 -0
- package/templates/nextblock-template/app/cms/dashboard/page.tsx +247 -0
- package/templates/nextblock-template/app/cms/layout.tsx +10 -0
- package/templates/nextblock-template/app/cms/media/UploadFolderContext.tsx +22 -0
- package/templates/nextblock-template/app/cms/media/[id]/edit/page.tsx +80 -0
- package/templates/nextblock-template/app/cms/media/actions.ts +577 -0
- package/templates/nextblock-template/app/cms/media/components/DeleteMediaButtonClient.tsx +53 -0
- package/templates/nextblock-template/app/cms/media/components/FolderNavigator.tsx +273 -0
- package/templates/nextblock-template/app/cms/media/components/FolderTree.tsx +122 -0
- package/templates/nextblock-template/app/cms/media/components/MediaEditForm.tsx +157 -0
- package/templates/nextblock-template/app/cms/media/components/MediaGridClient.tsx +275 -0
- package/templates/nextblock-template/app/cms/media/components/MediaImage.tsx +70 -0
- package/templates/nextblock-template/app/cms/media/components/MediaPickerDialog.tsx +195 -0
- package/templates/nextblock-template/app/cms/media/components/MediaUploadForm.tsx +362 -0
- package/templates/nextblock-template/app/cms/media/page.tsx +120 -0
- package/templates/nextblock-template/app/cms/navigation/[id]/edit/page.tsx +101 -0
- package/templates/nextblock-template/app/cms/navigation/actions.ts +358 -0
- package/templates/nextblock-template/app/cms/navigation/components/DeleteNavItemButton.tsx +52 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationItemForm.tsx +248 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationLanguageSwitcher.tsx +132 -0
- package/templates/nextblock-template/app/cms/navigation/components/NavigationMenuDnd.tsx +701 -0
- package/templates/nextblock-template/app/cms/navigation/components/SortableNavItem.tsx +98 -0
- package/templates/nextblock-template/app/cms/navigation/new/page.tsx +26 -0
- package/templates/nextblock-template/app/cms/navigation/page.tsx +102 -0
- package/templates/nextblock-template/app/cms/navigation/utils.ts +51 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/EditPageClient.tsx +121 -0
- package/templates/nextblock-template/app/cms/pages/[id]/edit/page.tsx +79 -0
- package/templates/nextblock-template/app/cms/pages/actions.ts +241 -0
- package/templates/nextblock-template/app/cms/pages/components/DeletePageButtonClient.tsx +47 -0
- package/templates/nextblock-template/app/cms/pages/components/PageForm.tsx +253 -0
- package/templates/nextblock-template/app/cms/pages/new/page.tsx +52 -0
- package/templates/nextblock-template/app/cms/pages/page.tsx +232 -0
- package/templates/nextblock-template/app/cms/posts/[id]/edit/page.tsx +183 -0
- package/templates/nextblock-template/app/cms/posts/actions.ts +309 -0
- package/templates/nextblock-template/app/cms/posts/components/DeletePostButtonClient.tsx +55 -0
- package/templates/nextblock-template/app/cms/posts/components/PostForm.tsx +419 -0
- package/templates/nextblock-template/app/cms/posts/new/page.tsx +21 -0
- package/templates/nextblock-template/app/cms/posts/page.tsx +192 -0
- package/templates/nextblock-template/app/cms/revisions/JsonDiffView.tsx +86 -0
- package/templates/nextblock-template/app/cms/revisions/RevisionHistoryButton.tsx +201 -0
- package/templates/nextblock-template/app/cms/revisions/actions.ts +84 -0
- package/templates/nextblock-template/app/cms/revisions/service.ts +344 -0
- package/templates/nextblock-template/app/cms/revisions/utils.ts +127 -0
- package/templates/nextblock-template/app/cms/settings/copyright/actions.ts +68 -0
- package/templates/nextblock-template/app/cms/settings/copyright/components/CopyrightForm.tsx +78 -0
- package/templates/nextblock-template/app/cms/settings/copyright/page.tsx +32 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/actions.ts +117 -0
- package/templates/nextblock-template/app/cms/settings/extra-translations/page.tsx +216 -0
- package/templates/nextblock-template/app/cms/settings/languages/[id]/edit/page.tsx +77 -0
- package/templates/nextblock-template/app/cms/settings/languages/actions.ts +261 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/DeleteLanguageButton.tsx +76 -0
- package/templates/nextblock-template/app/cms/settings/languages/components/LanguageForm.tsx +167 -0
- package/templates/nextblock-template/app/cms/settings/languages/new/page.tsx +34 -0
- package/templates/nextblock-template/app/cms/settings/languages/page.tsx +156 -0
- package/templates/nextblock-template/app/cms/settings/logos/[id]/edit/page.tsx +19 -0
- package/templates/nextblock-template/app/cms/settings/logos/actions.ts +114 -0
- package/templates/nextblock-template/app/cms/settings/logos/components/LogoForm.tsx +177 -0
- package/templates/nextblock-template/app/cms/settings/logos/new/page.tsx +11 -0
- package/templates/nextblock-template/app/cms/settings/logos/page.tsx +118 -0
- package/templates/nextblock-template/app/cms/settings/logos/types.ts +8 -0
- package/templates/nextblock-template/app/cms/users/[id]/edit/page.tsx +91 -0
- package/templates/nextblock-template/app/cms/users/actions.ts +156 -0
- package/templates/nextblock-template/app/cms/users/components/DeleteUserButton.tsx +71 -0
- package/templates/nextblock-template/app/cms/users/components/UserForm.tsx +138 -0
- package/templates/nextblock-template/app/cms/users/page.tsx +183 -0
- package/templates/nextblock-template/app/favicon.ico +0 -0
- package/templates/nextblock-template/app/globals.css +401 -0
- package/templates/nextblock-template/app/layout.tsx +191 -0
- package/templates/nextblock-template/app/lib/sitemap-utils.ts +68 -0
- package/templates/nextblock-template/app/page.tsx +109 -0
- package/templates/nextblock-template/app/providers.tsx +43 -0
- package/templates/nextblock-template/app/robots.txt/route.ts +19 -0
- package/templates/nextblock-template/app/sitemap.xml/route.ts +63 -0
- package/templates/nextblock-template/app/unauthorized/page.tsx +27 -0
- package/templates/nextblock-template/backup/backup_2025-06-19.sql +8057 -0
- package/templates/nextblock-template/backup/backup_2025-06-20.sql +8159 -0
- package/templates/nextblock-template/backup/backup_2025-07-08.sql +8411 -0
- package/templates/nextblock-template/backup/backup_2025-07-09.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-07-10.sql +8442 -0
- package/templates/nextblock-template/backup/backup_2025-10-01.sql +8803 -0
- package/templates/nextblock-template/backup/backup_2025-10-02.sql +9749 -0
- package/templates/nextblock-template/components/BlockRenderer.tsx +119 -0
- package/templates/nextblock-template/components/FooterNavigation.tsx +33 -0
- package/templates/nextblock-template/components/Header.tsx +42 -0
- package/templates/nextblock-template/components/HtmlScriptExecutor.tsx +47 -0
- package/templates/nextblock-template/components/LanguageSwitcher.tsx +103 -0
- package/templates/nextblock-template/components/ResponsiveNav.tsx +372 -0
- package/templates/nextblock-template/components/blocks/PostCardSkeleton.tsx +17 -0
- package/templates/nextblock-template/components/blocks/PostsGridBlock.tsx +93 -0
- package/templates/nextblock-template/components/blocks/PostsGridClient.tsx +180 -0
- package/templates/nextblock-template/components/blocks/renderers/ButtonBlockRenderer.tsx +92 -0
- package/templates/nextblock-template/components/blocks/renderers/ClientTextBlockRenderer.tsx +69 -0
- package/templates/nextblock-template/components/blocks/renderers/FormBlockRenderer.tsx +98 -0
- package/templates/nextblock-template/components/blocks/renderers/HeadingBlockRenderer.tsx +41 -0
- package/templates/nextblock-template/components/blocks/renderers/HeroBlockRenderer.tsx +240 -0
- package/templates/nextblock-template/components/blocks/renderers/ImageBlockRenderer.tsx +79 -0
- package/templates/nextblock-template/components/blocks/renderers/PostsGridBlockRenderer.tsx +33 -0
- package/templates/nextblock-template/components/blocks/renderers/SectionBlockRenderer.tsx +189 -0
- package/templates/nextblock-template/components/blocks/renderers/TextBlockRenderer.tsx +31 -0
- package/templates/nextblock-template/components/blocks/renderers/VideoEmbedBlockRenderer.tsx +59 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/AlertWidgetRenderer.tsx +51 -0
- package/templates/nextblock-template/components/blocks/renderers/inline/CtaWidgetRenderer.tsx +40 -0
- package/templates/nextblock-template/components/blocks/types.ts +8 -0
- package/templates/nextblock-template/components/env-var-warning.tsx +33 -0
- package/templates/nextblock-template/components/form-message.tsx +26 -0
- package/templates/nextblock-template/components/header-auth.tsx +71 -0
- package/templates/nextblock-template/components/submit-button.tsx +23 -0
- package/templates/nextblock-template/components/theme-switcher.tsx +78 -0
- package/templates/nextblock-template/context/AuthContext.tsx +138 -0
- package/templates/nextblock-template/context/CurrentContentContext.tsx +42 -0
- package/templates/nextblock-template/context/LanguageContext.tsx +206 -0
- package/templates/nextblock-template/docs/cms-application-overview.md +56 -0
- package/templates/nextblock-template/docs/cms-architecture-overview.md +73 -0
- package/templates/nextblock-template/docs/files-structure.md +426 -0
- package/templates/nextblock-template/docs/tiptap-bundle-optimization-summary.md +174 -0
- package/templates/nextblock-template/eslint.config.mjs +28 -0
- package/templates/nextblock-template/index.d.ts +5 -0
- package/templates/nextblock-template/lib/blocks/README.md +670 -0
- package/templates/nextblock-template/lib/blocks/blockRegistry.ts +1001 -0
- package/templates/nextblock-template/lib/ui/ColorPicker.ts +1 -0
- package/templates/nextblock-template/lib/ui/ConfirmationDialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/CustomSelectWithInput.ts +1 -0
- package/templates/nextblock-template/lib/ui/Skeleton.ts +1 -0
- package/templates/nextblock-template/lib/ui/avatar.ts +1 -0
- package/templates/nextblock-template/lib/ui/badge.ts +1 -0
- package/templates/nextblock-template/lib/ui/button.ts +1 -0
- package/templates/nextblock-template/lib/ui/card.ts +1 -0
- package/templates/nextblock-template/lib/ui/checkbox.ts +1 -0
- package/templates/nextblock-template/lib/ui/dialog.ts +1 -0
- package/templates/nextblock-template/lib/ui/dropdown-menu.ts +1 -0
- package/templates/nextblock-template/lib/ui/input.ts +1 -0
- package/templates/nextblock-template/lib/ui/label.ts +1 -0
- package/templates/nextblock-template/lib/ui/popover.ts +1 -0
- package/templates/nextblock-template/lib/ui/progress.ts +1 -0
- package/templates/nextblock-template/lib/ui/select.ts +1 -0
- package/templates/nextblock-template/lib/ui/separator.ts +1 -0
- package/templates/nextblock-template/lib/ui/table.ts +1 -0
- package/templates/nextblock-template/lib/ui/textarea.ts +1 -0
- package/templates/nextblock-template/lib/ui/tooltip.ts +1 -0
- package/templates/nextblock-template/lib/ui/ui.ts +1 -0
- package/templates/nextblock-template/middleware.ts +206 -0
- package/templates/nextblock-template/next-env.d.ts +6 -0
- package/templates/nextblock-template/next.config.js +99 -0
- package/templates/nextblock-template/package.json +52 -0
- package/templates/nextblock-template/postcss.config.js +6 -0
- package/templates/nextblock-template/project.json +7 -0
- package/templates/nextblock-template/public/.gitkeep +0 -0
- package/templates/nextblock-template/scripts/backfill-image-meta.ts +149 -0
- package/templates/nextblock-template/scripts/backup.js +53 -0
- package/templates/nextblock-template/scripts/test-bundle-optimization.js +114 -0
- package/templates/nextblock-template/tailwind.config.ts +19 -0
- package/templates/nextblock-template/tsconfig.json +62 -0
|
@@ -0,0 +1,670 @@
|
|
|
1
|
+
# Block Registry System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The Block Registry System is a centralized, registry-based architecture for managing content blocks in the CMS. This system replaces the previous switch-statement approach with a more scalable, maintainable solution that uses dynamic imports and a single source of truth for block definitions.
|
|
6
|
+
|
|
7
|
+
### Key Benefits
|
|
8
|
+
|
|
9
|
+
- **Centralized Configuration**: All block types are defined in one place ([`blockRegistry.ts`](./blockRegistry.ts))
|
|
10
|
+
- **Dynamic Loading**: Components are loaded on-demand using Next.js dynamic imports
|
|
11
|
+
- **Type Safety**: Full TypeScript support with proper interface declarations
|
|
12
|
+
- **Scalability**: Easy to add new block types without modifying core systems
|
|
13
|
+
- **Maintainability**: Clear separation of concerns and consistent patterns
|
|
14
|
+
- **Performance**: Components are only loaded when needed
|
|
15
|
+
- **Better IDE Support**: Proper TypeScript interfaces with IntelliSense and compile-time checking
|
|
16
|
+
|
|
17
|
+
## Quick Start: Adding a New Block Type
|
|
18
|
+
|
|
19
|
+
Follow these steps to add a new block type (e.g., "Video Embed"). With the enhanced registry system, you only need to update the registry and create component files!
|
|
20
|
+
|
|
21
|
+
### Step 1: Define the Block Type with Proper TypeScript Interface
|
|
22
|
+
|
|
23
|
+
Add your new block type to the registry in [`lib/blocks/blockRegistry.ts`](./blockRegistry.ts):
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// 1. Add the TypeScript interface at the top of the file
|
|
27
|
+
/**
|
|
28
|
+
* Content interface for video embed blocks
|
|
29
|
+
* Embeds videos from popular platforms with customizable playback options
|
|
30
|
+
*/
|
|
31
|
+
export interface VideoEmbedBlockContent {
|
|
32
|
+
/** The video URL (YouTube, Vimeo, etc.) */
|
|
33
|
+
url: string;
|
|
34
|
+
/** Optional title for the video */
|
|
35
|
+
title?: string;
|
|
36
|
+
/** Whether the video should autoplay */
|
|
37
|
+
autoplay?: boolean;
|
|
38
|
+
/** Whether to show video controls */
|
|
39
|
+
controls?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Add to availableBlockTypes array
|
|
43
|
+
export const availableBlockTypes = [
|
|
44
|
+
"text",
|
|
45
|
+
"heading",
|
|
46
|
+
"image",
|
|
47
|
+
"button",
|
|
48
|
+
"posts_grid",
|
|
49
|
+
"video_embed" // Add your new type
|
|
50
|
+
] as const;
|
|
51
|
+
|
|
52
|
+
// 3. Add to blockRegistry object with complete definition
|
|
53
|
+
export const blockRegistry: Record<BlockType, BlockDefinition> = {
|
|
54
|
+
// ... existing blocks ...
|
|
55
|
+
|
|
56
|
+
video_embed: {
|
|
57
|
+
type: "video_embed",
|
|
58
|
+
label: "Video Embed",
|
|
59
|
+
initialContent: {
|
|
60
|
+
url: "",
|
|
61
|
+
title: "",
|
|
62
|
+
autoplay: false,
|
|
63
|
+
controls: true
|
|
64
|
+
} as VideoEmbedBlockContent,
|
|
65
|
+
editorComponentFilename: "VideoEmbedBlockEditor.tsx",
|
|
66
|
+
rendererComponentFilename: "VideoEmbedBlockRenderer.tsx",
|
|
67
|
+
contentSchema: {
|
|
68
|
+
url: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
required: true,
|
|
71
|
+
description: 'The video URL (YouTube, Vimeo, etc.)',
|
|
72
|
+
default: '',
|
|
73
|
+
},
|
|
74
|
+
title: {
|
|
75
|
+
type: 'string',
|
|
76
|
+
required: false,
|
|
77
|
+
description: 'Optional title for the video',
|
|
78
|
+
default: '',
|
|
79
|
+
},
|
|
80
|
+
autoplay: {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
required: false,
|
|
83
|
+
description: 'Whether the video should autoplay',
|
|
84
|
+
default: false,
|
|
85
|
+
},
|
|
86
|
+
controls: {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
required: false,
|
|
89
|
+
description: 'Whether to show video controls',
|
|
90
|
+
default: true,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
documentation: {
|
|
94
|
+
description: 'Embeds videos from popular platforms with customizable playback options',
|
|
95
|
+
examples: [
|
|
96
|
+
'{ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", title: "Rick Roll", controls: true }',
|
|
97
|
+
'{ url: "https://vimeo.com/123456789", autoplay: false, controls: true }',
|
|
98
|
+
],
|
|
99
|
+
useCases: [
|
|
100
|
+
'Tutorial and educational videos',
|
|
101
|
+
'Product demonstrations',
|
|
102
|
+
'Marketing and promotional content',
|
|
103
|
+
],
|
|
104
|
+
notes: [
|
|
105
|
+
'Supports YouTube, Vimeo, and other major video platforms',
|
|
106
|
+
'Autoplay may be restricted by browser policies',
|
|
107
|
+
'Videos are responsive and adapt to container width',
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Step 2: Create the Editor Component
|
|
115
|
+
|
|
116
|
+
Create [`app/cms/blocks/editors/VideoEmbedBlockEditor.tsx`](../../app/cms/blocks/editors/VideoEmbedBlockEditor.tsx):
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
"use client";
|
|
120
|
+
|
|
121
|
+
import React from 'react';
|
|
122
|
+
import { Label } from "@nextblock-cms/ui";
|
|
123
|
+
import { Input } from "@nextblock-cms/ui";
|
|
124
|
+
import { Checkbox } from "@nextblock-cms/ui";
|
|
125
|
+
import { generateDefaultContent, VideoEmbedBlockContent } from "@/lib/blocks/blockRegistry";
|
|
126
|
+
|
|
127
|
+
interface VideoEmbedBlockEditorProps {
|
|
128
|
+
content: Partial<VideoEmbedBlockContent>;
|
|
129
|
+
onChange: (newContent: VideoEmbedBlockContent) => void;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export default function VideoEmbedBlockEditor({ content, onChange }: VideoEmbedBlockEditorProps) {
|
|
133
|
+
// Get default content from registry
|
|
134
|
+
const defaultContent = generateDefaultContent("video_embed") as VideoEmbedBlockContent;
|
|
135
|
+
|
|
136
|
+
const handleChange = (field: keyof VideoEmbedBlockContent, value: any) => {
|
|
137
|
+
onChange({
|
|
138
|
+
...defaultContent,
|
|
139
|
+
...content,
|
|
140
|
+
[field]: value,
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<div className="space-y-4 p-3 border-t mt-2">
|
|
146
|
+
<div>
|
|
147
|
+
<Label htmlFor="video-url">Video URL</Label>
|
|
148
|
+
<Input
|
|
149
|
+
id="video-url"
|
|
150
|
+
type="url"
|
|
151
|
+
value={content.url || ""}
|
|
152
|
+
onChange={(e) => handleChange("url", e.target.value)}
|
|
153
|
+
placeholder="https://www.youtube.com/watch?v=..."
|
|
154
|
+
/>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
<div>
|
|
158
|
+
<Label htmlFor="video-title">Title (Optional)</Label>
|
|
159
|
+
<Input
|
|
160
|
+
id="video-title"
|
|
161
|
+
value={content.title || ""}
|
|
162
|
+
onChange={(e) => handleChange("title", e.target.value)}
|
|
163
|
+
placeholder="Video title"
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<div className="flex items-center space-x-2">
|
|
168
|
+
<Checkbox
|
|
169
|
+
id="autoplay"
|
|
170
|
+
checked={content.autoplay || false}
|
|
171
|
+
onCheckedChange={(checked) => handleChange("autoplay", checked)}
|
|
172
|
+
/>
|
|
173
|
+
<Label htmlFor="autoplay">Autoplay</Label>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<div className="flex items-center space-x-2">
|
|
177
|
+
<Checkbox
|
|
178
|
+
id="controls"
|
|
179
|
+
checked={content.controls !== false}
|
|
180
|
+
onCheckedChange={(checked) => handleChange("controls", checked)}
|
|
181
|
+
/>
|
|
182
|
+
<Label htmlFor="controls">Show Controls</Label>
|
|
183
|
+
</div>
|
|
184
|
+
</div>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Step 3: Create the Renderer Component
|
|
190
|
+
|
|
191
|
+
Create [`components/blocks/renderers/VideoEmbedBlockRenderer.tsx`](../../components/blocks/renderers/VideoEmbedBlockRenderer.tsx):
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import React from "react";
|
|
195
|
+
import { validateBlockContent, VideoEmbedBlockContent } from "@/lib/blocks/blockRegistry";
|
|
196
|
+
|
|
197
|
+
interface VideoEmbedBlockRendererProps {
|
|
198
|
+
content: VideoEmbedBlockContent;
|
|
199
|
+
languageId: number;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const VideoEmbedBlockRenderer: React.FC<VideoEmbedBlockRendererProps> = ({
|
|
203
|
+
content,
|
|
204
|
+
languageId,
|
|
205
|
+
}) => {
|
|
206
|
+
// Optional: Validate content against registry schema
|
|
207
|
+
const validation = validateBlockContent("video_embed", content);
|
|
208
|
+
if (!validation.isValid) {
|
|
209
|
+
console.warn("Invalid video embed content:", validation.errors);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!content.url) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Convert YouTube URLs to embed format
|
|
217
|
+
const getEmbedUrl = (url: string) => {
|
|
218
|
+
const youtubeRegex = /(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\n?#]+)/;
|
|
219
|
+
const match = url.match(youtubeRegex);
|
|
220
|
+
|
|
221
|
+
if (match) {
|
|
222
|
+
const videoId = match[1];
|
|
223
|
+
const params = new URLSearchParams();
|
|
224
|
+
if (content.autoplay) params.set('autoplay', '1');
|
|
225
|
+
if (!content.controls) params.set('controls', '0');
|
|
226
|
+
|
|
227
|
+
return `https://www.youtube.com/embed/${videoId}?${params.toString()}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return url; // Return original URL if not YouTube
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div className="my-4">
|
|
235
|
+
{content.title && (
|
|
236
|
+
<h3 className="text-lg font-semibold mb-2">{content.title}</h3>
|
|
237
|
+
)}
|
|
238
|
+
<div className="relative aspect-video">
|
|
239
|
+
<iframe
|
|
240
|
+
src={getEmbedUrl(content.url)}
|
|
241
|
+
title={content.title || "Video"}
|
|
242
|
+
className="w-full h-full rounded-lg"
|
|
243
|
+
allowFullScreen
|
|
244
|
+
loading="lazy"
|
|
245
|
+
/>
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export default VideoEmbedBlockRenderer;
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Step 4: Test Your New Block
|
|
255
|
+
|
|
256
|
+
1. Restart your development server to ensure all changes are loaded
|
|
257
|
+
2. Navigate to a page or post editor in the CMS
|
|
258
|
+
3. Click "Add Block" and select "Video Embed"
|
|
259
|
+
4. Configure the video settings and save
|
|
260
|
+
5. View the page/post to see your rendered video block
|
|
261
|
+
|
|
262
|
+
**That's it!** The registry system handles all the TypeScript interface definitions and validation automatically.
|
|
263
|
+
|
|
264
|
+
## Architecture
|
|
265
|
+
|
|
266
|
+
### Enhanced Registry System
|
|
267
|
+
|
|
268
|
+
The new enhanced registry system provides:
|
|
269
|
+
|
|
270
|
+
- **Proper TypeScript Interfaces**: Each block has a real TypeScript interface declared at the top of the file
|
|
271
|
+
- **Better IDE Support**: Full IntelliSense, syntax highlighting, and compile-time checking
|
|
272
|
+
- **Runtime Validation**: Content can be validated against schema definitions
|
|
273
|
+
- **Auto-Generated Documentation**: Comprehensive documentation is built into each block definition
|
|
274
|
+
- **Utility Functions**: Helper functions for type generation, validation, and content management
|
|
275
|
+
- **Single Source of Truth**: All block information is centralized in the registry
|
|
276
|
+
|
|
277
|
+
### File Structure
|
|
278
|
+
|
|
279
|
+
```
|
|
280
|
+
lib/blocks/
|
|
281
|
+
├── blockRegistry.ts # Enhanced central registry with proper TypeScript interfaces
|
|
282
|
+
└── README.md # This documentation
|
|
283
|
+
|
|
284
|
+
app/cms/blocks/editors/ # Editor components for CMS
|
|
285
|
+
├── TextBlockEditor.tsx
|
|
286
|
+
├── HeadingBlockEditor.tsx
|
|
287
|
+
├── ImageBlockEditor.tsx
|
|
288
|
+
├── ButtonBlockEditor.tsx
|
|
289
|
+
├── PostsGridBlockEditor.tsx
|
|
290
|
+
└── [YourNewBlock]Editor.tsx
|
|
291
|
+
|
|
292
|
+
components/blocks/renderers/ # Renderer components for frontend
|
|
293
|
+
├── TextBlockRenderer.tsx
|
|
294
|
+
├── HeadingBlockRenderer.tsx
|
|
295
|
+
├── ImageBlockRenderer.tsx
|
|
296
|
+
├── ButtonBlockRenderer.tsx
|
|
297
|
+
├── PostsGridBlockRenderer.tsx
|
|
298
|
+
└── [YourNewBlock]Renderer.tsx
|
|
299
|
+
|
|
300
|
+
utils/supabase/types.ts # Core database types (block content types no longer needed here)
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Core Components
|
|
304
|
+
|
|
305
|
+
#### 1. Enhanced Block Registry ([`blockRegistry.ts`](./blockRegistry.ts))
|
|
306
|
+
|
|
307
|
+
The central registry now contains:
|
|
308
|
+
- **Proper TypeScript Interfaces**: Real interface declarations at the top of the file
|
|
309
|
+
- **Block Type Definitions**: Available block types as a const array
|
|
310
|
+
- **Complete Block Definitions**: Configuration and schemas for each block type
|
|
311
|
+
- **Content Schemas**: Structured property definitions with validation rules
|
|
312
|
+
- **Documentation**: Built-in examples, use cases, and implementation notes
|
|
313
|
+
- **Utility Functions**: Comprehensive helper functions for validation and content management
|
|
314
|
+
|
|
315
|
+
#### 2. Dynamic Block Renderer ([`components/BlockRenderer.tsx`](../../components/BlockRenderer.tsx))
|
|
316
|
+
|
|
317
|
+
Handles dynamic loading and rendering of blocks:
|
|
318
|
+
- Uses Next.js `dynamic()` for code splitting
|
|
319
|
+
- Provides loading states and error handling
|
|
320
|
+
- Supports SSR (Server-Side Rendering)
|
|
321
|
+
|
|
322
|
+
#### 3. Block Actions ([`app/cms/blocks/actions.ts`](../../app/cms/blocks/actions.ts))
|
|
323
|
+
|
|
324
|
+
Server actions for block CRUD operations:
|
|
325
|
+
- Uses registry for validation and initial content
|
|
326
|
+
- Handles authorization and database operations
|
|
327
|
+
- Provides revalidation for updated content
|
|
328
|
+
|
|
329
|
+
## API Reference
|
|
330
|
+
|
|
331
|
+
### TypeScript Interfaces
|
|
332
|
+
|
|
333
|
+
All block content interfaces are now properly declared at the top of [`blockRegistry.ts`](./blockRegistry.ts):
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
export interface TextBlockContent {
|
|
337
|
+
html_content: string;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface HeadingBlockContent {
|
|
341
|
+
level: 1 | 2 | 3 | 4 | 5 | 6;
|
|
342
|
+
text_content: string;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface ImageBlockContent {
|
|
346
|
+
media_id: string | null;
|
|
347
|
+
object_key?: string | null;
|
|
348
|
+
alt_text?: string;
|
|
349
|
+
caption?: string;
|
|
350
|
+
width?: number | null;
|
|
351
|
+
height?: number | null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export interface ButtonBlockContent {
|
|
355
|
+
text: string;
|
|
356
|
+
url: string;
|
|
357
|
+
variant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
358
|
+
size?: 'default' | 'sm' | 'lg';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export interface PostsGridBlockContent {
|
|
362
|
+
postsPerPage: number;
|
|
363
|
+
columns: number;
|
|
364
|
+
showPagination: boolean;
|
|
365
|
+
title?: string;
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Enhanced BlockDefinition Interface
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
interface BlockDefinition<T = any> {
|
|
373
|
+
type: BlockType; // Unique identifier
|
|
374
|
+
label: string; // Display name in CMS
|
|
375
|
+
initialContent: T; // Default content structure (properly typed)
|
|
376
|
+
editorComponentFilename: string; // Editor component filename
|
|
377
|
+
rendererComponentFilename: string; // Renderer component filename
|
|
378
|
+
previewComponentFilename?: string; // Optional preview component
|
|
379
|
+
contentSchema: Record<string, ContentPropertyDefinition>; // Structured schema
|
|
380
|
+
documentation?: { // Optional documentation
|
|
381
|
+
description?: string;
|
|
382
|
+
examples?: string[];
|
|
383
|
+
useCases?: string[];
|
|
384
|
+
notes?: string[];
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### Registry Functions
|
|
390
|
+
|
|
391
|
+
#### Core Registry Functions
|
|
392
|
+
|
|
393
|
+
#### `getBlockDefinition(blockType: BlockType)`
|
|
394
|
+
Returns the complete block definition for a given type.
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
const definition = getBlockDefinition("text");
|
|
398
|
+
// Returns: { type: "text", label: "HTML Block", initialContent: {...}, ... }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
#### `getInitialContent(blockType: BlockType)`
|
|
402
|
+
Returns the initial content structure for a new block.
|
|
403
|
+
|
|
404
|
+
```typescript
|
|
405
|
+
const content = getInitialContent("heading");
|
|
406
|
+
// Returns: { level: 1, text_content: "New Heading" }
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### `getBlockLabel(blockType: BlockType)`
|
|
410
|
+
Returns the user-friendly label for a block type.
|
|
411
|
+
|
|
412
|
+
```typescript
|
|
413
|
+
const label = getBlockLabel("image");
|
|
414
|
+
// Returns: "Image"
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### `isValidBlockType(blockType: string)`
|
|
418
|
+
Type guard to check if a string is a valid block type.
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
if (isValidBlockType(userInput)) {
|
|
422
|
+
// userInput is now typed as BlockType
|
|
423
|
+
const definition = getBlockDefinition(userInput);
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Enhanced Registry Functions
|
|
428
|
+
|
|
429
|
+
#### `getContentSchema(blockType: BlockType)`
|
|
430
|
+
Returns the structured content schema for validation and documentation.
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
const schema = getContentSchema("heading");
|
|
434
|
+
// Returns: { level: { type: 'union', required: true, ... }, text_content: { ... } }
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### `getBlockDocumentation(blockType: BlockType)`
|
|
438
|
+
Returns documentation including description, examples, and use cases.
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
const docs = getBlockDocumentation("image");
|
|
442
|
+
// Returns: { description: "...", examples: [...], useCases: [...], notes: [...] }
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### Validation and Utility Functions
|
|
446
|
+
|
|
447
|
+
#### `validateBlockContent(blockType: BlockType, content: Record<string, any>)`
|
|
448
|
+
Validates block content against its schema definition.
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
const validation = validateBlockContent("button", { text: "Click me" });
|
|
452
|
+
// Returns: { isValid: false, errors: ["Required property 'url' is missing"], warnings: [] }
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### `getPropertyDefinition(blockType: BlockType, propertyName: string)`
|
|
456
|
+
Returns the definition for a specific property of a block type.
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
const propDef = getPropertyDefinition("heading", "level");
|
|
460
|
+
// Returns: { type: 'union', required: true, description: "...", constraints: { ... } }
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
#### `getPropertyNames(blockType: BlockType)`
|
|
464
|
+
Returns all property names for a block type.
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
const props = getPropertyNames("image");
|
|
468
|
+
// Returns: ["media_id", "object_key", "alt_text", "caption", "width", "height"]
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
#### `getRequiredProperties(blockType: BlockType)`
|
|
472
|
+
Returns only the required property names for a block type.
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
const required = getRequiredProperties("text");
|
|
476
|
+
// Returns: ["html_content"]
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### `generateDefaultContent(blockType: BlockType)`
|
|
480
|
+
Generates complete default content including all properties with defaults.
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
const defaults = generateDefaultContent("button");
|
|
484
|
+
// Returns: { text: "Click Me", url: "#", variant: "default", size: "default" }
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Component Interfaces
|
|
488
|
+
|
|
489
|
+
#### Editor Component Props
|
|
490
|
+
```typescript
|
|
491
|
+
interface BlockEditorProps<T> {
|
|
492
|
+
content: Partial<T>;
|
|
493
|
+
onChange: (newContent: T) => void;
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### Renderer Component Props
|
|
498
|
+
```typescript
|
|
499
|
+
interface BlockRendererProps<T> {
|
|
500
|
+
content: T;
|
|
501
|
+
languageId: number;
|
|
502
|
+
block?: Block; // Optional, for blocks that need full block data
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Migration Guide
|
|
507
|
+
|
|
508
|
+
### What Changed
|
|
509
|
+
|
|
510
|
+
The system has evolved to use proper TypeScript interfaces:
|
|
511
|
+
|
|
512
|
+
**Before (String-based interfaces):**
|
|
513
|
+
```typescript
|
|
514
|
+
contentInterface: `interface TextBlockContent {
|
|
515
|
+
/** Raw HTML content for the text block */
|
|
516
|
+
html_content: string;
|
|
517
|
+
}`,
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**After (Proper TypeScript interfaces):**
|
|
521
|
+
```typescript
|
|
522
|
+
// At the top of blockRegistry.ts
|
|
523
|
+
export interface TextBlockContent {
|
|
524
|
+
/** Raw HTML content for the text block */
|
|
525
|
+
html_content: string;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// In the registry
|
|
529
|
+
text: {
|
|
530
|
+
type: "text",
|
|
531
|
+
label: "HTML Block",
|
|
532
|
+
initialContent: { html_content: "<p>New text block...</p>" } as TextBlockContent,
|
|
533
|
+
// ... rest of definition (no contentInterface property)
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### Benefits of the New System
|
|
538
|
+
|
|
539
|
+
1. **Better IDE Support**: Full IntelliSense, syntax highlighting, and error checking
|
|
540
|
+
2. **Compile-time Type Safety**: TypeScript can catch type errors at build time
|
|
541
|
+
3. **Cleaner Code**: No more string-based interface definitions
|
|
542
|
+
4. **Standard TypeScript Patterns**: Uses conventional TypeScript interface declarations
|
|
543
|
+
5. **Better Refactoring Support**: IDEs can properly rename and refactor interface properties
|
|
544
|
+
6. **Import/Export Support**: Interfaces can be imported and used across files
|
|
545
|
+
|
|
546
|
+
### Key Improvements
|
|
547
|
+
|
|
548
|
+
- **Proper Interface Declarations**: TypeScript interfaces are declared at the top of the file
|
|
549
|
+
- **Type-safe Initial Content**: Initial content is properly typed with `as InterfaceName`
|
|
550
|
+
- **Removed contentInterface Property**: No longer needed since we have real interfaces
|
|
551
|
+
- **Enhanced Type Union**: `AllBlockContent` provides a discriminated union of all block types
|
|
552
|
+
- **Better Component Typing**: Components can import and use the actual interfaces
|
|
553
|
+
|
|
554
|
+
### Breaking Changes
|
|
555
|
+
|
|
556
|
+
- The `contentInterface` property has been removed from `BlockDefinition`
|
|
557
|
+
- The `getContentInterface()` function has been removed
|
|
558
|
+
- The `getAllContentInterfaces()` and `generateSpecificBlockContentUnion()` functions have been removed
|
|
559
|
+
- Components should now import interfaces directly from the registry
|
|
560
|
+
|
|
561
|
+
### Migration Benefits
|
|
562
|
+
|
|
563
|
+
**Before (Required changes to add a new block):**
|
|
564
|
+
1. Update `availableBlockTypes` in `blockRegistry.ts`
|
|
565
|
+
2. Add block definition with string-based interface to `blockRegistry`
|
|
566
|
+
3. Create editor component with manually defined types
|
|
567
|
+
4. Create renderer component with manually defined types
|
|
568
|
+
|
|
569
|
+
**After (Required changes to add a new block):**
|
|
570
|
+
1. Add proper TypeScript interface at the top of `blockRegistry.ts`
|
|
571
|
+
2. Update `availableBlockTypes` in `blockRegistry.ts`
|
|
572
|
+
3. Add block definition with typed initial content to `blockRegistry`
|
|
573
|
+
4. Create editor component importing the interface
|
|
574
|
+
5. Create renderer component importing the interface
|
|
575
|
+
|
|
576
|
+
**Benefits:**
|
|
577
|
+
- Better type safety and IDE support
|
|
578
|
+
- Standard TypeScript patterns
|
|
579
|
+
- Easier refactoring and maintenance
|
|
580
|
+
- Compile-time error checking
|
|
581
|
+
|
|
582
|
+
## TypeScript Best Practices
|
|
583
|
+
|
|
584
|
+
### 1. Import Interfaces from Registry
|
|
585
|
+
```typescript
|
|
586
|
+
// Always import interfaces from the registry
|
|
587
|
+
import { VideoEmbedBlockContent } from "@/lib/blocks/blockRegistry";
|
|
588
|
+
|
|
589
|
+
// Don't redefine interfaces in components
|
|
590
|
+
type VideoEmbedBlockContent = { // ❌ Don't do this
|
|
591
|
+
url: string;
|
|
592
|
+
// ...
|
|
593
|
+
};
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### 2. Use Type Assertions for Generated Content
|
|
597
|
+
```typescript
|
|
598
|
+
// Use type assertions when getting generated content
|
|
599
|
+
const defaults = generateDefaultContent("video_embed") as VideoEmbedBlockContent;
|
|
600
|
+
const initialContent = getInitialContent("button") as ButtonBlockContent;
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### 3. Leverage Runtime Validation
|
|
604
|
+
```typescript
|
|
605
|
+
// Validate content in development
|
|
606
|
+
if (process.env.NODE_ENV === 'development') {
|
|
607
|
+
const validation = validateBlockContent(blockType, content);
|
|
608
|
+
if (!validation.isValid) {
|
|
609
|
+
console.warn(`Invalid ${blockType} content:`, validation.errors);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### 4. Use Property Definitions for Dynamic UIs
|
|
615
|
+
```typescript
|
|
616
|
+
// Build forms dynamically from registry schema
|
|
617
|
+
const schema = getContentSchema(blockType);
|
|
618
|
+
const formFields = Object.entries(schema).map(([name, def]) => ({
|
|
619
|
+
name,
|
|
620
|
+
type: def.type,
|
|
621
|
+
required: def.required,
|
|
622
|
+
description: def.description,
|
|
623
|
+
default: def.default,
|
|
624
|
+
}));
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
## Best Practices
|
|
628
|
+
|
|
629
|
+
### 1. Naming Conventions
|
|
630
|
+
- Use PascalCase for component filenames: `VideoEmbedBlockEditor.tsx`
|
|
631
|
+
- Use snake_case for block types: `video_embed`
|
|
632
|
+
- Use descriptive labels: "Video Embed" instead of "Video"
|
|
633
|
+
- Use PascalCase for interface names: `VideoEmbedBlockContent`
|
|
634
|
+
|
|
635
|
+
### 2. Interface Design
|
|
636
|
+
- Keep interfaces focused and minimal
|
|
637
|
+
- Use optional properties appropriately
|
|
638
|
+
- Include JSDoc comments for better documentation
|
|
639
|
+
- Use union types for constrained values
|
|
640
|
+
|
|
641
|
+
### 3. Content Structure
|
|
642
|
+
- Keep initial content simple and minimal
|
|
643
|
+
- Use sensible defaults for optional properties
|
|
644
|
+
- Ensure content structure matches TypeScript interfaces
|
|
645
|
+
- Type initial content with `as InterfaceName`
|
|
646
|
+
|
|
647
|
+
### 4. Component Design
|
|
648
|
+
- Import interfaces from the registry
|
|
649
|
+
- Keep editor components focused on editing functionality
|
|
650
|
+
- Keep renderer components focused on display
|
|
651
|
+
- Use consistent prop interfaces across similar components
|
|
652
|
+
|
|
653
|
+
### 5. Error Handling
|
|
654
|
+
- Provide fallbacks for missing or invalid content
|
|
655
|
+
- Show helpful error messages in development
|
|
656
|
+
- Gracefully handle dynamic import failures
|
|
657
|
+
- Use runtime validation in development mode
|
|
658
|
+
|
|
659
|
+
## Summary
|
|
660
|
+
|
|
661
|
+
The enhanced block registry system with proper TypeScript interfaces transforms the development experience by:
|
|
662
|
+
|
|
663
|
+
- **Proper Type Safety**: Real TypeScript interfaces with compile-time checking
|
|
664
|
+
- **Better IDE Support**: Full IntelliSense, syntax highlighting, and refactoring support
|
|
665
|
+
- **Cleaner Code**: Standard TypeScript patterns instead of string-based interfaces
|
|
666
|
+
- **Centralized Everything**: All block information in one place
|
|
667
|
+
- **Runtime Validation**: Content validation against schema definitions
|
|
668
|
+
- **Developer Experience**: Clear patterns and comprehensive tooling
|
|
669
|
+
|
|
670
|
+
This system achieves the "one registry file + component folder" ideal while providing superior TypeScript support and maintaining all existing functionality for validation, documentation, and tooling.
|