@lab2view/vue-email-editor 0.2.7 → 0.2.10
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/README.md +37 -488
- package/dist/{AiChatPanel-CKHjYcuP.js → AiChatPanel-DBC4q8wx.js} +2 -2
- package/dist/{CodeEditor-4dfVt7cm.js → CodeEditor-BfGD5J7z.js} +2 -2
- package/dist/{InlineTextEditor-DvzsFt8K.js → InlineTextEditor-Dm2jkEsL.js} +2 -2
- package/dist/email-editor.js +1 -1
- package/dist/{index-D2Vk6Cbv.js → index-Rt0aY248.js} +8129 -8095
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,129 +10,18 @@
|
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
13
|
-
A professional
|
|
14
|
-
Design responsive HTML emails visually
|
|
15
|
-
<!-- <strong>Free and open-source alternative to Unlayer, Beefree, and Stripo.</strong> -->
|
|
13
|
+
A professional <strong>drag-and-drop</strong> email editor built with <strong>Vue 3</strong> and <strong>MJML</strong>.<br/>
|
|
14
|
+
Design responsive HTML emails visually — 43 blocks, AI generation, merge tags, plugins, i18n, and more.
|
|
16
15
|
</p>
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
<a href="https://lab2view.github.io/vue-email-editor/#templates"><strong>Try the editor live →</strong></a>
|
|
20
|
-
</p> -->
|
|
21
|
-
|
|
22
|
-
## Screenshots
|
|
23
|
-
|
|
24
|
-
| Blocks & Layout | Ready-made Templates |
|
|
25
|
-
|:---:|:---:|
|
|
26
|
-
|  |  |
|
|
27
|
-
|
|
28
|
-
| Property Editing | Layers Panel |
|
|
17
|
+
| Drag & Drop Builder | Ready-made Templates |
|
|
29
18
|
|:---:|:---:|
|
|
30
|
-
|  |
|
|
35
|
-
|
|
36
|
-
## Features
|
|
37
|
-
|
|
38
|
-
### Visual Drag & Drop Builder
|
|
39
|
-
- Intuitive block-based editor with live preview
|
|
40
|
-
- Drag blocks from the sidebar onto the canvas
|
|
41
|
-
- Reorder and nest elements with visual drop indicators
|
|
42
|
-
- Responsive preview in Desktop, Tablet, and Mobile modes
|
|
43
|
-
- Iframe-isolated canvas for accurate email rendering
|
|
44
|
-
|
|
45
|
-
### 43 Pre-built Blocks
|
|
46
|
-
|
|
47
|
-
| Category | Count | Examples |
|
|
48
|
-
|----------|-------|---------|
|
|
49
|
-
| **Layout** | 6 | 1-4 columns, sidebar left/right |
|
|
50
|
-
| **Content** | 7 | Text, Image, Button, Divider, Spacer, Social, Hero |
|
|
51
|
-
| **Composite** | 30 | Header, Hero Banner, Pricing, Testimonial, FAQ, Product Card, Footer, and more |
|
|
52
|
-
|
|
53
|
-
### Rich Text Editing
|
|
54
|
-
Powered by [TipTap](https://tiptap.dev), with inline formatting:
|
|
55
|
-
- Bold, Italic, Underline, Strikethrough
|
|
56
|
-
- Text alignment (left, center, right)
|
|
57
|
-
- Text color picker
|
|
58
|
-
- Link insertion and editing
|
|
59
|
-
|
|
60
|
-
### MJML-Powered Rendering
|
|
61
|
-
- Real-time MJML to HTML compilation via [mjml-browser](https://github.com/mjmlio/mjml)
|
|
62
|
-
- 13 supported MJML node types
|
|
63
|
-
- Export as MJML, compiled HTML, or design JSON
|
|
64
|
-
- Legacy GrapesJS format detection for migration
|
|
65
|
-
|
|
66
|
-
### Undo / Redo
|
|
67
|
-
- Full history with keyboard shortcuts (`Ctrl+Z` / `Ctrl+Shift+Z`)
|
|
68
|
-
- Reactive `canUndo` / `canRedo` state
|
|
69
|
-
|
|
70
|
-
### Merge Tags
|
|
71
|
-
- Insert dynamic variables into text content (`{{first_name}}`, `{{company}}`, etc.)
|
|
72
|
-
- Categorized merge tag picker in the inline toolbar
|
|
73
|
-
- Visual chip rendering in the editor
|
|
74
|
-
- TipTap extension for atomic merge tag nodes
|
|
75
|
-
|
|
76
|
-
### Conditional Content
|
|
77
|
-
- Show/hide email sections based on merge tag values
|
|
78
|
-
- Visual condition builder on any node (variable, operator, value)
|
|
79
|
-
- 6 operators: equals, not equals, contains, not contains, exists, not exists
|
|
80
|
-
- Exports as HTML conditional comments for ESP processing
|
|
81
|
-
|
|
82
|
-
### AI Template Generation
|
|
83
|
-
- Describe an email in plain language and get a complete, production-ready template
|
|
84
|
-
- Built-in AI chat panel with multi-turn conversation for iterative refinement
|
|
85
|
-
- Live HTML preview of the generated template before applying
|
|
86
|
-
- Streaming support for real-time generation feedback
|
|
87
|
-
- Automatic JSON repair for robust parsing of AI responses with auto-retry
|
|
88
|
-
- BYOAI (Bring Your Own AI) — plug in OpenAI, Anthropic, or any LLM
|
|
89
|
-
|
|
90
|
-
### AI Text Generation
|
|
91
|
-
- Inline text generation, improvement, shortening, expansion, and translation
|
|
92
|
-
- Custom prompt input for freeform generation
|
|
93
|
-
- Non-blocking async generation with loading state
|
|
94
|
-
|
|
95
|
-
### Dark Mode Preview
|
|
96
|
-
- Toggle dark mode preview in the toolbar (Moon/Sun icon)
|
|
97
|
-
- Simulates email client dark mode behavior in the canvas
|
|
98
|
-
- Background inversion, text color adjustment, link color adaptation
|
|
99
|
-
|
|
100
|
-
### ESP Export Presets
|
|
101
|
-
- Export HTML pre-formatted for 6 email service providers
|
|
102
|
-
- **Mailchimp**: `*|FNAME|*` merge tags, auto-inject unsubscribe
|
|
103
|
-
- **SendGrid**: `{{variable}}` handlebars format
|
|
104
|
-
- **Brevo**: `{{ contact.ATTRIBUTE }}` format
|
|
105
|
-
- **AWS SES**: `{{camelCase}}` template variables
|
|
106
|
-
- **Postmark**: `{{variable}}` Mustache format
|
|
107
|
-
- **Resend**: `{{variable}}` format
|
|
108
|
-
- Custom preset support for any ESP
|
|
109
|
-
|
|
110
|
-
### Image Upload
|
|
111
|
-
- Drag-and-drop image upload with progress indicator
|
|
112
|
-
- Asset browser integration via callback
|
|
113
|
-
- File type validation and size limits
|
|
114
|
-
- Preview with change/remove actions
|
|
115
|
-
|
|
116
|
-
### Property Editing
|
|
117
|
-
- 40+ editable MJML attributes across 11 property types
|
|
118
|
-
- Color pickers, padding controls, alignment selectors
|
|
119
|
-
- Global styles panel (email background, default font, preview text)
|
|
120
|
-
- Custom fonts support
|
|
121
|
-
|
|
122
|
-
### 22 Starter Templates
|
|
123
|
-
Professional email templates for common use cases: welcome, newsletter, e-commerce, abandoned cart, product launch, shipping notification, birthday, seasonal sale, SaaS onboarding, and more.
|
|
124
|
-
|
|
125
|
-
---
|
|
19
|
+
|  |  |
|
|
126
20
|
|
|
127
21
|
## Installation
|
|
128
22
|
|
|
129
23
|
```bash
|
|
130
|
-
npm install @lab2view/vue-email-editor
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
**Peer dependencies:**
|
|
134
|
-
```bash
|
|
135
|
-
npm install vue@^3.4 mjml-browser@^4.15
|
|
24
|
+
npm install @lab2view/vue-email-editor vue@^3.4 mjml-browser@^4.15
|
|
136
25
|
```
|
|
137
26
|
|
|
138
27
|
## Quick Start
|
|
@@ -140,9 +29,8 @@ npm install vue@^3.4 mjml-browser@^4.15
|
|
|
140
29
|
```vue
|
|
141
30
|
<script setup lang="ts">
|
|
142
31
|
import { ref } from 'vue'
|
|
143
|
-
import { EmailEditor
|
|
32
|
+
import { EmailEditor } from '@lab2view/vue-email-editor'
|
|
144
33
|
|
|
145
|
-
const editorRef = ref()
|
|
146
34
|
const mjml = ref('')
|
|
147
35
|
const html = ref('')
|
|
148
36
|
const designJson = ref()
|
|
@@ -153,399 +41,60 @@ const designJson = ref()
|
|
|
153
41
|
ref="editorRef"
|
|
154
42
|
v-model="mjml"
|
|
155
43
|
:design-json="designJson"
|
|
156
|
-
:labels="FR_LABELS"
|
|
157
44
|
@update:compiled-html="html = $event"
|
|
158
45
|
@update:design-json="designJson = $event"
|
|
159
46
|
/>
|
|
160
47
|
</template>
|
|
161
48
|
```
|
|
162
49
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Customize the editor appearance with the `theme` prop:
|
|
166
|
-
|
|
167
|
-
```vue
|
|
168
|
-
<EmailEditor
|
|
169
|
-
:theme="{
|
|
170
|
-
primaryColor: '#7C3AED',
|
|
171
|
-
primaryHover: '#6D28D9',
|
|
172
|
-
borderRadius: '8px',
|
|
173
|
-
fontFamily: 'Inter, sans-serif',
|
|
174
|
-
}"
|
|
175
|
-
/>
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Available Theme Properties
|
|
179
|
-
|
|
180
|
-
| Property | Default | Description |
|
|
181
|
-
|----------|---------|-------------|
|
|
182
|
-
| `primaryColor` | `#01A8AB` | Main accent color |
|
|
183
|
-
| `primaryHover` | `#018F91` | Hover state |
|
|
184
|
-
| `primaryActive` | `#017375` | Active/pressed state |
|
|
185
|
-
| `backgroundColor` | `#ffffff` | Panel backgrounds |
|
|
186
|
-
| `borderColor` | `#e5e7eb` | Border color |
|
|
187
|
-
| `textPrimary` | `#1f2937` | Primary text |
|
|
188
|
-
| `textSecondary` | `#6b7280` | Secondary text |
|
|
189
|
-
| `canvasBg` | `#e5e7eb` | Canvas background |
|
|
190
|
-
| `selectionColor` | `#01A8AB` | Selected node outline |
|
|
191
|
-
| `fontFamily` | System stack | UI font family |
|
|
192
|
-
| `fontSize` | `13px` | UI font size |
|
|
193
|
-
| `borderRadius` | `6px` | UI border radius |
|
|
194
|
-
|
|
195
|
-
See `ThemeConfig` in [src/types.ts](src/types.ts) for the full list of 25 properties.
|
|
196
|
-
|
|
197
|
-
## Internationalization (i18n)
|
|
198
|
-
|
|
199
|
-
The editor ships with English defaults and a complete French translation. Pass custom labels via the `labels` prop:
|
|
200
|
-
|
|
201
|
-
```vue
|
|
202
|
-
<script setup>
|
|
203
|
-
import { EmailEditor, FR_LABELS } from '@lab2view/vue-email-editor'
|
|
204
|
-
</script>
|
|
205
|
-
|
|
206
|
-
<!-- French UI -->
|
|
207
|
-
<EmailEditor :labels="FR_LABELS" />
|
|
208
|
-
|
|
209
|
-
<!-- Custom labels (partial override) -->
|
|
210
|
-
<EmailEditor :labels="{ editor_title: 'Mein Editor', undo: 'Ruckgangig' }" />
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
175+ label keys cover all UI elements: toolbar, sidebar tabs, block names, property labels, option values, and status messages. See `EditorLabels` in [src/labels.ts](src/labels.ts).
|
|
214
|
-
|
|
215
|
-
## Imperative API
|
|
216
|
-
|
|
217
|
-
Access the editor programmatically via template ref:
|
|
218
|
-
|
|
219
|
-
```vue
|
|
220
|
-
<script setup>
|
|
221
|
-
import { ref, onMounted } from 'vue'
|
|
222
|
-
import { EmailEditor } from '@lab2view/vue-email-editor'
|
|
223
|
-
|
|
224
|
-
const editor = ref()
|
|
225
|
-
|
|
226
|
-
onMounted(() => {
|
|
227
|
-
// Export
|
|
228
|
-
const mjml = editor.value.getMjml()
|
|
229
|
-
const html = editor.value.getHtml()
|
|
230
|
-
const json = editor.value.getDesignJson()
|
|
231
|
-
|
|
232
|
-
// History
|
|
233
|
-
editor.value.undo()
|
|
234
|
-
editor.value.redo()
|
|
235
|
-
|
|
236
|
-
// Selection
|
|
237
|
-
editor.value.selectNode('node-id')
|
|
238
|
-
const selected = editor.value.getSelectedNode()
|
|
239
|
-
editor.value.clearSelection()
|
|
240
|
-
|
|
241
|
-
// Manipulation
|
|
242
|
-
editor.value.deleteNode('node-id')
|
|
243
|
-
editor.value.duplicateNode('node-id')
|
|
244
|
-
editor.value.updateNodeAttribute('node-id', 'color', '#ff0000')
|
|
245
|
-
|
|
246
|
-
// Events
|
|
247
|
-
editor.value.on('editor:change', ({ document }) => {
|
|
248
|
-
console.log('Document changed', document)
|
|
249
|
-
})
|
|
250
|
-
})
|
|
251
|
-
</script>
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Full API Reference
|
|
255
|
-
|
|
256
|
-
| Method | Returns | Description |
|
|
257
|
-
|--------|---------|-------------|
|
|
258
|
-
| `getDocument()` | `EmailDocument` | Current document tree |
|
|
259
|
-
| `setDocument(doc)` | `void` | Replace entire document |
|
|
260
|
-
| `getMjml()` | `string` | Export as MJML string |
|
|
261
|
-
| `getHtml()` | `string` | Export as compiled HTML |
|
|
262
|
-
| `getDesignJson()` | `EmailDesignJson` | Export persisted format |
|
|
263
|
-
| `undo()` | `void` | Undo last change |
|
|
264
|
-
| `redo()` | `void` | Redo last undone change |
|
|
265
|
-
| `canUndo()` | `boolean` | Whether undo is available |
|
|
266
|
-
| `canRedo()` | `boolean` | Whether redo is available |
|
|
267
|
-
| `selectNode(id)` | `void` | Select a node by ID |
|
|
268
|
-
| `getSelectedNode()` | `EmailNode \| null` | Get selected node |
|
|
269
|
-
| `clearSelection()` | `void` | Deselect current node |
|
|
270
|
-
| `deleteNode(id)` | `void` | Remove a node |
|
|
271
|
-
| `duplicateNode(id)` | `NodeId \| null` | Clone a node |
|
|
272
|
-
| `insertBlock(block, parentId, index?)` | `NodeId \| null` | Insert a block |
|
|
273
|
-
| `updateNodeAttribute(id, key, value)` | `void` | Update an attribute |
|
|
274
|
-
| `loadTemplate(doc)` | `void` | Load an EmailDocument |
|
|
275
|
-
| `on(event, handler)` | `void` | Subscribe to event |
|
|
276
|
-
| `off(event, handler)` | `void` | Unsubscribe from event |
|
|
277
|
-
|
|
278
|
-
## Events
|
|
279
|
-
|
|
280
|
-
Subscribe to editor events for real-time notifications:
|
|
281
|
-
|
|
282
|
-
```ts
|
|
283
|
-
editor.value.on('node:selected', ({ nodeId, node }) => {
|
|
284
|
-
console.log('Selected:', node.type)
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
editor.value.on('editor:change', ({ document }) => {
|
|
288
|
-
// Auto-save
|
|
289
|
-
saveToServer(document)
|
|
290
|
-
})
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
| Event | Payload | Trigger |
|
|
294
|
-
|-------|---------|---------|
|
|
295
|
-
| `editor:ready` | `{ document }` | Editor initialized |
|
|
296
|
-
| `editor:change` | `{ document }` | Any document change |
|
|
297
|
-
| `node:selected` | `{ nodeId, node }` | Node selected |
|
|
298
|
-
| `node:deselected` | `{ nodeId }` | Selection cleared |
|
|
299
|
-
| `node:deleted` | `{ nodeId }` | Node removed |
|
|
300
|
-
| `node:moved` | `{ nodeId, fromParentId, toParentId }` | Node repositioned |
|
|
301
|
-
| `node:duplicated` | `{ originalId, newId }` | Node cloned |
|
|
302
|
-
| `block:dropped` | `{ blockId, parentId }` | Block added from panel |
|
|
303
|
-
| `history:undo` | `{ canUndo, canRedo }` | Undo performed |
|
|
304
|
-
| `history:redo` | `{ canUndo, canRedo }` | Redo performed |
|
|
305
|
-
| `property:changed` | `{ nodeId, key, value }` | Attribute updated |
|
|
306
|
-
|
|
307
|
-
## Plugin System
|
|
308
|
-
|
|
309
|
-
Extend the editor with custom blocks, property editors, toolbar actions, and sidebar panels:
|
|
310
|
-
|
|
311
|
-
```ts
|
|
312
|
-
import type { Plugin } from '@lab2view/vue-email-editor'
|
|
313
|
-
import { createText } from '@lab2view/vue-email-editor'
|
|
314
|
-
|
|
315
|
-
const myPlugin: Plugin = (ctx) => {
|
|
316
|
-
// Add a custom block
|
|
317
|
-
ctx.registerBlock({
|
|
318
|
-
id: 'custom-banner',
|
|
319
|
-
label: 'Custom Banner',
|
|
320
|
-
category: 'content',
|
|
321
|
-
icon: 'Sparkles',
|
|
322
|
-
factory: () => createText('<h1>My Custom Block</h1>', {
|
|
323
|
-
align: 'center',
|
|
324
|
-
'font-size': '24px',
|
|
325
|
-
}),
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
// Add a new block category
|
|
329
|
-
ctx.registerBlockCategory({
|
|
330
|
-
id: 'my-category',
|
|
331
|
-
label: 'My Blocks',
|
|
332
|
-
icon: 'Package',
|
|
333
|
-
order: 50,
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
// Add a toolbar button
|
|
337
|
-
ctx.registerToolbarAction({
|
|
338
|
-
id: 'save',
|
|
339
|
-
label: 'Save',
|
|
340
|
-
icon: 'Save',
|
|
341
|
-
handler: () => console.log('Saving...'),
|
|
342
|
-
position: 'right',
|
|
343
|
-
})
|
|
344
|
-
|
|
345
|
-
// Listen to events
|
|
346
|
-
ctx.on('editor:change', ({ document }) => {
|
|
347
|
-
console.log('Auto-saving...')
|
|
348
|
-
})
|
|
349
|
-
}
|
|
350
|
-
```
|
|
351
|
-
|
|
352
|
-
```vue
|
|
353
|
-
<EmailEditor :plugins="[myPlugin]" />
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
### Plugin Context API
|
|
357
|
-
|
|
358
|
-
| Method | Description |
|
|
359
|
-
|--------|-------------|
|
|
360
|
-
| `registerBlock(block)` | Add a custom block to the blocks panel |
|
|
361
|
-
| `registerBlockCategory(category)` | Define a new block category |
|
|
362
|
-
| `registerPropertyEditor(type, component)` | Override a property editor component |
|
|
363
|
-
| `registerToolbarAction(action)` | Add a button to the toolbar |
|
|
364
|
-
| `registerSidebarPanel(panel)` | Add a custom sidebar tab/panel |
|
|
365
|
-
| `on(event, handler)` | Subscribe to editor events |
|
|
366
|
-
| `off(event, handler)` | Unsubscribe from events |
|
|
367
|
-
| `labels` | Reactive reference to current labels |
|
|
368
|
-
|
|
369
|
-
## Exports
|
|
370
|
-
|
|
371
|
-
The package provides 35+ exports for advanced usage:
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
// Component
|
|
375
|
-
import { EmailEditor } from '@lab2view/vue-email-editor'
|
|
376
|
-
|
|
377
|
-
// Labels & i18n
|
|
378
|
-
import { DEFAULT_LABELS, FR_LABELS } from '@lab2view/vue-email-editor'
|
|
379
|
-
|
|
380
|
-
// Types
|
|
381
|
-
import type {
|
|
382
|
-
EmailDocument, EmailNode, EmailDesignJson, EmailEditorAPI,
|
|
383
|
-
Plugin, PluginContext, ThemeConfig, EditorEventMap,
|
|
384
|
-
BlockDefinition, PropertyDefinition,
|
|
385
|
-
} from '@lab2view/vue-email-editor'
|
|
386
|
-
|
|
387
|
-
// Serialization
|
|
388
|
-
import { compileMjml, documentToMjml, mjmlToDocument } from '@lab2view/vue-email-editor'
|
|
389
|
-
|
|
390
|
-
// Node factories
|
|
391
|
-
import {
|
|
392
|
-
createDefaultDocument, createSection, createColumn,
|
|
393
|
-
createText, createImage, createButton, createDivider,
|
|
394
|
-
createSpacer, createSocial, createHero, createWrapper,
|
|
395
|
-
} from '@lab2view/vue-email-editor'
|
|
396
|
-
|
|
397
|
-
// Tree utilities
|
|
398
|
-
import { findNode, findParent, removeNode, moveNode, cloneSubtree } from '@lab2view/vue-email-editor'
|
|
399
|
-
|
|
400
|
-
// Constants
|
|
401
|
-
import {
|
|
402
|
-
DEFAULT_THEME, STATIC_BLOCKS,
|
|
403
|
-
CONTENT_NODE_TYPES, CONTAINER_NODE_TYPES, SELF_CLOSING_NODE_TYPES,
|
|
404
|
-
} from '@lab2view/vue-email-editor'
|
|
405
|
-
|
|
406
|
-
// ESP Export Presets
|
|
407
|
-
import {
|
|
408
|
-
exportForEsp, exportForMailchimp, exportForSendGrid,
|
|
409
|
-
exportForBrevo, exportForAwsSes, exportForPostmark, exportForResend,
|
|
410
|
-
ESP_PRESETS, MAILCHIMP_PRESET, SENDGRID_PRESET,
|
|
411
|
-
} from '@lab2view/vue-email-editor'
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
## Keyboard Shortcuts
|
|
415
|
-
|
|
416
|
-
| Shortcut | Action |
|
|
417
|
-
|----------|--------|
|
|
418
|
-
| `Ctrl+Z` | Undo |
|
|
419
|
-
| `Ctrl+Shift+Z` | Redo |
|
|
420
|
-
| `Ctrl+D` | Duplicate selected node |
|
|
421
|
-
| `Delete` / `Backspace` | Delete selected node |
|
|
422
|
-
| `Escape` | Deselect / close inline editor |
|
|
50
|
+
That's it. The editor renders a full drag-and-drop email builder with live preview, undo/redo, and HTML export.
|
|
423
51
|
|
|
424
|
-
##
|
|
52
|
+
## Highlights
|
|
425
53
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
| `onBrowseAssets` | `() => Promise<string \| null>` | — | Asset browser handler |
|
|
54
|
+
- **43 blocks** — layouts, content, and 30 ready-made composites (hero, pricing, testimonial, FAQ, etc.)
|
|
55
|
+
- **Inline editing** — double-click any text to edit with TipTap (bold, italic, links, colors)
|
|
56
|
+
- **AI generation** — describe an email in plain language, get a production-ready template (BYOAI)
|
|
57
|
+
- **Merge tags** — insert dynamic variables (`{{first_name}}`) with visual chips
|
|
58
|
+
- **Conditional content** — show/hide sections based on merge tag values
|
|
59
|
+
- **22 starter templates** — welcome, newsletter, e-commerce, abandoned cart, and more
|
|
60
|
+
- **ESP export** — pre-formatted HTML for Mailchimp, SendGrid, Brevo, AWS SES, Postmark, Resend
|
|
61
|
+
- **Dark mode preview** — simulate email client dark mode in the canvas
|
|
62
|
+
- **Plugin system** — add custom blocks, toolbar actions, sidebar panels
|
|
63
|
+
- **i18n** — English + French included, 175+ label keys for full translation
|
|
64
|
+
- **Theming** — customize colors, fonts, border radius via `theme` prop
|
|
65
|
+
- **Undo/Redo** — full history with `Ctrl+Z` / `Ctrl+Shift+Z`
|
|
66
|
+
- **Imperative API** — `getMjml()`, `getHtml()`, `selectNode()`, `deleteNode()`, and more via ref
|
|
440
67
|
|
|
441
|
-
##
|
|
68
|
+
## AI Template Generation
|
|
442
69
|
|
|
443
|
-
|
|
70
|
+
Plug in any LLM — OpenAI, Anthropic, Gemini, or your own backend:
|
|
444
71
|
|
|
445
72
|
```vue
|
|
446
73
|
<EmailEditor
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
74
|
+
v-model="mjml"
|
|
75
|
+
:ai-provider="{
|
|
76
|
+
generateText: async (prompt, ctx) => { /* your API call */ },
|
|
77
|
+
generateTemplate: async (messages, systemPrompt) => { /* your API call */ },
|
|
78
|
+
}"
|
|
452
79
|
/>
|
|
453
80
|
```
|
|
454
81
|
|
|
455
|
-
|
|
82
|
+
The editor handles JSON parsing, repair, and retry automatically.
|
|
456
83
|
|
|
457
|
-
|
|
84
|
+
## Theming & i18n
|
|
458
85
|
|
|
459
86
|
```vue
|
|
460
|
-
<
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const mjml = ref('')
|
|
466
|
-
|
|
467
|
-
const aiProvider: AiProvider = {
|
|
468
|
-
generateText: async (prompt, context) => {
|
|
469
|
-
const res = await fetch('/api/ai/text', {
|
|
470
|
-
method: 'POST',
|
|
471
|
-
headers: { 'Content-Type': 'application/json' },
|
|
472
|
-
body: JSON.stringify({ prompt, context }),
|
|
473
|
-
})
|
|
474
|
-
return (await res.json()).text
|
|
475
|
-
},
|
|
476
|
-
generateTemplate: async (messages, systemPrompt) => {
|
|
477
|
-
const res = await fetch('/api/ai/chat', {
|
|
478
|
-
method: 'POST',
|
|
479
|
-
headers: { 'Content-Type': 'application/json' },
|
|
480
|
-
body: JSON.stringify({ messages, systemPrompt }),
|
|
481
|
-
})
|
|
482
|
-
return (await res.json()).content
|
|
483
|
-
},
|
|
484
|
-
}
|
|
485
|
-
</script>
|
|
486
|
-
|
|
487
|
-
<template>
|
|
488
|
-
<EmailEditor v-model="mjml" :ai-provider="aiProvider" />
|
|
489
|
-
</template>
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
Your backend just forwards to any LLM and returns the raw response. The editor handles JSON parsing, repair, and retry automatically.
|
|
493
|
-
|
|
494
|
-
**Want to test without an API key?** Use the [mock provider](https://lab2view.github.io/vue-email-editor/guide/ai#mock-provider-for-testing) — a static `AiProvider` that returns dummy responses for development and testing.
|
|
495
|
-
|
|
496
|
-
**[Full AI integration guide →](https://lab2view.github.io/vue-email-editor/guide/ai)** — complete backend examples for OpenAI, Anthropic, and Google Gemini, streaming setup, error handling, and more.
|
|
497
|
-
|
|
498
|
-
## ESP Export
|
|
499
|
-
|
|
500
|
-
Export HTML pre-formatted for your email service provider:
|
|
501
|
-
|
|
502
|
-
```ts
|
|
503
|
-
import {
|
|
504
|
-
exportForMailchimp,
|
|
505
|
-
exportForSendGrid,
|
|
506
|
-
exportForBrevo,
|
|
507
|
-
exportForAwsSes,
|
|
508
|
-
exportForEsp,
|
|
509
|
-
} from '@lab2view/vue-email-editor'
|
|
510
|
-
|
|
511
|
-
// Get the document from the editor
|
|
512
|
-
const document = editorRef.value.getDocument()
|
|
513
|
-
|
|
514
|
-
// Export for Mailchimp (transforms {{first_name}} → *|FNAME|*)
|
|
515
|
-
const { html, mjml } = await exportForMailchimp(document)
|
|
516
|
-
|
|
517
|
-
// Export for SendGrid
|
|
518
|
-
const result = await exportForSendGrid(document)
|
|
519
|
-
|
|
520
|
-
// Export with custom merge tag mappings
|
|
521
|
-
const custom = await exportForEsp(document, 'brevo', {
|
|
522
|
-
mergeTags: { plan_name: '{{ contact.PLAN }}' },
|
|
523
|
-
})
|
|
87
|
+
<EmailEditor
|
|
88
|
+
:theme="{ primaryColor: '#7C3AED', borderRadius: '8px' }"
|
|
89
|
+
:labels="FR_LABELS"
|
|
90
|
+
/>
|
|
524
91
|
```
|
|
525
92
|
|
|
526
|
-
##
|
|
527
|
-
|
|
528
|
-
```bash
|
|
529
|
-
# Type check
|
|
530
|
-
npm run typecheck
|
|
531
|
-
|
|
532
|
-
# Run tests (147 tests)
|
|
533
|
-
npm test
|
|
534
|
-
|
|
535
|
-
# Build library
|
|
536
|
-
npm run build
|
|
537
|
-
```
|
|
93
|
+
## Documentation
|
|
538
94
|
|
|
539
|
-
|
|
95
|
+
Full API reference, plugin guide, AI integration examples, and more:
|
|
540
96
|
|
|
541
|
-
|
|
542
|
-
- **[MJML](https://mjml.io)** — Email markup language
|
|
543
|
-
- **[TipTap](https://tiptap.dev)** — Rich text editing
|
|
544
|
-
- **[Lucide](https://lucide.dev)** — Icon system (400+ icons)
|
|
545
|
-
- **[VueUse](https://vueuse.org)** — Vue composable utilities
|
|
546
|
-
- **[TypeScript](https://www.typescriptlang.org)** — Full type safety
|
|
547
|
-
- **[Vite](https://vite.dev)** — Build tooling
|
|
548
|
-
- **[Vitest](https://vitest.dev)** — Testing framework
|
|
97
|
+
**[Documentation →](https://lab2view.github.io/vue-email-editor/)**
|
|
549
98
|
|
|
550
99
|
## License
|
|
551
100
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref as y, defineComponent as te, inject as B, computed as K, watch as J, openBlock as o, createElementBlock as r, createElementVNode as i, normalizeClass as Y, toDisplayString as _, unref as l, createCommentVNode as k, createVNode as v, createTextVNode as A, withDirectives as V, Fragment as I, renderList as R, vShow as ae, vModelText as se, nextTick as ne } from "vue";
|
|
2
|
-
import { p as W, A as H, c as ie, d as le, e as ce, a as oe, D as re, E as ue, b as _e, _ as p } from "./index-
|
|
2
|
+
import { p as W, A as H, c as ie, d as le, e as ce, a as oe, D as re, E as ue, b as _e, _ as p } from "./index-Rt0aY248.js";
|
|
3
3
|
function de(w) {
|
|
4
4
|
const c = w.trim();
|
|
5
5
|
return !!(c.startsWith("{") || /```(?:json)?\s*\n?\s*\{/.test(c) || c.includes('"version"') && c.includes('"body"') && c.includes('"mj-body"') || c.includes('"headAttributes"') && c.includes('"mj-body"'));
|
|
@@ -462,4 +462,4 @@ const me = { class: "ebb-ai-chat" }, be = {
|
|
|
462
462
|
export {
|
|
463
463
|
Xe as default
|
|
464
464
|
};
|
|
465
|
-
//# sourceMappingURL=AiChatPanel-
|
|
465
|
+
//# sourceMappingURL=AiChatPanel-DBC4q8wx.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent as u, inject as f, ref as p, watch as h, onMounted as _, onBeforeUnmount as g, openBlock as E, createElementBlock as M } from "vue";
|
|
2
2
|
import { E as j, o as x, l as C, h as k, k as v, d as y, i as w, x as D, a as c } from "./codemirror-CWpT3Qh8.js";
|
|
3
|
-
import { E as S, m as b, d as B } from "./index-
|
|
3
|
+
import { E as S, m as b, d as B } from "./index-Rt0aY248.js";
|
|
4
4
|
const A = /* @__PURE__ */ u({
|
|
5
5
|
__name: "CodeEditor",
|
|
6
6
|
setup(T) {
|
|
@@ -66,4 +66,4 @@ const A = /* @__PURE__ */ u({
|
|
|
66
66
|
export {
|
|
67
67
|
A as default
|
|
68
68
|
};
|
|
69
|
-
//# sourceMappingURL=CodeEditor-
|
|
69
|
+
//# sourceMappingURL=CodeEditor-BfGD5J7z.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineComponent as F, inject as S, ref as A, computed as R, openBlock as c, createElementBlock as g, withModifiers as $, createElementVNode as a, normalizeClass as p, createVNode as r, createCommentVNode as h, Fragment as w, renderList as N, toDisplayString as y, createTextVNode as M, withDirectives as Y, withKeys as j, vModelText as G, markRaw as W, watch as q, nextTick as J, onBeforeUnmount as Q, normalizeStyle as V, unref as E, createBlock as X } from "vue";
|
|
2
2
|
import { T as Z, i as ee, a as te, c as ne, d as ie, e as ae, u as le, E as re } from "./tiptap-gdUcLDLI.js";
|
|
3
|
-
import { a as oe, D as se, b as O, _ as o, M as be } from "./index-
|
|
3
|
+
import { a as oe, D as se, b as O, _ as o, M as be } from "./index-Rt0aY248.js";
|
|
4
4
|
const ue = ["aria-pressed", "title", "aria-label"], de = ["aria-pressed", "title", "aria-label"], ce = ["aria-pressed", "title", "aria-label"], me = ["aria-pressed", "title", "aria-label"], ge = ["title", "aria-label"], _e = ["title", "aria-label"], ve = ["aria-pressed", "title", "aria-label"], fe = ["aria-pressed", "title", "aria-label"], pe = ["aria-pressed", "title", "aria-label"], ke = ["title"], xe = ["aria-label"], he = { class: "ebb-inline-toolbar__merge-wrap" }, ye = ["title", "aria-label", "aria-expanded"], Te = {
|
|
5
5
|
key: 0,
|
|
6
6
|
class: "ebb-merge-menu__category"
|
|
@@ -488,4 +488,4 @@ Text: ` + e), u && t.editor) {
|
|
|
488
488
|
export {
|
|
489
489
|
Re as default
|
|
490
490
|
};
|
|
491
|
-
//# sourceMappingURL=InlineTextEditor-
|
|
491
|
+
//# sourceMappingURL=InlineTextEditor-Dm2jkEsL.js.map
|
package/dist/email-editor.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { f as s, A as r, B as E, C as o, g as t, D as S, h as T, i as c, j as m, F as p, k as _, M as i, P, R, S as n, l as A, n as N, o as d, c as l, q as D, e as F, r as L, s as x, t as M, u as O, v as u, w as C, x as I, y as B, z as f, G as v, H as g, d as j, I as G, J as H, K as w, L as K, N as U, O as W, Q as Y, T as b, U as h, V as k, m as y, W as J, p as V, X as q } from "./index-
|
|
1
|
+
import { f as s, A as r, B as E, C as o, g as t, D as S, h as T, i as c, j as m, F as p, k as _, M as i, P, R, S as n, l as A, n as N, o as d, c as l, q as D, e as F, r as L, s as x, t as M, u as O, v as u, w as C, x as I, y as B, z as f, G as v, H as g, d as j, I as G, J as H, K as w, L as K, N as U, O as W, Q as Y, T as b, U as h, V as k, m as y, W as J, p as V, X as q } from "./index-Rt0aY248.js";
|
|
2
2
|
export {
|
|
3
3
|
s as AWS_SES_PRESET,
|
|
4
4
|
r as AiParseError,
|