@jhits/plugin-newsletter 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -9
- package/src/api/handler.ts +0 -693
- package/src/api/router.ts +0 -111
- package/src/index.server.ts +0 -12
- package/src/index.tsx +0 -313
- package/src/index.tsx.patch +0 -98
- package/src/init.tsx +0 -72
- package/src/lib/blocks/BlockRenderer.tsx +0 -125
- package/src/lib/email/EmailRenderer.tsx +0 -425
- package/src/lib/email/index.ts +0 -6
- package/src/lib/mappers/apiMapper.ts +0 -57
- package/src/lib/utils/blockHelpers.ts +0 -71
- package/src/lib/utils/slugify.ts +0 -43
- package/src/registry/BlockRegistry.ts +0 -53
- package/src/registry/index.ts +0 -5
- package/src/state/EditorContext.tsx +0 -279
- package/src/state/index.ts +0 -10
- package/src/state/reducer.ts +0 -561
- package/src/state/types.ts +0 -154
- package/src/types/block.ts +0 -275
- package/src/types/newsletter.ts +0 -151
- package/src/types/registry.ts +0 -14
- package/src/views/CanvasEditor/BlockWrapper.tsx +0 -143
- package/src/views/CanvasEditor/CanvasEditorView.tsx +0 -249
- package/src/views/CanvasEditor/EditorBody.tsx +0 -95
- package/src/views/CanvasEditor/EditorHeader.tsx +0 -139
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +0 -83
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +0 -674
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +0 -120
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +0 -156
- package/src/views/CanvasEditor/components/ErrorBanner.tsx +0 -31
- package/src/views/CanvasEditor/components/LibraryItem.tsx +0 -71
- package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +0 -196
- package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +0 -131
- package/src/views/CanvasEditor/components/index.ts +0 -16
- package/src/views/CanvasEditor/hooks/index.ts +0 -7
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +0 -136
- package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +0 -34
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +0 -54
- package/src/views/CanvasEditor/hooks/useSlashCommand.ts +0 -106
- package/src/views/CanvasEditor/index.ts +0 -12
- package/src/views/NewsletterEditor.tsx +0 -38
- package/src/views/NewsletterManager.tsx +0 -240
- package/src/views/SettingsView.tsx +0 -216
- package/src/views/SubscribersView.tsx +0 -269
package/src/types/block.ts
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core Block Types for Newsletter Plugin
|
|
3
|
-
* Foundation for the Block-Based Architecture
|
|
4
|
-
* Multi-Tenant Plugin Architecture - Blocks are provided by client applications
|
|
5
|
-
*
|
|
6
|
-
* This is a headless implementation - no styling dependencies
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import React from 'react';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Base Block Interface
|
|
13
|
-
* Every block in the system extends this structure
|
|
14
|
-
*/
|
|
15
|
-
export interface Block {
|
|
16
|
-
/** Unique identifier for the block */
|
|
17
|
-
id: string;
|
|
18
|
-
|
|
19
|
-
/** Block type identifier (e.g., 'heading', 'paragraph', 'image') */
|
|
20
|
-
type: string;
|
|
21
|
-
|
|
22
|
-
/** Block-specific data (varies by type) */
|
|
23
|
-
data: Record<string, unknown>;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Child blocks (for container blocks like Section, Columns)
|
|
27
|
-
* Can be an array of Block IDs (for flat storage) or Block objects (for nested structure)
|
|
28
|
-
*/
|
|
29
|
-
children?: string[] | Block[];
|
|
30
|
-
|
|
31
|
-
/** Optional metadata for editor state, styling, etc. */
|
|
32
|
-
meta?: {
|
|
33
|
-
/** Visual order in the editor */
|
|
34
|
-
order?: number;
|
|
35
|
-
/** Whether block is collapsed in editor */
|
|
36
|
-
collapsed?: boolean;
|
|
37
|
-
/** Custom styling overrides */
|
|
38
|
-
style?: Record<string, string>;
|
|
39
|
-
/** Editor-specific flags */
|
|
40
|
-
flags?: string[];
|
|
41
|
-
/** Column index (for column container blocks) */
|
|
42
|
-
columnIndex?: number;
|
|
43
|
-
/** Additional custom metadata */
|
|
44
|
-
[key: string]: unknown;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Block Edit Component Props
|
|
50
|
-
* Props passed to the Edit component in the dashboard editor
|
|
51
|
-
*/
|
|
52
|
-
export interface BlockEditProps {
|
|
53
|
-
/** The block being edited */
|
|
54
|
-
block: Block;
|
|
55
|
-
|
|
56
|
-
/** Update handler - call this to update the block's data */
|
|
57
|
-
onUpdate: (data: Partial<Block['data']>) => void;
|
|
58
|
-
|
|
59
|
-
/** Delete handler - call this to remove the block */
|
|
60
|
-
onDelete?: () => void;
|
|
61
|
-
|
|
62
|
-
/** Duplicate handler - call this to duplicate the block */
|
|
63
|
-
onDuplicate?: () => void;
|
|
64
|
-
|
|
65
|
-
/** Whether the block is currently selected */
|
|
66
|
-
isSelected?: boolean;
|
|
67
|
-
|
|
68
|
-
/** Whether the block is currently being dragged */
|
|
69
|
-
isDragging?: boolean;
|
|
70
|
-
|
|
71
|
-
/** Focus mode state */
|
|
72
|
-
focusMode?: boolean;
|
|
73
|
-
|
|
74
|
-
/** Child blocks (for container blocks like Section, Columns) */
|
|
75
|
-
childBlocks?: Block[];
|
|
76
|
-
|
|
77
|
-
/** Add a child block (for container blocks) */
|
|
78
|
-
onChildBlockAdd?: (type: string, index?: number, containerId?: string) => void;
|
|
79
|
-
|
|
80
|
-
/** Update a child block (for container blocks) */
|
|
81
|
-
onChildBlockUpdate?: (id: string, data: Partial<Block['data']>, containerId?: string) => void;
|
|
82
|
-
|
|
83
|
-
/** Delete a child block (for container blocks) */
|
|
84
|
-
onChildBlockDelete?: (id: string, containerId?: string) => void;
|
|
85
|
-
|
|
86
|
-
/** Move a child block (for container blocks) */
|
|
87
|
-
onChildBlockMove?: (id: string, newIndex: number, containerId?: string) => void;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Block Preview Component Props
|
|
92
|
-
* Props passed to the Preview component for headless rendering
|
|
93
|
-
*/
|
|
94
|
-
export interface BlockPreviewProps {
|
|
95
|
-
/** The block to render */
|
|
96
|
-
block: Block;
|
|
97
|
-
|
|
98
|
-
/** Additional rendering context */
|
|
99
|
-
context?: {
|
|
100
|
-
/** Site ID */
|
|
101
|
-
siteId?: string;
|
|
102
|
-
/** Locale */
|
|
103
|
-
locale?: string;
|
|
104
|
-
/** Custom render props */
|
|
105
|
-
[key: string]: unknown;
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
/** Child blocks (for container blocks like Section, Columns) */
|
|
109
|
-
childBlocks?: Block[];
|
|
110
|
-
|
|
111
|
-
/** Render a child block (for container blocks) */
|
|
112
|
-
renderChild?: (childBlock: Block) => React.ReactElement;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Block Email Component Props
|
|
117
|
-
* Props passed to the Email component for email rendering
|
|
118
|
-
*/
|
|
119
|
-
export interface BlockEmailProps {
|
|
120
|
-
/** The block to render */
|
|
121
|
-
block: Block;
|
|
122
|
-
|
|
123
|
-
/** Additional rendering context */
|
|
124
|
-
context?: {
|
|
125
|
-
/** Site ID */
|
|
126
|
-
siteId?: string;
|
|
127
|
-
/** Locale */
|
|
128
|
-
locale?: string;
|
|
129
|
-
/** Base URL for images and links */
|
|
130
|
-
baseUrl?: string;
|
|
131
|
-
/** Custom render props */
|
|
132
|
-
[key: string]: unknown;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/** Child blocks (for container blocks) */
|
|
136
|
-
childBlocks?: Block[];
|
|
137
|
-
|
|
138
|
-
/** Render a child block to email HTML (for container blocks) */
|
|
139
|
-
renderChild?: (childBlock: Block) => string;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Strict Block Component Interface
|
|
144
|
-
* Every client-provided block MUST implement this interface
|
|
145
|
-
*/
|
|
146
|
-
export interface IBlockComponent {
|
|
147
|
-
/**
|
|
148
|
-
* Edit Component - Rendered in the dashboard editor
|
|
149
|
-
* Must use dashboard design system
|
|
150
|
-
*/
|
|
151
|
-
Edit: React.ComponentType<BlockEditProps>;
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Preview Component - Rendered in headless/frontend context
|
|
155
|
-
* Can use client's own design system
|
|
156
|
-
*/
|
|
157
|
-
Preview: React.ComponentType<BlockPreviewProps>;
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Email Component - OPTIONAL - Renders block as email-safe HTML string
|
|
161
|
-
* If not provided, EmailRenderer will use fallback conversion
|
|
162
|
-
* Should return email-safe HTML with inline styles
|
|
163
|
-
*/
|
|
164
|
-
Email?: (props: BlockEmailProps) => string;
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Icon Component - Shown in block palette
|
|
168
|
-
* Should be a simple icon (lucide-react recommended)
|
|
169
|
-
*/
|
|
170
|
-
Icon?: React.ComponentType<{ className?: string }>;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Block Type Definition
|
|
175
|
-
* Complete definition of a block type for the registry
|
|
176
|
-
*/
|
|
177
|
-
export interface BlockTypeDefinition {
|
|
178
|
-
/** Unique type identifier */
|
|
179
|
-
type: string;
|
|
180
|
-
|
|
181
|
-
/** Human-readable name */
|
|
182
|
-
name: string;
|
|
183
|
-
|
|
184
|
-
/** Description of the block (shown in tooltips) */
|
|
185
|
-
description: string;
|
|
186
|
-
|
|
187
|
-
/** Icon component (React component) - Optional, falls back to block component's Icon */
|
|
188
|
-
icon?: React.ComponentType<{ className?: string }>;
|
|
189
|
-
|
|
190
|
-
/** Default data structure for this block type */
|
|
191
|
-
defaultData: Record<string, unknown>;
|
|
192
|
-
|
|
193
|
-
/** Validation schema (optional, for runtime validation) */
|
|
194
|
-
validate?: (data: Record<string, unknown>) => boolean;
|
|
195
|
-
|
|
196
|
-
/** Whether this block can contain nested blocks */
|
|
197
|
-
isContainer?: boolean;
|
|
198
|
-
|
|
199
|
-
/** Allowed child block types (if container) */
|
|
200
|
-
allowedChildren?: string[];
|
|
201
|
-
|
|
202
|
-
/** Category for grouping in block palette */
|
|
203
|
-
category?: 'text' | 'media' | 'layout' | 'interactive' | 'custom';
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Block Components - REQUIRED for client-provided blocks
|
|
207
|
-
* Edit and Preview components must be provided
|
|
208
|
-
*/
|
|
209
|
-
components: IBlockComponent;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Rich Text Formatting Configuration
|
|
214
|
-
* Optional configuration for rich text formatting in text blocks
|
|
215
|
-
*/
|
|
216
|
-
export interface RichTextFormattingConfig {
|
|
217
|
-
/** Whether bold formatting is available */
|
|
218
|
-
bold?: boolean;
|
|
219
|
-
/** Whether italic formatting is available */
|
|
220
|
-
italic?: boolean;
|
|
221
|
-
/** Whether underline formatting is available */
|
|
222
|
-
underline?: boolean;
|
|
223
|
-
/** Whether links are available */
|
|
224
|
-
links?: boolean;
|
|
225
|
-
/** Available colors (array of color values or Tailwind classes) */
|
|
226
|
-
colors?: string[];
|
|
227
|
-
/** Custom CSS classes for formatted text */
|
|
228
|
-
styles?: {
|
|
229
|
-
bold?: string;
|
|
230
|
-
italic?: string;
|
|
231
|
-
underline?: string;
|
|
232
|
-
link?: string;
|
|
233
|
-
/** Color classes mapped by color value */
|
|
234
|
-
colorClasses?: Record<string, string>;
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Client Block Definition
|
|
240
|
-
* Simplified interface for client apps to provide blocks
|
|
241
|
-
*/
|
|
242
|
-
export interface ClientBlockDefinition {
|
|
243
|
-
/** Unique type identifier */
|
|
244
|
-
type: string;
|
|
245
|
-
|
|
246
|
-
/** Human-readable name */
|
|
247
|
-
name: string;
|
|
248
|
-
|
|
249
|
-
/** Description of the block */
|
|
250
|
-
description: string;
|
|
251
|
-
|
|
252
|
-
/** Icon component (optional) */
|
|
253
|
-
icon?: React.ComponentType<{ className?: string }>;
|
|
254
|
-
|
|
255
|
-
/** Default data structure */
|
|
256
|
-
defaultData: Record<string, unknown>;
|
|
257
|
-
|
|
258
|
-
/** Validation function (optional) */
|
|
259
|
-
validate?: (data: Record<string, unknown>) => boolean;
|
|
260
|
-
|
|
261
|
-
/** Whether this block can contain nested blocks */
|
|
262
|
-
isContainer?: boolean;
|
|
263
|
-
|
|
264
|
-
/** Allowed child block types (if container) */
|
|
265
|
-
allowedChildren?: string[];
|
|
266
|
-
|
|
267
|
-
/** Category for grouping */
|
|
268
|
-
category?: 'text' | 'media' | 'layout' | 'interactive' | 'custom';
|
|
269
|
-
|
|
270
|
-
/** Block components - REQUIRED */
|
|
271
|
-
components: IBlockComponent;
|
|
272
|
-
|
|
273
|
-
/** Rich text formatting configuration (optional) */
|
|
274
|
-
richTextFormatting?: RichTextFormattingConfig;
|
|
275
|
-
}
|
package/src/types/newsletter.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Newsletter Plugin Types
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Block } from './block';
|
|
6
|
-
|
|
7
|
-
export interface Subscriber {
|
|
8
|
-
_id?: string;
|
|
9
|
-
email: string;
|
|
10
|
-
language: string;
|
|
11
|
-
subscribedAt: Date | string;
|
|
12
|
-
unsubscribedAt?: Date | string;
|
|
13
|
-
status?: 'active' | 'unsubscribed';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface NewsletterSettings {
|
|
17
|
-
id: string;
|
|
18
|
-
languages: {
|
|
19
|
-
[key: string]: {
|
|
20
|
-
title: string;
|
|
21
|
-
message: string;
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
updatedAt?: Date;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Newsletter Status
|
|
29
|
-
*/
|
|
30
|
-
export type NewsletterStatus = 'draft' | 'scheduled' | 'sent' | 'archived';
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Newsletter Publication Data
|
|
34
|
-
*/
|
|
35
|
-
export interface NewsletterPublicationData {
|
|
36
|
-
/** Publication status */
|
|
37
|
-
status: NewsletterStatus;
|
|
38
|
-
|
|
39
|
-
/** Scheduled send date (ISO string) */
|
|
40
|
-
scheduledDate?: string;
|
|
41
|
-
|
|
42
|
-
/** Actual send date (ISO string) */
|
|
43
|
-
sentDate?: string;
|
|
44
|
-
|
|
45
|
-
/** Author ID */
|
|
46
|
-
authorId?: string;
|
|
47
|
-
|
|
48
|
-
/** Last modified date */
|
|
49
|
-
updatedAt?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Newsletter Metadata
|
|
54
|
-
*/
|
|
55
|
-
export interface NewsletterMetadata {
|
|
56
|
-
/** Subject line */
|
|
57
|
-
subject: string;
|
|
58
|
-
|
|
59
|
-
/** Preview text */
|
|
60
|
-
previewText?: string;
|
|
61
|
-
|
|
62
|
-
/** Language code */
|
|
63
|
-
lang?: string;
|
|
64
|
-
|
|
65
|
-
/** Recipient filter (all, specific language, etc.) */
|
|
66
|
-
recipientFilter?: {
|
|
67
|
-
type: 'all' | 'language' | 'custom';
|
|
68
|
-
value?: string;
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Complete Newsletter Structure
|
|
74
|
-
* This is the headless JSON structure stored in the database
|
|
75
|
-
*/
|
|
76
|
-
export interface Newsletter {
|
|
77
|
-
/** Unique newsletter identifier */
|
|
78
|
-
id: string;
|
|
79
|
-
|
|
80
|
-
/** Newsletter title */
|
|
81
|
-
title: string;
|
|
82
|
-
|
|
83
|
-
/** URL slug (unique, auto-generated from title) */
|
|
84
|
-
slug: string;
|
|
85
|
-
|
|
86
|
-
/** Array of content blocks */
|
|
87
|
-
blocks: Block[];
|
|
88
|
-
|
|
89
|
-
/** Publication data */
|
|
90
|
-
publication: NewsletterPublicationData;
|
|
91
|
-
|
|
92
|
-
/** Additional metadata */
|
|
93
|
-
metadata: NewsletterMetadata;
|
|
94
|
-
|
|
95
|
-
/** Creation timestamp */
|
|
96
|
-
createdAt: string;
|
|
97
|
-
|
|
98
|
-
/** Last update timestamp */
|
|
99
|
-
updatedAt: string;
|
|
100
|
-
|
|
101
|
-
/** Version number for revision tracking */
|
|
102
|
-
version?: number;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Newsletter List Item (for list views)
|
|
107
|
-
* Lightweight version of Newsletter for performance
|
|
108
|
-
*/
|
|
109
|
-
export interface NewsletterListItem {
|
|
110
|
-
id: string;
|
|
111
|
-
title: string;
|
|
112
|
-
slug: string;
|
|
113
|
-
status: NewsletterStatus;
|
|
114
|
-
subject: string;
|
|
115
|
-
scheduledDate?: string;
|
|
116
|
-
sentDate?: string;
|
|
117
|
-
authorId?: string;
|
|
118
|
-
updatedAt: string;
|
|
119
|
-
recipientCount?: number;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Newsletter Filter Options
|
|
124
|
-
*/
|
|
125
|
-
export interface NewsletterFilterOptions {
|
|
126
|
-
status?: NewsletterStatus | NewsletterStatus[];
|
|
127
|
-
language?: string;
|
|
128
|
-
authorId?: string;
|
|
129
|
-
search?: string;
|
|
130
|
-
dateFrom?: string;
|
|
131
|
-
dateTo?: string;
|
|
132
|
-
limit?: number;
|
|
133
|
-
skip?: number;
|
|
134
|
-
sortBy?: 'date' | 'title' | 'updatedAt' | 'sentDate';
|
|
135
|
-
sortOrder?: 'asc' | 'desc';
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export interface NewsletterApiConfig {
|
|
139
|
-
getDb: () => Promise<{ db: () => any }>;
|
|
140
|
-
getUserId?: (req: any) => Promise<string | null>;
|
|
141
|
-
emailConfig?: {
|
|
142
|
-
host: string;
|
|
143
|
-
port: number;
|
|
144
|
-
user: string;
|
|
145
|
-
password: string;
|
|
146
|
-
from: string;
|
|
147
|
-
};
|
|
148
|
-
baseUrl?: string;
|
|
149
|
-
collectionName?: string;
|
|
150
|
-
[key: string]: any;
|
|
151
|
-
}
|
package/src/types/registry.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry types for newsletter plugin
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export interface BlockRegistry {
|
|
6
|
-
register(definition: any): void;
|
|
7
|
-
get(type: string): any | undefined;
|
|
8
|
-
getAll(): any[];
|
|
9
|
-
has(type: string): boolean;
|
|
10
|
-
clear(): void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type BlockTypeDefinition = any;
|
|
14
|
-
export type ClientBlockDefinition = any;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block Wrapper Component
|
|
3
|
-
* Notion-style minimal block wrapper with simple hover controls
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
'use client';
|
|
7
|
-
|
|
8
|
-
import React, { useState, useEffect, useRef } from 'react';
|
|
9
|
-
import { Trash2 } from 'lucide-react';
|
|
10
|
-
import { Block } from '../../types/block';
|
|
11
|
-
import { blockRegistry } from '../../registry/BlockRegistry';
|
|
12
|
-
import { getChildBlocks, isContainerBlock } from '../../lib/utils/blockHelpers';
|
|
13
|
-
import { useEditor } from '../../state/EditorContext';
|
|
14
|
-
import { SlashCommandDetector } from './components/SlashCommandDetector';
|
|
15
|
-
import type { useSlashCommand } from './hooks/useSlashCommand';
|
|
16
|
-
|
|
17
|
-
export interface BlockWrapperProps {
|
|
18
|
-
block: Block;
|
|
19
|
-
onUpdate: (data: Partial<Block['data']>) => void;
|
|
20
|
-
onDelete: () => void;
|
|
21
|
-
onMoveUp?: () => void;
|
|
22
|
-
onMoveDown?: () => void;
|
|
23
|
-
/** All blocks in the editor (for resolving child block IDs) */
|
|
24
|
-
allBlocks?: Block[];
|
|
25
|
-
/** Slash command handler */
|
|
26
|
-
slashCommand?: ReturnType<typeof useSlashCommand>;
|
|
27
|
-
/** Index of this block */
|
|
28
|
-
blockIndex?: number;
|
|
29
|
-
/** Callback to add a new block below this one */
|
|
30
|
-
onAddBlockBelow?: (blockType: string) => void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function BlockWrapper({
|
|
34
|
-
block,
|
|
35
|
-
onUpdate,
|
|
36
|
-
onDelete,
|
|
37
|
-
onMoveUp,
|
|
38
|
-
onMoveDown,
|
|
39
|
-
allBlocks = [],
|
|
40
|
-
slashCommand,
|
|
41
|
-
blockIndex,
|
|
42
|
-
onAddBlockBelow,
|
|
43
|
-
}: BlockWrapperProps) {
|
|
44
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
45
|
-
const { state } = useEditor();
|
|
46
|
-
const blockDefinition = blockRegistry.get(block.type);
|
|
47
|
-
|
|
48
|
-
// Check if this is a container block
|
|
49
|
-
const isContainer = isContainerBlock(block, blockRegistry);
|
|
50
|
-
const childBlocks = isContainer && block.children && Array.isArray(block.children) && block.children.length > 0
|
|
51
|
-
? (typeof block.children[0] === 'object'
|
|
52
|
-
? block.children as Block[]
|
|
53
|
-
: getChildBlocks(block, state.blocks))
|
|
54
|
-
: [];
|
|
55
|
-
|
|
56
|
-
if (!blockDefinition) {
|
|
57
|
-
return (
|
|
58
|
-
<div className="p-4 border border-red-300 dark:border-red-700 rounded-lg bg-red-50 dark:bg-red-900/20">
|
|
59
|
-
<p className="text-sm text-red-600 dark:text-red-400">
|
|
60
|
-
Unknown block type: {block.type}
|
|
61
|
-
</p>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const EditComponent = blockDefinition.components.Edit;
|
|
67
|
-
|
|
68
|
-
// Store block ID when hovering for paste context
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (isHovered) {
|
|
71
|
-
if (typeof window !== 'undefined') {
|
|
72
|
-
(window as any).__NEWSLETTER_EDITOR_HOVERED_BLOCK_ID__ = block.id;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}, [isHovered, block.id]);
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div
|
|
79
|
-
className="group relative flex items-start gap-1 py-0.5"
|
|
80
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
81
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
82
|
-
data-block-wrapper
|
|
83
|
-
data-block-id={block.id}
|
|
84
|
-
>
|
|
85
|
-
{/* Block Content */}
|
|
86
|
-
<div className="flex-1 min-w-0 email-block-content">
|
|
87
|
-
{block.type === 'paragraph' && slashCommand ? (
|
|
88
|
-
<SlashCommandDetector
|
|
89
|
-
blockId={block.id}
|
|
90
|
-
blockIndex={blockIndex || 0}
|
|
91
|
-
blockType={block.type}
|
|
92
|
-
content={(block.data.html as string) || (block.data.text as string) || ''}
|
|
93
|
-
onContentChange={(content) => {
|
|
94
|
-
onUpdate({ html: content, text: content.replace(/<[^>]*>/g, '') });
|
|
95
|
-
}}
|
|
96
|
-
slashCommand={slashCommand}
|
|
97
|
-
onAddBlockBelow={onAddBlockBelow}
|
|
98
|
-
>
|
|
99
|
-
<div className="email-block-wrapper">
|
|
100
|
-
<EditComponent
|
|
101
|
-
block={block}
|
|
102
|
-
onUpdate={onUpdate}
|
|
103
|
-
onDelete={onDelete}
|
|
104
|
-
isSelected={state.selectedBlockId === block.id}
|
|
105
|
-
childBlocks={childBlocks}
|
|
106
|
-
/>
|
|
107
|
-
</div>
|
|
108
|
-
</SlashCommandDetector>
|
|
109
|
-
) : (
|
|
110
|
-
<div className="email-block-wrapper">
|
|
111
|
-
<EditComponent
|
|
112
|
-
block={block}
|
|
113
|
-
onUpdate={onUpdate}
|
|
114
|
-
onDelete={onDelete}
|
|
115
|
-
isSelected={state.selectedBlockId === block.id}
|
|
116
|
-
childBlocks={childBlocks}
|
|
117
|
-
/>
|
|
118
|
-
</div>
|
|
119
|
-
)}
|
|
120
|
-
</div>
|
|
121
|
-
|
|
122
|
-
{/* Delete Button - Visible on Hover */}
|
|
123
|
-
<div
|
|
124
|
-
className={`flex-shrink-0 w-6 flex items-start justify-center pt-1 transition-opacity duration-150 ${
|
|
125
|
-
isHovered ? 'opacity-100' : 'opacity-0'
|
|
126
|
-
}`}
|
|
127
|
-
>
|
|
128
|
-
<button
|
|
129
|
-
onClick={(e) => {
|
|
130
|
-
e.stopPropagation();
|
|
131
|
-
if (confirm('Delete this block?')) {
|
|
132
|
-
onDelete();
|
|
133
|
-
}
|
|
134
|
-
}}
|
|
135
|
-
className="p-1 -mr-1 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors"
|
|
136
|
-
title="Delete block"
|
|
137
|
-
>
|
|
138
|
-
<Trash2 size={16} className="text-neutral-400 dark:text-neutral-500 hover:text-red-500 dark:hover:text-red-400 transition-colors" />
|
|
139
|
-
</button>
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
);
|
|
143
|
-
}
|