Categories: CSSScriptWeb Design

Self-Hosted WYSIWYG Editor in Vanilla JS – Neiki Editor

Neiki Editor is a vanilla JavaScript rich text editor that turns a textarea into a full-featured WYSIWYG HTML editor with a single JS call.

It supports configurable toolbars, light and dark themes, built-in localization for eight languages, and a plugin registration API for custom toolbar extensions.

Features:

  • Fully configurable toolbar with support for button ordering, group separators, and responsive wrapping on smaller screens.
  • Light and dark theme support with automatic persistence across page reloads.
  • Built-in localization for English, Czech, Chinese, Spanish, German, French, Portuguese, and Japanese.
  • Custom translation support for any language via static registration or inline config.
  • Find and Replace with case-sensitive search and regular expression support.
  • Floating toolbar appears above selected text for quick inline formatting access.
  • Table insertion with a right-click context menu for adding rows, columns, and merging or splitting cells.
  • Image insertion by URL, file upload, or direct drag-and-drop into the editor area.
  • Autosave to localStorage with debounced writes, status bar feedback, and restore on page reload.
  • Plugin API for registering custom toolbar buttons with SVG icons, tooltips, and initialization callbacks.
  • HTML source view toggle for switching between visual mode and raw markup editing.
  • Status bar displays live word count, character count, autosave state, and current block type.
  • PHP integration helper with asset loading, editor rendering, and server-side HTML sanitization methods.
  • Content export as HTML file download and browser print dialog support.

Use Cases:

  • CMS content editors that need a self-hosted rich text field with no reliance on third-party services.
  • Blog platforms that need a configurable toolbar scoped to specific formatting options per post type.
  • Admin dashboards that require structured HTML content authoring with table editing support.
  • PHP web applications that need a server-side render helper and sanitization layer for form-submitted HTML.

How to use it:

1. Load the Neiki Editor’s JavaScript and stylesheet:

<!– Load the editor stylesheet –>
<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.css”>

<!– Load the editor script –>
<script src=”https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.js”></script>

2. Add a textarea to your webpage.

<textarea id="editor">
  <h2>Weekly Product Digest</h2>
  <p>Write a polished update for the team.</p>
</textarea>

3. Initialize Neiki Editor with default options.

const editor = new NeikiEditor('#editor');

4. Cofigure your editor with the following options:

  • placeholder (string): Placeholder text displayed when the editor contains no content. Default: 'Start typing...'.
  • minHeight (number): Minimum editor height in pixels. Default: 300.
  • maxHeight (number|null): Maximum editor height in pixels. The editor adds vertical scrolling when content exceeds this value. Default: null.
  • autofocus (boolean): Moves keyboard focus to the editor on initialization. Default: false.
  • spellcheck (boolean): Activates the browser’s built-in spellcheck on editor content. Default: true.
  • readonly (boolean): Puts the editor into read-only mode on load. Default: false.
  • theme (string): Sets the initial color theme. Accepts 'light' or 'dark'. Default: 'light'.
  • language (string): Sets the UI language. Accepts 'en', 'cs', 'zh', 'es', 'de', 'fr', 'pt', or 'ja'. Default: 'en'.
  • translations (object|null): Custom translation key map merged on top of the selected built-in language pack. Default: null.
  • toolbar (array): Controls which toolbar buttons appear and in what order. Insert the string '|' at any position to add a visual group separator.
  • onChange (function|null): Callback invoked on every content change. Receives (content, editor) where content is the current HTML string. Default: null.
  • onSave (function|null): Callback invoked when the user triggers Save via Ctrl+S or the More menu. Receives (content, editor). Default: null.
  • onFocus (function|null): Callback invoked when the editor gains keyboard focus. Receives (editor). Default: null.
  • onBlur (function|null): Callback invoked when the editor loses focus. Receives (editor). Default: null.
  • onReady (function|null): Callback invoked once after the editor finishes initializing. Receives (editor). Default: null.
  • showHelp (boolean): Shows or hides the Help entry in the More menu (). Default: true.
