@lab2view/vue-email-editor 0.1.0 → 0.1.2
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 +141 -4
- package/dist/CodeEditor-CQ4mmQCG.js +69 -0
- package/dist/InlineTextEditor-CMQG9tEQ.js +491 -0
- package/dist/{CodeEditor-DMG_wAzo.js → codemirror-CWpT3Qh8.js} +802 -856
- package/dist/email-editor.css +1 -1
- package/dist/email-editor.d.ts +185 -0
- package/dist/email-editor.js +44 -28
- package/dist/{index-CJIiXHwY.js → index-Cplw1OsP.js} +19625 -32176
- package/dist/tiptap-gdUcLDLI.js +16919 -0
- package/package.json +21 -2
package/README.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="src/assets/logo.png" alt="@lab2view/vue-email-editor" width="200" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">@lab2view/vue-email-editor</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@lab2view/vue-email-editor"><img src="https://img.shields.io/npm/v/@lab2view/vue-email-editor.svg?style=flat-square" alt="npm version" /></a>
|
|
9
|
+
<a href="https://github.com/lab2view/vue-email-editor/actions"><img src="https://img.shields.io/github/actions/workflow/status/lab2view/vue-email-editor/ci.yml?style=flat-square" alt="CI" /></a>
|
|
10
|
+
<a href="https://github.com/lab2view/vue-email-editor/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@lab2view/vue-email-editor.svg?style=flat-square" alt="license" /></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/vue-3.x-42b883?style=flat-square&logo=vue.js" alt="Vue 3" />
|
|
12
|
+
<img src="https://img.shields.io/badge/mjml-4.x-e54434?style=flat-square" alt="MJML" />
|
|
13
|
+
<img src="https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square&logo=typescript" alt="TypeScript" />
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
A professional, extensible drag-and-drop email editor built with <strong>Vue 3</strong> and <strong>MJML</strong>.<br/>
|
|
18
|
+
Design responsive HTML emails visually with 43 pre-built blocks, a plugin system, full i18n support, and a complete imperative API.<br/>
|
|
19
|
+
<strong>Free and open-source alternative to Unlayer, Beefree, and Stripo.</strong>
|
|
20
|
+
</p>
|
|
4
21
|
|
|
5
22
|
## Screenshots
|
|
6
23
|
|
|
@@ -46,12 +63,54 @@ Powered by [TipTap](https://tiptap.dev), with inline formatting:
|
|
|
46
63
|
- Full history with keyboard shortcuts (`Ctrl+Z` / `Ctrl+Shift+Z`)
|
|
47
64
|
- Reactive `canUndo` / `canRedo` state
|
|
48
65
|
|
|
66
|
+
### Merge Tags
|
|
67
|
+
- Insert dynamic variables into text content (`{{first_name}}`, `{{company}}`, etc.)
|
|
68
|
+
- Categorized merge tag picker in the inline toolbar
|
|
69
|
+
- Visual chip rendering in the editor
|
|
70
|
+
- TipTap extension for atomic merge tag nodes
|
|
71
|
+
|
|
72
|
+
### Conditional Content
|
|
73
|
+
- Show/hide email sections based on merge tag values
|
|
74
|
+
- Visual condition builder on any node (variable, operator, value)
|
|
75
|
+
- 6 operators: equals, not equals, contains, not contains, exists, not exists
|
|
76
|
+
- Exports as HTML conditional comments for ESP processing
|
|
77
|
+
|
|
78
|
+
### AI Text Generation
|
|
79
|
+
- BYOAI (Bring Your Own AI) pattern — plug in any AI provider
|
|
80
|
+
- Generate, improve, shorten, expand, and translate text
|
|
81
|
+
- Custom prompt input for freeform generation
|
|
82
|
+
- Non-blocking async generation with loading state
|
|
83
|
+
|
|
84
|
+
### Dark Mode Preview
|
|
85
|
+
- Toggle dark mode preview in the toolbar (Moon/Sun icon)
|
|
86
|
+
- Simulates email client dark mode behavior in the canvas
|
|
87
|
+
- Background inversion, text color adjustment, link color adaptation
|
|
88
|
+
|
|
89
|
+
### ESP Export Presets
|
|
90
|
+
- Export HTML pre-formatted for 6 email service providers
|
|
91
|
+
- **Mailchimp**: `*|FNAME|*` merge tags, auto-inject unsubscribe
|
|
92
|
+
- **SendGrid**: `{{variable}}` handlebars format
|
|
93
|
+
- **Brevo**: `{{ contact.ATTRIBUTE }}` format
|
|
94
|
+
- **AWS SES**: `{{camelCase}}` template variables
|
|
95
|
+
- **Postmark**: `{{variable}}` Mustache format
|
|
96
|
+
- **Resend**: `{{variable}}` format
|
|
97
|
+
- Custom preset support for any ESP
|
|
98
|
+
|
|
99
|
+
### Image Upload
|
|
100
|
+
- Drag-and-drop image upload with progress indicator
|
|
101
|
+
- Asset browser integration via callback
|
|
102
|
+
- File type validation and size limits
|
|
103
|
+
- Preview with change/remove actions
|
|
104
|
+
|
|
49
105
|
### Property Editing
|
|
50
106
|
- 40+ editable MJML attributes across 11 property types
|
|
51
107
|
- Color pickers, padding controls, alignment selectors
|
|
52
108
|
- Global styles panel (email background, default font, preview text)
|
|
53
109
|
- Custom fonts support
|
|
54
110
|
|
|
111
|
+
### 22 Starter Templates
|
|
112
|
+
Professional email templates for common use cases: welcome, newsletter, e-commerce, abandoned cart, product launch, shipping notification, birthday, seasonal sale, SaaS onboarding, and more.
|
|
113
|
+
|
|
55
114
|
---
|
|
56
115
|
|
|
57
116
|
## Installation
|
|
@@ -332,6 +391,13 @@ import {
|
|
|
332
391
|
DEFAULT_THEME, STATIC_BLOCKS,
|
|
333
392
|
CONTENT_NODE_TYPES, CONTAINER_NODE_TYPES, SELF_CLOSING_NODE_TYPES,
|
|
334
393
|
} from '@lab2view/vue-email-editor'
|
|
394
|
+
|
|
395
|
+
// ESP Export Presets
|
|
396
|
+
import {
|
|
397
|
+
exportForEsp, exportForMailchimp, exportForSendGrid,
|
|
398
|
+
exportForBrevo, exportForAwsSes, exportForPostmark, exportForResend,
|
|
399
|
+
ESP_PRESETS, MAILCHIMP_PRESET, SENDGRID_PRESET,
|
|
400
|
+
} from '@lab2view/vue-email-editor'
|
|
335
401
|
```
|
|
336
402
|
|
|
337
403
|
## Keyboard Shortcuts
|
|
@@ -356,6 +422,77 @@ import {
|
|
|
356
422
|
| `required` | `boolean` | `false` | Form validation flag |
|
|
357
423
|
| `theme` | `Partial<ThemeConfig>` | `DEFAULT_THEME` | Visual customization |
|
|
358
424
|
| `plugins` | `Plugin[]` | `[]` | Editor extensions |
|
|
425
|
+
| `mergeTags` | `MergeTag[]` | — | Dynamic variable tags |
|
|
426
|
+
| `aiProvider` | `AiProvider` | — | AI text generation callbacks |
|
|
427
|
+
| `onImageUpload` | `(file: File) => Promise<{ url }>` | — | Image upload handler |
|
|
428
|
+
| `onBrowseAssets` | `() => Promise<string \| null>` | — | Asset browser handler |
|
|
429
|
+
|
|
430
|
+
## Merge Tags
|
|
431
|
+
|
|
432
|
+
Insert dynamic content with merge tag variables:
|
|
433
|
+
|
|
434
|
+
```vue
|
|
435
|
+
<EmailEditor
|
|
436
|
+
:merge-tags="[
|
|
437
|
+
{ name: 'First Name', value: '{{first_name}}', category: 'Contact' },
|
|
438
|
+
{ name: 'Company', value: '{{company}}', category: 'Contact' },
|
|
439
|
+
{ name: 'Unsubscribe', value: '{{unsubscribe_url}}', category: 'Links' },
|
|
440
|
+
]"
|
|
441
|
+
/>
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
## AI Text Generation
|
|
445
|
+
|
|
446
|
+
Bring your own AI provider for inline text generation:
|
|
447
|
+
|
|
448
|
+
```vue
|
|
449
|
+
<EmailEditor
|
|
450
|
+
:ai-provider="{
|
|
451
|
+
generateText: async (prompt, context) => {
|
|
452
|
+
const response = await fetch('/api/ai/generate', {
|
|
453
|
+
method: 'POST',
|
|
454
|
+
body: JSON.stringify({ prompt, context }),
|
|
455
|
+
})
|
|
456
|
+
return (await response.json()).text
|
|
457
|
+
},
|
|
458
|
+
improveText: async (text, instruction) => {
|
|
459
|
+
const response = await fetch('/api/ai/improve', {
|
|
460
|
+
method: 'POST',
|
|
461
|
+
body: JSON.stringify({ text, instruction }),
|
|
462
|
+
})
|
|
463
|
+
return (await response.json()).text
|
|
464
|
+
},
|
|
465
|
+
}"
|
|
466
|
+
/>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## ESP Export
|
|
470
|
+
|
|
471
|
+
Export HTML pre-formatted for your email service provider:
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
import {
|
|
475
|
+
exportForMailchimp,
|
|
476
|
+
exportForSendGrid,
|
|
477
|
+
exportForBrevo,
|
|
478
|
+
exportForAwsSes,
|
|
479
|
+
exportForEsp,
|
|
480
|
+
} from '@lab2view/vue-email-editor'
|
|
481
|
+
|
|
482
|
+
// Get the document from the editor
|
|
483
|
+
const document = editorRef.value.getDocument()
|
|
484
|
+
|
|
485
|
+
// Export for Mailchimp (transforms {{first_name}} → *|FNAME|*)
|
|
486
|
+
const { html, mjml } = await exportForMailchimp(document)
|
|
487
|
+
|
|
488
|
+
// Export for SendGrid
|
|
489
|
+
const result = await exportForSendGrid(document)
|
|
490
|
+
|
|
491
|
+
// Export with custom merge tag mappings
|
|
492
|
+
const custom = await exportForEsp(document, 'brevo', {
|
|
493
|
+
mergeTags: { plan_name: '{{ contact.PLAN }}' },
|
|
494
|
+
})
|
|
495
|
+
```
|
|
359
496
|
|
|
360
497
|
## Development
|
|
361
498
|
|
|
@@ -363,7 +500,7 @@ import {
|
|
|
363
500
|
# Type check
|
|
364
501
|
npm run typecheck
|
|
365
502
|
|
|
366
|
-
# Run tests (
|
|
503
|
+
# Run tests (147 tests)
|
|
367
504
|
npm test
|
|
368
505
|
|
|
369
506
|
# Build library
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
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-Cplw1OsP.js";
|
|
4
|
+
const A = /* @__PURE__ */ u({
|
|
5
|
+
__name: "CodeEditor",
|
|
6
|
+
setup(T) {
|
|
7
|
+
const n = f(S), r = p(null);
|
|
8
|
+
let e = null, a = !1;
|
|
9
|
+
function i() {
|
|
10
|
+
return B(n.document.value);
|
|
11
|
+
}
|
|
12
|
+
function l(t) {
|
|
13
|
+
const o = j.create({
|
|
14
|
+
doc: i(),
|
|
15
|
+
extensions: [
|
|
16
|
+
C(),
|
|
17
|
+
k(),
|
|
18
|
+
v.of([...y, w]),
|
|
19
|
+
D(),
|
|
20
|
+
x,
|
|
21
|
+
c.updateListener.of((s) => {
|
|
22
|
+
if (s.docChanged) {
|
|
23
|
+
a = !0;
|
|
24
|
+
const m = s.state.doc.toString();
|
|
25
|
+
try {
|
|
26
|
+
const d = b(m);
|
|
27
|
+
n.replaceDocument(d);
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
c.theme({
|
|
33
|
+
"&": { height: "100%", fontSize: "13px" },
|
|
34
|
+
".cm-scroller": { overflow: "auto" },
|
|
35
|
+
".cm-content": { fontFamily: "'JetBrains Mono', 'Fira Code', monospace" }
|
|
36
|
+
})
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
e = new c({ state: o, parent: t });
|
|
40
|
+
}
|
|
41
|
+
return h(
|
|
42
|
+
() => n.document.value,
|
|
43
|
+
() => {
|
|
44
|
+
if (a) {
|
|
45
|
+
a = !1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!e) return;
|
|
49
|
+
const t = i(), o = e.state.doc.toString();
|
|
50
|
+
t !== o && e.dispatch({
|
|
51
|
+
changes: { from: 0, to: e.state.doc.length, insert: t }
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
{ deep: !0 }
|
|
55
|
+
), _(() => {
|
|
56
|
+
r.value && l(r.value);
|
|
57
|
+
}), g(() => {
|
|
58
|
+
e == null || e.destroy(), e = null;
|
|
59
|
+
}), (t, o) => (E(), M("div", {
|
|
60
|
+
ref_key: "editorContainer",
|
|
61
|
+
ref: r,
|
|
62
|
+
class: "ebb-code-editor"
|
|
63
|
+
}, null, 512));
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
export {
|
|
67
|
+
A as default
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=CodeEditor-CQ4mmQCG.js.map
|