mirror of
https://github.com/lab2view/vue-email-editor.git
synced 2026-11-05 16:16:35 +00:00
- 7 guide pages: getting-started, why, theming, i18n, blocks, plugins, events, imperative-api - 5 API reference pages: props, events, methods, types, exports - GitHub Pages deployment workflow (deploy-docs.yml) - Home page with feature highlights - Local search enabled - docs:dev, docs:build, docs:preview scripts
182 lines
4.2 KiB
Markdown
182 lines
4.2 KiB
Markdown
# Plugins
|
|
|
|
The plugin system lets you extend the editor with custom blocks, categories, toolbar actions, sidebar panels, and property editors — without modifying the source.
|
|
|
|
## Creating a Plugin
|
|
|
|
A plugin is a function that receives a `PluginContext`:
|
|
|
|
```ts
|
|
import type { Plugin } from '@lab2view/vue-email-editor'
|
|
|
|
const myPlugin: Plugin = (ctx) => {
|
|
// Use ctx to register extensions
|
|
}
|
|
```
|
|
|
|
### Register It
|
|
|
|
```vue
|
|
<EmailEditor :plugins="[myPlugin]" />
|
|
```
|
|
|
|
Plugins are initialized **before** the `editor:ready` event fires.
|
|
|
|
## Adding Custom Blocks
|
|
|
|
```ts
|
|
import type { Plugin } from '@lab2view/vue-email-editor'
|
|
import { createText, createImage, createSection, createColumn } from '@lab2view/vue-email-editor'
|
|
|
|
const brandPlugin: Plugin = (ctx) => {
|
|
// Simple block
|
|
ctx.registerBlock({
|
|
id: 'brand-signature',
|
|
label: 'Signature',
|
|
category: 'content',
|
|
icon: 'PenTool',
|
|
factory: () => createText(`
|
|
<p style="font-size: 14px; color: #666;">
|
|
<strong>John Doe</strong><br/>
|
|
Product Manager at Acme Corp<br/>
|
|
<a href="mailto:john@acme.com">john@acme.com</a>
|
|
</p>
|
|
`),
|
|
})
|
|
|
|
// Complex block (section with multiple columns)
|
|
ctx.registerBlock({
|
|
id: 'brand-header',
|
|
label: 'Brand Header',
|
|
category: 'composite',
|
|
icon: 'Layout',
|
|
factory: () => {
|
|
const logo = createImage({
|
|
src: 'https://acme.com/logo.png',
|
|
alt: 'Acme Corp',
|
|
width: '150px',
|
|
})
|
|
const col = createColumn([logo])
|
|
return createSection([col], {
|
|
'background-color': '#1a1a2e',
|
|
padding: '20px 0',
|
|
})
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
## Adding Block Categories
|
|
|
|
```ts
|
|
const myPlugin: Plugin = (ctx) => {
|
|
ctx.registerBlockCategory({
|
|
id: 'brand',
|
|
label: 'Brand',
|
|
icon: 'Building',
|
|
order: 5, // Lower = displayed first
|
|
})
|
|
|
|
ctx.registerBlock({
|
|
id: 'brand-banner',
|
|
label: 'Brand Banner',
|
|
category: 'brand', // Uses our new category
|
|
icon: 'Flag',
|
|
factory: () => createText('<h1>Welcome to Acme</h1>'),
|
|
})
|
|
}
|
|
```
|
|
|
|
## Adding Toolbar Actions
|
|
|
|
```ts
|
|
const savePlugin: Plugin = (ctx) => {
|
|
ctx.registerToolbarAction({
|
|
id: 'save',
|
|
label: 'Save',
|
|
icon: 'Save',
|
|
position: 'right',
|
|
order: 10,
|
|
handler: () => {
|
|
console.log('Saving...')
|
|
},
|
|
})
|
|
|
|
ctx.registerToolbarAction({
|
|
id: 'preview',
|
|
label: 'Preview',
|
|
icon: 'Eye',
|
|
position: 'right',
|
|
order: 20,
|
|
handler: () => {
|
|
console.log('Opening preview...')
|
|
},
|
|
})
|
|
}
|
|
```
|
|
|
|
## Listening to Events
|
|
|
|
Plugins receive the full event system:
|
|
|
|
```ts
|
|
const analyticsPlugin: Plugin = (ctx) => {
|
|
ctx.on('editor:change', ({ document }) => {
|
|
// Track changes
|
|
analytics.track('email_edited', {
|
|
nodeCount: countNodes(document.body),
|
|
})
|
|
})
|
|
|
|
ctx.on('node:deleted', ({ nodeId }) => {
|
|
console.log(`Node ${nodeId} deleted`)
|
|
})
|
|
|
|
ctx.on('block:dropped', ({ blockId, parentId }) => {
|
|
console.log(`Block ${blockId} dropped into ${parentId}`)
|
|
})
|
|
}
|
|
```
|
|
|
|
## Async Plugins
|
|
|
|
Plugins can be async (e.g., to fetch remote block definitions):
|
|
|
|
```ts
|
|
const remoteBlocksPlugin: Plugin = async (ctx) => {
|
|
const response = await fetch('/api/custom-blocks')
|
|
const blocks = await response.json()
|
|
|
|
for (const block of blocks) {
|
|
ctx.registerBlock({
|
|
id: block.id,
|
|
label: block.name,
|
|
category: 'custom',
|
|
icon: block.icon || 'Box',
|
|
factory: () => createText(block.defaultHtml),
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
## Plugin Context API
|
|
|
|
| Method | Description |
|
|
|--------|-------------|
|
|
| `registerBlock(block)` | Add a block to the blocks panel |
|
|
| `registerBlockCategory(category)` | Add a new category tab |
|
|
| `registerPropertyEditor(type, component)` | Override a property editor for a type |
|
|
| `registerToolbarAction(action)` | Add a button to the toolbar |
|
|
| `registerSidebarPanel(panel)` | Add a sidebar tab |
|
|
| `on(event, handler)` | Listen to editor events |
|
|
| `off(event, handler)` | Remove an event listener |
|
|
| `labels` | Reactive reference to current `EditorLabels` |
|
|
|
|
## Multiple Plugins
|
|
|
|
```vue
|
|
<EmailEditor :plugins="[brandPlugin, analyticsPlugin, savePlugin]" />
|
|
```
|
|
|
|
Plugins are executed in order. Duplicate block/category IDs are silently ignored (first registration wins).
|