# 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 ``` 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(`

John Doe
Product Manager at Acme Corp
john@acme.com

`), }) // 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('

Welcome to Acme

'), }) } ``` ## 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 ``` Plugins are executed in order. Duplicate block/category IDs are silently ignored (first registration wins).