@jhits/plugin-newsletter 0.0.4 → 0.0.6
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/handler.d.ts +51 -0
- package/dist/api/handler.d.ts.map +1 -0
- package/dist/api/handler.js +526 -0
- package/dist/api/router.d.ts +11 -0
- package/dist/api/router.d.ts.map +1 -0
- package/dist/api/router.js +82 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +222 -0
- package/dist/index.server.d.ts +10 -0
- package/dist/index.server.d.ts.map +1 -0
- package/dist/index.server.js +8 -0
- package/dist/init.d.ts +49 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +42 -0
- package/dist/lib/blocks/BlockRenderer.d.ts +43 -0
- package/dist/lib/blocks/BlockRenderer.d.ts.map +1 -0
- package/dist/lib/blocks/BlockRenderer.js +48 -0
- package/dist/lib/email/EmailRenderer.d.ts +47 -0
- package/dist/lib/email/EmailRenderer.d.ts.map +1 -0
- package/dist/lib/email/EmailRenderer.js +359 -0
- package/dist/lib/email/index.d.ts +6 -0
- package/dist/lib/email/index.d.ts.map +1 -0
- package/dist/lib/email/index.js +4 -0
- package/dist/lib/mappers/apiMapper.d.ts +30 -0
- package/dist/lib/mappers/apiMapper.d.ts.map +1 -0
- package/dist/lib/mappers/apiMapper.js +36 -0
- package/dist/lib/utils/blockHelpers.d.ts +23 -0
- package/dist/lib/utils/blockHelpers.d.ts.map +1 -0
- package/dist/lib/utils/blockHelpers.js +65 -0
- package/dist/lib/utils/slugify.d.ts +14 -0
- package/dist/lib/utils/slugify.d.ts.map +1 -0
- package/dist/lib/utils/slugify.js +37 -0
- package/dist/registry/BlockRegistry.d.ts +31 -0
- package/dist/registry/BlockRegistry.d.ts.map +1 -0
- package/dist/registry/BlockRegistry.js +34 -0
- package/dist/registry/index.d.ts +5 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +4 -0
- package/dist/state/EditorContext.d.ts +44 -0
- package/dist/state/EditorContext.d.ts.map +1 -0
- package/dist/state/EditorContext.js +212 -0
- package/dist/state/index.d.ts +10 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +6 -0
- package/dist/state/reducer.d.ts +11 -0
- package/dist/state/reducer.d.ts.map +1 -0
- package/dist/state/reducer.js +488 -0
- package/dist/state/types.d.ts +157 -0
- package/dist/state/types.d.ts.map +1 -0
- package/dist/state/types.js +26 -0
- package/dist/types/block.d.ts +230 -0
- package/dist/types/block.d.ts.map +1 -0
- package/dist/types/block.js +8 -0
- package/dist/types/newsletter.d.ts +129 -0
- package/dist/types/newsletter.d.ts.map +1 -0
- package/dist/types/newsletter.js +4 -0
- package/dist/types/registry.d.ts +13 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/registry.js +4 -0
- package/dist/views/CanvasEditor/BlockWrapper.d.ts +23 -0
- package/dist/views/CanvasEditor/BlockWrapper.d.ts.map +1 -0
- package/dist/views/CanvasEditor/BlockWrapper.js +44 -0
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts +14 -0
- package/dist/views/CanvasEditor/CanvasEditorView.d.ts.map +1 -0
- package/dist/views/CanvasEditor/CanvasEditorView.js +139 -0
- package/dist/views/CanvasEditor/EditorBody.d.ts +24 -0
- package/dist/views/CanvasEditor/EditorBody.d.ts.map +1 -0
- package/dist/views/CanvasEditor/EditorBody.js +21 -0
- package/dist/views/CanvasEditor/EditorHeader.d.ts +12 -0
- package/dist/views/CanvasEditor/EditorHeader.d.ts.map +1 -0
- package/dist/views/CanvasEditor/EditorHeader.js +47 -0
- package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts +10 -0
- package/dist/views/CanvasEditor/components/CustomBlockItem.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/CustomBlockItem.js +36 -0
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts +25 -0
- package/dist/views/CanvasEditor/components/EditorCanvas.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/EditorCanvas.js +397 -0
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts +7 -0
- package/dist/views/CanvasEditor/components/EditorLibrary.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/EditorLibrary.js +25 -0
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts +9 -0
- package/dist/views/CanvasEditor/components/EditorSidebar.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/EditorSidebar.js +16 -0
- package/dist/views/CanvasEditor/components/ErrorBanner.d.ts +6 -0
- package/dist/views/CanvasEditor/components/ErrorBanner.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/ErrorBanner.js +8 -0
- package/dist/views/CanvasEditor/components/LibraryItem.d.ts +10 -0
- package/dist/views/CanvasEditor/components/LibraryItem.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/LibraryItem.js +35 -0
- package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts +18 -0
- package/dist/views/CanvasEditor/components/SlashCommandDetector.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/SlashCommandDetector.js +164 -0
- package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts +22 -0
- package/dist/views/CanvasEditor/components/SlashCommandMenu.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/SlashCommandMenu.js +57 -0
- package/dist/views/CanvasEditor/components/index.d.ts +16 -0
- package/dist/views/CanvasEditor/components/index.d.ts.map +1 -0
- package/dist/views/CanvasEditor/components/index.js +9 -0
- package/dist/views/CanvasEditor/hooks/index.d.ts +7 -0
- package/dist/views/CanvasEditor/hooks/index.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/index.js +6 -0
- package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts +3 -0
- package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useKeyboardShortcuts.js +114 -0
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts +5 -0
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useNewsletterLoader.js +28 -0
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts +2 -0
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useRegisteredBlocks.js +46 -0
- package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts +31 -0
- package/dist/views/CanvasEditor/hooks/useSlashCommand.d.ts.map +1 -0
- package/dist/views/CanvasEditor/hooks/useSlashCommand.js +87 -0
- package/dist/views/CanvasEditor/index.d.ts +12 -0
- package/dist/views/CanvasEditor/index.d.ts.map +1 -0
- package/dist/views/CanvasEditor/index.js +7 -0
- package/dist/views/NewsletterEditor.d.ts +16 -0
- package/dist/views/NewsletterEditor.d.ts.map +1 -0
- package/dist/views/NewsletterEditor.js +10 -0
- package/dist/views/NewsletterManager.d.ts +10 -0
- package/dist/views/NewsletterManager.d.ts.map +1 -0
- package/dist/views/NewsletterManager.js +95 -0
- package/dist/views/SettingsView.d.ts +10 -0
- package/dist/views/SettingsView.d.ts.map +1 -0
- package/dist/views/SettingsView.js +103 -0
- package/dist/views/SubscribersView.d.ts +10 -0
- package/dist/views/SubscribersView.d.ts.map +1 -0
- package/dist/views/SubscribersView.js +94 -0
- package/package.json +24 -23
- package/src/api/handler.ts +340 -1
- package/src/api/router.ts +35 -0
- package/src/index.tsx +284 -4
- package/src/index.tsx.patch +98 -0
- package/src/init.tsx +72 -0
- package/src/lib/blocks/BlockRenderer.tsx +125 -0
- package/src/lib/email/EmailRenderer.tsx +425 -0
- package/src/lib/email/index.ts +6 -0
- package/src/lib/mappers/apiMapper.ts +57 -0
- package/src/lib/utils/blockHelpers.ts +71 -0
- package/src/lib/utils/slugify.ts +43 -0
- package/src/registry/BlockRegistry.ts +53 -0
- package/src/registry/index.ts +5 -0
- package/src/state/EditorContext.tsx +279 -0
- package/src/state/index.ts +10 -0
- package/src/state/reducer.ts +561 -0
- package/src/state/types.ts +154 -0
- package/src/types/block.ts +275 -0
- package/src/types/newsletter.ts +114 -1
- package/src/types/registry.ts +14 -0
- package/src/views/CanvasEditor/BlockWrapper.tsx +143 -0
- package/src/views/CanvasEditor/CanvasEditorView.tsx +249 -0
- package/src/views/CanvasEditor/EditorBody.tsx +95 -0
- package/src/views/CanvasEditor/EditorHeader.tsx +139 -0
- package/src/views/CanvasEditor/components/CustomBlockItem.tsx +83 -0
- package/src/views/CanvasEditor/components/EditorCanvas.tsx +674 -0
- package/src/views/CanvasEditor/components/EditorLibrary.tsx +120 -0
- package/src/views/CanvasEditor/components/EditorSidebar.tsx +156 -0
- package/src/views/CanvasEditor/components/ErrorBanner.tsx +31 -0
- package/src/views/CanvasEditor/components/LibraryItem.tsx +71 -0
- package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +196 -0
- package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +131 -0
- package/src/views/CanvasEditor/components/index.ts +16 -0
- package/src/views/CanvasEditor/hooks/index.ts +7 -0
- package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +136 -0
- package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +34 -0
- package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +54 -0
- package/src/views/CanvasEditor/hooks/useSlashCommand.ts +106 -0
- package/src/views/CanvasEditor/index.ts +12 -0
- package/src/views/NewsletterEditor.tsx +38 -0
- package/src/views/NewsletterManager.tsx +240 -0
- package/src/views/SettingsView.tsx +14 -14
- package/src/views/SubscribersView.tsx +20 -20
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
import React from 'react';
|
|
9
|
+
/**
|
|
10
|
+
* Base Block Interface
|
|
11
|
+
* Every block in the system extends this structure
|
|
12
|
+
*/
|
|
13
|
+
export interface Block {
|
|
14
|
+
/** Unique identifier for the block */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Block type identifier (e.g., 'heading', 'paragraph', 'image') */
|
|
17
|
+
type: string;
|
|
18
|
+
/** Block-specific data (varies by type) */
|
|
19
|
+
data: Record<string, unknown>;
|
|
20
|
+
/**
|
|
21
|
+
* Child blocks (for container blocks like Section, Columns)
|
|
22
|
+
* Can be an array of Block IDs (for flat storage) or Block objects (for nested structure)
|
|
23
|
+
*/
|
|
24
|
+
children?: string[] | Block[];
|
|
25
|
+
/** Optional metadata for editor state, styling, etc. */
|
|
26
|
+
meta?: {
|
|
27
|
+
/** Visual order in the editor */
|
|
28
|
+
order?: number;
|
|
29
|
+
/** Whether block is collapsed in editor */
|
|
30
|
+
collapsed?: boolean;
|
|
31
|
+
/** Custom styling overrides */
|
|
32
|
+
style?: Record<string, string>;
|
|
33
|
+
/** Editor-specific flags */
|
|
34
|
+
flags?: string[];
|
|
35
|
+
/** Column index (for column container blocks) */
|
|
36
|
+
columnIndex?: number;
|
|
37
|
+
/** Additional custom metadata */
|
|
38
|
+
[key: string]: unknown;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Block Edit Component Props
|
|
43
|
+
* Props passed to the Edit component in the dashboard editor
|
|
44
|
+
*/
|
|
45
|
+
export interface BlockEditProps {
|
|
46
|
+
/** The block being edited */
|
|
47
|
+
block: Block;
|
|
48
|
+
/** Update handler - call this to update the block's data */
|
|
49
|
+
onUpdate: (data: Partial<Block['data']>) => void;
|
|
50
|
+
/** Delete handler - call this to remove the block */
|
|
51
|
+
onDelete?: () => void;
|
|
52
|
+
/** Duplicate handler - call this to duplicate the block */
|
|
53
|
+
onDuplicate?: () => void;
|
|
54
|
+
/** Whether the block is currently selected */
|
|
55
|
+
isSelected?: boolean;
|
|
56
|
+
/** Whether the block is currently being dragged */
|
|
57
|
+
isDragging?: boolean;
|
|
58
|
+
/** Focus mode state */
|
|
59
|
+
focusMode?: boolean;
|
|
60
|
+
/** Child blocks (for container blocks like Section, Columns) */
|
|
61
|
+
childBlocks?: Block[];
|
|
62
|
+
/** Add a child block (for container blocks) */
|
|
63
|
+
onChildBlockAdd?: (type: string, index?: number, containerId?: string) => void;
|
|
64
|
+
/** Update a child block (for container blocks) */
|
|
65
|
+
onChildBlockUpdate?: (id: string, data: Partial<Block['data']>, containerId?: string) => void;
|
|
66
|
+
/** Delete a child block (for container blocks) */
|
|
67
|
+
onChildBlockDelete?: (id: string, containerId?: string) => void;
|
|
68
|
+
/** Move a child block (for container blocks) */
|
|
69
|
+
onChildBlockMove?: (id: string, newIndex: number, containerId?: string) => void;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Block Preview Component Props
|
|
73
|
+
* Props passed to the Preview component for headless rendering
|
|
74
|
+
*/
|
|
75
|
+
export interface BlockPreviewProps {
|
|
76
|
+
/** The block to render */
|
|
77
|
+
block: Block;
|
|
78
|
+
/** Additional rendering context */
|
|
79
|
+
context?: {
|
|
80
|
+
/** Site ID */
|
|
81
|
+
siteId?: string;
|
|
82
|
+
/** Locale */
|
|
83
|
+
locale?: string;
|
|
84
|
+
/** Custom render props */
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
};
|
|
87
|
+
/** Child blocks (for container blocks like Section, Columns) */
|
|
88
|
+
childBlocks?: Block[];
|
|
89
|
+
/** Render a child block (for container blocks) */
|
|
90
|
+
renderChild?: (childBlock: Block) => React.ReactElement;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Block Email Component Props
|
|
94
|
+
* Props passed to the Email component for email rendering
|
|
95
|
+
*/
|
|
96
|
+
export interface BlockEmailProps {
|
|
97
|
+
/** The block to render */
|
|
98
|
+
block: Block;
|
|
99
|
+
/** Additional rendering context */
|
|
100
|
+
context?: {
|
|
101
|
+
/** Site ID */
|
|
102
|
+
siteId?: string;
|
|
103
|
+
/** Locale */
|
|
104
|
+
locale?: string;
|
|
105
|
+
/** Base URL for images and links */
|
|
106
|
+
baseUrl?: string;
|
|
107
|
+
/** Custom render props */
|
|
108
|
+
[key: string]: unknown;
|
|
109
|
+
};
|
|
110
|
+
/** Child blocks (for container blocks) */
|
|
111
|
+
childBlocks?: Block[];
|
|
112
|
+
/** Render a child block to email HTML (for container blocks) */
|
|
113
|
+
renderChild?: (childBlock: Block) => string;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Strict Block Component Interface
|
|
117
|
+
* Every client-provided block MUST implement this interface
|
|
118
|
+
*/
|
|
119
|
+
export interface IBlockComponent {
|
|
120
|
+
/**
|
|
121
|
+
* Edit Component - Rendered in the dashboard editor
|
|
122
|
+
* Must use dashboard design system
|
|
123
|
+
*/
|
|
124
|
+
Edit: React.ComponentType<BlockEditProps>;
|
|
125
|
+
/**
|
|
126
|
+
* Preview Component - Rendered in headless/frontend context
|
|
127
|
+
* Can use client's own design system
|
|
128
|
+
*/
|
|
129
|
+
Preview: React.ComponentType<BlockPreviewProps>;
|
|
130
|
+
/**
|
|
131
|
+
* Email Component - OPTIONAL - Renders block as email-safe HTML string
|
|
132
|
+
* If not provided, EmailRenderer will use fallback conversion
|
|
133
|
+
* Should return email-safe HTML with inline styles
|
|
134
|
+
*/
|
|
135
|
+
Email?: (props: BlockEmailProps) => string;
|
|
136
|
+
/**
|
|
137
|
+
* Icon Component - Shown in block palette
|
|
138
|
+
* Should be a simple icon (lucide-react recommended)
|
|
139
|
+
*/
|
|
140
|
+
Icon?: React.ComponentType<{
|
|
141
|
+
className?: string;
|
|
142
|
+
}>;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Block Type Definition
|
|
146
|
+
* Complete definition of a block type for the registry
|
|
147
|
+
*/
|
|
148
|
+
export interface BlockTypeDefinition {
|
|
149
|
+
/** Unique type identifier */
|
|
150
|
+
type: string;
|
|
151
|
+
/** Human-readable name */
|
|
152
|
+
name: string;
|
|
153
|
+
/** Description of the block (shown in tooltips) */
|
|
154
|
+
description: string;
|
|
155
|
+
/** Icon component (React component) - Optional, falls back to block component's Icon */
|
|
156
|
+
icon?: React.ComponentType<{
|
|
157
|
+
className?: string;
|
|
158
|
+
}>;
|
|
159
|
+
/** Default data structure for this block type */
|
|
160
|
+
defaultData: Record<string, unknown>;
|
|
161
|
+
/** Validation schema (optional, for runtime validation) */
|
|
162
|
+
validate?: (data: Record<string, unknown>) => boolean;
|
|
163
|
+
/** Whether this block can contain nested blocks */
|
|
164
|
+
isContainer?: boolean;
|
|
165
|
+
/** Allowed child block types (if container) */
|
|
166
|
+
allowedChildren?: string[];
|
|
167
|
+
/** Category for grouping in block palette */
|
|
168
|
+
category?: 'text' | 'media' | 'layout' | 'interactive' | 'custom';
|
|
169
|
+
/**
|
|
170
|
+
* Block Components - REQUIRED for client-provided blocks
|
|
171
|
+
* Edit and Preview components must be provided
|
|
172
|
+
*/
|
|
173
|
+
components: IBlockComponent;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Rich Text Formatting Configuration
|
|
177
|
+
* Optional configuration for rich text formatting in text blocks
|
|
178
|
+
*/
|
|
179
|
+
export interface RichTextFormattingConfig {
|
|
180
|
+
/** Whether bold formatting is available */
|
|
181
|
+
bold?: boolean;
|
|
182
|
+
/** Whether italic formatting is available */
|
|
183
|
+
italic?: boolean;
|
|
184
|
+
/** Whether underline formatting is available */
|
|
185
|
+
underline?: boolean;
|
|
186
|
+
/** Whether links are available */
|
|
187
|
+
links?: boolean;
|
|
188
|
+
/** Available colors (array of color values or Tailwind classes) */
|
|
189
|
+
colors?: string[];
|
|
190
|
+
/** Custom CSS classes for formatted text */
|
|
191
|
+
styles?: {
|
|
192
|
+
bold?: string;
|
|
193
|
+
italic?: string;
|
|
194
|
+
underline?: string;
|
|
195
|
+
link?: string;
|
|
196
|
+
/** Color classes mapped by color value */
|
|
197
|
+
colorClasses?: Record<string, string>;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Client Block Definition
|
|
202
|
+
* Simplified interface for client apps to provide blocks
|
|
203
|
+
*/
|
|
204
|
+
export interface ClientBlockDefinition {
|
|
205
|
+
/** Unique type identifier */
|
|
206
|
+
type: string;
|
|
207
|
+
/** Human-readable name */
|
|
208
|
+
name: string;
|
|
209
|
+
/** Description of the block */
|
|
210
|
+
description: string;
|
|
211
|
+
/** Icon component (optional) */
|
|
212
|
+
icon?: React.ComponentType<{
|
|
213
|
+
className?: string;
|
|
214
|
+
}>;
|
|
215
|
+
/** Default data structure */
|
|
216
|
+
defaultData: Record<string, unknown>;
|
|
217
|
+
/** Validation function (optional) */
|
|
218
|
+
validate?: (data: Record<string, unknown>) => boolean;
|
|
219
|
+
/** Whether this block can contain nested blocks */
|
|
220
|
+
isContainer?: boolean;
|
|
221
|
+
/** Allowed child block types (if container) */
|
|
222
|
+
allowedChildren?: string[];
|
|
223
|
+
/** Category for grouping */
|
|
224
|
+
category?: 'text' | 'media' | 'layout' | 'interactive' | 'custom';
|
|
225
|
+
/** Block components - REQUIRED */
|
|
226
|
+
components: IBlockComponent;
|
|
227
|
+
/** Rich text formatting configuration (optional) */
|
|
228
|
+
richTextFormatting?: RichTextFormattingConfig;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=block.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block.d.ts","sourceRoot":"","sources":["../../src/types/block.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Newsletter Plugin Types
|
|
3
|
+
*/
|
|
4
|
+
import { Block } from './block';
|
|
5
|
+
export interface Subscriber {
|
|
6
|
+
_id?: string;
|
|
7
|
+
email: string;
|
|
8
|
+
language: string;
|
|
9
|
+
subscribedAt: Date | string;
|
|
10
|
+
unsubscribedAt?: Date | string;
|
|
11
|
+
status?: 'active' | 'unsubscribed';
|
|
12
|
+
}
|
|
13
|
+
export interface NewsletterSettings {
|
|
14
|
+
id: string;
|
|
15
|
+
languages: {
|
|
16
|
+
[key: string]: {
|
|
17
|
+
title: string;
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
updatedAt?: Date;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Newsletter Status
|
|
25
|
+
*/
|
|
26
|
+
export type NewsletterStatus = 'draft' | 'scheduled' | 'sent' | 'archived';
|
|
27
|
+
/**
|
|
28
|
+
* Newsletter Publication Data
|
|
29
|
+
*/
|
|
30
|
+
export interface NewsletterPublicationData {
|
|
31
|
+
/** Publication status */
|
|
32
|
+
status: NewsletterStatus;
|
|
33
|
+
/** Scheduled send date (ISO string) */
|
|
34
|
+
scheduledDate?: string;
|
|
35
|
+
/** Actual send date (ISO string) */
|
|
36
|
+
sentDate?: string;
|
|
37
|
+
/** Author ID */
|
|
38
|
+
authorId?: string;
|
|
39
|
+
/** Last modified date */
|
|
40
|
+
updatedAt?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Newsletter Metadata
|
|
44
|
+
*/
|
|
45
|
+
export interface NewsletterMetadata {
|
|
46
|
+
/** Subject line */
|
|
47
|
+
subject: string;
|
|
48
|
+
/** Preview text */
|
|
49
|
+
previewText?: string;
|
|
50
|
+
/** Language code */
|
|
51
|
+
lang?: string;
|
|
52
|
+
/** Recipient filter (all, specific language, etc.) */
|
|
53
|
+
recipientFilter?: {
|
|
54
|
+
type: 'all' | 'language' | 'custom';
|
|
55
|
+
value?: string;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Complete Newsletter Structure
|
|
60
|
+
* This is the headless JSON structure stored in the database
|
|
61
|
+
*/
|
|
62
|
+
export interface Newsletter {
|
|
63
|
+
/** Unique newsletter identifier */
|
|
64
|
+
id: string;
|
|
65
|
+
/** Newsletter title */
|
|
66
|
+
title: string;
|
|
67
|
+
/** URL slug (unique, auto-generated from title) */
|
|
68
|
+
slug: string;
|
|
69
|
+
/** Array of content blocks */
|
|
70
|
+
blocks: Block[];
|
|
71
|
+
/** Publication data */
|
|
72
|
+
publication: NewsletterPublicationData;
|
|
73
|
+
/** Additional metadata */
|
|
74
|
+
metadata: NewsletterMetadata;
|
|
75
|
+
/** Creation timestamp */
|
|
76
|
+
createdAt: string;
|
|
77
|
+
/** Last update timestamp */
|
|
78
|
+
updatedAt: string;
|
|
79
|
+
/** Version number for revision tracking */
|
|
80
|
+
version?: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Newsletter List Item (for list views)
|
|
84
|
+
* Lightweight version of Newsletter for performance
|
|
85
|
+
*/
|
|
86
|
+
export interface NewsletterListItem {
|
|
87
|
+
id: string;
|
|
88
|
+
title: string;
|
|
89
|
+
slug: string;
|
|
90
|
+
status: NewsletterStatus;
|
|
91
|
+
subject: string;
|
|
92
|
+
scheduledDate?: string;
|
|
93
|
+
sentDate?: string;
|
|
94
|
+
authorId?: string;
|
|
95
|
+
updatedAt: string;
|
|
96
|
+
recipientCount?: number;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Newsletter Filter Options
|
|
100
|
+
*/
|
|
101
|
+
export interface NewsletterFilterOptions {
|
|
102
|
+
status?: NewsletterStatus | NewsletterStatus[];
|
|
103
|
+
language?: string;
|
|
104
|
+
authorId?: string;
|
|
105
|
+
search?: string;
|
|
106
|
+
dateFrom?: string;
|
|
107
|
+
dateTo?: string;
|
|
108
|
+
limit?: number;
|
|
109
|
+
skip?: number;
|
|
110
|
+
sortBy?: 'date' | 'title' | 'updatedAt' | 'sentDate';
|
|
111
|
+
sortOrder?: 'asc' | 'desc';
|
|
112
|
+
}
|
|
113
|
+
export interface NewsletterApiConfig {
|
|
114
|
+
getDb: () => Promise<{
|
|
115
|
+
db: () => any;
|
|
116
|
+
}>;
|
|
117
|
+
getUserId?: (req: any) => Promise<string | null>;
|
|
118
|
+
emailConfig?: {
|
|
119
|
+
host: string;
|
|
120
|
+
port: number;
|
|
121
|
+
user: string;
|
|
122
|
+
password: string;
|
|
123
|
+
from: string;
|
|
124
|
+
};
|
|
125
|
+
baseUrl?: string;
|
|
126
|
+
collectionName?: string;
|
|
127
|
+
[key: string]: any;
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=newsletter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"newsletter.d.ts","sourceRoot":"","sources":["../../src/types/newsletter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry types for newsletter plugin
|
|
3
|
+
*/
|
|
4
|
+
export interface BlockRegistry {
|
|
5
|
+
register(definition: any): void;
|
|
6
|
+
get(type: string): any | undefined;
|
|
7
|
+
getAll(): any[];
|
|
8
|
+
has(type: string): boolean;
|
|
9
|
+
clear(): void;
|
|
10
|
+
}
|
|
11
|
+
export type BlockTypeDefinition = any;
|
|
12
|
+
export type ClientBlockDefinition = any;
|
|
13
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/types/registry.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,UAAU,EAAE,GAAG,GAAG,IAAI,CAAC;IAChC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC;IACnC,MAAM,IAAI,GAAG,EAAE,CAAC;IAChB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,KAAK,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,mBAAmB,GAAG,GAAG,CAAC;AACtC,MAAM,MAAM,qBAAqB,GAAG,GAAG,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Wrapper Component
|
|
3
|
+
* Notion-style minimal block wrapper with simple hover controls
|
|
4
|
+
*/
|
|
5
|
+
import { Block } from '../../types/block';
|
|
6
|
+
import type { useSlashCommand } from './hooks/useSlashCommand';
|
|
7
|
+
export interface BlockWrapperProps {
|
|
8
|
+
block: Block;
|
|
9
|
+
onUpdate: (data: Partial<Block['data']>) => void;
|
|
10
|
+
onDelete: () => void;
|
|
11
|
+
onMoveUp?: () => void;
|
|
12
|
+
onMoveDown?: () => void;
|
|
13
|
+
/** All blocks in the editor (for resolving child block IDs) */
|
|
14
|
+
allBlocks?: Block[];
|
|
15
|
+
/** Slash command handler */
|
|
16
|
+
slashCommand?: ReturnType<typeof useSlashCommand>;
|
|
17
|
+
/** Index of this block */
|
|
18
|
+
blockIndex?: number;
|
|
19
|
+
/** Callback to add a new block below this one */
|
|
20
|
+
onAddBlockBelow?: (blockType: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function BlockWrapper({ block, onUpdate, onDelete, onMoveUp, onMoveDown, allBlocks, slashCommand, blockIndex, onAddBlockBelow, }: BlockWrapperProps): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
//# sourceMappingURL=BlockWrapper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BlockWrapper.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/BlockWrapper.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAK1C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,KAAK,CAAC;IACb,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,IAAI,CAAC;IACjD,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;IACpB,4BAA4B;IAC5B,YAAY,CAAC,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;IAClD,0BAA0B;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,eAAe,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,wBAAgB,YAAY,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,SAAc,EACd,YAAY,EACZ,UAAU,EACV,eAAe,GAClB,EAAE,iBAAiB,2CAoGnB"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Wrapper Component
|
|
3
|
+
* Notion-style minimal block wrapper with simple hover controls
|
|
4
|
+
*/
|
|
5
|
+
'use client';
|
|
6
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
import { useState, useEffect } from 'react';
|
|
8
|
+
import { Trash2 } from 'lucide-react';
|
|
9
|
+
import { blockRegistry } from '../../registry/BlockRegistry';
|
|
10
|
+
import { getChildBlocks, isContainerBlock } from '../../lib/utils/blockHelpers';
|
|
11
|
+
import { useEditor } from '../../state/EditorContext';
|
|
12
|
+
import { SlashCommandDetector } from './components/SlashCommandDetector';
|
|
13
|
+
export function BlockWrapper({ block, onUpdate, onDelete, onMoveUp, onMoveDown, allBlocks = [], slashCommand, blockIndex, onAddBlockBelow, }) {
|
|
14
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
15
|
+
const { state } = useEditor();
|
|
16
|
+
const blockDefinition = blockRegistry.get(block.type);
|
|
17
|
+
// Check if this is a container block
|
|
18
|
+
const isContainer = isContainerBlock(block, blockRegistry);
|
|
19
|
+
const childBlocks = isContainer && block.children && Array.isArray(block.children) && block.children.length > 0
|
|
20
|
+
? (typeof block.children[0] === 'object'
|
|
21
|
+
? block.children
|
|
22
|
+
: getChildBlocks(block, state.blocks))
|
|
23
|
+
: [];
|
|
24
|
+
if (!blockDefinition) {
|
|
25
|
+
return (_jsx("div", { className: "p-4 border border-red-300 dark:border-red-700 rounded-lg bg-red-50 dark:bg-red-900/20", children: _jsxs("p", { className: "text-sm text-red-600 dark:text-red-400", children: ["Unknown block type: ", block.type] }) }));
|
|
26
|
+
}
|
|
27
|
+
const EditComponent = blockDefinition.components.Edit;
|
|
28
|
+
// Store block ID when hovering for paste context
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (isHovered) {
|
|
31
|
+
if (typeof window !== 'undefined') {
|
|
32
|
+
window.__NEWSLETTER_EDITOR_HOVERED_BLOCK_ID__ = block.id;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}, [isHovered, block.id]);
|
|
36
|
+
return (_jsxs("div", { className: "group relative flex items-start gap-1 py-0.5", onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), "data-block-wrapper": true, "data-block-id": block.id, children: [_jsx("div", { className: "flex-1 min-w-0 email-block-content", children: block.type === 'paragraph' && slashCommand ? (_jsx(SlashCommandDetector, { blockId: block.id, blockIndex: blockIndex || 0, blockType: block.type, content: block.data.html || block.data.text || '', onContentChange: (content) => {
|
|
37
|
+
onUpdate({ html: content, text: content.replace(/<[^>]*>/g, '') });
|
|
38
|
+
}, slashCommand: slashCommand, onAddBlockBelow: onAddBlockBelow, children: _jsx("div", { className: "email-block-wrapper", children: _jsx(EditComponent, { block: block, onUpdate: onUpdate, onDelete: onDelete, isSelected: state.selectedBlockId === block.id, childBlocks: childBlocks }) }) })) : (_jsx("div", { className: "email-block-wrapper", children: _jsx(EditComponent, { block: block, onUpdate: onUpdate, onDelete: onDelete, isSelected: state.selectedBlockId === block.id, childBlocks: childBlocks }) })) }), _jsx("div", { className: `flex-shrink-0 w-6 flex items-start justify-center pt-1 transition-opacity duration-150 ${isHovered ? 'opacity-100' : 'opacity-0'}`, children: _jsx("button", { onClick: (e) => {
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
if (confirm('Delete this block?')) {
|
|
41
|
+
onDelete();
|
|
42
|
+
}
|
|
43
|
+
}, className: "p-1 -mr-1 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors", title: "Delete block", children: _jsx(Trash2, { size: 16, className: "text-neutral-400 dark:text-neutral-500 hover:text-red-500 dark:hover:text-red-400 transition-colors" }) }) })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface CanvasEditorViewProps {
|
|
2
|
+
newsletterSlug?: string;
|
|
3
|
+
siteId: string;
|
|
4
|
+
locale: string;
|
|
5
|
+
/** Enable dark mode for content area and wrappers (default: true) */
|
|
6
|
+
darkMode?: boolean;
|
|
7
|
+
/** Background colors for the editor */
|
|
8
|
+
backgroundColors?: {
|
|
9
|
+
light: string;
|
|
10
|
+
dark?: string;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export declare function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: propsBackgroundColors, siteId, locale }: CanvasEditorViewProps): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=CanvasEditorView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CanvasEditorView.d.ts","sourceRoot":"","sources":["../../../src/views/CanvasEditor/CanvasEditorView.tsx"],"names":[],"mappings":"AAeA,MAAM,WAAW,qBAAqB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE;QACf,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;CACL;AAED,wBAAgB,gBAAgB,CAAC,EAAE,cAAc,EAAE,QAAQ,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE,qBAAqB,2CA4N5I"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useRef, useCallback } from 'react';
|
|
4
|
+
import { useEditor } from '../../state/EditorContext';
|
|
5
|
+
import { EditorHeader } from './EditorHeader';
|
|
6
|
+
import { ErrorBanner } from './components/ErrorBanner';
|
|
7
|
+
import { EditorCanvas } from './components/EditorCanvas';
|
|
8
|
+
import { EditorSidebar } from './components/EditorSidebar';
|
|
9
|
+
import { SlashCommandMenu } from './components/SlashCommandMenu';
|
|
10
|
+
import { useSlashCommand } from './hooks/useSlashCommand';
|
|
11
|
+
import { useNewsletterLoader, useRegisteredBlocks, useKeyboardShortcuts } from './hooks';
|
|
12
|
+
import { blockRegistry } from '../../registry';
|
|
13
|
+
export function CanvasEditorView({ newsletterSlug, darkMode, backgroundColors: propsBackgroundColors, siteId, locale }) {
|
|
14
|
+
const { state, helpers, dispatch, darkMode: contextDarkMode, backgroundColors: contextBackgroundColors, canUndo, canRedo } = useEditor();
|
|
15
|
+
const effectiveDarkMode = darkMode !== undefined ? darkMode : contextDarkMode;
|
|
16
|
+
const effectiveBackgroundColors = propsBackgroundColors || contextBackgroundColors;
|
|
17
|
+
const [isSidebarOpen, setSidebarOpen] = useState(true);
|
|
18
|
+
const [isPreviewMode, setIsPreviewMode] = useState(false);
|
|
19
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
20
|
+
const [saveError, setSaveError] = useState(null);
|
|
21
|
+
// Get registered blocks
|
|
22
|
+
const registeredBlocks = useRegisteredBlocks();
|
|
23
|
+
// Newsletter loading
|
|
24
|
+
const { isLoadingNewsletter } = useNewsletterLoader(newsletterSlug, state.newsletterId, (newsletter) => {
|
|
25
|
+
helpers.loadNewsletter(newsletter);
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
dispatch({ type: 'MARK_CLEAN' });
|
|
28
|
+
}, 0);
|
|
29
|
+
});
|
|
30
|
+
// Ensure at least one paragraph block exists when creating new newsletter
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!newsletterSlug && state.blocks.length === 0 && !isLoadingNewsletter) {
|
|
33
|
+
helpers.addBlock('paragraph', 0, undefined);
|
|
34
|
+
}
|
|
35
|
+
}, [newsletterSlug, state.blocks.length, isLoadingNewsletter, helpers]);
|
|
36
|
+
// Track if we just loaded a newsletter to prevent marking as dirty during cleanup
|
|
37
|
+
const justLoadedRef = useRef(false);
|
|
38
|
+
const previousIsLoadingRef = useRef(false);
|
|
39
|
+
const loadingCleanupTimerRef = useRef(null);
|
|
40
|
+
// Mark when newsletter loading completes and ensure it stays clean after all effects
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
const loadingJustFinished = previousIsLoadingRef.current && !isLoadingNewsletter && state.newsletterId;
|
|
43
|
+
if (loadingJustFinished) {
|
|
44
|
+
justLoadedRef.current = true;
|
|
45
|
+
if (loadingCleanupTimerRef.current) {
|
|
46
|
+
clearTimeout(loadingCleanupTimerRef.current);
|
|
47
|
+
}
|
|
48
|
+
requestAnimationFrame(() => {
|
|
49
|
+
requestAnimationFrame(() => {
|
|
50
|
+
loadingCleanupTimerRef.current = setTimeout(() => {
|
|
51
|
+
console.log('[CanvasEditorView] Newsletter loading complete - ensuring clean state');
|
|
52
|
+
dispatch({ type: 'MARK_CLEAN' });
|
|
53
|
+
justLoadedRef.current = false;
|
|
54
|
+
loadingCleanupTimerRef.current = null;
|
|
55
|
+
}, 500);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
previousIsLoadingRef.current = isLoadingNewsletter;
|
|
60
|
+
return () => {
|
|
61
|
+
if (loadingCleanupTimerRef.current) {
|
|
62
|
+
clearTimeout(loadingCleanupTimerRef.current);
|
|
63
|
+
loadingCleanupTimerRef.current = null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}, [isLoadingNewsletter, state.newsletterId, dispatch]);
|
|
67
|
+
// Keyboard shortcuts
|
|
68
|
+
useKeyboardShortcuts(state, dispatch, canUndo, canRedo, helpers.undo, helpers.redo);
|
|
69
|
+
// Slash command handler - always replaces the current paragraph block
|
|
70
|
+
const handleSlashCommandSelect = useCallback((blockType, replaceBlockId) => {
|
|
71
|
+
if (replaceBlockId) {
|
|
72
|
+
// Replace existing block (the paragraph that triggered the slash command)
|
|
73
|
+
const blockIndex = state.blocks.findIndex(b => b.id === replaceBlockId);
|
|
74
|
+
if (blockIndex === -1) {
|
|
75
|
+
console.warn(`[CanvasEditorView] Block with ID "${replaceBlockId}" not found. Available blocks:`, state.blocks.map(b => b.id));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const blockDefinition = blockRegistry.get(blockType);
|
|
79
|
+
if (!blockDefinition) {
|
|
80
|
+
console.warn(`[CanvasEditorView] Block type "${blockType}" not found in registry`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Replace the block in place by updating the blocks array directly
|
|
84
|
+
// This preserves the position and ID
|
|
85
|
+
const newBlocks = state.blocks.map((block, idx) => {
|
|
86
|
+
if (idx === blockIndex) {
|
|
87
|
+
// Replace this block with the new type
|
|
88
|
+
return {
|
|
89
|
+
...block,
|
|
90
|
+
type: blockType,
|
|
91
|
+
data: { ...blockDefinition.defaultData },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// Keep all other blocks unchanged
|
|
95
|
+
return block;
|
|
96
|
+
});
|
|
97
|
+
dispatch({ type: 'SET_BLOCKS', payload: newBlocks });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.warn('[CanvasEditorView] No replaceBlockId provided to handleSlashCommandSelect');
|
|
101
|
+
}
|
|
102
|
+
}, [state.blocks, dispatch]);
|
|
103
|
+
// Slash command hook
|
|
104
|
+
const slashCommand = useSlashCommand(registeredBlocks, handleSlashCommandSelect);
|
|
105
|
+
// Handle save
|
|
106
|
+
const handleSave = async () => {
|
|
107
|
+
setIsSaving(true);
|
|
108
|
+
setSaveError(null);
|
|
109
|
+
try {
|
|
110
|
+
await helpers.save();
|
|
111
|
+
setIsSaving(false);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error('[CanvasEditorView] Save error:', error);
|
|
115
|
+
let errorMessage = error.message || 'Failed to save newsletter';
|
|
116
|
+
if (errorMessage.includes('Unauthorized')) {
|
|
117
|
+
errorMessage = 'You are not authorized to save. Please log in again.';
|
|
118
|
+
}
|
|
119
|
+
setSaveError(errorMessage);
|
|
120
|
+
setIsSaving(false);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
if (isLoadingNewsletter) {
|
|
125
|
+
return (_jsx("div", { className: "h-full w-full bg-dashboard-card text-dashboard-text flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin mx-auto mb-4" }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: "Loading newsletter..." })] }) }));
|
|
126
|
+
}
|
|
127
|
+
return (_jsx("div", { className: "h-full rounded-[2.5rem] w-full bg-dashboard-card text-dashboard-text flex flex-col font-sans transition-colors duration-300 overflow-hidden relative", children: _jsxs("main", { className: "flex flex-1 flex-col relative min-h-0", children: [_jsx(ErrorBanner, { error: saveError, onDismiss: () => setSaveError(null) }), _jsx(EditorHeader, { isPreviewMode: isPreviewMode, onPreviewToggle: () => setIsPreviewMode(!isPreviewMode), isSidebarOpen: isSidebarOpen, onSidebarToggle: () => setSidebarOpen(!isSidebarOpen), isSaving: isSaving, onSave: handleSave, onSaveError: (error) => {
|
|
128
|
+
if (error) {
|
|
129
|
+
setSaveError(error);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
setSaveError(null);
|
|
133
|
+
}
|
|
134
|
+
}, isDirty: state.isDirty }), _jsxs("div", { className: "flex flex-1 relative overflow-hidden min-h-0 flex-nowrap", children: [_jsx(EditorCanvas, { isPreviewMode: isPreviewMode, contentBlocks: state.blocks, title: state.title, siteId: siteId, locale: locale, darkMode: effectiveDarkMode, backgroundColors: effectiveBackgroundColors, metadata: state.metadata, onTitleChange: (title) => dispatch({ type: 'SET_TITLE', payload: title }), onMetadataChange: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }), onBlockAdd: (type, index, containerId) => helpers.addBlock(type, index, containerId), onBlockUpdate: (id, data) => helpers.updateBlock(id, data), onBlockDelete: (id) => helpers.deleteBlock(id), onBlockMove: (id, newIndex, containerId) => helpers.moveBlock(id, newIndex, containerId), slashCommand: slashCommand }), !isPreviewMode && (_jsx("aside", { className: `transition-all duration-500 ease-[cubic-bezier(0.4,0,0.2,1)] border-l border-dashboard-border bg-dashboard-sidebar overflow-y-auto overflow-x-hidden h-full ${isSidebarOpen ? 'w-80' : 'w-0 opacity-0 pointer-events-none'}`, children: _jsx(EditorSidebar, { slug: state.slug, metadata: state.metadata, status: state.status, onMetadataUpdate: (metadata) => dispatch({ type: 'SET_METADATA', payload: metadata }) }) }))] }), slashCommand.isOpen && slashCommand.position && (_jsx(SlashCommandMenu, { blocks: slashCommand.filteredBlocks, query: slashCommand.query, selectedIndex: slashCommand.selectedIndex, onSelect: (replaceBlockId) => {
|
|
135
|
+
// Use the replaceBlockId passed from the menu (which comes from state)
|
|
136
|
+
// This ensures we use the correct block ID that was set when the menu opened
|
|
137
|
+
slashCommand.selectCurrent(replaceBlockId);
|
|
138
|
+
}, position: slashCommand.position, onClose: slashCommand.closeMenu, replaceBlockId: slashCommand.replaceBlockId }))] }) }));
|
|
139
|
+
}
|