const editor = new NeikiEditor('#editor', {

  // Placeholder text shown when the editor is empty
  placeholder: 'Write your article content here...',

  // Minimum editor height in pixels
  minHeight: 400,

  // Maximum height before scrolling kicks in (null = no limit)
  maxHeight: 800,

  // Focus the editor immediately after load
  autofocus: false,

  // Enable browser-native spellcheck
  spellcheck: true,

  // Set to true to block all editing input
  readonly: false,

  // 'light' or 'dark'
  theme: 'light',

  // UI language — see i18n section for all supported codes
  language: 'en',

  // Custom translation overrides merged with built-in keys
  translations: null,

  // Toolbar buttons in left-to-right order — '|' adds a visual separator
  toolbar: [
    'viewCode', 'undo', 'redo', 'findReplace', '|',
    'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'removeFormat', '|',
    'heading', 'fontFamily', 'fontSize', '|',
    'foreColor', 'backColor', '|',
    'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
    'indent', 'outdent', '|',
    'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',
    'insertDropdown', '|',
    'moreMenu'
  ],

  // Fires on every content change — receives the HTML string and editor instance
  onChange: function(content, editor) {
    console.log('Content updated at', new Date().toISOString());
  },

  // Fires on Ctrl+S or More menu → Save
  onSave: function(content, editor) {
    console.log('Save requested — content length:', content.length);
  },

  // Fires when the editor gains focus
  onFocus: function(editor) {
    console.log('Editor is focused');
  },

  // Fires when the editor loses focus
  onBlur: function(editor) {
    console.log('Editor lost focus');
  },

  // Fires once after the editor finishes initializing
  onReady: function(editor) {
    console.log('Neiki Editor is ready');
  },

  // Show or hide the Help item inside the More menu
  showHelp: true
  
});

5. The toolbar array accepts any of the following string keys. Add '|' between keys to create visual separator groups.

  • Text formatting: bold, italic, underline, strikethrough, subscript, superscript, removeFormat
  • Text style: heading, fontSize, fontFamily, foreColor, backColor
  • Alignment and lists: alignLeft, alignCenter, alignRight, alignJustify, bulletList, numberedList, indent, outdent
  • Insert Dropdown (insertDropdown): Renders a single Insert button that opens a dropdown with Link, Image, Table, Emoji, and Special Characters items. Use the individual keys link, image, table, emoji, and specialChars to place these as standalone buttons.
  • Standalone tools: undo, redo, findReplace, viewCode, blockquote, horizontalRule
  • More Menu (moreMenu): Renders a button pinned to the right that opens a dropdown with Save, Preview, Download, Print, Autosave, Clear All, Toggle Theme, Fullscreen, and Help.

6. API methods:

// Get the current editor content as an HTML string
const html = editor.getContent();

// Replace editor content with new HTML
editor.setContent('<p>Replaced content goes here.</p>');

// Get the editor content as plain text — all HTML tags stripped
const plain = editor.getText();

// Returns true when the editor has no content
const isEmpty = editor.isEmpty();

// Alias for getContent()
const html2 = editor.getHTML();

// Alias for setContent()
editor.setHTML('<p>Set via alias method.</p>');

// Get a structured JSON representation of the current content
const json = editor.getJSON();

// Populate the editor from a previously captured JSON structure
editor.setJSON(json);

// Move keyboard focus to the editor
editor.focus();

// Remove keyboard focus from the editor
editor.blur();

// Re-enable editing after the editor was disabled
editor.enable();

// Disable all editing input — puts the editor into read-only mode
editor.disable();

// Remove the editor instance and restore the original textarea element
editor.destroy();

// Toggle fullscreen mode on and off
editor.toggleFullscreen();

// Toggle between light and dark themes
editor.toggleTheme();

// Set a specific theme directly
editor.setTheme('dark');

// Manually trigger the onSave callback
editor.triggerSave();

// Open the content preview modal
editor.previewContent();

// Trigger the browser download dialog with content as an HTML file
editor.downloadContent();

// Remove all content from the editor
editor.clearAll();

// Execute a built-in formatting command by name
editor.execCommand('bold');

// Insert an HTML string at the current cursor position
editor.insertHTML('<mark>Highlighted passage</mark>');

// Wrap the current selection in a specified element with optional attributes
editor.wrapSelection('mark', { class: 'callout' });

// Remove a wrapping element from the current selection
editor.unwrapSelection('mark');

// Return the browser's Selection object for the current selection
const selection = editor.getSelection();

7. The plugin system adds custom toolbar buttons with full access to the editor instance. Register plugins before or after initialization. They remain available across all instances on the page.

  • name (string, required): Unique plugin identifier. No spaces. Used internally to register and retrieve the plugin.
  • icon (string, optional): SVG markup for the toolbar button icon. Omit this property to register a background-only plugin with no button.
  • tooltip (string, optional): Text shown in the button’s tooltip on hover.
  • action (function, optional): Function called when the toolbar button is clicked. Receives the editor instance as its argument.
  • init (function, optional): Function called once during editor initialization. Receives the editor instance. Use this for one-time setup logic.
// Register a custom reading-time plugin with a toolbar button
NeikiEditor.registerPlugin({

    // Unique identifier for this plugin — no spaces
    name: 'reading-time-counter',

    // SVG icon rendered as the toolbar button graphic
    icon: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',

    // Tooltip text shown when hovering the toolbar button
    tooltip: 'Estimate Reading Time',

    // Runs when the user clicks the toolbar button
    action: function(editor) {
        const words = editor.getText().trim().split(/s+/).filter(Boolean).length;
        const minutes = Math.ceil(words / 200);
        alert('Estimated reading time: ' + minutes + ' minute(s)');
    },

    // Runs once when the editor initializes — useful for DOM setup or event listeners
    init: function(editor) {
        console.log('Reading time plugin initialized');
    }
});

