@jhits/plugin-newsletter 0.0.7 → 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.
Files changed (45) hide show
  1. package/package.json +2 -3
  2. package/src/api/handler.ts +0 -693
  3. package/src/api/router.ts +0 -111
  4. package/src/index.server.ts +0 -12
  5. package/src/index.tsx +0 -313
  6. package/src/index.tsx.patch +0 -98
  7. package/src/init.tsx +0 -72
  8. package/src/lib/blocks/BlockRenderer.tsx +0 -125
  9. package/src/lib/email/EmailRenderer.tsx +0 -425
  10. package/src/lib/email/index.ts +0 -6
  11. package/src/lib/mappers/apiMapper.ts +0 -57
  12. package/src/lib/utils/blockHelpers.ts +0 -71
  13. package/src/lib/utils/slugify.ts +0 -43
  14. package/src/registry/BlockRegistry.ts +0 -53
  15. package/src/registry/index.ts +0 -5
  16. package/src/state/EditorContext.tsx +0 -279
  17. package/src/state/index.ts +0 -10
  18. package/src/state/reducer.ts +0 -561
  19. package/src/state/types.ts +0 -154
  20. package/src/types/block.ts +0 -275
  21. package/src/types/newsletter.ts +0 -151
  22. package/src/types/registry.ts +0 -14
  23. package/src/views/CanvasEditor/BlockWrapper.tsx +0 -143
  24. package/src/views/CanvasEditor/CanvasEditorView.tsx +0 -249
  25. package/src/views/CanvasEditor/EditorBody.tsx +0 -95
  26. package/src/views/CanvasEditor/EditorHeader.tsx +0 -139
  27. package/src/views/CanvasEditor/components/CustomBlockItem.tsx +0 -83
  28. package/src/views/CanvasEditor/components/EditorCanvas.tsx +0 -674
  29. package/src/views/CanvasEditor/components/EditorLibrary.tsx +0 -120
  30. package/src/views/CanvasEditor/components/EditorSidebar.tsx +0 -156
  31. package/src/views/CanvasEditor/components/ErrorBanner.tsx +0 -31
  32. package/src/views/CanvasEditor/components/LibraryItem.tsx +0 -71
  33. package/src/views/CanvasEditor/components/SlashCommandDetector.tsx +0 -196
  34. package/src/views/CanvasEditor/components/SlashCommandMenu.tsx +0 -131
  35. package/src/views/CanvasEditor/components/index.ts +0 -16
  36. package/src/views/CanvasEditor/hooks/index.ts +0 -7
  37. package/src/views/CanvasEditor/hooks/useKeyboardShortcuts.ts +0 -136
  38. package/src/views/CanvasEditor/hooks/useNewsletterLoader.ts +0 -34
  39. package/src/views/CanvasEditor/hooks/useRegisteredBlocks.ts +0 -54
  40. package/src/views/CanvasEditor/hooks/useSlashCommand.ts +0 -106
  41. package/src/views/CanvasEditor/index.ts +0 -12
  42. package/src/views/NewsletterEditor.tsx +0 -38
  43. package/src/views/NewsletterManager.tsx +0 -240
  44. package/src/views/SettingsView.tsx +0 -216
  45. package/src/views/SubscribersView.tsx +0 -269
@@ -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
- }
@@ -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
- }
@@ -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
- }