@lab2view/vue-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 +386 -0
- package/dist/CodeEditor-DMG_wAzo.js +14076 -0
- package/dist/email-editor.css +1 -0
- package/dist/email-editor.d.ts +540 -0
- package/dist/email-editor.js +32 -0
- package/dist/index-CJIiXHwY.js +60121 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lab2View
|
|
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,386 @@
|
|
|
1
|
+
# @lab2view/vue-email-editor
|
|
2
|
+
|
|
3
|
+
A professional, extensible drag-and-drop email editor built with **Vue 3** and **MJML**. Design responsive HTML emails visually with 43 pre-built blocks, a plugin system, full i18n support, and a complete imperative API.
|
|
4
|
+
|
|
5
|
+
## Screenshots
|
|
6
|
+
|
|
7
|
+
| Blocks & Layout | Ready-made Templates |
|
|
8
|
+
|:---:|:---:|
|
|
9
|
+
|  |  |
|
|
10
|
+
|
|
11
|
+
| Property Editing | Layers Panel |
|
|
12
|
+
|:---:|:---:|
|
|
13
|
+
|  |  |
|
|
14
|
+
|
|
15
|
+
## Features
|
|
16
|
+
|
|
17
|
+
### Visual Drag & Drop Builder
|
|
18
|
+
- Intuitive block-based editor with live preview
|
|
19
|
+
- Drag blocks from the sidebar onto the canvas
|
|
20
|
+
- Reorder and nest elements with visual drop indicators
|
|
21
|
+
- Responsive preview in Desktop, Tablet, and Mobile modes
|
|
22
|
+
- Iframe-isolated canvas for accurate email rendering
|
|
23
|
+
|
|
24
|
+
### 43 Pre-built Blocks
|
|
25
|
+
|
|
26
|
+
| Category | Count | Examples |
|
|
27
|
+
|----------|-------|---------|
|
|
28
|
+
| **Layout** | 6 | 1-4 columns, sidebar left/right |
|
|
29
|
+
| **Content** | 7 | Text, Image, Button, Divider, Spacer, Social, Hero |
|
|
30
|
+
| **Composite** | 30 | Header, Hero Banner, Pricing, Testimonial, FAQ, Product Card, Footer, and more |
|
|
31
|
+
|
|
32
|
+
### Rich Text Editing
|
|
33
|
+
Powered by [TipTap](https://tiptap.dev), with inline formatting:
|
|
34
|
+
- Bold, Italic, Underline, Strikethrough
|
|
35
|
+
- Text alignment (left, center, right)
|
|
36
|
+
- Text color picker
|
|
37
|
+
- Link insertion and editing
|
|
38
|
+
|
|
39
|
+
### MJML-Powered Rendering
|
|
40
|
+
- Real-time MJML to HTML compilation via [mjml-browser](https://github.com/mjmlio/mjml)
|
|
41
|
+
- 13 supported MJML node types
|
|
42
|
+
- Export as MJML, compiled HTML, or design JSON
|
|
43
|
+
- Legacy GrapesJS format detection for migration
|
|
44
|
+
|
|
45
|
+
### Undo / Redo
|
|
46
|
+
- Full history with keyboard shortcuts (`Ctrl+Z` / `Ctrl+Shift+Z`)
|
|
47
|
+
- Reactive `canUndo` / `canRedo` state
|
|
48
|
+
|
|
49
|
+
### Property Editing
|
|
50
|
+
- 40+ editable MJML attributes across 11 property types
|
|
51
|
+
- Color pickers, padding controls, alignment selectors
|
|
52
|
+
- Global styles panel (email background, default font, preview text)
|
|
53
|
+
- Custom fonts support
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install @lab2view/vue-email-editor
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Peer dependencies:**
|
|
64
|
+
```bash
|
|
65
|
+
npm install vue@^3.4 mjml-browser@^4.15
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { ref } from 'vue'
|
|
73
|
+
import { EmailEditor, FR_LABELS } from '@lab2view/vue-email-editor'
|
|
74
|
+
|
|
75
|
+
const editorRef = ref()
|
|
76
|
+
const mjml = ref('')
|
|
77
|
+
const html = ref('')
|
|
78
|
+
const designJson = ref()
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<EmailEditor
|
|
83
|
+
ref="editorRef"
|
|
84
|
+
v-model="mjml"
|
|
85
|
+
:design-json="designJson"
|
|
86
|
+
:labels="FR_LABELS"
|
|
87
|
+
@update:compiled-html="html = $event"
|
|
88
|
+
@update:design-json="designJson = $event"
|
|
89
|
+
/>
|
|
90
|
+
</template>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Theming
|
|
94
|
+
|
|
95
|
+
Customize the editor appearance with the `theme` prop:
|
|
96
|
+
|
|
97
|
+
```vue
|
|
98
|
+
<EmailEditor
|
|
99
|
+
:theme="{
|
|
100
|
+
primaryColor: '#7C3AED',
|
|
101
|
+
primaryHover: '#6D28D9',
|
|
102
|
+
borderRadius: '8px',
|
|
103
|
+
fontFamily: 'Inter, sans-serif',
|
|
104
|
+
}"
|
|
105
|
+
/>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Available Theme Properties
|
|
109
|
+
|
|
110
|
+
| Property | Default | Description |
|
|
111
|
+
|----------|---------|-------------|
|
|
112
|
+
| `primaryColor` | `#01A8AB` | Main accent color |
|
|
113
|
+
| `primaryHover` | `#018F91` | Hover state |
|
|
114
|
+
| `primaryActive` | `#017375` | Active/pressed state |
|
|
115
|
+
| `backgroundColor` | `#ffffff` | Panel backgrounds |
|
|
116
|
+
| `borderColor` | `#e5e7eb` | Border color |
|
|
117
|
+
| `textPrimary` | `#1f2937` | Primary text |
|
|
118
|
+
| `textSecondary` | `#6b7280` | Secondary text |
|
|
119
|
+
| `canvasBg` | `#e5e7eb` | Canvas background |
|
|
120
|
+
| `selectionColor` | `#01A8AB` | Selected node outline |
|
|
121
|
+
| `fontFamily` | System stack | UI font family |
|
|
122
|
+
| `fontSize` | `13px` | UI font size |
|
|
123
|
+
| `borderRadius` | `6px` | UI border radius |
|
|
124
|
+
|
|
125
|
+
See `ThemeConfig` in [src/types.ts](src/types.ts) for the full list of 25 properties.
|
|
126
|
+
|
|
127
|
+
## Internationalization (i18n)
|
|
128
|
+
|
|
129
|
+
The editor ships with English defaults and a complete French translation. Pass custom labels via the `labels` prop:
|
|
130
|
+
|
|
131
|
+
```vue
|
|
132
|
+
<script setup>
|
|
133
|
+
import { EmailEditor, FR_LABELS } from '@lab2view/vue-email-editor'
|
|
134
|
+
</script>
|
|
135
|
+
|
|
136
|
+
<!-- French UI -->
|
|
137
|
+
<EmailEditor :labels="FR_LABELS" />
|
|
138
|
+
|
|
139
|
+
<!-- Custom labels (partial override) -->
|
|
140
|
+
<EmailEditor :labels="{ editor_title: 'Mein Editor', undo: 'Ruckgangig' }" />
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
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).
|
|
144
|
+
|
|
145
|
+
## Imperative API
|
|
146
|
+
|
|
147
|
+
Access the editor programmatically via template ref:
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<script setup>
|
|
151
|
+
import { ref, onMounted } from 'vue'
|
|
152
|
+
import { EmailEditor } from '@lab2view/vue-email-editor'
|
|
153
|
+
|
|
154
|
+
const editor = ref()
|
|
155
|
+
|
|
156
|
+
onMounted(() => {
|
|
157
|
+
// Export
|
|
158
|
+
const mjml = editor.value.getMjml()
|
|
159
|
+
const html = editor.value.getHtml()
|
|
160
|
+
const json = editor.value.getDesignJson()
|
|
161
|
+
|
|
162
|
+
// History
|
|
163
|
+
editor.value.undo()
|
|
164
|
+
editor.value.redo()
|
|
165
|
+
|
|
166
|
+
// Selection
|
|
167
|
+
editor.value.selectNode('node-id')
|
|
168
|
+
const selected = editor.value.getSelectedNode()
|
|
169
|
+
editor.value.clearSelection()
|
|
170
|
+
|
|
171
|
+
// Manipulation
|
|
172
|
+
editor.value.deleteNode('node-id')
|
|
173
|
+
editor.value.duplicateNode('node-id')
|
|
174
|
+
editor.value.updateNodeAttribute('node-id', 'color', '#ff0000')
|
|
175
|
+
|
|
176
|
+
// Events
|
|
177
|
+
editor.value.on('editor:change', ({ document }) => {
|
|
178
|
+
console.log('Document changed', document)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
</script>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Full API Reference
|
|
185
|
+
|
|
186
|
+
| Method | Returns | Description |
|
|
187
|
+
|--------|---------|-------------|
|
|
188
|
+
| `getDocument()` | `EmailDocument` | Current document tree |
|
|
189
|
+
| `setDocument(doc)` | `void` | Replace entire document |
|
|
190
|
+
| `getMjml()` | `string` | Export as MJML string |
|
|
191
|
+
| `getHtml()` | `string` | Export as compiled HTML |
|
|
192
|
+
| `getDesignJson()` | `EmailDesignJson` | Export persisted format |
|
|
193
|
+
| `undo()` | `void` | Undo last change |
|
|
194
|
+
| `redo()` | `void` | Redo last undone change |
|
|
195
|
+
| `canUndo()` | `boolean` | Whether undo is available |
|
|
196
|
+
| `canRedo()` | `boolean` | Whether redo is available |
|
|
197
|
+
| `selectNode(id)` | `void` | Select a node by ID |
|
|
198
|
+
| `getSelectedNode()` | `EmailNode \| null` | Get selected node |
|
|
199
|
+
| `clearSelection()` | `void` | Deselect current node |
|
|
200
|
+
| `deleteNode(id)` | `void` | Remove a node |
|
|
201
|
+
| `duplicateNode(id)` | `NodeId \| null` | Clone a node |
|
|
202
|
+
| `insertBlock(block, parentId, index?)` | `NodeId \| null` | Insert a block |
|
|
203
|
+
| `updateNodeAttribute(id, key, value)` | `void` | Update an attribute |
|
|
204
|
+
| `loadTemplate(doc)` | `void` | Load an EmailDocument |
|
|
205
|
+
| `on(event, handler)` | `void` | Subscribe to event |
|
|
206
|
+
| `off(event, handler)` | `void` | Unsubscribe from event |
|
|
207
|
+
|
|
208
|
+
## Events
|
|
209
|
+
|
|
210
|
+
Subscribe to editor events for real-time notifications:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
editor.value.on('node:selected', ({ nodeId, node }) => {
|
|
214
|
+
console.log('Selected:', node.type)
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
editor.value.on('editor:change', ({ document }) => {
|
|
218
|
+
// Auto-save
|
|
219
|
+
saveToServer(document)
|
|
220
|
+
})
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
| Event | Payload | Trigger |
|
|
224
|
+
|-------|---------|---------|
|
|
225
|
+
| `editor:ready` | `{ document }` | Editor initialized |
|
|
226
|
+
| `editor:change` | `{ document }` | Any document change |
|
|
227
|
+
| `node:selected` | `{ nodeId, node }` | Node selected |
|
|
228
|
+
| `node:deselected` | `{ nodeId }` | Selection cleared |
|
|
229
|
+
| `node:deleted` | `{ nodeId }` | Node removed |
|
|
230
|
+
| `node:moved` | `{ nodeId, fromParentId, toParentId }` | Node repositioned |
|
|
231
|
+
| `node:duplicated` | `{ originalId, newId }` | Node cloned |
|
|
232
|
+
| `block:dropped` | `{ blockId, parentId }` | Block added from panel |
|
|
233
|
+
| `history:undo` | `{ canUndo, canRedo }` | Undo performed |
|
|
234
|
+
| `history:redo` | `{ canUndo, canRedo }` | Redo performed |
|
|
235
|
+
| `property:changed` | `{ nodeId, key, value }` | Attribute updated |
|
|
236
|
+
|
|
237
|
+
## Plugin System
|
|
238
|
+
|
|
239
|
+
Extend the editor with custom blocks, property editors, toolbar actions, and sidebar panels:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
import type { Plugin } from '@lab2view/vue-email-editor'
|
|
243
|
+
import { createText } from '@lab2view/vue-email-editor'
|
|
244
|
+
|
|
245
|
+
const myPlugin: Plugin = (ctx) => {
|
|
246
|
+
// Add a custom block
|
|
247
|
+
ctx.registerBlock({
|
|
248
|
+
id: 'custom-banner',
|
|
249
|
+
label: 'Custom Banner',
|
|
250
|
+
category: 'content',
|
|
251
|
+
icon: 'Sparkles',
|
|
252
|
+
factory: () => createText('<h1>My Custom Block</h1>', {
|
|
253
|
+
align: 'center',
|
|
254
|
+
'font-size': '24px',
|
|
255
|
+
}),
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Add a new block category
|
|
259
|
+
ctx.registerBlockCategory({
|
|
260
|
+
id: 'my-category',
|
|
261
|
+
label: 'My Blocks',
|
|
262
|
+
icon: 'Package',
|
|
263
|
+
order: 50,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// Add a toolbar button
|
|
267
|
+
ctx.registerToolbarAction({
|
|
268
|
+
id: 'save',
|
|
269
|
+
label: 'Save',
|
|
270
|
+
icon: 'Save',
|
|
271
|
+
handler: () => console.log('Saving...'),
|
|
272
|
+
position: 'right',
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
// Listen to events
|
|
276
|
+
ctx.on('editor:change', ({ document }) => {
|
|
277
|
+
console.log('Auto-saving...')
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
```vue
|
|
283
|
+
<EmailEditor :plugins="[myPlugin]" />
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Plugin Context API
|
|
287
|
+
|
|
288
|
+
| Method | Description |
|
|
289
|
+
|--------|-------------|
|
|
290
|
+
| `registerBlock(block)` | Add a custom block to the blocks panel |
|
|
291
|
+
| `registerBlockCategory(category)` | Define a new block category |
|
|
292
|
+
| `registerPropertyEditor(type, component)` | Override a property editor component |
|
|
293
|
+
| `registerToolbarAction(action)` | Add a button to the toolbar |
|
|
294
|
+
| `registerSidebarPanel(panel)` | Add a custom sidebar tab/panel |
|
|
295
|
+
| `on(event, handler)` | Subscribe to editor events |
|
|
296
|
+
| `off(event, handler)` | Unsubscribe from events |
|
|
297
|
+
| `labels` | Reactive reference to current labels |
|
|
298
|
+
|
|
299
|
+
## Exports
|
|
300
|
+
|
|
301
|
+
The package provides 35+ exports for advanced usage:
|
|
302
|
+
|
|
303
|
+
```ts
|
|
304
|
+
// Component
|
|
305
|
+
import { EmailEditor } from '@lab2view/vue-email-editor'
|
|
306
|
+
|
|
307
|
+
// Labels & i18n
|
|
308
|
+
import { DEFAULT_LABELS, FR_LABELS } from '@lab2view/vue-email-editor'
|
|
309
|
+
|
|
310
|
+
// Types
|
|
311
|
+
import type {
|
|
312
|
+
EmailDocument, EmailNode, EmailDesignJson, EmailEditorAPI,
|
|
313
|
+
Plugin, PluginContext, ThemeConfig, EditorEventMap,
|
|
314
|
+
BlockDefinition, PropertyDefinition,
|
|
315
|
+
} from '@lab2view/vue-email-editor'
|
|
316
|
+
|
|
317
|
+
// Serialization
|
|
318
|
+
import { compileMjml, documentToMjml, mjmlToDocument } from '@lab2view/vue-email-editor'
|
|
319
|
+
|
|
320
|
+
// Node factories
|
|
321
|
+
import {
|
|
322
|
+
createDefaultDocument, createSection, createColumn,
|
|
323
|
+
createText, createImage, createButton, createDivider,
|
|
324
|
+
createSpacer, createSocial, createHero, createWrapper,
|
|
325
|
+
} from '@lab2view/vue-email-editor'
|
|
326
|
+
|
|
327
|
+
// Tree utilities
|
|
328
|
+
import { findNode, findParent, removeNode, moveNode, cloneSubtree } from '@lab2view/vue-email-editor'
|
|
329
|
+
|
|
330
|
+
// Constants
|
|
331
|
+
import {
|
|
332
|
+
DEFAULT_THEME, STATIC_BLOCKS,
|
|
333
|
+
CONTENT_NODE_TYPES, CONTAINER_NODE_TYPES, SELF_CLOSING_NODE_TYPES,
|
|
334
|
+
} from '@lab2view/vue-email-editor'
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Keyboard Shortcuts
|
|
338
|
+
|
|
339
|
+
| Shortcut | Action |
|
|
340
|
+
|----------|--------|
|
|
341
|
+
| `Ctrl+Z` | Undo |
|
|
342
|
+
| `Ctrl+Shift+Z` | Redo |
|
|
343
|
+
| `Ctrl+D` | Duplicate selected node |
|
|
344
|
+
| `Delete` / `Backspace` | Delete selected node |
|
|
345
|
+
| `Escape` | Deselect / close inline editor |
|
|
346
|
+
|
|
347
|
+
## Props
|
|
348
|
+
|
|
349
|
+
| Prop | Type | Default | Description |
|
|
350
|
+
|------|------|---------|-------------|
|
|
351
|
+
| `modelValue` | `string` | `''` | MJML content (v-model) |
|
|
352
|
+
| `designJson` | `Record<string, unknown>` | — | Persisted design JSON |
|
|
353
|
+
| `variables` | `string[]` | `[]` | Available merge variables |
|
|
354
|
+
| `labels` | `Partial<EditorLabels>` | `DEFAULT_LABELS` | i18n label overrides |
|
|
355
|
+
| `label` | `string` | — | Form field label |
|
|
356
|
+
| `required` | `boolean` | `false` | Form validation flag |
|
|
357
|
+
| `theme` | `Partial<ThemeConfig>` | `DEFAULT_THEME` | Visual customization |
|
|
358
|
+
| `plugins` | `Plugin[]` | `[]` | Editor extensions |
|
|
359
|
+
|
|
360
|
+
## Development
|
|
361
|
+
|
|
362
|
+
```bash
|
|
363
|
+
# Type check
|
|
364
|
+
npm run typecheck
|
|
365
|
+
|
|
366
|
+
# Run tests (64 tests)
|
|
367
|
+
npm test
|
|
368
|
+
|
|
369
|
+
# Build library
|
|
370
|
+
npm run build
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Tech Stack
|
|
374
|
+
|
|
375
|
+
- **[Vue 3](https://vuejs.org)** — Reactive component framework
|
|
376
|
+
- **[MJML](https://mjml.io)** — Email markup language
|
|
377
|
+
- **[TipTap](https://tiptap.dev)** — Rich text editing
|
|
378
|
+
- **[Lucide](https://lucide.dev)** — Icon system (400+ icons)
|
|
379
|
+
- **[VueUse](https://vueuse.org)** — Vue composable utilities
|
|
380
|
+
- **[TypeScript](https://www.typescriptlang.org)** — Full type safety
|
|
381
|
+
- **[Vite](https://vite.dev)** — Build tooling
|
|
382
|
+
- **[Vitest](https://vitest.dev)** — Testing framework
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
[MIT](LICENSE)
|