// Get an array of all registered plugin objects
const plugins = NeikiEditor.getPlugins();

8. Themes and Localization:

// Initialize with dark theme
const editor = new NeikiEditor('#editor', { theme: 'dark' });

// Toggle between light and dark at runtime
editor.toggleTheme();

// Set a specific theme programmatically
editor.setTheme('light');

// Initialize with French UI
const editorFr = new NeikiEditor('#article-body', { language: 'fr' });

// Register custom translation keys for German — only override what you need
// English serves as the fallback for any key you leave out
NeikiEditor.addTranslation('de', {
  'toolbar.bold': 'Fett (Strg+B)',
  'toolbar.italic': 'Kursiv (Strg+I)',
  'toolbar.undo': 'Rückgängig (Strg+Z)',
  'toolbar.redo': 'Wiederholen (Strg+Y)'
});

const editorDe = new NeikiEditor('#article-body', { language: 'de' });

// Alternatively, pass translations inline in the config object
const editorInline = new NeikiEditor('#editor', {
  language: 'de',
  translations: {
    de: {
      'toolbar.bold': 'Fett (Strg+B)',
      'toolbar.italic': 'Kursiv (Strg+I)'
    }
  }
});

Framework Integration:

React:

import { useEffect, useRef } from 'react';

function ArticleEditor({ initialValue, onChange }) {
    const textareaRef = useRef(null);
    const editorRef = useRef(null);

    useEffect(() => {
        // Initialize the editor once the component mounts
        editorRef.current = new NeikiEditor(textareaRef.current, {
            onChange: (content) => onChange?.(content)
        });

        // Destroy the editor instance when the component unmounts
        return () => editorRef.current?.destroy();
    }, []);

    return <textarea ref={textareaRef} defaultValue={initialValue} />;
}

Vue.js:

<template>
  <textarea ref="editorEl"></textarea>
</template>

<script>
export default {
    mounted() {
        // Initialize after the component's DOM is ready
        this.editor = new NeikiEditor(this.$refs.editorEl, {
            onChange: (content) => {
                this.$emit('update:modelValue', content);
            }
        });
    },
    beforeUnmount() {
        // Remove the editor before Vue tears down the component
        this.editor.destroy();
    }
}
</script>

AJAX auto-save:

// Debounce saves so the server receives one request per 2-second idle window
const editor = new NeikiEditor('#article-body', {
    onChange: debounce(function(content) {
        fetch('/api/posts/draft', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ content })
        });
    }, 2000)
});

PHP:

<?php require_once 'php/neiki-editor.php'; ?>
<!DOCTYPE html>
<html>
<head>
    <!-- Output CDN <link> and <script> tags — call once per page -->
    <?= NeikiEditor::assets() ?>
</head>
<body>
    <form method="POST" action="publish.php">
        <!-- Render the textarea and initialization script for the 'body' field -->
        <?= NeikiEditor::render('body', $post->content, [
            'minHeight' => 450,
            'placeholder' => 'Write your post content here...'
        ]) ?>
        <button type="submit">Publish</button>
    </form>
</body>
</html>
// publish.php — always sanitize HTML before writing to the database
require_once 'php/neiki-editor.php';
$safe = NeikiEditor::sanitize($_POST['body']);
$db->query('UPDATE posts SET content = ? WHERE id = ?', [$safe, $postId]);

Alternatives:

The post Self-Hosted WYSIWYG Editor in Vanilla JS – Neiki Editor appeared first on CSS Script.

rssfeeds-admin

Share
Published by
rssfeeds-admin

Recent Posts

Vanilla Calendar JS Library for Date Picking, Scheduling, and Timelines

CalendarJS is a feature-rich JavaScript calendar library that allows you to create calendars, date pickers,…

1 hour ago

Lawmakers hear concerns over data center expansion in Michigan

LANSING, MI (WOWO) A Michigan township official is urging communities to update zoning policies as…

2 hours ago

Critical Vulnerability In Flowise Allows Remote Command Execution Via MCP Adapters

A critical vulnerability in Flowise and multiple AI frameworks has been discovered by OX Security,…

2 hours ago

Vercel Confirms Data Breach — Hackers Claim Access to Internal Systems

Vercel has disclosed a significant security incident after threat actors gained unauthorized access to internal…

2 hours ago

New toll road plan links rate hikes to stadium-related funding

HAMMOND, IND. (WOWO) Indiana officials have approved a lease amendment that will allow more frequent…

2 hours ago

New toll road plan links rate hikes to stadium-related funding

HAMMOND, IND. (WOWO) Indiana officials have approved a lease amendment that will allow more frequent…

2 hours ago

This website uses cookies.