neiki-editor 2.9.1 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,729 +1,751 @@
1
- <p align="center">
2
- <img src="logo.png" alt="Neiki's Editor" width="400">
3
- </p>
4
-
5
- <h1 align="center">Neiki's Editor</h1>
6
-
7
- <p align="center">
8
- <img src="https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E" alt="JavaScript">
9
- <img src="https://img.shields.io/badge/php-%23777BB4.svg?style=for-the-badge&logo=php&logoColor=white" alt="PHP">
10
- <img src="https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white" alt="HTML5">
11
- <img src="https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge&logo=css&logoColor=white" alt="CSS">
12
- <br>
13
- <img src="https://img.shields.io/badge/License-MIT-2563EB?style=for-the-badge&logo=open-source-initiative&logoColor=white&labelColor=000F15&logoWidth=20" alt="License">
14
- <img src="https://img.shields.io/badge/Version-2.9.1-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
15
- </p>
16
-
17
- <p align="center">
18
- <b>Lightweight WYSIWYG Rich Text Editor</b><br>
19
- <i>Easy to integrate, fully customizable, zero dependencies.</i>
20
- </p>
21
-
22
- <p align="center">
23
- <img src="https://img.shields.io/badge/Features-30%2B%20Tools-3b82f6?style=flat&labelColor=383C43" />
24
- <img src="https://img.shields.io/badge/Themes-Light%20%26%20Dark-8b5cf6?style=flat&labelColor=383C43" />
25
- <img src="https://img.shields.io/badge/Setup-Zero%20Config-22c55e?style=flat&labelColor=383C43" />
26
- <img src="https://img.shields.io/badge/Size-Lightweight-f97316?style=flat&labelColor=383C43" />
27
- </p>
28
-
29
- <p align="center">
30
- <a href="https://oosmetrics.com/repo/neikiri/neiki-editor"><img src="https://api.oosmetrics.com/api/v1/badge/achievement/875222f0-f55c-4209-b894-8bf60b75c590.svg" alt="oosmetrics"/></a>
31
- <a href="https://oosmetrics.com/repo/neikiri/neiki-editor"><img src="https://api.oosmetrics.com/api/v1/badge/achievement/71eabb82-6f56-4dc3-bb41-e82a5f8cf939.svg" alt="oosmetrics"/></a>
32
- </p>
33
-
34
- ---
35
- <p align="center">
36
- <img src="preview.png" alt="Neiki's Editor" width="900">
37
- </p>
38
-
39
- ---
40
-
41
- **Live version:** [https://neiki.eu/editor](https://neiki.eu/editor)
42
-
43
- ---
44
-
45
- ## 💡 Why Neiki's Editor?
46
-
47
- Most 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 `<script>` 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.
48
-
49
- ---
50
- ## 📦 Installation
51
-
52
- Add this single line — CSS is included automatically, always the **latest version**:
53
-
54
- ```html
55
- <script src="https://cdn.neiki.eu/neiki-editor/neiki-editor.min.js"></script>
56
- ```
57
-
58
- <details>
59
- <summary><b>📋 More installation options</b> (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)</summary>
60
- <br>
61
-
62
- #### Pin a specific version
63
-
64
- ```html
65
- <script src="https://cdn.neiki.eu/neiki-editor/2.9.1/neiki-editor.min.js"></script>
66
- ```
67
-
68
- #### Load CSS and JS separately
69
-
70
- ```html
71
- <!-- Latest -->
72
- <link rel="stylesheet" href="https://cdn.neiki.eu/neiki-editor/neiki-editor.css">
73
- <script src="https://cdn.neiki.eu/neiki-editor/neiki-editor.js"></script>
74
-
75
- <!-- Or pinned -->
76
- <link rel="stylesheet" href="https://cdn.neiki.eu/neiki-editor/2.9.1/neiki-editor.css">
77
- <script src="https://cdn.neiki.eu/neiki-editor/2.9.1/neiki-editor.js"></script>
78
- ```
79
-
80
- #### Alternative CDN — jsDelivr
81
-
82
- ```html
83
- <!-- Latest -->
84
- <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.min.js"></script>
85
-
86
- <!-- Pinned -->
87
- <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.9.1/dist/neiki-editor.min.js"></script>
88
-
89
- <!-- Separate files (latest) -->
90
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.css">
91
- <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.js"></script>
92
-
93
- <!-- Separate files (pinned) -->
94
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.9.1/dist/neiki-editor.css">
95
- <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.9.1/dist/neiki-editor.js"></script>
96
- ```
97
-
98
- #### Package Manager
99
-
100
- ```bash
101
- npm install neiki-editor
102
- # or
103
- yarn add neiki-editor
104
- # or
105
- pnpm add neiki-editor
106
- ```
107
-
108
- #### Self-hosted
109
-
110
- ```html
111
- <script src="path/to/neiki-editor.min.js"></script>
112
-
113
- <!-- Or separate files -->
114
- <link rel="stylesheet" href="path/to/neiki-editor.css">
115
- <script src="path/to/neiki-editor.js"></script>
116
- ```
117
-
118
- </details>
119
-
120
- ---
121
-
122
- ## 🚀 Quick Start
123
-
124
- ```html
125
- <textarea id="editor"></textarea>
126
-
127
- <script>
128
- const editor = new NeikiEditor('#editor');
129
- </script>
130
- ```
131
-
132
- That's it — zero config required. The editor replaces the `<textarea>` with a full-featured WYSIWYG editor.
133
-
134
- ---
135
-
136
- ## ⚙️ Configuration
137
-
138
- ```javascript
139
- const editor = new NeikiEditor('#editor', {
140
- placeholder: 'Start typing...',
141
- minHeight: 300,
142
- maxHeight: 600,
143
- autofocus: false,
144
- spellcheck: true,
145
- readonly: false,
146
- theme: 'light', // 'light' or 'dark'
147
- language: 'en', // 'en', 'cs', or custom via addTranslation()
148
- translations: null, // custom translation keys (merged with built-in)
149
- toolbar: [
150
- 'viewCode', 'undo', 'redo', 'findReplace', '|',
151
- 'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'removeFormat', '|',
152
- 'heading', 'fontFamily', 'fontSize', '|',
153
- 'foreColor', 'backColor', '|',
154
- 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
155
- 'indent', 'outdent', '|',
156
- 'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',
157
- 'insertDropdown', '|',
158
- 'moreMenu'
159
- ],
160
- onChange: function(content, editor) {
161
- console.log('Content changed:', content);
162
- },
163
- onSave: function(content, editor) {
164
- console.log('Save triggered:', content);
165
- },
166
- onReady: function(editor) {
167
- console.log('Editor is ready!');
168
- }
169
- });
170
- ```
171
-
172
- ### Configuration Options
173
-
174
- | Option | Type | Default | Description |
175
- |--------|------|---------|-------------|
176
- | `placeholder` | `string` | `'Start typing...'` | Placeholder text when editor is empty |
177
- | `minHeight` | `number` | `300` | Minimum height in pixels |
178
- | `maxHeight` | `number\|null` | `null` | Maximum height in pixels (enables scroll) |
179
- | `autofocus` | `boolean` | `false` | Focus editor on initialization |
180
- | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
181
- | `readonly` | `boolean` | `false` | Make editor read-only |
182
- | `theme` | `string` | `'light'` | `'light'` or `'dark'` |
183
- | `language` | `string` | `'en'` | UI language — `en`, `cs`, `zh`, `es`, `de`, `fr`, `pt`, `ja` |
184
- | `translations` | `object\|null` | `null` | Custom translation keys (merged with built-in) |
185
- | `toolbar` | `array` | *(see above)* | Toolbar button configuration |
186
- | `onChange` | `function\|null` | `null` | Callback on content change |
187
- | `onSave` | `function\|null` | `null` | Callback on save (triggered by Ctrl+S or More menu → Save) |
188
- | `onFocus` | `function\|null` | `null` | Callback when editor gains focus |
189
- | `onBlur` | `function\|null` | `null` | Callback when editor loses focus |
190
- | `onReady` | `function\|null` | `null` | Callback when editor is ready |
191
- | `showHelp` | `boolean` | `true` | Show Help button in More menu (⋯) |
192
-
193
- ---
194
-
195
- ## 🔧 Toolbar Buttons
196
-
197
- Use 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.
198
-
199
- ### Text Formatting
200
-
201
- | Button | Description |
202
- |--------|-------------|
203
- | `bold` | Bold text (**Ctrl+B**) |
204
- | `italic` | Italic text (**Ctrl+I**) |
205
- | `underline` | Underline text (**Ctrl+U**) |
206
- | `strikethrough` | Strikethrough text |
207
- | `subscript` | Subscript text |
208
- | `superscript` | Superscript text |
209
- | `removeFormat` | Remove all formatting |
210
-
211
- > **Note:** When no text is selected, formatting commands (including Remove Formatting) automatically expand to the word at the cursor position.
212
-
213
- ### Text Style
214
-
215
- | Button | Type | Description |
216
- |--------|------|-------------|
217
- | `heading` | Select | Paragraph, H1, H2, H3, H4, H5, H6. Defaults to Paragraph. |
218
- | `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 |
219
- | `fontFamily` | Select | Sans Serif (Arial), Serif (Georgia), Monospace (Consolas), Cursive (Comic Sans MS) |
220
- | `foreColor` | Color Picker | Text color palette, native color input, hex code input |
221
- | `backColor` | Color Picker | Background color palette, native color input, hex code input |
222
-
223
- ### Alignment & Lists
224
-
225
- | Button | Description |
226
- |--------|-------------|
227
- | `alignLeft` | Align text left |
228
- | `alignCenter` | Center text |
229
- | `alignRight` | Align text right |
230
- | `alignJustify` | Justify text |
231
- | `bulletList` | Unordered list |
232
- | `numberedList` | Ordered list |
233
- | `indent` | Increase indent |
234
- | `outdent` | Decrease indent |
235
-
236
- ### Insert Dropdown
237
-
238
- The `insertDropdown` toolbar item renders a single **Insert** button that opens a dropdown containing:
239
-
240
- | Item | Description |
241
- |------|-------------|
242
- | **Link** | Insert/edit hyperlink (**Ctrl+K**) |
243
- | **Image** | Insert image (URL or file upload → base64) |
244
- | **Table** | Insert table with custom rows/columns |
245
- | **Emoji** | Emoji picker (100+ emojis) |
246
- | **Symbol** | Special characters (©, ®, €, π, Ω, arrows, etc.) |
247
-
248
- You can still use `link`, `image`, `table`, `emoji`, `specialChars` as standalone toolbar buttons if preferred.
249
-
250
- ### More Menu
251
-
252
- The `moreMenu` toolbar item renders a **⋯** button (pushed to the right) that opens a dropdown containing:
253
-
254
- | Item | Description |
255
- |------|-------------|
256
- | **Save** | Trigger the `onSave` callback |
257
- | **Preview** | Open a document preview modal |
258
- | **Download** | Download content as an HTML file |
259
- | **Print** | Print editor content |
260
- | **Autosave** | Toggle autosave to localStorage |
261
- | **Clear all** | Clear all editor content |
262
- | **Toggle Theme** | Switch between light/dark theme |
263
- | **Fullscreen** | Toggle fullscreen mode |
264
- | **Help** | Show help modal with author, version, and links (configurable via `showHelp`) |
265
-
266
- ### Standalone Tools
267
-
268
- | Button | Description |
269
- |--------|-------------|
270
- | `undo` | Undo (**Ctrl+Z**) |
271
- | `redo` | Redo (**Ctrl+Y** / **Ctrl+Shift+Z**) |
272
- | `findReplace` | Find & Replace with regex support |
273
- | `viewCode` | Toggle HTML source editor |
274
- | `blockquote` | Block quote |
275
- | `horizontalRule` | Horizontal line |
276
-
277
- ---
278
-
279
- ## 🎨 Themes
280
-
281
- Neiki's Editor ships with **Light** and **Dark** themes.
282
-
283
- ### Set theme on init:
284
-
285
- ```javascript
286
- const editor = new NeikiEditor('#editor', {
287
- theme: 'dark'
288
- });
289
- ```
290
-
291
- ### Toggle theme at runtime:
292
-
293
- Use the **Toggle Theme** item in the More menu (⋯), or toggle programmatically:
294
-
295
- ```javascript
296
- editor.toggleTheme();
297
- // or set a specific theme:
298
- editor.setTheme('dark');
299
- ```
300
-
301
- The selected theme persists across page reloads via `localStorage`.
302
-
303
- ---
304
-
305
- ## 🌍 Localization (i18n)
306
-
307
- Neiki's Editor supports multiple UI languages. Built-in:
308
-
309
- - **English** (`en`) — default
310
- - **Czech** (`cs`)
311
- - **Chinese** (`zh`)
312
- - **Spanish** (`es`)
313
- - **German** (`de`)
314
- - **French** (`fr`)
315
- - **Portuguese** (`pt`)
316
- - **Japanese** (`ja`)
317
-
318
- ### Set language on init:
319
-
320
- ```javascript
321
- const editor = new NeikiEditor('#editor', {
322
- language: 'cs' // Czech UI
323
- });
324
- ```
325
-
326
- ### Custom translations
327
-
328
- Add your own language or override existing translations using the static method:
329
-
330
- ```javascript
331
- NeikiEditor.addTranslation('de', {
332
- 'toolbar.bold': 'Fett (Strg+B)',
333
- 'toolbar.italic': 'Kursiv (Strg+I)',
334
- 'toolbar.undo': 'Rückgängig (Strg+Z)',
335
- // only override what you need — English is the fallback
336
- });
337
-
338
- const editor = new NeikiEditor('#editor', { language: 'de' });
339
- ```
340
-
341
- Or pass translations directly in config:
342
-
343
- ```javascript
344
- const editor = new NeikiEditor('#editor', {
345
- language: 'de',
346
- translations: {
347
- de: {
348
- 'toolbar.bold': 'Fett (Strg+B)',
349
- 'toolbar.italic': 'Kursiv (Strg+I)',
350
- // ...
351
- }
352
- }
353
- });
354
- ```
355
-
356
- All toolbar tooltips, modal dialogs, status bar texts, and system messages are translatable.
357
-
358
- ---
359
-
360
- ## 💾 Autosave
361
-
362
- Autosave is accessible from the **More menu** (⋯) in the default toolbar. When activated:
363
-
364
- - Content is saved to `localStorage` on every content change (debounced)
365
- - The status bar shows "Autosaving..." / "Saved locally"
366
- - Content is restored on page reload **only when autosave was enabled**
367
-
368
- > **Note:** For production use (CMS, blog, etc.), use the `onSave` callback or `onChange` callback to save content to your database instead.
369
-
370
- ---
371
-
372
- ## 📋 API Methods
373
-
374
- ### Content
375
-
376
- ```javascript
377
- editor.getContent(); // Get HTML content
378
- editor.setContent('<p>Hello</p>'); // Set HTML content
379
- editor.getText(); // Get plain text content
380
- editor.isEmpty(); // Check if editor is empty
381
-
382
- editor.getHTML(); // Alias for getContent()
383
- editor.setHTML(html); // Alias for setContent()
384
-
385
- editor.getJSON(); // Get structured JSON representation
386
- editor.setJSON(json); // Set content from JSON
387
- ```
388
-
389
- ### Editor Control
390
-
391
- ```javascript
392
- editor.focus(); // Focus the editor
393
- editor.blur(); // Blur the editor
394
- editor.enable(); // Enable editing
395
- editor.disable(); // Disable editing (read-only)
396
- editor.destroy(); // Remove editor, restore original element
397
- editor.toggleFullscreen(); // Toggle fullscreen mode
398
- editor.toggleTheme(); // Toggle light/dark theme
399
- editor.setTheme('dark'); // Set a specific theme
400
- editor.triggerSave(); // Trigger onSave callback
401
- editor.previewContent(); // Open preview modal
402
- editor.downloadContent(); // Download content as HTML file
403
- editor.clearAll(); // Clear all content
404
- ```
405
-
406
- ### Localization
407
-
408
- ```javascript
409
- NeikiEditor.addTranslation('de', { ... }); // Add/override translations (static)
410
- ```
411
-
412
- ### Command Execution
413
-
414
- ```javascript
415
- editor.execCommand('bold'); // Execute a command
416
- editor.insertHTML('<span>Hello</span>'); // Insert HTML at cursor
417
- editor.wrapSelection('mark', { class: 'highlight' }); // Wrap selection
418
- editor.unwrapSelection('mark'); // Unwrap selection
419
- ```
420
-
421
- ### Selection
422
-
423
- ```javascript
424
- editor.getSelection(); // Get current Selection object
425
- ```
426
-
427
- ---
428
-
429
- ## 🔌 Plugin API
430
-
431
- Extend the editor with custom plugins:
432
-
433
- ```javascript
434
- NeikiEditor.registerPlugin({
435
- name: 'word-counter-alert',
436
- icon: '<svg viewBox="0 0 24 24">...</svg>', // optional toolbar button
437
- tooltip: 'Show Word Count',
438
- action: function(editor) {
439
- const text = editor.getContent().replace(/<[^>]*>/g, '');
440
- const words = text.trim().split(/\s+/).filter(Boolean).length;
441
- alert('Word count: ' + words);
442
- },
443
- init: function(editor) {
444
- // Called once when editor initializes (optional)
445
- console.log('Plugin initialized!');
446
- }
447
- });
448
- ```
449
-
450
- ### Plugin Properties
451
-
452
- | Property | Type | Required | Description |
453
- |----------|------|----------|-------------|
454
- | `name` | `string` | ✅ | Unique plugin identifier |
455
- | `icon` | `string` | ❌ | SVG icon for toolbar button |
456
- | `tooltip` | `string` | | Button tooltip text |
457
- | `action` | `function` | ❌ | Called when toolbar button is clicked |
458
- | `init` | `function` | ❌ | Called once during editor initialization |
459
-
460
- ### List Registered Plugins
461
-
462
- ```javascript
463
- NeikiEditor.getPlugins(); // Returns array of registered plugins
464
- ```
465
-
466
- ---
467
-
468
- ## 📊 Table Features
469
-
470
- Insert tables via the toolbar button with configurable rows, columns, and optional header row.
471
-
472
- ### Table Context Menu
473
-
474
- Right-click on any table cell to access:
475
-
476
- - **Insert Row Above / Below**
477
- - **Insert Column Left / Right**
478
- - **Delete Row / Column / Table**
479
- - **Merge Cells** — merge selected cells horizontally
480
- - **Split Cell** — split a previously merged cell
481
-
482
- ### Column Resize
483
-
484
- Hover 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.
485
-
486
- ---
487
-
488
- ## 🖼️ Image Support
489
-
490
- ### Insert via URL
491
-
492
- Click the **Image** toolbar button and paste a URL.
493
-
494
- ### Upload from File
495
-
496
- The Image dialog includes a file upload input. Selected images are converted to **base64** and embedded directly in the content.
497
-
498
- ### Drag & Drop
499
-
500
- Drag image files directly into the editor content area. Images are automatically converted to base64 and inserted at the drop position.
501
-
502
- ### Resize Images
503
-
504
- Click 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.
505
-
506
- ---
507
-
508
- ## 🔍 Find & Replace
509
-
510
- Open with the toolbar button or implement via the modal API.
511
-
512
- Features:
513
- - **Case-sensitive** search toggle
514
- - **Regular expression** support
515
- - **Find Next** — navigate through matches with highlighting
516
- - **Replace** replace current match
517
- - **Replace All** — replace all matches at once
518
-
519
- ---
520
-
521
- ## ✏️ Floating Toolbar
522
-
523
- When you select text in the editor, a floating toolbar appears above the selection with quick access to:
524
-
525
- - **Move Block Up / Down** — reorder the current content block (left side)
526
- - **Bold, Italic, Underline, Strikethrough** quick formatting
527
- - **Insert Link**
528
-
529
- The toolbar follows the selection and disappears when the selection is cleared.
530
-
531
- ---
532
-
533
- ## 🔀 Block Drag & Drop
534
-
535
- Hover 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.
536
-
537
- ---
538
-
539
- ## 🖨️ Print
540
-
541
- Click the **Print** button to open the browser print dialog with the editor content formatted for printing.
542
-
543
- ---
544
-
545
- ## ⌨️ Keyboard Shortcuts
546
-
547
- | Shortcut | Action |
548
- |----------|--------|
549
- | **Ctrl+B** | Bold |
550
- | **Ctrl+I** | Italic |
551
- | **Ctrl+U** | Underline |
552
- | **Ctrl+K** | Insert Link |
553
- | **Ctrl+S** | Save (triggers `onSave` callback) |
554
- | **Ctrl+Z** | Undo |
555
- | **Ctrl+Y** / **Ctrl+Shift+Z** | Redo |
556
- | **Tab** | Indent |
557
- | **Shift+Tab** | Outdent |
558
-
559
- ---
560
-
561
- ## 📐 Status Bar
562
-
563
- The editor includes a status bar at the bottom displaying:
564
-
565
- - **Left side:** Word count and character count
566
- - **Right side:** Autosave status (when enabled) and current block type (p, h1, h2, etc.)
567
-
568
- ---
569
-
570
- ## 🔗 Integration Examples
571
-
572
- ### PHP Helper (Recommended)
573
-
574
- Neiki's Editor includes a PHP integration helper (`php/neiki-editor.php`) that provides asset loading, editor rendering, and HTML sanitization:
575
-
576
- ```php
577
- <?php require_once 'php/neiki-editor.php'; ?>
578
- <!DOCTYPE html>
579
- <html>
580
- <head>
581
- <?= NeikiEditor::assets() ?>
582
- </head>
583
- <body>
584
- <form method="POST" action="save.php">
585
- <?= NeikiEditor::render('content', $article->content, [
586
- 'minHeight' => 400,
587
- 'placeholder' => 'Write your article...'
588
- ]) ?>
589
- <button type="submit">Save</button>
590
- </form>
591
- </body>
592
- </html>
593
- ```
594
-
595
- ```php
596
- // save.php sanitize before saving to database
597
- require_once 'php/neiki-editor.php';
598
- $clean = NeikiEditor::sanitize($_POST['content']);
599
- $db->save($clean);
600
- ```
601
-
602
- #### PHP Helper Methods
603
-
604
- | Method | Description |
605
- |--------|-------------|
606
- | `NeikiEditor::assets()` | Output CSS & JS tags (CDN). Call once per page. |
607
- | `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of CDN. |
608
- | `NeikiEditor::render($id, $content, $options)` | Render textarea + init script. |
609
- | `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before DB save. |
610
-
611
- ### PHP Form (Manual)
612
-
613
- ```php
614
- <form method="POST" action="save.php">
615
- <textarea id="editor" name="content"><?= htmlspecialchars($article->content) ?></textarea>
616
- <button type="submit">Save</button>
617
- </form>
618
-
619
- <script>
620
- const editor = new NeikiEditor('#editor');
621
- </script>
622
- ```
623
-
624
- ### AJAX Save
625
-
626
- ```javascript
627
- const editor = new NeikiEditor('#editor', {
628
- onChange: debounce(function(content) {
629
- fetch('/api/save', {
630
- method: 'POST',
631
- headers: { 'Content-Type': 'application/json' },
632
- body: JSON.stringify({ content })
633
- });
634
- }, 2000)
635
- });
636
- ```
637
-
638
- ### Vue.js
639
-
640
- ```vue
641
- <template>
642
- <textarea ref="editor"></textarea>
643
- </template>
644
-
645
- <script>
646
- export default {
647
- mounted() {
648
- this.editor = new NeikiEditor(this.$refs.editor, {
649
- onChange: (content) => {
650
- this.$emit('update:modelValue', content);
651
- }
652
- });
653
- },
654
- beforeUnmount() {
655
- this.editor.destroy();
656
- }
657
- }
658
- </script>
659
- ```
660
-
661
- ### React
662
-
663
- ```jsx
664
- import { useEffect, useRef } from 'react';
665
-
666
- function NeikiEditorComponent({ value, onChange }) {
667
- const ref = useRef(null);
668
- const editorRef = useRef(null);
669
-
670
- useEffect(() => {
671
- editorRef.current = new NeikiEditor(ref.current, {
672
- onChange: (content) => onChange?.(content)
673
- });
674
- return () => editorRef.current?.destroy();
675
- }, []);
676
-
677
- return <textarea ref={ref} defaultValue={value} />;
678
- }
679
- ```
680
-
681
- ---
682
-
683
- ## 🌐 Browser Support
684
-
685
- | Browser | Support |
686
- |---------|---------|
687
- | Chrome | ✅ Latest |
688
- | Firefox | Latest |
689
- | Safari | ✅ Latest |
690
- | Edge | ✅ Latest |
691
- | Opera | ✅ Latest |
692
-
693
- ---
694
-
695
- ## 📁 File Structure
696
-
697
- ```
698
- neiki-editor/
699
- ├── dist/
700
- │ ├── neiki-editor.min.js # Minified editor + embedded CSS (recommended)
701
- │ ├── neiki-editor.min.css # Minified styles
702
- │ ├── neiki-editor.js # Editor core (unminified)
703
- │ └── neiki-editor.css # Editor styles (unminified)
704
- ├── demo/
705
- │ └── index.html # Interactive demo page
706
- │ └── logo.png # Demo logo
707
- ├── php/
708
- │ └── neiki-editor.php # PHP integration helper
709
- ├── logo.png
710
- ├── package.json
711
- ├── README.md
712
- ├── LICENSE
713
- ├── CHANGELOG.md
714
- ├── CONTRIBUTING.md
715
- ├── CODE_OF_CONDUCT.md
716
- └── SECURITY.md
717
- ```
718
-
719
- ---
720
-
721
- ## 📄 License
722
-
723
- This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
724
-
725
- ---
726
-
727
- <p align="center">
728
- Made with ❤️ for the web community
729
- </p>
1
+ <p align="center">
2
+ <img src="logo.png" alt="Neiki's Editor" width="400">
3
+ </p>
4
+
5
+ <h1 align="center">Neiki's Editor</h1>
6
+
7
+ <p align="center">
8
+ <img src="https://img.shields.io/badge/javascript-%23323330.svg?style=for-the-badge&logo=javascript&logoColor=%23F7DF1E" alt="JavaScript">
9
+ <img src="https://img.shields.io/badge/php-%23777BB4.svg?style=for-the-badge&logo=php&logoColor=white" alt="PHP">
10
+ <img src="https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white" alt="HTML5">
11
+ <img src="https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge&logo=css&logoColor=white" alt="CSS">
12
+ <br>
13
+ <img src="https://img.shields.io/badge/License-MIT-2563EB?style=for-the-badge&logo=open-source-initiative&logoColor=white&labelColor=000F15&logoWidth=20" alt="License">
14
+ <img src="https://img.shields.io/badge/Version-2.9.3-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
15
+ </p>
16
+
17
+ <p align="center">
18
+ <b>Lightweight WYSIWYG Rich Text Editor</b><br>
19
+ <i>Easy to integrate, fully customizable, zero dependencies.</i>
20
+ </p>
21
+
22
+ <p align="center">
23
+ <img src="https://img.shields.io/badge/Features-30%2B%20Tools-3b82f6?style=flat&labelColor=383C43" />
24
+ <img src="https://img.shields.io/badge/Themes-Light%20%26%20Dark-8b5cf6?style=flat&labelColor=383C43" />
25
+ <img src="https://img.shields.io/badge/Setup-Zero%20Config-22c55e?style=flat&labelColor=383C43" />
26
+ <img src="https://img.shields.io/badge/Size-Lightweight-f97316?style=flat&labelColor=383C43" />
27
+ </p>
28
+
29
+ <p align="center">
30
+ <a href="https://oosmetrics.com/repo/neikiri/neiki-editor"><img src="https://api.oosmetrics.com/api/v1/badge/achievement/875222f0-f55c-4209-b894-8bf60b75c590.svg" alt="oosmetrics"/></a>
31
+ <a href="https://oosmetrics.com/repo/neikiri/neiki-editor"><img src="https://api.oosmetrics.com/api/v1/badge/achievement/71eabb82-6f56-4dc3-bb41-e82a5f8cf939.svg" alt="oosmetrics"/></a>
32
+ </p>
33
+
34
+ ---
35
+ <p align="center">
36
+ <img src="preview.png" alt="Neiki's Editor" width="900">
37
+ </p>
38
+
39
+ ---
40
+
41
+ **Live version:** [https://neikiri.dev/editor](https://neikiri.dev/editor)
42
+
43
+ ---
44
+
45
+ ## 💡 Why Neiki's Editor?
46
+
47
+ Most 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 `<script>` 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.
48
+
49
+ ---
50
+ ## 📦 Installation
51
+
52
+ Add this single line — CSS is included automatically, always the **latest version**:
53
+
54
+ ```html
55
+ <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
56
+ ```
57
+
58
+ <details>
59
+ <summary><b>📋 More installation options</b> (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)</summary>
60
+ <br>
61
+
62
+ #### Pin a specific version
63
+
64
+ ```html
65
+ <script src="https://cdn.neikiri.dev/neiki-editor/2.9.3/neiki-editor.min.js"></script>
66
+ ```
67
+
68
+ #### Load CSS and JS separately
69
+
70
+ ```html
71
+ <!-- Latest -->
72
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/neiki-editor.css">
73
+ <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.js"></script>
74
+
75
+ <!-- Or pinned -->
76
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/2.9.3/neiki-editor.css">
77
+ <script src="https://cdn.neikiri.dev/neiki-editor/2.9.3/neiki-editor.js"></script>
78
+ ```
79
+
80
+ #### Alternative CDN — jsDelivr
81
+
82
+ ```html
83
+ <!-- Latest -->
84
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.min.js"></script>
85
+
86
+ <!-- Pinned -->
87
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.9.3/dist/neiki-editor.min.js"></script>
88
+
89
+ <!-- Separate files (latest) -->
90
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.css">
91
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.js"></script>
92
+
93
+ <!-- Separate files (pinned) -->
94
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.9.3/dist/neiki-editor.css">
95
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@2.9.3/dist/neiki-editor.js"></script>
96
+ ```
97
+
98
+ #### Package Manager
99
+
100
+ ```bash
101
+ npm install neiki-editor
102
+ # or
103
+ yarn add neiki-editor
104
+ # or
105
+ pnpm add neiki-editor
106
+ ```
107
+
108
+ #### Self-hosted
109
+
110
+ ```html
111
+ <script src="path/to/neiki-editor.min.js"></script>
112
+
113
+ <!-- Or separate files -->
114
+ <link rel="stylesheet" href="path/to/neiki-editor.css">
115
+ <script src="path/to/neiki-editor.js"></script>
116
+ ```
117
+
118
+ </details>
119
+
120
+ ---
121
+
122
+ ## 🚀 Quick Start
123
+
124
+ ```html
125
+ <textarea id="editor"></textarea>
126
+
127
+ <script>
128
+ const editor = new NeikiEditor('#editor');
129
+ </script>
130
+ ```
131
+
132
+ That's it — zero config required. The editor replaces the `<textarea>` with a full-featured WYSIWYG editor.
133
+
134
+ ---
135
+
136
+ ## ⚙️ Configuration
137
+
138
+ ```javascript
139
+ const editor = new NeikiEditor('#editor', {
140
+ placeholder: 'Start typing...',
141
+ minHeight: 300,
142
+ maxHeight: 600,
143
+ autofocus: false,
144
+ spellcheck: true,
145
+ readonly: false,
146
+ theme: 'light', // 'light' or 'dark'
147
+ language: 'en', // 'en', 'cs', or custom via addTranslation()
148
+ translations: null, // custom translation keys (merged with built-in)
149
+ autosaveKey: null, // optional custom localStorage scope for autosave
150
+ toolbar: [
151
+ 'viewCode', 'undo', 'redo', 'findReplace', '|',
152
+ 'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'removeFormat', '|',
153
+ 'heading', 'fontFamily', 'fontSize', '|',
154
+ 'foreColor', 'backColor', '|',
155
+ 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
156
+ 'indent', 'outdent', '|',
157
+ 'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',
158
+ 'insertDropdown', '|',
159
+ 'moreMenu'
160
+ ],
161
+ onChange: function(content, editor) {
162
+ console.log('Content changed:', content);
163
+ },
164
+ onSave: function(content, editor) {
165
+ console.log('Save triggered:', content);
166
+ },
167
+ onReady: function(editor) {
168
+ console.log('Editor is ready!');
169
+ }
170
+ });
171
+ ```
172
+
173
+ ### Configuration Options
174
+
175
+ | Option | Type | Default | Description |
176
+ |--------|------|---------|-------------|
177
+ | `placeholder` | `string` | `'Start typing...'` | Placeholder text when editor is empty |
178
+ | `minHeight` | `number` | `300` | Minimum height in pixels |
179
+ | `maxHeight` | `number\|null` | `null` | Maximum height in pixels (enables scroll). When `null`, the toolbar uses `position: sticky` to remain visible while scrolling. |
180
+ | `autofocus` | `boolean` | `false` | Focus editor on initialization |
181
+ | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
182
+ | `readonly` | `boolean` | `false` | Make editor read-only |
183
+ | `theme` | `string` | `'light'` | `'light'` or `'dark'` |
184
+ | `language` | `string` | `'en'` | UI language `en`, `cs`, `zh`, `es`, `de`, `fr`, `pt`, `ja` |
185
+ | `translations` | `object\|null` | `null` | Custom translation keys (merged with built-in) |
186
+ | `autosaveKey` | `string\|null` | `null` | Custom `localStorage` scope for autosave content and enabled state |
187
+ | `toolbar` | `array` | *(see above)* | Toolbar button configuration |
188
+ | `onChange` | `function\|null` | `null` | Callback on content change |
189
+ | `onSave` | `function\|null` | `null` | Callback on save (triggered by Ctrl+S or More menu → Save) |
190
+ | `onFocus` | `function\|null` | `null` | Callback when editor gains focus |
191
+ | `onBlur` | `function\|null` | `null` | Callback when editor loses focus |
192
+ | `onReady` | `function\|null` | `null` | Callback when editor is ready |
193
+ | `showHelp` | `boolean` | `true` | Show Help button in More menu (⋯) |
194
+ | `imageUploadHandler` | `function\|null` | `null` | Async callback `(file) => Promise<url>` for uploading images to a server/CDN instead of base64 |
195
+
196
+ ---
197
+
198
+ ## 🔧 Toolbar Buttons
199
+
200
+ Use 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.
201
+
202
+ ### Text Formatting
203
+
204
+ | Button | Description |
205
+ |--------|-------------|
206
+ | `bold` | Bold text (**Ctrl+B**) |
207
+ | `italic` | Italic text (**Ctrl+I**) |
208
+ | `underline` | Underline text (**Ctrl+U**) |
209
+ | `strikethrough` | Strikethrough text |
210
+ | `subscript` | Subscript text |
211
+ | `superscript` | Superscript text |
212
+ | `removeFormat` | Remove all formatting |
213
+
214
+ > **Note:** When no text is selected, formatting commands (including Remove Formatting) automatically expand to the word at the cursor position.
215
+
216
+ ### Text Style
217
+
218
+ | Button | Type | Description |
219
+ |--------|------|-------------|
220
+ | `heading` | Select | Paragraph, H1, H2, H3, H4, H5, H6. Defaults to Paragraph. |
221
+ | `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 |
222
+ | `fontFamily` | Select | Sans Serif (Arial), Serif (Georgia), Monospace (Consolas), Cursive (Comic Sans MS) |
223
+ | `foreColor` | Color Picker | Text color — palette, native color input, hex code input |
224
+ | `backColor` | Color Picker | Background color — palette, native color input, hex code input |
225
+
226
+ ### Alignment & Lists
227
+
228
+ | Button | Description |
229
+ |--------|-------------|
230
+ | `alignLeft` | Align text left |
231
+ | `alignCenter` | Center text |
232
+ | `alignRight` | Align text right |
233
+ | `alignJustify` | Justify text |
234
+ | `bulletList` | Unordered list |
235
+ | `numberedList` | Ordered list |
236
+ | `indent` | Increase indent |
237
+ | `outdent` | Decrease indent |
238
+
239
+ ### Insert Dropdown
240
+
241
+ The `insertDropdown` toolbar item renders a single **Insert** button that opens a dropdown containing:
242
+
243
+ | Item | Description |
244
+ |------|-------------|
245
+ | **Link** | Insert/edit hyperlink (**Ctrl+K**) |
246
+ | **Image** | Insert image (URL or file upload base64) |
247
+ | **Table** | Insert table with custom rows/columns |
248
+ | **Emoji** | Emoji picker (100+ emojis) |
249
+ | **Symbol** | Special characters (©, ®, €, π, Ω, arrows, etc.) |
250
+
251
+ You can still use `link`, `image`, `table`, `emoji`, `specialChars` as standalone toolbar buttons if preferred.
252
+
253
+ ### More Menu
254
+
255
+ The `moreMenu` toolbar item renders a **⋯** button (pushed to the right) that opens a dropdown containing:
256
+
257
+ | Item | Description |
258
+ |------|-------------|
259
+ | **Save** | Trigger the `onSave` callback |
260
+ | **Preview** | Open a document preview modal |
261
+ | **Download** | Download content as an HTML file |
262
+ | **Print** | Print editor content |
263
+ | **Autosave** | Toggle autosave to localStorage |
264
+ | **Clear all** | Clear all editor content |
265
+ | **Toggle Theme** | Switch between light/dark theme |
266
+ | **Fullscreen** | Toggle fullscreen mode |
267
+ | **Help** | Show help modal with author, version, and links (configurable via `showHelp`) |
268
+
269
+ ### Standalone Tools
270
+
271
+ | Button | Description |
272
+ |--------|-------------|
273
+ | `undo` | Undo (**Ctrl+Z**) |
274
+ | `redo` | Redo (**Ctrl+Y** / **Ctrl+Shift+Z**) |
275
+ | `findReplace` | Find & Replace with regex support |
276
+ | `viewCode` | Toggle HTML source editor |
277
+ | `blockquote` | Block quote (toggles on/off) |
278
+ | `horizontalRule` | Horizontal line |
279
+
280
+ ---
281
+
282
+ ## 🎨 Themes
283
+
284
+ Neiki's Editor ships with **Light** and **Dark** themes.
285
+
286
+ ### Set theme on init:
287
+
288
+ ```javascript
289
+ const editor = new NeikiEditor('#editor', {
290
+ theme: 'dark'
291
+ });
292
+ ```
293
+
294
+ ### Toggle theme at runtime:
295
+
296
+ Use the **Toggle Theme** item in the More menu (), or toggle programmatically:
297
+
298
+ ```javascript
299
+ editor.toggleTheme();
300
+ // or set a specific theme:
301
+ editor.setTheme('dark');
302
+ ```
303
+
304
+ The selected theme persists across page reloads via `localStorage`.
305
+
306
+ ---
307
+
308
+ ## 🌍 Localization (i18n)
309
+
310
+ Neiki's Editor supports multiple UI languages. Built-in:
311
+
312
+ - **English** (`en`) — default
313
+ - **Czech** (`cs`)
314
+ - **Chinese** (`zh`)
315
+ - **Spanish** (`es`)
316
+ - **German** (`de`)
317
+ - **French** (`fr`)
318
+ - **Portuguese** (`pt`)
319
+ - **Japanese** (`ja`)
320
+
321
+ ### Set language on init:
322
+
323
+ ```javascript
324
+ const editor = new NeikiEditor('#editor', {
325
+ language: 'cs' // Czech UI
326
+ });
327
+ ```
328
+
329
+ ### Custom translations
330
+
331
+ Add your own language or override existing translations using the static method:
332
+
333
+ ```javascript
334
+ NeikiEditor.addTranslation('de', {
335
+ 'toolbar.bold': 'Fett (Strg+B)',
336
+ 'toolbar.italic': 'Kursiv (Strg+I)',
337
+ 'toolbar.undo': 'Rückgängig (Strg+Z)',
338
+ // only override what you need English is the fallback
339
+ });
340
+
341
+ const editor = new NeikiEditor('#editor', { language: 'de' });
342
+ ```
343
+
344
+ Or pass translations directly in config:
345
+
346
+ ```javascript
347
+ const editor = new NeikiEditor('#editor', {
348
+ language: 'de',
349
+ translations: {
350
+ de: {
351
+ 'toolbar.bold': 'Fett (Strg+B)',
352
+ 'toolbar.italic': 'Kursiv (Strg+I)',
353
+ // ...
354
+ }
355
+ }
356
+ });
357
+ ```
358
+
359
+ All toolbar tooltips, modal dialogs, status bar texts, and system messages are translatable.
360
+
361
+ ---
362
+
363
+ ## 💾 Autosave
364
+
365
+ Autosave is accessible from the **More menu** (⋯) in the default toolbar. When activated:
366
+
367
+ - Content is saved to `localStorage` on every content change (debounced)
368
+ - The status bar shows "Autosaving..." / "Saved locally"
369
+ - Content is restored on page reload **only when autosave was enabled**
370
+ - Autosave keys are scoped by page URL and editor identity by default, so multiple editors do not overwrite each other
371
+
372
+ For apps where the same URL can edit different records, pass a custom `autosaveKey`:
373
+
374
+ ```javascript
375
+ new NeikiEditor('#editor', {
376
+ autosaveKey: 'article-42'
377
+ });
378
+ ```
379
+
380
+ > **Note:** For production use (CMS, blog, etc.), use the `onSave` callback or `onChange` callback to save content to your database instead.
381
+
382
+ ---
383
+
384
+ ## 📋 API Methods
385
+
386
+ ### Content
387
+
388
+ ```javascript
389
+ editor.getContent(); // Get HTML content
390
+ editor.setContent('<p>Hello</p>'); // Set HTML content
391
+ editor.getText(); // Get plain text content
392
+ editor.isEmpty(); // Check if editor is empty
393
+
394
+ editor.getHTML(); // Alias for getContent()
395
+ editor.setHTML(html); // Alias for setContent()
396
+
397
+ editor.getJSON(); // Get structured JSON representation
398
+ editor.setJSON(json); // Set content from JSON
399
+ ```
400
+
401
+ ### Editor Control
402
+
403
+ ```javascript
404
+ editor.focus(); // Focus the editor
405
+ editor.blur(); // Blur the editor
406
+ editor.enable(); // Enable editing
407
+ editor.disable(); // Disable editing (read-only)
408
+ editor.destroy(); // Remove editor, restore original element
409
+ editor.toggleFullscreen(); // Toggle fullscreen mode
410
+ editor.toggleTheme(); // Toggle light/dark theme
411
+ editor.setTheme('dark'); // Set a specific theme
412
+ editor.triggerSave(); // Trigger onSave callback
413
+ editor.previewContent(); // Open preview modal
414
+ editor.downloadContent(); // Download content as HTML file
415
+ editor.clearAll(); // Clear all content
416
+ ```
417
+
418
+ ### Localization
419
+
420
+ ```javascript
421
+ NeikiEditor.addTranslation('de', { ... }); // Add/override translations (static)
422
+ ```
423
+
424
+ ### Command Execution
425
+
426
+ ```javascript
427
+ editor.execCommand('bold'); // Execute a command
428
+ editor.insertHTML('<span>Hello</span>'); // Insert HTML at cursor
429
+ editor.wrapSelection('mark', { class: 'highlight' }); // Wrap selection
430
+ editor.unwrapSelection('mark'); // Unwrap selection
431
+ ```
432
+
433
+ ### Selection
434
+
435
+ ```javascript
436
+ editor.getSelection(); // Get current Selection object
437
+ ```
438
+
439
+ ---
440
+
441
+ ## 🔌 Plugin API
442
+
443
+ Extend the editor with custom plugins:
444
+
445
+ ```javascript
446
+ NeikiEditor.registerPlugin({
447
+ name: 'word-counter-alert',
448
+ icon: '<svg viewBox="0 0 24 24">...</svg>', // optional toolbar button
449
+ tooltip: 'Show Word Count',
450
+ action: function(editor) {
451
+ const text = editor.getContent().replace(/<[^>]*>/g, '');
452
+ const words = text.trim().split(/\s+/).filter(Boolean).length;
453
+ alert('Word count: ' + words);
454
+ },
455
+ init: function(editor) {
456
+ // Called once when editor initializes (optional)
457
+ console.log('Plugin initialized!');
458
+ }
459
+ });
460
+ ```
461
+
462
+ ### Plugin Properties
463
+
464
+ | Property | Type | Required | Description |
465
+ |----------|------|----------|-------------|
466
+ | `name` | `string` | ✅ | Unique plugin identifier |
467
+ | `icon` | `string` | ❌ | SVG icon for toolbar button |
468
+ | `tooltip` | `string` | ❌ | Button tooltip text |
469
+ | `action` | `function` | ❌ | Called when toolbar button is clicked |
470
+ | `init` | `function` | | Called once during editor initialization |
471
+
472
+ ### List Registered Plugins
473
+
474
+ ```javascript
475
+ NeikiEditor.getPlugins(); // Returns array of registered plugins
476
+ ```
477
+
478
+ ---
479
+
480
+ ## 📊 Table Features
481
+
482
+ Insert tables via the toolbar button with configurable rows, columns, and optional header row.
483
+
484
+ ### Table Context Menu
485
+
486
+ Right-click on any table cell to access:
487
+
488
+ - **Insert Row Above / Below**
489
+ - **Insert Column Left / Right**
490
+ - **Delete Row / Column / Table**
491
+ - **Merge Cells** — merge selected cells horizontally
492
+ - **Split Cell** split a previously merged cell
493
+
494
+ ### Column Resize
495
+
496
+ Hover 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.
497
+
498
+ ---
499
+
500
+ ## 🖼️ Image Support
501
+
502
+ ### Insert via URL
503
+
504
+ Click the **Image** toolbar button and paste a URL.
505
+
506
+ ### Upload from File
507
+
508
+ The Image dialog includes a file upload input. Selected images are converted to **base64** and embedded directly in the content.
509
+
510
+ ### Drag & Drop
511
+
512
+ Drag image files directly into the editor content area. Images are automatically converted to base64 and inserted at the drop position.
513
+
514
+ ### Resize Images
515
+
516
+ Click 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.
517
+
518
+ ### Image Toolbar
519
+
520
+ When an image is selected, a contextual toolbar appears above it with image-specific actions:
521
+
522
+ - **Drag handle** (⠿) — click and drag to reposition the image within the editor
523
+ - **Replace** swap the image via a file picker (supports `imageUploadHandler` or base64)
524
+ - **Delete** — remove the selected image
525
+
526
+ The floating text-formatting toolbar is automatically hidden when an image is selected.
527
+
528
+ ---
529
+
530
+ ## 🔍 Find & Replace
531
+
532
+ Open with the toolbar button or implement via the modal API.
533
+
534
+ Features:
535
+ - **Case-sensitive** search toggle
536
+ - **Regular expression** support
537
+ - **Find Next** — navigate through matches with highlighting
538
+ - **Replace** — replace current match
539
+ - **Replace All** — replace all matches at once
540
+
541
+ ---
542
+
543
+ ## ✏️ Floating Toolbar
544
+
545
+ When you select text in the editor, a floating toolbar appears above the selection with quick access to:
546
+
547
+ - **Move Block Up / Down** — reorder the current content block (left side)
548
+ - **Bold, Italic, Underline, Strikethrough** — quick formatting
549
+ - **Insert Link**
550
+
551
+ The 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.
552
+
553
+ ---
554
+
555
+ ## 🔀 Block Drag & Drop
556
+
557
+ Hover 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.
558
+
559
+ ---
560
+
561
+ ## 🖨️ Print
562
+
563
+ Click the **Print** button to open the browser print dialog with the editor content formatted for printing.
564
+
565
+ ---
566
+
567
+ ## ⌨️ Keyboard Shortcuts
568
+
569
+ | Shortcut | Action |
570
+ |----------|--------|
571
+ | **Ctrl+B** | Bold |
572
+ | **Ctrl+I** | Italic |
573
+ | **Ctrl+U** | Underline |
574
+ | **Ctrl+K** | Insert Link |
575
+ | **Ctrl+S** | Save (triggers `onSave` callback) |
576
+ | **Ctrl+Z** | Undo |
577
+ | **Ctrl+Y** / **Ctrl+Shift+Z** | Redo |
578
+ | **Tab** | Indent |
579
+ | **Shift+Tab** | Outdent |
580
+
581
+ ---
582
+
583
+ ## 📐 Status Bar
584
+
585
+ The editor includes a status bar at the bottom displaying:
586
+
587
+ - **Left side:** Word count and character count
588
+ - **Right side:** Autosave status (when enabled) and current block type (p, h1, h2, etc.)
589
+
590
+ ---
591
+
592
+ ## 🔗 Integration Examples
593
+
594
+ ### PHP Helper (Recommended)
595
+
596
+ Neiki's Editor includes a PHP integration helper (`php/neiki-editor.php`) that provides asset loading, editor rendering, and HTML sanitization:
597
+
598
+ ```php
599
+ <?php require_once 'php/neiki-editor.php'; ?>
600
+ <!DOCTYPE html>
601
+ <html>
602
+ <head>
603
+ <?= NeikiEditor::assets() ?>
604
+ </head>
605
+ <body>
606
+ <form method="POST" action="save.php">
607
+ <?= NeikiEditor::render('content', $article->content, [
608
+ 'minHeight' => 400,
609
+ 'placeholder' => 'Write your article...'
610
+ ]) ?>
611
+ <button type="submit">Save</button>
612
+ </form>
613
+ </body>
614
+ </html>
615
+ ```
616
+
617
+ ```php
618
+ // save.php — sanitize before saving to database
619
+ require_once 'php/neiki-editor.php';
620
+ $clean = NeikiEditor::sanitize($_POST['content']);
621
+ $db->save($clean);
622
+ ```
623
+
624
+ #### PHP Helper Methods
625
+
626
+ | Method | Description |
627
+ |--------|-------------|
628
+ | `NeikiEditor::assets()` | Output CSS & JS tags (CDN). Call once per page. |
629
+ | `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of CDN. |
630
+ | `NeikiEditor::render($id, $content, $options)` | Render textarea + init script. |
631
+ | `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before DB save. |
632
+
633
+ ### PHP Form (Manual)
634
+
635
+ ```php
636
+ <form method="POST" action="save.php">
637
+ <textarea id="editor" name="content"><?= htmlspecialchars($article->content) ?></textarea>
638
+ <button type="submit">Save</button>
639
+ </form>
640
+
641
+ <script>
642
+ const editor = new NeikiEditor('#editor');
643
+ </script>
644
+ ```
645
+
646
+ ### AJAX Save
647
+
648
+ ```javascript
649
+ const editor = new NeikiEditor('#editor', {
650
+ onChange: debounce(function(content) {
651
+ fetch('/api/save', {
652
+ method: 'POST',
653
+ headers: { 'Content-Type': 'application/json' },
654
+ body: JSON.stringify({ content })
655
+ });
656
+ }, 2000)
657
+ });
658
+ ```
659
+
660
+ ### Vue.js
661
+
662
+ ```vue
663
+ <template>
664
+ <textarea ref="editor"></textarea>
665
+ </template>
666
+
667
+ <script>
668
+ export default {
669
+ mounted() {
670
+ this.editor = new NeikiEditor(this.$refs.editor, {
671
+ onChange: (content) => {
672
+ this.$emit('update:modelValue', content);
673
+ }
674
+ });
675
+ },
676
+ beforeUnmount() {
677
+ this.editor.destroy();
678
+ }
679
+ }
680
+ </script>
681
+ ```
682
+
683
+ ### React
684
+
685
+ ```jsx
686
+ import { useEffect, useRef } from 'react';
687
+
688
+ function NeikiEditorComponent({ value, onChange }) {
689
+ const ref = useRef(null);
690
+ const editorRef = useRef(null);
691
+
692
+ useEffect(() => {
693
+ editorRef.current = new NeikiEditor(ref.current, {
694
+ onChange: (content) => onChange?.(content)
695
+ });
696
+ return () => editorRef.current?.destroy();
697
+ }, []);
698
+
699
+ return <textarea ref={ref} defaultValue={value} />;
700
+ }
701
+ ```
702
+
703
+ ---
704
+
705
+ ## 🌐 Browser Support
706
+
707
+ | Browser | Support |
708
+ |---------|---------|
709
+ | Chrome | ✅ Latest |
710
+ | Firefox | ✅ Latest |
711
+ | Safari | ✅ Latest |
712
+ | Edge | ✅ Latest |
713
+ | Opera | ✅ Latest |
714
+
715
+ ---
716
+
717
+ ## 📁 File Structure
718
+
719
+ ```
720
+ neiki-editor/
721
+ ├── dist/
722
+ │ ├── neiki-editor.min.js # Minified editor + embedded CSS (recommended)
723
+ │ ├── neiki-editor.min.css # Minified styles
724
+ │ ├── neiki-editor.js # Editor core (unminified)
725
+ │ └── neiki-editor.css # Editor styles (unminified)
726
+ ├── demo/
727
+ │ └── index.html # Interactive demo page
728
+ │ └── logo.png # Demo logo
729
+ ├── php/
730
+ │ └── neiki-editor.php # PHP integration helper
731
+ ├── logo.png
732
+ ├── package.json
733
+ ├── README.md
734
+ ├── LICENSE
735
+ ├── CHANGELOG.md
736
+ ├── CONTRIBUTING.md
737
+ ├── CODE_OF_CONDUCT.md
738
+ └── SECURITY.md
739
+ ```
740
+
741
+ ---
742
+
743
+ ## 📄 License
744
+
745
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
746
+
747
+ ---
748
+
749
+ <p align="center">
750
+ Made with ❤️ for the web community
751
+ </p>