{"id":49984851,"url":"https://github.com/neikiri/neiki-editor","last_synced_at":"2026-05-18T20:01:24.758Z","repository":{"id":350337412,"uuid":"1206419037","full_name":"neikiri/neiki-editor","owner":"neikiri","description":"Lightweight, dependency-free WYSIWYG editor with 30+ tools, themes, and easy integration for JavaScript \u0026 PHP.","archived":false,"fork":false,"pushed_at":"2026-05-15T14:45:44.000Z","size":518,"stargazers_count":28,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-15T16:32:52.657Z","etag":null,"topics":["cms","content-editor","customizable","dark-mode","editor","html-editor","javascript","lightweight","no-dependencies","open-source","php","rich-text-editor","text-editor","toolbar","vanilla-js","web-editor","wysiwyg"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neikiri.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-09T22:35:56.000Z","updated_at":"2026-05-15T14:45:15.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/neikiri/neiki-editor","commit_stats":null,"previous_names":["neikiri/neiki-editor"],"tags_count":18,"template":false,"template_full_name":null,"purl":"pkg:github/neikiri/neiki-editor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neikiri%2Fneiki-editor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neikiri%2Fneiki-editor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neikiri%2Fneiki-editor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neikiri%2Fneiki-editor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neikiri","download_url":"https://codeload.github.com/neikiri/neiki-editor/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neikiri%2Fneiki-editor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33189279,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-18T09:27:30.708Z","status":"ssl_error","status_checked_at":"2026-05-18T09:27:28.300Z","response_time":71,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cms","content-editor","customizable","dark-mode","editor","html-editor","javascript","lightweight","no-dependencies","open-source","php","rich-text-editor","text-editor","toolbar","vanilla-js","web-editor","wysiwyg"],"created_at":"2026-05-18T20:01:06.785Z","updated_at":"2026-05-18T20:01:24.736Z","avatar_url":"https://github.com/neikiri.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"logo.png\" alt=\"Neiki's Editor\" width=\"400\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003eNeiki's Editor\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge\u0026logo=javascript\u0026logoColor=%23F7DF1E\" alt=\"JavaScript\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/php-%23777BB4.svg?style=for-the-badge\u0026logo=php\u0026logoColor=white\" alt=\"PHP\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge\u0026logo=html5\u0026logoColor=white\" alt=\"HTML5\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge\u0026logo=css\u0026logoColor=white\" alt=\"CSS\"\u003e\n  \u003cbr\u003e\n  \u003cimg src=\"https://img.shields.io/badge/License-AGPL--3.0-2563EB?style=for-the-badge\u0026logo=open-source-initiative\u0026logoColor=white\u0026labelColor=000F15\u0026logoWidth=20\" alt=\"License\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Version-2.10.1-2563EB?style=for-the-badge\u0026logo=semantic-release\u0026logoColor=white\u0026labelColor=000F15\u0026logoWidth=20\" alt=\"Version\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eLightweight WYSIWYG Rich Text Editor\u003c/b\u003e\u003cbr\u003e\n  \u003ci\u003eEasy to integrate, fully customizable, zero dependencies.\u003c/i\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Features-30%2B%20Tools-3b82f6?style=flat\u0026labelColor=383C43\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Themes-Light%20%26%20Dark-8b5cf6?style=flat\u0026labelColor=383C43\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Setup-Zero%20Config-22c55e?style=flat\u0026labelColor=383C43\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/Size-Lightweight-f97316?style=flat\u0026labelColor=383C43\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://oosmetrics.com/repo/neikiri/neiki-editor\"\u003e\u003cimg src=\"https://api.oosmetrics.com/api/v1/badge/achievement/875222f0-f55c-4209-b894-8bf60b75c590.svg\" alt=\"oosmetrics\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://oosmetrics.com/repo/neikiri/neiki-editor\"\u003e\u003cimg src=\"https://api.oosmetrics.com/api/v1/badge/achievement/71eabb82-6f56-4dc3-bb41-e82a5f8cf939.svg\" alt=\"oosmetrics\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n---\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"preview.png\" alt=\"Neiki's Editor\" width=\"900\"\u003e\n\u003c/p\u003e\n\n---\n\n**Live version:** [https://neikiri.dev/editor](https://neikiri.dev/editor)\n\n---\n\n## 💡 Why Neiki's Editor?\n\nMost WYSIWYG editors either pull in a tree of dependencies or force you into a heavyweight framework. Neiki's Editor is a **single file with zero dependencies** — drop one `\u003cscript\u003e` tag into any page and you get 30+ formatting tools, drag-and-drop blocks, image resizing, a plugin API, and full i18n out of the box. It stays tiny enough for a quick prototype yet powerful enough for a production CMS, so you spend time writing content instead of wrestling with your editor.\n\n---\n## 📦 Installation\n\nAdd this single line — CSS is included automatically, always the **latest version**:\n\n```html\n\u003cscript src=\"https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js\"\u003e\u003c/script\u003e\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cb\u003e📋 More installation options\u003c/b\u003e (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)\u003c/summary\u003e\n\u003cbr\u003e\n\n#### Pin a specific version\n\n```html\n\u003cscript src=\"https://cdn.neikiri.dev/neiki-editor/2.10.1/neiki-editor.min.js\"\u003e\u003c/script\u003e\n```\n\n#### Load CSS and JS separately\n\n```html\n\u003c!-- Latest --\u003e\n\u003clink rel=\"stylesheet\" href=\"https://cdn.neikiri.dev/neiki-editor/neiki-editor.css\"\u003e\n\u003cscript src=\"https://cdn.neikiri.dev/neiki-editor/neiki-editor.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Or pinned --\u003e\n\u003clink rel=\"stylesheet\" href=\"https://cdn.neikiri.dev/neiki-editor/2.10.1/neiki-editor.css\"\u003e\n\u003cscript src=\"https://cdn.neikiri.dev/neiki-editor/2.10.1/neiki-editor.js\"\u003e\u003c/script\u003e\n```\n\n#### Alternative CDN — jsDelivr\n\n```html\n\u003c!-- Latest --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.min.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Pinned --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.10.1/dist/neiki-editor.min.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Separate files (latest) --\u003e\n\u003clink rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.css\"\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Separate files (pinned) --\u003e\n\u003clink rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.10.1/dist/neiki-editor.css\"\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.10.1/dist/neiki-editor.js\"\u003e\u003c/script\u003e\n```\n\n#### Package Manager\n\n```bash\nnpm install neiki-editor\n# or\nyarn add neiki-editor\n# or\npnpm add neiki-editor\n```\n\n#### Self-hosted\n\n```html\n\u003cscript src=\"path/to/neiki-editor.min.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Or separate files --\u003e\n\u003clink rel=\"stylesheet\" href=\"path/to/neiki-editor.css\"\u003e\n\u003cscript src=\"path/to/neiki-editor.js\"\u003e\u003c/script\u003e\n```\n\n\u003c/details\u003e\n\n---\n\n## 🚀 Quick Start\n\n```html\n\u003ctextarea id=\"editor\"\u003e\u003c/textarea\u003e\n\n\u003cscript\u003e\n  const editor = new NeikiEditor('#editor');\n\u003c/script\u003e\n```\n\nThat's it — zero config required. The editor replaces the `\u003ctextarea\u003e` with a full-featured WYSIWYG editor.\n\n---\n\n## ⚙️ Configuration\n\n```javascript\nconst editor = new NeikiEditor('#editor', {\n    placeholder: 'Start typing...',\n    minHeight: 300,\n    maxHeight: 600,\n    autofocus: false,\n    spellcheck: true,\n    readonly: false,\n    theme: 'light',       // 'light' or 'dark'\n    language: 'en',       // 'en', 'cs', or custom via addTranslation()\n    translations: null,   // custom translation keys (merged with built-in)\n    autosaveKey: null,    // optional custom localStorage scope for autosave\n    customClass: null,    // optional custom CSS class for the content area\n    toolbar: [\n        'viewCode', 'undo', 'redo', 'findReplace', '|',\n        'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'code', 'removeFormat', '|',\n        'heading', 'fontFamily', 'fontSize', '|',\n        'foreColor', 'backColor', '|',\n        'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',\n        'indent', 'outdent', '|',\n        'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',\n        'insertDropdown', '|',\n        'moreMenu'\n    ],\n    onChange: function(content, editor) {\n        console.log('Content changed:', content);\n    },\n    onSave: function(content, editor) {\n        console.log('Save triggered:', content);\n    },\n    onReady: function(editor) {\n        console.log('Editor is ready!');\n    }\n});\n```\n\n### Configuration Options\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `placeholder` | `string` | `'Start typing...'` | Placeholder text when editor is empty |\n| `minHeight` | `number` | `300` | Minimum height in pixels |\n| `maxHeight` | `number\\|null` | `null` | Maximum height in pixels (enables scroll). When `null`, the toolbar uses `position: sticky` to remain visible while scrolling. |\n| `autofocus` | `boolean` | `false` | Focus editor on initialization |\n| `spellcheck` | `boolean` | `true` | Enable browser spellcheck |\n| `readonly` | `boolean` | `false` | Make editor read-only |\n| `theme` | `string` | `'light'` | `'light'` or `'dark'` |\n| `language` | `string` | `'en'` | UI language — `en`, `cs`, `zh`, `es`, `de`, `fr`, `pt`, `ja` |\n| `translations` | `object\\|null` | `null` | Custom translation keys (merged with built-in) |\n| `autosaveKey` | `string\\|null` | `null` | Custom `localStorage` scope for autosave content and enabled state |\n| `toolbar` | `array` | *(see above)* | Toolbar button configuration |\n| `onChange` | `function\\|null` | `null` | Callback on content change |\n| `onSave` | `function\\|null` | `null` | Callback on save (triggered by Ctrl+S or More menu → Save) |\n| `onFocus` | `function\\|null` | `null` | Callback when editor gains focus |\n| `onBlur` | `function\\|null` | `null` | Callback when editor loses focus |\n| `onReady` | `function\\|null` | `null` | Callback when editor is ready |\n| `showHelp` | `boolean` | `true` | Show Help button in More menu (⋯) |\n| `imageUploadHandler` | `function\\|null` | `null` | Async callback `(file) =\u003e Promise\u003curl\u003e` for uploading images to a server/CDN instead of base64 |\n| `customClass` | `string\\|null` | `null` | Custom CSS class appended to the editor content area (`neiki-content`) |\n\n---\n\n## 🔧 Toolbar Buttons\n\nUse the `toolbar` array to customize which buttons appear and in what order. Use `'|'` for a visual separator between groups. Groups of buttons between separators wrap as whole units on smaller screens.\n\n### Text Formatting\n\n| Button | Description |\n|--------|-------------|\n| `bold` | Bold text (**Ctrl+B**) |\n| `italic` | Italic text (**Ctrl+I**) |\n| `underline` | Underline text (**Ctrl+U**) |\n| `strikethrough` | Strikethrough text |\n| `subscript` | Subscript text |\n| `superscript` | Superscript text |\n| `code` | Toggle code formatting — inline `\u003ccode\u003e` or `\u003cpre\u003e\u003ccode\u003e` block |\n| `removeFormat` | Remove all formatting |\n\n\u003e **Note:** When no text is selected, formatting commands (including Remove Formatting) automatically expand to the word at the cursor position.\n\n### Text Style\n\n| Button | Type | Description |\n|--------|------|-------------|\n| `heading` | Select | Paragraph, H1, H2, H3, H4, H5, H6. Defaults to Paragraph. |\n| `fontSize` | Widget | Font size widget with **[−]** / **[+]** buttons, text input, and dropdown presets: 8, 9, 10, 11, 12, 14, 18, 24, 30, 36, 48, 60, 72, 96 |\n| `fontFamily` | Select | Sans Serif (Arial), Serif (Georgia), Monospace (Consolas), Cursive (Comic Sans MS) |\n| `foreColor` | Color Picker | Text color — palette, native color input, hex code input |\n| `backColor` | Color Picker | Background color — palette, native color input, hex code input |\n\n### Alignment \u0026 Lists\n\n| Button | Description |\n|--------|-------------|\n| `alignLeft` | Align text left |\n| `alignCenter` | Center text |\n| `alignRight` | Align text right |\n| `alignJustify` | Justify text |\n| `bulletList` | Unordered list |\n| `numberedList` | Ordered list |\n| `indent` | Increase indent |\n| `outdent` | Decrease indent |\n\n### Insert Dropdown\n\nThe `insertDropdown` toolbar item renders a single **Insert** button that opens a dropdown containing:\n\n| Item | Description |\n|------|-------------|\n| **Link** | Insert/edit hyperlink (**Ctrl+K**) |\n| **Image** | Insert image (URL or file upload → base64) |\n| **Table** | Insert table with custom rows/columns |\n| **Emoji** | Emoji picker (100+ emojis) |\n| **Symbol** | Special characters (©, ®, €, π, Ω, arrows, etc.) |\n\nYou can still use `link`, `image`, `table`, `emoji`, `specialChars` as standalone toolbar buttons if preferred.\n\n### More Menu\n\nThe `moreMenu` toolbar item renders a **⋯** button (pushed to the right) that opens a dropdown containing:\n\n| Item | Description |\n|------|-------------|\n| **Save** | Trigger the `onSave` callback |\n| **Preview** | Open a document preview modal |\n| **Download** | Download content as an HTML file |\n| **Print** | Print editor content |\n| **Autosave** | Toggle autosave to localStorage |\n| **Clear all** | Clear all editor content |\n| **Toggle Theme** | Switch between light/dark theme |\n| **Fullscreen** | Toggle fullscreen mode |\n| **Help** | Show help modal with author, version, and links (configurable via `showHelp`) |\n\n### Standalone Tools\n\n| Button | Description |\n|--------|-------------|\n| `undo` | Undo (**Ctrl+Z**) |\n| `redo` | Redo (**Ctrl+Y** / **Ctrl+Shift+Z**) |\n| `findReplace` | Find \u0026 Replace with regex support |\n| `viewCode` | Toggle HTML source editor |\n| `blockquote` | Block quote (toggles on/off) |\n| `horizontalRule` | Horizontal line |\n\n---\n\n## 🎨 Themes\n\nNeiki's Editor ships with **Light** and **Dark** themes.\n\n### Set theme on init:\n\n```javascript\nconst editor = new NeikiEditor('#editor', {\n    theme: 'dark'\n});\n```\n\n### Toggle theme at runtime:\n\nUse the **Toggle Theme** item in the More menu (⋯), or toggle programmatically:\n\n```javascript\neditor.toggleTheme();\n// or set a specific theme:\neditor.setTheme('dark');\n```\n\nThe selected theme persists across page reloads via `localStorage`.\n\n---\n\n## 🌍 Localization (i18n)\n\nNeiki's Editor supports multiple UI languages. Built-in:\n\n- **English** (`en`) — default\n- **Czech** (`cs`)\n- **Chinese** (`zh`)\n- **Spanish** (`es`)\n- **German** (`de`)\n- **French** (`fr`)\n- **Portuguese** (`pt`)\n- **Japanese** (`ja`)\n\n### Set language on init:\n\n```javascript\nconst editor = new NeikiEditor('#editor', {\n    language: 'cs'  // Czech UI\n});\n```\n\n### Custom translations\n\nAdd your own language or override existing translations using the static method:\n\n```javascript\nNeikiEditor.addTranslation('de', {\n    'toolbar.bold': 'Fett (Strg+B)',\n    'toolbar.italic': 'Kursiv (Strg+I)',\n    'toolbar.undo': 'Rückgängig (Strg+Z)',\n    // only override what you need — English is the fallback\n});\n\nconst editor = new NeikiEditor('#editor', { language: 'de' });\n```\n\nOr pass translations directly in config:\n\n```javascript\nconst editor = new NeikiEditor('#editor', {\n    language: 'de',\n    translations: {\n        de: {\n            'toolbar.bold': 'Fett (Strg+B)',\n            'toolbar.italic': 'Kursiv (Strg+I)',\n            // ...\n        }\n    }\n});\n```\n\nAll toolbar tooltips, modal dialogs, status bar texts, and system messages are translatable.\n\n---\n\n## 💾 Autosave\n\nAutosave is accessible from the **More menu** (⋯) in the default toolbar. When activated:\n\n- Content is saved to `localStorage` on every content change (debounced)\n- The status bar shows \"Autosaving...\" / \"Saved locally\"\n- Content is restored on page reload **only when autosave was enabled**\n- Autosave keys are scoped by page URL and editor identity by default, so multiple editors do not overwrite each other\n\nFor apps where the same URL can edit different records, pass a custom `autosaveKey`:\n\n```javascript\nnew NeikiEditor('#editor', {\n    autosaveKey: 'article-42'\n});\n```\n\n\u003e **Note:** For production use (CMS, blog, etc.), use the `onSave` callback or `onChange` callback to save content to your database instead.\n\n---\n\n## 📋 API Methods\n\n### Content\n\n```javascript\neditor.getContent();          // Get HTML content\neditor.setContent('\u003cp\u003eHello\u003c/p\u003e');  // Set HTML content\neditor.getText();             // Get plain text content\neditor.isEmpty();             // Check if editor is empty\n\neditor.getHTML();             // Alias for getContent()\neditor.setHTML(html);         // Alias for setContent()\n\neditor.getJSON();             // Get structured JSON representation\neditor.setJSON(json);         // Set content from JSON\n```\n\n### Editor Control\n\n```javascript\neditor.focus();               // Focus the editor\neditor.blur();                // Blur the editor\neditor.enable();              // Enable editing\neditor.disable();             // Disable editing (read-only)\neditor.destroy();             // Remove editor, restore original element\neditor.toggleFullscreen();    // Toggle fullscreen mode\neditor.toggleTheme();         // Toggle light/dark theme\neditor.setTheme('dark');      // Set a specific theme\neditor.triggerSave();         // Trigger onSave callback\neditor.previewContent();      // Open preview modal\neditor.downloadContent();     // Download content as HTML file\neditor.clearAll();            // Clear all content\n```\n\n### Localization\n\n```javascript\nNeikiEditor.addTranslation('de', { ... });  // Add/override translations (static)\n```\n\n### Command Execution\n\n```javascript\neditor.execCommand('bold');                  // Execute a command\neditor.insertHTML('\u003cspan\u003eHello\u003c/span\u003e');     // Insert HTML at cursor\neditor.wrapSelection('mark', { class: 'highlight' }); // Wrap selection\neditor.unwrapSelection('mark');              // Unwrap selection\n```\n\n### Selection\n\n```javascript\neditor.getSelection();        // Get current Selection object\n```\n\n---\n\n## 🔌 Plugin API\n\nExtend the editor with custom plugins:\n\n```javascript\nNeikiEditor.registerPlugin({\n    name: 'word-counter-alert',\n    icon: '\u003csvg viewBox=\"0 0 24 24\"\u003e...\u003c/svg\u003e',   // optional toolbar button\n    tooltip: 'Show Word Count',\n    action: function(editor) {\n        const text = editor.getContent().replace(/\u003c[^\u003e]*\u003e/g, '');\n        const words = text.trim().split(/\\s+/).filter(Boolean).length;\n        alert('Word count: ' + words);\n    },\n    init: function(editor) {\n        // Called once when editor initializes (optional)\n        console.log('Plugin initialized!');\n    }\n});\n```\n\n### Plugin Properties\n\n| Property | Type | Required | Description |\n|----------|------|----------|-------------|\n| `name` | `string` | ✅ | Unique plugin identifier |\n| `icon` | `string` | ❌ | SVG icon for toolbar button |\n| `tooltip` | `string` | ❌ | Button tooltip text |\n| `action` | `function` | ❌ | Called when toolbar button is clicked |\n| `init` | `function` | ❌ | Called once during editor initialization |\n\n### List Registered Plugins\n\n```javascript\nNeikiEditor.getPlugins(); // Returns array of registered plugins\n```\n\n---\n\n## 📊 Table Features\n\nInsert tables via the toolbar button with configurable rows, columns, and optional header row.\n\n### Table Context Menu\n\nRight-click on any table cell to access:\n\n- **Insert Row Above / Below**\n- **Insert Column Left / Right**\n- **Delete Row / Column / Table**\n- **Merge Cells** — merge selected cells horizontally\n- **Split Cell** — split a previously merged cell\n\n### Column Resize\n\nHover near any column border to reveal a **drag handle**. Drag to resize adjacent column widths. The table uses fixed layout during resize for precise control.\n\n---\n\n## 🖼️ Image Support\n\n### Insert via URL\n\nClick the **Image** toolbar button and paste a URL.\n\n### Upload from File\n\nThe Image dialog includes a file upload input. Selected images are converted to **base64** and embedded directly in the content.\n\n### Drag \u0026 Drop\n\nDrag image files directly into the editor content area. Images are automatically converted to base64 and inserted at the drop position.\n\n### Resize Images\n\nClick any image in the editor to select it — **resize handles** appear on all four corners. Drag any handle to resize while maintaining aspect ratio. A live **size label** (width × height) is displayed below the image during resizing.\n\n### Image Toolbar\n\nWhen an image is selected, a contextual toolbar appears above it with image-specific actions:\n\n- **Drag handle** (⠿) — click and drag to reposition the image within the editor\n- **Replace** — swap the image via a file picker (supports `imageUploadHandler` or base64)\n- **Delete** — remove the selected image\n\nThe floating text-formatting toolbar is automatically hidden when an image is selected.\n\n---\n\n## 🔍 Find \u0026 Replace\n\nOpen with the toolbar button or implement via the modal API.\n\nFeatures:\n- **Case-sensitive** search toggle\n- **Regular expression** support\n- **Find Next** — navigate through matches with highlighting\n- **Replace** — replace current match\n- **Replace All** — replace all matches at once\n\n---\n\n## ✏️ Floating Toolbar\n\nWhen you select text in the editor, a floating toolbar appears above the selection with quick access to:\n\n- **Move Block Up / Down** — reorder the current content block (left side)\n- **Bold, Italic, Underline, Strikethrough** — quick formatting\n- **Insert Link**\n\nThe toolbar follows the selection and disappears when the selection is cleared. When an image is selected, the floating toolbar is replaced by an **image-specific toolbar** with relevant actions.\n\n---\n\n## 🔀 Block Drag \u0026 Drop\n\nHover over any content block (paragraph, heading, table, image, list, etc.) to reveal a **grip handle** (⠿) on the left side. Drag to reorder blocks within the editor. A ghost preview and drop placeholder guide the drop position.\n\n---\n\n## 🖨️ Print\n\nClick the **Print** button to open the browser print dialog with the editor content formatted for printing.\n\n---\n\n## ⌨️ Keyboard Shortcuts\n\n| Shortcut | Action |\n|----------|--------|\n| **Ctrl+B** | Bold |\n| **Ctrl+I** | Italic |\n| **Ctrl+U** | Underline |\n| **Ctrl+K** | Insert Link |\n| **Ctrl+S** | Save (triggers `onSave` callback) |\n| **Ctrl+Z** | Undo |\n| **Ctrl+Y** / **Ctrl+Shift+Z** | Redo |\n| **Tab** | Indent |\n| **Shift+Tab** | Outdent |\n\n---\n\n## 📐 Status Bar\n\nThe editor includes a status bar at the bottom displaying:\n\n- **Left side:** Word count and character count\n- **Right side:** Autosave status (when enabled) and current block type (p, h1, h2, etc.)\n\n---\n\n## 🔗 Integration Examples\n\n### PHP Helper (Recommended)\n\nNeiki's Editor includes a PHP integration helper (`php/neiki-editor.php`) that provides asset loading, editor rendering, and HTML sanitization:\n\n```php\n\u003c?php require_once 'php/neiki-editor.php'; ?\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003c?= NeikiEditor::assets() ?\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cform method=\"POST\" action=\"save.php\"\u003e\n        \u003c?= NeikiEditor::render('content', $article-\u003econtent, [\n            'minHeight' =\u003e 400,\n            'placeholder' =\u003e 'Write your article...'\n        ]) ?\u003e\n        \u003cbutton type=\"submit\"\u003eSave\u003c/button\u003e\n    \u003c/form\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n```php\n// save.php — sanitize before saving to database\nrequire_once 'php/neiki-editor.php';\n$clean = NeikiEditor::sanitize($_POST['content']);\n$db-\u003esave($clean);\n```\n\n#### PHP Helper Methods\n\n| Method | Description |\n|--------|-------------|\n| `NeikiEditor::assets()` | Output CSS \u0026 JS tags (CDN). Call once per page. |\n| `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of CDN. |\n| `NeikiEditor::render($id, $content, $options)` | Render textarea + init script. |\n| `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before DB save. |\n\n### PHP Form (Manual)\n\n```php\n\u003cform method=\"POST\" action=\"save.php\"\u003e\n    \u003ctextarea id=\"editor\" name=\"content\"\u003e\u003c?= htmlspecialchars($article-\u003econtent) ?\u003e\u003c/textarea\u003e\n    \u003cbutton type=\"submit\"\u003eSave\u003c/button\u003e\n\u003c/form\u003e\n\n\u003cscript\u003e\n    const editor = new NeikiEditor('#editor');\n\u003c/script\u003e\n```\n\n### AJAX Save\n\n```javascript\nconst editor = new NeikiEditor('#editor', {\n    onChange: debounce(function(content) {\n        fetch('/api/save', {\n            method: 'POST',\n            headers: { 'Content-Type': 'application/json' },\n            body: JSON.stringify({ content })\n        });\n    }, 2000)\n});\n```\n\n### Vue.js\n\n```vue\n\u003ctemplate\u003e\n  \u003ctextarea ref=\"editor\"\u003e\u003c/textarea\u003e\n\u003c/template\u003e\n\n\u003cscript\u003e\nexport default {\n    mounted() {\n        this.editor = new NeikiEditor(this.$refs.editor, {\n            onChange: (content) =\u003e {\n                this.$emit('update:modelValue', content);\n            }\n        });\n    },\n    beforeUnmount() {\n        this.editor.destroy();\n    }\n}\n\u003c/script\u003e\n```\n\n### React\n\n```jsx\nimport { useEffect, useRef } from 'react';\n\nfunction NeikiEditorComponent({ value, onChange }) {\n    const ref = useRef(null);\n    const editorRef = useRef(null);\n\n    useEffect(() =\u003e {\n        editorRef.current = new NeikiEditor(ref.current, {\n            onChange: (content) =\u003e onChange?.(content)\n        });\n        return () =\u003e editorRef.current?.destroy();\n    }, []);\n\n    return \u003ctextarea ref={ref} defaultValue={value} /\u003e;\n}\n```\n\n---\n\n## 🌐 Browser Support\n\n| Browser | Support |\n|---------|---------|\n| Chrome | ✅ Latest |\n| Firefox | ✅ Latest |\n| Safari | ✅ Latest |\n| Edge | ✅ Latest |\n| Opera | ✅ Latest |\n\n---\n\n## 📁 File Structure\n\n```\nneiki-editor/\n├── dist/\n│   ├── neiki-editor.min.js   # Minified editor + embedded CSS (recommended)\n│   ├── neiki-editor.min.css  # Minified styles\n│   ├── neiki-editor.js       # Editor core (unminified)\n│   └── neiki-editor.css      # Editor styles (unminified)\n├── demo/\n│   └── index.html            # Interactive demo page\n│   └── logo.png              # Demo logo\n├── php/\n│   └── neiki-editor.php      # PHP integration helper\n├── logo.png\n├── package.json\n├── README.md\n├── LICENSE\n├── CHANGELOG.md\n├── CONTRIBUTING.md\n├── CODE_OF_CONDUCT.md\n└── SECURITY.md\n```\n\n---\n\n## 📄 License\n\nThis project is licensed under the GNU Affero General Public License v3 — see the [LICENSE](LICENSE) file for details.\n\n---\n\n\u003cp align=\"center\"\u003e\n  Made with ❤️ for the web community\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneikiri%2Fneiki-editor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneikiri%2Fneiki-editor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneikiri%2Fneiki-editor/lists"}