@parathantl/react-email-editor 0.1.0
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/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/index.cjs +7354 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.css +1616 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +515 -0
- package/dist/index.d.ts +515 -0
- package/dist/index.js +7290 -0
- package/dist/index.js.map +1 -0
- package/package.json +83 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 parathantl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# @parathantl/react-email-editor
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@parathantl/react-email-editor)
|
|
4
|
+
[](https://github.com/parathantl/react-email-editor/blob/main/LICENSE)
|
|
5
|
+
|
|
6
|
+
A visual drag-and-drop email template editor for React, powered by MJML. Build responsive HTML emails with a rich block-based editor, real-time preview, and full MJML round-trip support.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- 12 block types: Text, Heading, Button, Image, Video, Divider, Spacer, Social, HTML, Countdown, Menu, Hero
|
|
11
|
+
- Drag-and-drop block reordering and section management
|
|
12
|
+
- Rich text editing (TipTap) with formatting toolbar
|
|
13
|
+
- MJML generation, parsing, and HTML compilation
|
|
14
|
+
- Template variable support (`{{ variable }}` syntax)
|
|
15
|
+
- Built-in persistence with localStorage or custom adapters
|
|
16
|
+
- Responsive editor UI with collapsible panels
|
|
17
|
+
- Undo/redo history (50 steps)
|
|
18
|
+
- Export to MJML, HTML, and PDF
|
|
19
|
+
- Extensible via registry pattern (add custom block types)
|
|
20
|
+
- Full TypeScript support with strict mode
|
|
21
|
+
- CSS variables for easy theming
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install @parathantl/react-email-editor
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Peer dependencies:** React 18+, React DOM 18+
|
|
30
|
+
|
|
31
|
+
**Optional:** Install `mjml-browser` for HTML compilation (MJML to HTML conversion):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install mjml-browser
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { EmailEditor } from '@parathantl/react-email-editor';
|
|
41
|
+
import '@parathantl/react-email-editor/styles.css';
|
|
42
|
+
|
|
43
|
+
function App() {
|
|
44
|
+
return (
|
|
45
|
+
<div style={{ height: '100vh' }}>
|
|
46
|
+
<EmailEditor
|
|
47
|
+
onChange={(template) => console.log(template)}
|
|
48
|
+
onSave={(mjml, html) => console.log(mjml, html)}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Persistence
|
|
56
|
+
|
|
57
|
+
Templates auto-save and restore when you provide a `persistenceKey`. Each key stores a separate template, so multiple editor instances can coexist.
|
|
58
|
+
|
|
59
|
+
### localStorage (default)
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<EmailEditor persistenceKey="campaign-123" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Custom Adapter (server, IndexedDB, etc.)
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import type { PersistenceAdapter } from '@parathantl/react-email-editor';
|
|
69
|
+
|
|
70
|
+
const serverAdapter: PersistenceAdapter = {
|
|
71
|
+
save(key, template) {
|
|
72
|
+
fetch(`/api/templates/${key}`, {
|
|
73
|
+
method: 'PUT',
|
|
74
|
+
body: JSON.stringify(template),
|
|
75
|
+
headers: { 'Content-Type': 'application/json' },
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
load(key) {
|
|
79
|
+
// Must be synchronous — preload data before rendering the editor
|
|
80
|
+
return window.__PRELOADED_TEMPLATES__?.[key] ?? null;
|
|
81
|
+
},
|
|
82
|
+
remove(key) {
|
|
83
|
+
fetch(`/api/templates/${key}`, { method: 'DELETE' });
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
<EmailEditor persistenceKey="campaign-123" persistenceAdapter={serverAdapter} />
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Priority order:** persisted data > `initialTemplate` > `initialMJML`
|
|
91
|
+
|
|
92
|
+
## Loading MJML from Another Component
|
|
93
|
+
|
|
94
|
+
Use the ref API to control the editor from parent components:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
import { useRef } from 'react';
|
|
98
|
+
import { EmailEditor } from '@parathantl/react-email-editor';
|
|
99
|
+
import type { EmailEditorRef } from '@parathantl/react-email-editor';
|
|
100
|
+
|
|
101
|
+
function TemplateDesigner() {
|
|
102
|
+
const editorRef = useRef<EmailEditorRef>(null);
|
|
103
|
+
|
|
104
|
+
const loadFromServer = async () => {
|
|
105
|
+
const res = await fetch('/api/templates/welcome');
|
|
106
|
+
const mjml = await res.text();
|
|
107
|
+
editorRef.current?.loadMJML(mjml);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleSave = async () => {
|
|
111
|
+
const mjml = editorRef.current?.getMJML();
|
|
112
|
+
const html = await editorRef.current?.getHTML();
|
|
113
|
+
// Send to your API
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div>
|
|
118
|
+
<button onClick={loadFromServer}>Load Template</button>
|
|
119
|
+
<button onClick={handleSave}>Save</button>
|
|
120
|
+
<EmailEditor ref={editorRef} persistenceKey="welcome" />
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or pass MJML at init time without a ref:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
<EmailEditor initialMJML={mjmlString} />
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Ref API
|
|
133
|
+
|
|
134
|
+
| Method | Returns | Description |
|
|
135
|
+
|--------|---------|-------------|
|
|
136
|
+
| `getMJML()` | `string` | Get current template as MJML |
|
|
137
|
+
| `getHTML()` | `Promise<string>` | Compile and get HTML output |
|
|
138
|
+
| `getJSON()` | `EmailTemplate` | Get template as JSON object |
|
|
139
|
+
| `loadMJML(source)` | `void` | Parse MJML and load into editor |
|
|
140
|
+
| `loadJSON(template)` | `void` | Load an EmailTemplate object |
|
|
141
|
+
| `insertBlock(type, sectionIdx?)` | `void` | Programmatically add a block |
|
|
142
|
+
| `getVariables()` | `string[]` | Extract `{{ variable }}` keys |
|
|
143
|
+
| `undo()` | `void` | Undo last action |
|
|
144
|
+
| `redo()` | `void` | Redo last undone action |
|
|
145
|
+
| `reset()` | `void` | Clear all content |
|
|
146
|
+
| `clearPersisted()` | `void` | Remove saved data for the current key |
|
|
147
|
+
| `exportPDF()` | `Promise<void>` | Generate PDF via print dialog |
|
|
148
|
+
|
|
149
|
+
## Props
|
|
150
|
+
|
|
151
|
+
| Prop | Type | Description |
|
|
152
|
+
|------|------|-------------|
|
|
153
|
+
| `initialTemplate` | `EmailTemplate` | Initial template object |
|
|
154
|
+
| `initialMJML` | `string` | Initial MJML string (parsed on mount) |
|
|
155
|
+
| `variables` | `Variable[]` | Template variables for `{{ }}` insertion |
|
|
156
|
+
| `imageUploadAdapter` | `ImageUploadAdapter` | Custom image upload handler |
|
|
157
|
+
| `onChange` | `(template: EmailTemplate) => void` | Called on every template change (debounced 150ms) |
|
|
158
|
+
| `onSave` | `(mjml: string, html: string) => void` | Called on Ctrl+S |
|
|
159
|
+
| `onReady` | `() => void` | Called once after editor mounts |
|
|
160
|
+
| `fontFamilies` | `string[]` | Custom font options for the toolbar |
|
|
161
|
+
| `fontSizes` | `string[]` | Custom font size options |
|
|
162
|
+
| `persistenceKey` | `string` | Key for auto-save/restore (enables persistence) |
|
|
163
|
+
| `persistenceAdapter` | `PersistenceAdapter` | Custom storage adapter (defaults to localStorage) |
|
|
164
|
+
| `className` | `string` | CSS class for the outer wrapper |
|
|
165
|
+
| `style` | `CSSProperties` | Inline styles for the outer wrapper |
|
|
166
|
+
|
|
167
|
+
## Block Types
|
|
168
|
+
|
|
169
|
+
| Type | Description | MJML Output |
|
|
170
|
+
|------|-------------|-------------|
|
|
171
|
+
| `text` | Rich text with formatting | `<mj-text>` |
|
|
172
|
+
| `heading` | Heading (h1–h4) with level selector | `<mj-text><h2>...</h2></mj-text>` |
|
|
173
|
+
| `button` | Call-to-action button | `<mj-button>` |
|
|
174
|
+
| `image` | Image with optional link | `<mj-image>` |
|
|
175
|
+
| `video` | Video thumbnail with play overlay | `<mj-image>` (linked) |
|
|
176
|
+
| `divider` | Horizontal line | `<mj-divider>` |
|
|
177
|
+
| `spacer` | Vertical spacing | `<mj-spacer>` |
|
|
178
|
+
| `social` | Social media icon links | `<mj-social>` |
|
|
179
|
+
| `html` | Raw HTML content | `<mj-text>` |
|
|
180
|
+
| `countdown` | Live countdown timer with digit boxes | `<mj-text>` (HTML table) |
|
|
181
|
+
| `menu` | Navigation menu links | `<mj-navbar>` |
|
|
182
|
+
| `hero` | Heading + subtext + CTA button | `<mj-text>` (composite HTML) |
|
|
183
|
+
|
|
184
|
+
## Template Variables
|
|
185
|
+
|
|
186
|
+
Define variables and insert them into text blocks as `{{ variable_name }}`:
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
<EmailEditor
|
|
190
|
+
variables={[
|
|
191
|
+
{ key: 'first_name', sample: 'John', label: 'First Name', group: 'Contact' },
|
|
192
|
+
{ key: 'company', sample: 'Acme Inc', label: 'Company', group: 'Contact' },
|
|
193
|
+
{ key: 'unsubscribe_url', sample: '#', label: 'Unsubscribe URL', group: 'Links' },
|
|
194
|
+
]}
|
|
195
|
+
/>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Extract used variables programmatically:
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
const keys = editorRef.current?.getVariables();
|
|
202
|
+
// ['first_name', 'unsubscribe_url']
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Custom Block Types
|
|
206
|
+
|
|
207
|
+
Extend the editor with your own block types using the registry:
|
|
208
|
+
|
|
209
|
+
```tsx
|
|
210
|
+
import {
|
|
211
|
+
registerBlockRenderer,
|
|
212
|
+
registerBlockProperties,
|
|
213
|
+
registerBlockGenerator,
|
|
214
|
+
} from '@parathantl/react-email-editor';
|
|
215
|
+
|
|
216
|
+
// Register a canvas renderer
|
|
217
|
+
registerBlockRenderer('my-block', MyBlockComponent);
|
|
218
|
+
|
|
219
|
+
// Register a properties panel
|
|
220
|
+
registerBlockProperties('my-block', MyBlockPropertiesPanel);
|
|
221
|
+
|
|
222
|
+
// Register an MJML generator
|
|
223
|
+
registerBlockGenerator('my-block', (block, indent) => {
|
|
224
|
+
return `${indent}<mj-text>${block.properties.content}</mj-text>`;
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Theming
|
|
229
|
+
|
|
230
|
+
The editor uses CSS custom properties scoped under `--ee-*`. Override them to match your app:
|
|
231
|
+
|
|
232
|
+
```css
|
|
233
|
+
:root {
|
|
234
|
+
--ee-color-primary: #8b5cf6;
|
|
235
|
+
--ee-color-primary-hover: #7c3aed;
|
|
236
|
+
--ee-bg-panel: #fafafa;
|
|
237
|
+
--ee-border-radius: 8px;
|
|
238
|
+
--ee-font-family: 'Inter', sans-serif;
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
See `src/styles/variables.css` for the full list of 70+ customizable tokens.
|
|
243
|
+
|
|
244
|
+
## Keyboard Shortcuts
|
|
245
|
+
|
|
246
|
+
| Shortcut | Action |
|
|
247
|
+
|----------|--------|
|
|
248
|
+
| `Ctrl/Cmd + Z` | Undo |
|
|
249
|
+
| `Ctrl/Cmd + Shift + Z` / `Ctrl/Cmd + Y` | Redo |
|
|
250
|
+
| `Ctrl/Cmd + S` | Save (triggers `onSave`) |
|
|
251
|
+
| `Escape` | Deselect block/section |
|
|
252
|
+
| `Delete` / `Backspace` | Remove selected block or section |
|
|
253
|
+
|
|
254
|
+
## Responsive Editor
|
|
255
|
+
|
|
256
|
+
The editor automatically adapts to smaller screens:
|
|
257
|
+
|
|
258
|
+
- **>= 1024px** — Full 3-panel layout (sidebar, canvas, properties)
|
|
259
|
+
- **< 1024px** — Panels collapse into toggleable overlays with toolbar buttons
|
|
260
|
+
|
|
261
|
+
## Browser Support
|
|
262
|
+
|
|
263
|
+
Supports all modern browsers (Chrome, Firefox, Safari, Edge). Requires React 18+.
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
[MIT](LICENSE)
|