Skip to content

Plugin API

Jindřich Stoklasa edited this page Apr 10, 2026 · 4 revisions

🔌 Plugin API

Extend Neiki Editor with custom functionality using the Plugin API. Plugins can add toolbar buttons, hook into initialization, and interact with the editor programmatically.


🧩 Overview

Plugins are registered globally before or after editor initialization. Each plugin can optionally:

  • Add a toolbar button with a custom icon
  • Run an action when the button is clicked
  • Run an init function when the editor starts

Note

Plugins are registered globally via NeikiEditor.registerPlugin() and are available to all editor instances on the page.


📝 Registering a Plugin

NeikiEditor.registerPlugin({
    name: 'my-plugin',
    icon: '<svg viewBox="0 0 24 24">...</svg>',
    tooltip: 'My Custom Action',
    action: function(editor) {
        // Called when toolbar button is clicked
    },
    init: function(editor) {
        // Called once when editor initializes
    }
});

📊 Plugin Properties

Property Type Required Description
name string Unique identifier for the plugin
icon string SVG icon markup for the toolbar button
tooltip string Tooltip text shown on hover
action function(editor) Handler called on toolbar button click
init function(editor) Handler called once during editor initialization

Important

The name must be unique across all registered plugins. If you register two plugins with the same name, the second will overwrite the first.


💡 Plugin Examples

Word Counter Alert

A simple plugin that shows a word count alert:

NeikiEditor.registerPlugin({
    name: 'word-counter',
    icon: '<svg viewBox="0 0 24 24"><path d="M3 18h12v-2H3v2zM3 6v2h18V6H3zm0 7h18v-2H3v2z"/></svg>',
    tooltip: 'Show Word Count',
    action: function(editor) {
        const text = editor.getContent().replace(/<[^>]*>/g, '');
        const words = text.trim().split(/\s+/).filter(Boolean).length;
        const chars = text.length;
        alert(`Words: ${words}\nCharacters: ${chars}`);
    }
});

Insert Timestamp

Insert the current date and time at the cursor position:

NeikiEditor.registerPlugin({
    name: 'timestamp',
    icon: '<svg viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm.5-13H11v6l5.25 3.15.75-1.23-4.5-2.67V7z"/></svg>',
    tooltip: 'Insert Timestamp',
    action: function(editor) {
        const now = new Date();
        const formatted = now.toLocaleString();
        editor.insertHTML(`<span style="color:#6b7280;font-size:0.9em">[${formatted}]</span>&nbsp;`);
    }
});

Auto-Capitalize Headings

A plugin that automatically capitalizes the first letter of every heading on init:

NeikiEditor.registerPlugin({
    name: 'auto-capitalize',
    init: function(editor) {
        editor.contentArea.addEventListener('input', function() {
            const headings = editor.contentArea.querySelectorAll('h1, h2, h3, h4, h5, h6');
            headings.forEach(function(h) {
                if (h.textContent.length > 0) {
                    const first = h.textContent.charAt(0);
                    if (first !== first.toUpperCase()) {
                        // Use selection-safe approach
                        h.textContent = first.toUpperCase() + h.textContent.slice(1);
                    }
                }
            });
        });
    }
});

Caution

Be careful with init plugins that modify content on every input event — they can interfere with the user's typing and cursor position. Always test thoroughly.


Insert Divider with Style

NeikiEditor.registerPlugin({
    name: 'styled-divider',
    icon: '<svg viewBox="0 0 24 24"><path d="M2 12h4v1H2v-1zm6 0h4v1H8v-1zm6 0h4v1h-4v-1zm6 0h2v1h-2v-1z"/></svg>',
    tooltip: 'Insert Styled Divider',
    action: function(editor) {
        editor.insertHTML(
            '<div style="text-align:center;margin:1.5em 0;color:#d1d5db;letter-spacing:0.5em;font-size:1.2em">• • •</div>'
        );
    }
});

Clear Formatting & Normalize

A cleanup plugin that strips all inline styles and extra attributes:

NeikiEditor.registerPlugin({
    name: 'clean-paste',
    icon: '<svg viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>',
    tooltip: 'Clean All Formatting',
    action: function(editor) {
        const text = editor.contentArea.innerText;
        editor.setContent('<p>' + text.split('\n\n').join('</p><p>') + '</p>');
    }
});

📋 Listing Plugins

Retrieve all registered plugins:

const plugins = NeikiEditor.getPlugins();

plugins.forEach(function(plugin) {
    console.log('Plugin:', plugin.name);
});

🏗️ Plugin Architecture

graph TD
    A[NeikiEditor Instance] --> B[Toolbar Area]
    A --> C[Content Area]
    A --> D[Status Bar]
    B --> E[Plugin Buttons]
    E --> F[Word Counter]
    E --> G[Timestamp]
    E --> H[Divider]
    A --> I[Plugin Init Hooks]
    I --> J["auto-capitalize ​#8594; addEventListener"]
Loading

🔒 Best Practices

Tip

Follow these guidelines for reliable plugins:

  1. Use unique names — prefix with your project name if needed (e.g., myapp-timestamp)
  2. Keep actions focused — one plugin, one responsibility
  3. Don't modify contentArea directly in init — use editor API methods when possible
  4. Test with both themes — ensure your plugin UI works in Light and Dark mode
  5. Handle edge cases — check for empty selection before operating on it

🔗 Related Pages


Clone this wiki locally