Creating Custom Plugins
Coming Soon
Comprehensive plugin development guide is coming in v0.2.0 (February 2025).
Plugin Architecture
LikhaEditor uses a plugin-based architecture where all features are implemented as plugins. This makes the editor highly modular and extensible.
Quick Start
Here's a basic plugin structure:
typescript
import { Plugin } from '@likhaeditor/core';
export class MyCustomPlugin extends Plugin {
name = 'my-custom-plugin';
init(editor) {
this.editor = editor;
// Plugin initialization
}
commands() {
return {
myCommand: (editor, ...args) => {
// Command implementation
return true;
}
};
}
keymap() {
return {
'Mod-Shift-X': (editor, state, dispatch) => {
// Keyboard shortcut handler
return true;
}
};
}
prosemirrorPlugins() {
// Return ProseMirror plugins if needed
return [];
}
destroy() {
// Cleanup when plugin is destroyed
}
}Using Your Plugin
typescript
import { Editor } from '@likhaeditor/core';
import { MyCustomPlugin } from './my-custom-plugin';
const editor = new Editor({
element: document.getElementById('editor'),
plugins: [
new MyCustomPlugin()
]
});
// Execute custom command
editor.executeCommand('myCommand');Plugin Examples
Simple Format Plugin
typescript
export class BoldPlugin extends Plugin {
name = 'bold';
commands() {
return {
toggleBold: (editor) => {
const { state, dispatch } = editor.view;
const mark = state.schema.marks.strong;
if (!mark) return false;
const { from, to } = state.selection;
const hasMark = state.doc.rangeHasMark(from, to, mark);
if (hasMark) {
dispatch(state.tr.removeMark(from, to, mark));
} else {
dispatch(state.tr.addMark(from, to, mark.create()));
}
return true;
}
};
}
keymap() {
return {
'Mod-B': (editor) => editor.executeCommand('toggleBold')
};
}
}Custom Block Plugin
typescript
export class CalloutPlugin extends Plugin {
name = 'callout';
commands() {
return {
insertCallout: (editor, type = 'info') => {
const { state, dispatch } = editor.view;
const nodeType = state.schema.nodes.callout;
if (!nodeType) return false;
const node = nodeType.create({ type });
dispatch(state.tr.replaceSelectionWith(node));
return true;
}
};
}
}Toolbar Button Plugin
typescript
export class ToolbarButtonPlugin extends Plugin {
name = 'toolbar-button';
init(editor) {
super.init(editor);
this.addToolbarButton();
}
addToolbarButton() {
const toolbar = document.querySelector('.likha-toolbar');
if (!toolbar) return;
const button = document.createElement('button');
button.textContent = 'Custom';
button.addEventListener('click', () => {
this.editor.executeCommand('myCustomCommand');
});
toolbar.appendChild(button);
}
}Plugin Lifecycle
typescript
export class MyPlugin extends Plugin {
// 1. Plugin is instantiated
constructor(config = {}) {
super(config);
this.config = config;
}
// 2. Editor calls init()
init(editor) {
this.editor = editor;
// Set up event listeners, state, etc.
}
// 3. Commands are registered
commands() {
return {
// Your commands
};
}
// 4. Keyboard shortcuts are registered
keymap() {
return {
// Your shortcuts
};
}
// 5. ProseMirror plugins are added
prosemirrorPlugins() {
return [
// ProseMirror plugins
];
}
// 6. Plugin is destroyed when editor is destroyed
destroy() {
// Clean up event listeners, intervals, etc.
}
}Available APIs
Editor Instance
typescript
this.editor.view // ProseMirror EditorView
this.editor.view.state // ProseMirror EditorState
this.editor.getHTML() // Get HTML content
this.editor.setContent() // Set content
this.editor.executeCommand() // Execute commandPlugin Configuration
typescript
export class MyPlugin extends Plugin {
constructor(config = {}) {
super(config);
}
getConfig(key, defaultValue) {
return this.config[key] ?? defaultValue;
}
}
// Usage
new MyPlugin({
enabled: true,
customOption: 'value'
})Best Practices
- Name your plugin - Use a unique, descriptive name
- Clean up resources - Implement
destroy()method - Handle errors gracefully - Don't crash the editor
- Document your API - Help users understand commands
- Test thoroughly - Write unit tests for your plugin
- Keep it focused - One plugin, one responsibility
Sharing Your Plugin
Built a useful plugin? Share it with the community:
- Publish to npm - Make it easy to install
- Add documentation - README with examples
- Share in Discussions - Show and Tell
- Submit to showcase - We'll feature community plugins
Full Documentation (Coming Soon)
The complete plugin development guide will include:
- ProseMirror schema customization
- Transaction manipulation
- Event system
- State management
- Collaborative editing hooks
- Testing strategies
- TypeScript best practices
- Real-world examples
Get Help
Contributing
Want to contribute your plugin to the official package?
- Fork the repository
- Add your plugin to
packages/plugins/src/ - Write tests
- Update documentation
- Submit a pull request
See our Contributing Guide for details.