neiki-editor 3.0.3 โ†’ 3.1.0

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,793 +1,668 @@
1
- <p align="center">
2
- <img src="assets/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-AGPL--3.0-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-3.0.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="assets/preview.png" alt="Neiki's Editor" width="900">
37
- </p>
38
-
39
- ---
40
-
41
- **Live version:** [https://neikiri.dev/editor](https://neikiri.dev/editor) ยท **Documentation:** [Wiki](https://github.com/neikiri/neiki-editor/wiki)
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/3.0.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/3.0.3/neiki-editor.css">
77
- <script src="https://cdn.neikiri.dev/neiki-editor/3.0.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@3.0.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@3.0.3/dist/neiki-editor.css">
95
- <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@3.0.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', 'dark', 'blue', or 'dark-blue'
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
- customClass: null, // optional custom CSS class for the content area
151
- toolbar: [
152
- 'viewCode', 'undo', 'redo', 'findReplace', '|',
153
- 'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'code', 'removeFormat', '|',
154
- 'heading', 'fontFamily', 'fontSize', '|',
155
- 'foreColor', 'backColor', '|',
156
- 'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
157
- 'indent', 'outdent', '|',
158
- 'bulletList', 'numberedList', 'blockquote', 'horizontalRule', '|',
159
- 'insertDropdown', '|',
160
- 'moreMenu'
161
- ],
162
- onChange: function(content, editor) {
163
- console.log('Content changed:', content);
164
- },
165
- onSave: function(content, editor) {
166
- console.log('Save triggered:', content);
167
- },
168
- onReady: function(editor) {
169
- console.log('Editor is ready!');
170
- }
171
- });
172
- ```
173
-
174
- ### Configuration Options
175
-
176
- | Option | Type | Default | Description |
177
- |--------|------|---------|-------------|
178
- | `placeholder` | `string` | `'Start typing...'` | Placeholder text when editor is empty |
179
- | `minHeight` | `number` | `300` | Minimum height in pixels |
180
- | `maxHeight` | `number\|null` | `null` | Maximum height in pixels (enables scroll). When `null`, the toolbar uses `position: sticky` to remain visible while scrolling. |
181
- | `autofocus` | `boolean` | `false` | Focus editor on initialization |
182
- | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
183
- | `readonly` | `boolean` | `false` | Make editor read-only |
184
- | `theme` | `string` | `'light'` | `'light'`, `'dark'`, `'blue'`, or `'dark-blue'` |
185
- | `language` | `string` | `'en'` | UI language โ€” `en`, `cs`, `zh`, `es`, `de`, `fr`, `pt`, `ja` |
186
- | `translations` | `object\|null` | `null` | Custom translation keys (merged with built-in) |
187
- | `autosaveKey` | `string\|null` | `null` | Custom `localStorage` scope for autosave content and enabled state |
188
- | `toolbar` | `array` | *(see above)* | Toolbar button configuration |
189
- | `onChange` | `function\|null` | `null` | Callback on content change |
190
- | `onSave` | `function\|null` | `null` | Callback on save (triggered by Ctrl+S or More menu โ†’ Save) |
191
- | `onFocus` | `function\|null` | `null` | Callback when editor gains focus |
192
- | `onBlur` | `function\|null` | `null` | Callback when editor loses focus |
193
- | `onReady` | `function\|null` | `null` | Callback when editor is ready |
194
- | `showHelp` | `boolean` | `true` | Show Help button in More menu (โ‹ฏ) |
195
- | `imageUploadHandler` | `function\|null` | `null` | Async callback `(file) => Promise<url>` for uploading images to a server/CDN instead of base64 |
196
- | `videoUploadHandler` | `function\|null` | `null` | Async callback `(file) => Promise<url>` for uploading videos to a server/CDN instead of base64 |
197
- | `customClass` | `string\|null` | `null` | Custom CSS class appended to the editor content area (`neiki-content`) |
198
-
199
- ---
200
-
201
- ## ๐Ÿ”ง Toolbar Buttons
202
-
203
- 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.
204
-
205
- ### Text Formatting
206
-
207
- | Button | Description |
208
- |--------|-------------|
209
- | `bold` | Bold text (**Ctrl+B**) |
210
- | `italic` | Italic text (**Ctrl+I**) |
211
- | `underline` | Underline text (**Ctrl+U**) |
212
- | `strikethrough` | Strikethrough text |
213
- | `subscript` | Subscript text |
214
- | `superscript` | Superscript text |
215
- | `code` | Toggle code formatting โ€” inline `<code>` or `<pre><code>` block |
216
- | `removeFormat` | Remove all formatting |
217
-
218
- > **Note:** When no text is selected, formatting commands (including Remove Formatting) automatically expand to the word at the cursor position.
219
-
220
- ### Text Style
221
-
222
- | Button | Type | Description |
223
- |--------|------|-------------|
224
- | `heading` | Select | Paragraph, H1, H2, H3, H4, H5, H6. Defaults to Paragraph. |
225
- | `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 |
226
- | `fontFamily` | Select | Sans Serif (Arial), Serif (Georgia), Monospace (Consolas), Cursive (Comic Sans MS) |
227
- | `foreColor` | Color Picker | Text color โ€” palette, native color input, hex code input |
228
- | `backColor` | Color Picker | Background color โ€” palette, native color input, hex code input |
229
-
230
- ### Alignment & Lists
231
-
232
- | Button | Description |
233
- |--------|-------------|
234
- | `alignLeft` | Align text left |
235
- | `alignCenter` | Center text |
236
- | `alignRight` | Align text right |
237
- | `alignJustify` | Justify text |
238
- | `bulletList` | Unordered list |
239
- | `numberedList` | Ordered list |
240
- | `indent` | Increase indent |
241
- | `outdent` | Decrease indent |
242
-
243
- ### Insert Dropdown
244
-
245
- The `insertDropdown` toolbar item renders a single **Insert** button that opens a dropdown containing:
246
-
247
- | Item | Description |
248
- |------|-------------|
249
- | **Link** | Insert/edit hyperlink (**Ctrl+K**) |
250
- | **Image** | Insert image (URL or file upload โ†’ base64) |
251
- | **Video** | Insert video (URL or file upload โ†’ base64, or via `videoUploadHandler`) |
252
- | **Table** | Insert table with custom rows/columns |
253
- | **Emoji** | Emoji picker (100+ emojis) |
254
- | **Symbol** | Special characters (ยฉ, ยฎ, โ‚ฌ, ฯ€, ฮฉ, arrows, etc.) |
255
-
256
- You can still use `link`, `image`, `video`, `table`, `emoji`, `specialChars` as standalone toolbar buttons if preferred.
257
-
258
- ### More Menu
259
-
260
- The `moreMenu` toolbar item renders a **โ‹ฏ** button (pushed to the right) that opens a dropdown containing:
261
-
262
- | Item | Description |
263
- |------|-------------|
264
- | **Save** | Trigger the `onSave` callback |
265
- | **Preview** | Open a document preview modal |
266
- | **Download** | Download content as an HTML file |
267
- | **Print** | Print editor content |
268
- | **Autosave** | Toggle autosave to localStorage |
269
- | **Clear all** | Clear all editor content |
270
- | **Change theme** | Choose light, dark, blue, or dark-blue from a select |
271
- | **Fullscreen** | Toggle fullscreen mode |
272
- | **Help** | Show help modal with author, version, and links (configurable via `showHelp`) |
273
-
274
- ### Standalone Tools
275
-
276
- | Button | Description |
277
- |--------|-------------|
278
- | `undo` | Undo (**Ctrl+Z**) |
279
- | `redo` | Redo (**Ctrl+Y** / **Ctrl+Shift+Z**) |
280
- | `findReplace` | Find & Replace with regex support |
281
- | `viewCode` | Toggle formatted HTML source editor with syntax highlighting |
282
- | `blockquote` | Block quote (toggles on/off) |
283
- | `horizontalRule` | Horizontal line |
284
-
285
- ---
286
-
287
- ## ๐ŸŽจ Themes
288
-
289
- Neiki's Editor ships with **Light**, **Dark**, **Blue**, and **Dark Blue** themes.
290
-
291
- ### Set theme on init:
292
-
293
- ```javascript
294
- const editor = new NeikiEditor('#editor', {
295
- theme: 'dark'
296
- });
297
- ```
298
-
299
- ### Change theme at runtime:
300
-
301
- Use the **Change theme** select in the More menu (โ‹ฏ), or change themes programmatically:
302
-
303
- ```javascript
304
- editor.toggleTheme();
305
- // or set a specific theme:
306
- editor.setTheme('dark');
307
- editor.setTheme('blue');
308
- editor.setTheme('dark-blue');
309
- ```
310
-
311
- The selected theme persists across page reloads via `localStorage`.
312
-
313
- ---
314
-
315
- ## ๐ŸŒ Localization (i18n)
316
-
317
- Neiki's Editor supports multiple UI languages. Built-in:
318
-
319
- - **English** (`en`) โ€” default
320
- - **Czech** (`cs`)
321
- - **Chinese** (`zh`)
322
- - **Spanish** (`es`)
323
- - **German** (`de`)
324
- - **French** (`fr`)
325
- - **Portuguese** (`pt`)
326
- - **Japanese** (`ja`)
327
-
328
- ### Set language on init:
329
-
330
- ```javascript
331
- const editor = new NeikiEditor('#editor', {
332
- language: 'cs' // Czech UI
333
- });
334
- ```
335
-
336
- ### Custom translations
337
-
338
- Add your own language or override existing translations using the static method:
339
-
340
- ```javascript
341
- NeikiEditor.addTranslation('de', {
342
- 'toolbar.bold': 'Fett (Strg+B)',
343
- 'toolbar.italic': 'Kursiv (Strg+I)',
344
- 'toolbar.undo': 'Rรผckgรคngig (Strg+Z)',
345
- // only override what you need โ€” English is the fallback
346
- });
347
-
348
- const editor = new NeikiEditor('#editor', { language: 'de' });
349
- ```
350
-
351
- Or pass translations directly in config:
352
-
353
- ```javascript
354
- const editor = new NeikiEditor('#editor', {
355
- language: 'de',
356
- translations: {
357
- de: {
358
- 'toolbar.bold': 'Fett (Strg+B)',
359
- 'toolbar.italic': 'Kursiv (Strg+I)',
360
- // ...
361
- }
362
- }
363
- });
364
- ```
365
-
366
- All toolbar tooltips, modal dialogs, status bar texts, and system messages are translatable.
367
-
368
- ---
369
-
370
- ## ๐Ÿ’พ Autosave
371
-
372
- Autosave is accessible from the **More menu** (โ‹ฏ) in the default toolbar. When activated:
373
-
374
- - Content is saved to `localStorage` on every content change (debounced)
375
- - The status bar shows "Autosaving..." / "Saved locally"
376
- - Content is restored on page reload **only when autosave was enabled**
377
- - Autosave keys are scoped by page URL and editor identity by default, so multiple editors do not overwrite each other
378
-
379
- For apps where the same URL can edit different records, pass a custom `autosaveKey`:
380
-
381
- ```javascript
382
- new NeikiEditor('#editor', {
383
- autosaveKey: 'article-42'
384
- });
385
- ```
386
-
387
- > **Note:** For production use (CMS, blog, etc.), use the `onSave` callback or `onChange` callback to save content to your database instead.
388
-
389
- ---
390
-
391
- ## ๐Ÿ“‹ API Methods
392
-
393
- ### Content
394
-
395
- ```javascript
396
- editor.getContent(); // Get HTML content
397
- editor.setContent('<p>Hello</p>'); // Set HTML content
398
- editor.getText(); // Get plain text content
399
- editor.isEmpty(); // Check if editor is empty
400
-
401
- editor.getHTML(); // Alias for getContent()
402
- editor.setHTML(html); // Alias for setContent()
403
-
404
- editor.getJSON(); // Get structured JSON representation
405
- editor.setJSON(json); // Set content from JSON
406
- ```
407
-
408
- ### Editor Control
409
-
410
- ```javascript
411
- editor.focus(); // Focus the editor
412
- editor.blur(); // Blur the editor
413
- editor.enable(); // Enable editing
414
- editor.disable(); // Disable editing (read-only)
415
- editor.destroy(); // Remove editor, restore original element
416
- editor.toggleFullscreen(); // Toggle fullscreen mode
417
- editor.toggleTheme(); // Cycle through built-in themes
418
- editor.setTheme('dark'); // Set a specific theme ('light', 'dark', 'blue', 'dark-blue')
419
- editor.triggerSave(); // Trigger onSave callback
420
- editor.previewContent(); // Open preview modal
421
- editor.downloadContent(); // Download content as HTML file
422
- editor.clearAll(); // Clear all content
423
- ```
424
-
425
- ### Localization
426
-
427
- ```javascript
428
- NeikiEditor.addTranslation('de', { ... }); // Add/override translations (static)
429
- ```
430
-
431
- ### Command Execution
432
-
433
- ```javascript
434
- editor.execCommand('bold'); // Execute a command
435
- editor.insertHTML('<span>Hello</span>'); // Insert HTML at cursor
436
- editor.wrapSelection('mark', { class: 'highlight' }); // Wrap selection
437
- editor.unwrapSelection('mark'); // Unwrap selection
438
- ```
439
-
440
- ### Selection
441
-
442
- ```javascript
443
- editor.getSelection(); // Get current Selection object
444
- ```
445
-
446
- ---
447
-
448
- ## ๐Ÿ”Œ Plugin API
449
-
450
- Extend the editor with custom plugins:
451
-
452
- ```javascript
453
- NeikiEditor.registerPlugin({
454
- name: 'word-counter-alert',
455
- icon: '<svg viewBox="0 0 24 24">...</svg>', // optional toolbar button
456
- tooltip: 'Show Word Count',
457
- action: function(editor) {
458
- const text = editor.getContent().replace(/<[^>]*>/g, '');
459
- const words = text.trim().split(/\s+/).filter(Boolean).length;
460
- alert('Word count: ' + words);
461
- },
462
- init: function(editor) {
463
- // Called once when editor initializes (optional)
464
- console.log('Plugin initialized!');
465
- }
466
- });
467
- ```
468
-
469
- ### Plugin Properties
470
-
471
- | Property | Type | Required | Description |
472
- |----------|------|----------|-------------|
473
- | `name` | `string` | โœ… | Unique plugin identifier |
474
- | `icon` | `string` | โŒ | SVG icon for toolbar button |
475
- | `tooltip` | `string` | โŒ | Button tooltip text |
476
- | `action` | `function` | โŒ | Called when toolbar button is clicked |
477
- | `init` | `function` | โŒ | Called once during editor initialization |
478
-
479
- ### List Registered Plugins
480
-
481
- ```javascript
482
- NeikiEditor.getPlugins(); // Returns array of registered plugins
483
- ```
484
-
485
- ---
486
-
487
- ## ๐Ÿ“Š Table Features
488
-
489
- Insert tables via the toolbar button with configurable rows, columns, and optional header row.
490
-
491
- ### Table Context Menu
492
-
493
- Right-click on any table cell to access:
494
-
495
- - **Insert Row Above / Below**
496
- - **Insert Column Left / Right**
497
- - **Delete Row / Column / Table**
498
- - **Merge Cells** โ€” merge selected cells horizontally
499
- - **Split Cell** โ€” split a previously merged cell
500
-
501
- ### Column Resize
502
-
503
- 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.
504
-
505
- ---
506
-
507
- ## ๐Ÿ–ผ๏ธ Image Support
508
-
509
- ### Insert via URL
510
-
511
- Click the **Image** toolbar button and paste a URL.
512
-
513
- ### Upload from File
514
-
515
- The Image dialog includes a file upload input. Selected images are converted to **base64** and embedded directly in the content.
516
-
517
- ### Drag & Drop
518
-
519
- Drag image files directly into the editor content area. Images are automatically converted to base64 and inserted at the drop position.
520
-
521
- Selected text can also be dragged to a new position inside the editor. Text, image, and video drags show the same insertion indicator so the drop position is visible before release.
522
-
523
- ### Resize Images
524
-
525
- 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.
526
-
527
- ### Image Toolbar
528
-
529
- When an image is selected, a contextual toolbar appears above it with image-specific actions:
530
-
531
- - **Drag handle** (โ ฟ) โ€” click and drag to reposition the image within the editor
532
- - **Replace** โ€” swap the image via a file picker (supports `imageUploadHandler` or base64)
533
- - **Delete** โ€” remove the selected image
534
-
535
- The floating text-formatting toolbar is automatically hidden when an image is selected.
536
-
537
- ---
538
-
539
- ## ๐ŸŽฌ Video Support
540
-
541
- Use **Insert โ†’ Video** to add a video by URL, by selecting a local video file, or by dragging a video file into the editor. Without `videoUploadHandler`, selected videos are embedded as base64 data URIs. With `videoUploadHandler`, the editor uploads the file through your callback and inserts the returned URL.
542
-
543
- ```javascript
544
- const editor = new NeikiEditor('#editor', {
545
- videoUploadHandler: async (file) => {
546
- const formData = new FormData();
547
- formData.append('video', file);
548
- const response = await fetch('/upload-video', { method: 'POST', body: formData });
549
- const data = await response.json();
550
- return data.url;
551
- }
552
- });
553
- ```
554
-
555
- Inserted videos render as `<video controls>`, are preserved by the built-in sanitizer, and can be resized or repositioned like images.
556
-
557
- ---
558
-
559
- ## ๐Ÿ” Find & Replace
560
-
561
- Open with the toolbar button or implement via the modal API.
562
-
563
- Features:
564
- - **Case-sensitive** search toggle
565
- - **Regular expression** support
566
- - **Find Next** โ€” navigate through matches with highlighting
567
- - **Replace** โ€” replace current match
568
- - **Replace All** โ€” replace all matches at once
569
-
570
- ---
571
-
572
- ## โœ๏ธ Floating Toolbar
573
-
574
- When you select text in the editor, a floating toolbar appears above the selection with quick access to:
575
-
576
- - **Move Block Up / Down** โ€” reorder the current content block (left side)
577
- - **Bold, Italic, Underline, Strikethrough** โ€” quick formatting
578
- - **Insert Link**
579
-
580
- 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.
581
-
582
- ---
583
-
584
- ## ๐Ÿ”€ Block Drag & Drop
585
-
586
- 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.
587
-
588
- ---
589
-
590
- ## ๐Ÿ–จ๏ธ Print
591
-
592
- Click the **Print** button to open the browser print dialog with the editor content formatted for printing.
593
-
594
- ---
595
-
596
- ## โŒจ๏ธ Keyboard Shortcuts
597
-
598
- | Shortcut | Action |
599
- |----------|--------|
600
- | **Ctrl+B** | Bold |
601
- | **Ctrl+I** | Italic |
602
- | **Ctrl+U** | Underline |
603
- | **Ctrl+K** | Insert Link |
604
- | **Ctrl+S** | Save (triggers `onSave` callback) |
605
- | **Ctrl+Z** | Undo |
606
- | **Ctrl+Y** / **Ctrl+Shift+Z** | Redo |
607
- | **Tab** | Indent |
608
- | **Shift+Tab** | Outdent |
609
-
610
- ---
611
-
612
- ## ๐Ÿ“ Status Bar
613
-
614
- The editor includes a status bar at the bottom displaying:
615
-
616
- - **Left side:** Word count and character count
617
- - **Right side:** Autosave status (when enabled) and current block type (p, h1, h2, etc.)
618
-
619
- ---
620
-
621
- ## ๐Ÿ”— Integration Examples
622
-
623
- ### PHP Helper (Recommended)
624
-
625
- Neiki's Editor includes a PHP integration helper (`php/neiki-editor.php`) that provides asset loading, editor rendering, and HTML sanitization:
626
-
627
- ```php
628
- <?php require_once 'php/neiki-editor.php'; ?>
629
- <!DOCTYPE html>
630
- <html>
631
- <head>
632
- <?= NeikiEditor::assets() ?>
633
- </head>
634
- <body>
635
- <form method="POST" action="save.php">
636
- <?= NeikiEditor::render('content', $article->content, [
637
- 'minHeight' => 400,
638
- 'placeholder' => 'Write your article...'
639
- ]) ?>
640
- <button type="submit">Save</button>
641
- </form>
642
- </body>
643
- </html>
644
- ```
645
-
646
- ```php
647
- // save.php โ€” sanitize before saving to database
648
- require_once 'php/neiki-editor.php';
649
- $clean = NeikiEditor::sanitize($_POST['content']);
650
- $db->save($clean);
651
- ```
652
-
653
- #### PHP Helper Methods
654
-
655
- | Method | Description |
656
- |--------|-------------|
657
- | `NeikiEditor::assets()` | Output CSS & JS tags (CDN). Call once per page. |
658
- | `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of CDN. |
659
- | `NeikiEditor::render($id, $content, $options)` | Render textarea + init script. |
660
- | `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before DB save. |
661
-
662
- ### PHP Form (Manual)
663
-
664
- ```php
665
- <form method="POST" action="save.php">
666
- <textarea id="editor" name="content"><?= htmlspecialchars($article->content) ?></textarea>
667
- <button type="submit">Save</button>
668
- </form>
669
-
670
- <script>
671
- const editor = new NeikiEditor('#editor');
672
- </script>
673
- ```
674
-
675
- ### AJAX Save
676
-
677
- ```javascript
678
- const editor = new NeikiEditor('#editor', {
679
- onChange: debounce(function(content) {
680
- fetch('/api/save', {
681
- method: 'POST',
682
- headers: { 'Content-Type': 'application/json' },
683
- body: JSON.stringify({ content })
684
- });
685
- }, 2000)
686
- });
687
- ```
688
-
689
- ### Vue.js
690
-
691
- ```vue
692
- <template>
693
- <textarea ref="editor"></textarea>
694
- </template>
695
-
696
- <script>
697
- export default {
698
- mounted() {
699
- this.editor = new NeikiEditor(this.$refs.editor, {
700
- onChange: (content) => {
701
- this.$emit('update:modelValue', content);
702
- }
703
- });
704
- },
705
- beforeUnmount() {
706
- this.editor.destroy();
707
- }
708
- }
709
- </script>
710
- ```
711
-
712
- ### React
713
-
714
- ```jsx
715
- import { useEffect, useRef } from 'react';
716
-
717
- function NeikiEditorComponent({ value, onChange }) {
718
- const ref = useRef(null);
719
- const editorRef = useRef(null);
720
-
721
- useEffect(() => {
722
- editorRef.current = new NeikiEditor(ref.current, {
723
- onChange: (content) => onChange?.(content)
724
- });
725
- return () => editorRef.current?.destroy();
726
- }, []);
727
-
728
- return <textarea ref={ref} defaultValue={value} />;
729
- }
730
- ```
731
-
732
- ---
733
-
734
- ## ๐ŸŒ Browser Support
735
-
736
- | Browser | Support |
737
- |---------|---------|
738
- | Chrome | โœ… Latest |
739
- | Firefox | โœ… Latest |
740
- | Safari | โœ… Latest |
741
- | Edge | โœ… Latest |
742
- | Opera | โœ… Latest |
743
-
744
- ---
745
-
746
- ## ๐Ÿ“ File Structure
747
-
748
- ```
749
- neiki-editor/
750
- โ”œโ”€โ”€ .github/
751
- โ”‚ โ””โ”€โ”€ workflows/
752
- โ”‚ โ”œโ”€โ”€ ci.yml # Continuous integration workflow
753
- โ”‚ โ”œโ”€โ”€ codeql.yml # CodeQL security analysis
754
- โ”‚ โ””โ”€โ”€ publish.yml # NPM/CDN publish workflow
755
- โ”œโ”€โ”€ assets/
756
- โ”‚ โ”œโ”€โ”€ logo.png # Project logo
757
- โ”‚ โ””โ”€โ”€ preview.png # Editor preview screenshot
758
- โ”œโ”€โ”€ demo/
759
- โ”‚ โ”œโ”€โ”€ index.html # Interactive demo page
760
- โ”‚ โ””โ”€โ”€ logo.png # Demo page logo
761
- โ”œโ”€โ”€ dist/
762
- โ”‚ โ”œโ”€โ”€ neiki-editor.css # Editor styles (unminified)
763
- โ”‚ โ”œโ”€โ”€ neiki-editor.js # Editor core (unminified)
764
- โ”‚ โ”œโ”€โ”€ neiki-editor.min.css # Minified styles
765
- โ”‚ โ””โ”€โ”€ neiki-editor.min.js # Minified editor + embedded CSS (recommended)
766
- โ”œโ”€โ”€ php/
767
- โ”‚ โ””โ”€โ”€ neiki-editor.php # PHP integration helper
768
- โ”œโ”€โ”€ src/
769
- โ”‚ โ”œโ”€โ”€ neiki-editor.css # Source CSS styles
770
- โ”‚ โ””โ”€โ”€ neiki-editor.js # Source JavaScript
771
- โ”œโ”€โ”€ .gitattributes # Git attributes configuration
772
- โ”œโ”€โ”€ .gitignore # Git ignore rules
773
- โ”œโ”€โ”€ CHANGELOG.md # Project changelog
774
- โ”œโ”€โ”€ CODE_OF_CONDUCT.md # Community code of conduct
775
- โ”œโ”€โ”€ CONTRIBUTING.md # Contribution guidelines
776
- โ”œโ”€โ”€ LICENSE # AGPL-3.0 license
777
- โ”œโ”€โ”€ README.md # Project documentation
778
- โ”œโ”€โ”€ SECURITY.md # Security policy
779
- โ”œโ”€โ”€ package-lock.json # NPM lock file
780
- โ””โ”€โ”€ package.json # NPM package configuration
781
- ```
782
-
783
- ---
784
-
785
- ## ๐Ÿ“„ License
786
-
787
- This project is licensed under the GNU Affero General Public License v3 โ€” see the [LICENSE](LICENSE) file for details.
788
-
789
- ---
790
-
791
- <p align="center">
792
- Made with โค๏ธ for the web community
793
- </p>
1
+ <p align="center">
2
+ <img src="assets/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-AGPL--3.0-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-3.1.0-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%20%26%20More-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
+ <p align="center">
35
+ <a href="https://sourceforge.net/projects/neiki-editor/files/latest/download"><img alt="Download Neiki&#39;s Editor" src="https://a.fsdn.com/con/app/sf-download-button" width=276 height=48 srcset="https://a.fsdn.com/con/app/sf-download-button?button_size=2x 2x"></a>
36
+ </p>
37
+
38
+ ---
39
+ <p align="center">
40
+ <img src="assets/preview.png" alt="Neiki's Editor" width="900">
41
+ </p>
42
+
43
+ ---
44
+
45
+ **Live version:** [https://neikiri.dev/editor](https://neikiri.dev/editor) ยท **Documentation:** [Wiki](https://github.com/neikiri/neiki-editor/wiki)
46
+
47
+ ---
48
+
49
+ ## Overview
50
+
51
+ Neiki's Editor is a WYSIWYG rich text editor written in plain JavaScript with **zero dependencies**. You attach it to an existing `<textarea>` (or any element), and it becomes a full editing surface โ€” toolbar, formatting tools, tables, images, video, and a status bar โ€” without pulling in a framework or a build step.
52
+
53
+ ```html
54
+ <textarea id="editor"></textarea>
55
+ <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
56
+ <script>
57
+ const editor = new NeikiEditor('#editor');
58
+ </script>
59
+ ```
60
+
61
+ That snippet is a complete, working editor. The minified build bundles its own CSS, so a single `<script>` tag is enough to get started. From there you can configure the toolbar, switch themes, wire up callbacks, and extend behaviour through the plugin API.
62
+
63
+ ---
64
+
65
+ ## Why Neiki's Editor?
66
+
67
+ Most rich text editors ask you to make a trade-off: either pull in a dependency tree and a bundler, or commit to a specific framework. Neiki's Editor avoids that trade-off.
68
+
69
+ - **One file, no dependencies.** The editor ships as a single script. The minified build embeds its CSS, so there is nothing else to install, import, or bundle. Drop it into a static page, a PHP template, or a SPA component โ€” it behaves the same way.
70
+ - **Zero-config by default, configurable when you need it.** `new NeikiEditor('#editor')` gives you the full toolbar immediately. Every option is optional, so you only reach for configuration when you actually want to change something.
71
+ - **Real editing features, not just bold and italic.** Tables with a context menu and column resizing, image resize handles, drag-and-drop block reordering, a floating selection toolbar, find & replace with regex, an HTML source view, autosave, fullscreen, and print are all built in.
72
+ - **Built-in sanitization.** All HTML entering the editor is sanitized client-side, and the bundled PHP helper exposes a server-side `sanitize()` method. Security is treated as part of the editor, not an afterthought (see [Security](#security-notes)).
73
+ - **Framework-friendly without being framework-bound.** It works with plain HTML, and the docs include patterns for React, Vue 2/3, Laravel Blade, and AJAX workflows. A `destroy()` method makes clean teardown in SPA components straightforward.
74
+ - **Localized out of the box.** Eight UI languages are bundled, and you can override or add translations with your own keys.
75
+
76
+ If you want a content editor that you can read, host, and reason about as a single file โ€” while still getting the features a production CMS needs โ€” that is the gap this project fills.
77
+
78
+ ---
79
+
80
+ ## Features
81
+
82
+ ### Text formatting
83
+
84
+ - Bold, italic, underline, strikethrough, subscript, superscript
85
+ - Inline `<code>` and `<pre><code>` code blocks
86
+ - Remove formatting
87
+ - Headings (Paragraph, H1โ€“H6), font family, and a font-size widget with `โˆ’` / `+` controls and presets
88
+ - Text color and background color pickers (preset palette, native color input, hex input)
89
+
90
+ > When no text is selected, formatting commands automatically apply to the word at the cursor.
91
+
92
+ ### Structure and content blocks
93
+
94
+ - Bulleted and numbered lists, indent / outdent
95
+ - Left, center, right, and justify alignment
96
+ - Blockquotes and horizontal rules
97
+ - **Tables** โ€” configurable rows/columns and optional header row, a right-click context menu (insert/delete rows and columns, delete table, merge and split cells), and drag-to-resize columns
98
+ - **Images** โ€” insert by URL, file upload, drag & drop, or clipboard paste; resize via corner handles with a live size label; replace or delete from a contextual image toolbar
99
+ - **Video** โ€” insert by URL, file upload, or drag & drop; rendered as `<video controls>` and resizable like images
100
+
101
+ ### Editing experience
102
+
103
+ - **Floating toolbar** that appears on text selection (move block up/down, bold, italic, underline, strikethrough, insert link)
104
+ - **Block drag & drop** reordering using a grip handle, with ghost preview and drop placeholder
105
+ - **Find & Replace** with case-sensitive and regular-expression modes
106
+ - **HTML source view** with syntax highlighting
107
+ - Undo / redo, autosave to `localStorage`, fullscreen mode, content preview, download as HTML, and print
108
+ - Status bar showing word count, character count, and the current block type
109
+
110
+ ### Developer features
111
+
112
+ - Zero dependencies, single-file drop-in
113
+ - Plugin API for custom toolbar buttons and init hooks
114
+ - PHP integration helper with asset loading, rendering, and HTML sanitization
115
+ - Eight built-in UI languages: `en`, `cs`, `zh`, `es`, `de`, `fr`, `pt`, `ja`
116
+ - Four built-in themes: Light, Dark, Blue, Dark Blue, Midnight
117
+ - Lifecycle callbacks: `onReady`, `onChange`, `onSave`, `onFocus`, `onBlur`
118
+
119
+ ---
120
+
121
+ ## Getting started
122
+
123
+ The recommended install is the single bundled script from the CDN. CSS is included automatically.
124
+
125
+ ```html
126
+ <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
127
+ ```
128
+
129
+ <details>
130
+ <summary><b>Other installation options</b> (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)</summary>
131
+ <br>
132
+
133
+ **Pin a specific version**
134
+
135
+ ```html
136
+ <script src="https://cdn.neikiri.dev/neiki-editor/3.1.0/neiki-editor.min.js"></script>
137
+ ```
138
+
139
+ **Load CSS and JS separately**
140
+
141
+ ```html
142
+ <!-- Latest -->
143
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/neiki-editor.css">
144
+ <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.js"></script>
145
+
146
+ <!-- Or pinned -->
147
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/3.1.0/neiki-editor.css">
148
+ <script src="https://cdn.neikiri.dev/neiki-editor/3.1.0/neiki-editor.js"></script>
149
+ ```
150
+
151
+ **Alternative CDN โ€” jsDelivr**
152
+
153
+ ```html
154
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.min.js"></script>
155
+ <!-- Pinned -->
156
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@3.1.0/dist/neiki-editor.min.js"></script>
157
+ ```
158
+
159
+ **Package manager**
160
+
161
+ ```bash
162
+ npm install neiki-editor
163
+ # or
164
+ yarn add neiki-editor
165
+ # or
166
+ pnpm add neiki-editor
167
+ ```
168
+
169
+ **Self-hosted**
170
+
171
+ ```html
172
+ <script src="path/to/neiki-editor.min.js"></script>
173
+
174
+ <!-- Or separate files -->
175
+ <link rel="stylesheet" href="path/to/neiki-editor.css">
176
+ <script src="path/to/neiki-editor.js"></script>
177
+ ```
178
+
179
+ </details>
180
+
181
+ > **Note:** When using separate CSS and JS files, load the CSS **before** the JS so the editor renders correctly during initialization.
182
+
183
+ ---
184
+
185
+ ## Basic usage
186
+
187
+ Attach the editor to a `<textarea>`. Any HTML already inside the element is loaded automatically.
188
+
189
+ ```html
190
+ <textarea id="editor"><p>Hello, world!</p></textarea>
191
+
192
+ <script>
193
+ const editor = new NeikiEditor('#editor');
194
+ </script>
195
+ ```
196
+
197
+ The editor can also be attached to a `<div>` with existing content, or to a DOM element reference, and multiple editors can coexist on the same page:
198
+
199
+ ```javascript
200
+ const editor1 = new NeikiEditor('#editor-1', { theme: 'light' });
201
+ const editor2 = new NeikiEditor('#editor-2', { theme: 'dark', minHeight: 200 });
202
+ ```
203
+
204
+ ### Getting content back
205
+
206
+ ```javascript
207
+ const html = editor.getContent(); // HTML string
208
+ const text = editor.getText(); // plain text, tags stripped
209
+ const empty = editor.isEmpty(); // boolean
210
+ const json = editor.getJSON(); // structured JSON
211
+ ```
212
+
213
+ ### Saving on form submit
214
+
215
+ ```javascript
216
+ const editor = new NeikiEditor('#editor');
217
+
218
+ document.getElementById('my-form').addEventListener('submit', function (e) {
219
+ e.preventDefault();
220
+ const content = editor.getContent();
221
+ // send `content` to your backend...
222
+ });
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Configuration
228
+
229
+ All options are optional. Pass them as the second argument to the constructor.
230
+
231
+ ```javascript
232
+ const editor = new NeikiEditor('#editor', {
233
+ placeholder: 'Start typing...',
234
+ minHeight: 300,
235
+ maxHeight: 600,
236
+ theme: 'light', // 'light' | 'dark' | 'blue' | 'dark-blue'
237
+ language: 'en', // 'en' | 'cs' | 'zh' | 'es' | 'de' | 'fr' | 'pt' | 'ja'
238
+ onChange: function (content, editor) {
239
+ console.log('Content changed:', content);
240
+ }
241
+ });
242
+ ```
243
+
244
+ ### Options
245
+
246
+ | Option | Type | Default | Description |
247
+ |--------|------|---------|-------------|
248
+ | `placeholder` | `string` | `'Start typing...'` | Ghost text shown when the editor is empty |
249
+ | `minHeight` | `number` | `300` | Minimum height in pixels |
250
+ | `maxHeight` | `number \| null` | `null` | Maximum height in pixels (enables scroll). When `null`, the toolbar uses `position: sticky` while scrolling |
251
+ | `autofocus` | `boolean` | `false` | Focus the editor on initialization |
252
+ | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
253
+ | `readonly` | `boolean` | `false` | Make the editor read-only |
254
+ | `theme` | `string` | `'light'` | `'light'`, `'dark'`, `'blue'`, `'dark-blue'`, or `'midnight'` |
255
+ | `language` | `string` | `'en'` | UI language (see list above) |
256
+ | `translations` | `object \| null` | `null` | Custom translation keys, merged with built-ins |
257
+ | `autosaveKey` | `string \| null` | `null` | Custom `localStorage` scope for autosave |
258
+ | `customClass` | `string \| null` | `null` | Extra CSS class appended to the content area (`neiki-content`) |
259
+ | `toolbar` | `array` | *(full set)* | Toolbar button configuration |
260
+ | `showHelp` | `boolean` | `true` | Show the Help item in the More menu (โ‹ฏ) |
261
+ | `imageUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN image uploads instead of base64 |
262
+ | `videoUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN video uploads instead of base64 |
263
+ | `onChange` | `function \| null` | `null` | Fired on every content change |
264
+ | `onSave` | `function \| null` | `null` | Fired on save (Ctrl+S or More โ†’ Save) |
265
+ | `onFocus` | `function \| null` | `null` | Fired when the editor gains focus |
266
+ | `onBlur` | `function \| null` | `null` | Fired when the editor loses focus |
267
+ | `onReady` | `function \| null` | `null` | Fired once the editor is fully initialized |
268
+
269
+ > The `language` option is read at initialization. Changing the UI language at runtime requires re-initializing the editor.
270
+
271
+ ### Customizing the toolbar
272
+
273
+ The `toolbar` option takes an array of button identifiers. Use `'|'` to add a visual separator; groups between separators wrap together as units on narrow screens.
274
+
275
+ ```javascript
276
+ new NeikiEditor('#editor', {
277
+ toolbar: [
278
+ 'bold', 'italic', 'underline', '|',
279
+ 'heading', 'fontSize', '|',
280
+ 'bulletList', 'numberedList', '|',
281
+ 'insertDropdown', '|',
282
+ 'moreMenu'
283
+ ]
284
+ });
285
+ ```
286
+
287
+ Available identifiers include text formatting (`bold`, `italic`, `underline`, `strikethrough`, `subscript`, `superscript`, `code`, `removeFormat`), style (`heading`, `fontFamily`, `fontSize`, `foreColor`, `backColor`), alignment and lists (`alignLeft`, `alignCenter`, `alignRight`, `alignJustify`, `bulletList`, `numberedList`, `indent`, `outdent`), structure (`blockquote`, `horizontalRule`), tools (`undo`, `redo`, `findReplace`, `viewCode`), the grouped `insertDropdown`, the right-aligned `moreMenu`, and a standalone `themeToggle`. See the [Toolbar Reference](https://github.com/neikiri/neiki-editor/wiki/Toolbar-Reference) for the full list.
288
+
289
+ ### Themes
290
+
291
+ Five themes ship by default: `light`, `dark`, `blue`, `dark-blue`, and `midnight`. Set one at init or change it at runtime:
292
+
293
+ ```javascript
294
+ const editor = new NeikiEditor('#editor', { theme: 'dark' });
295
+
296
+ editor.setTheme('midnight'); // set a specific theme
297
+ editor.toggleTheme(); // cycle: light โ†’ dark โ†’ blue โ†’ dark-blue โ†’ midnight โ†’ light
298
+ ```
299
+
300
+ > The selected theme is persisted to `localStorage` as a **global** setting. It applies to all editor instances on the page and persists across reloads. If a user has already chosen a theme, that saved preference takes priority over the `theme` config value โ€” call `setTheme()` after init if you need to override it.
301
+
302
+ ### Custom content styling
303
+
304
+ Use `customClass` to add your own class to the content area without overriding the defaults:
305
+
306
+ ```javascript
307
+ new NeikiEditor('#editor', { customClass: 'article-content' });
308
+ ```
309
+
310
+ ```css
311
+ .article-content {
312
+ font-family: Georgia, serif;
313
+ font-size: 18px;
314
+ line-height: 1.8;
315
+ }
316
+ ```
317
+
318
+ ### Localization
319
+
320
+ Eight languages are bundled: English (`en`, default), Czech (`cs`), Chinese (`zh`), Spanish (`es`), German (`de`), French (`fr`), Portuguese (`pt`), and Japanese (`ja`). Override or extend any string via `translations`, or register a language globally:
321
+
322
+ ```javascript
323
+ // Inline overrides (merged with built-ins)
324
+ new NeikiEditor('#editor', {
325
+ language: 'en',
326
+ translations: {
327
+ 'toolbar.bold': 'Make it bold',
328
+ 'placeholder': 'Start your story...'
329
+ }
330
+ });
331
+
332
+ // Or register globally
333
+ NeikiEditor.addTranslation('de', {
334
+ 'toolbar.bold': 'Fett (Strg+B)'
335
+ });
336
+ ```
337
+
338
+ ### Autosave
339
+
340
+ Autosave is toggled from the More menu (โ‹ฏ). When enabled, content is written to `localStorage` on change and restored on the next page load (only while autosave is still enabled). Keys are scoped by page URL and editor identity; use `autosaveKey` to isolate drafts when the same URL edits different records:
341
+
342
+ ```javascript
343
+ new NeikiEditor('#editor', { autosaveKey: 'article-42' });
344
+ ```
345
+
346
+ > Autosave is intended for drafts and recovery. For production persistence, use the `onSave` or `onChange` callbacks to save to your backend.
347
+
348
+ ---
349
+
350
+ ## API
351
+
352
+ Methods are called on the editor instance unless noted as static.
353
+
354
+ ```javascript
355
+ // Content
356
+ editor.getContent(); // HTML string (alias: getHTML)
357
+ editor.setContent('<p>Hello</p>'); // (alias: setHTML)
358
+ editor.getText(); // plain text
359
+ editor.isEmpty(); // boolean
360
+ editor.getJSON(); // structured JSON
361
+ editor.setJSON(json);
362
+ editor.insertHTML('<mark>hi</mark>');// insert at cursor
363
+ editor.clearAll();
364
+
365
+ // Selection
366
+ editor.getSelection();
367
+ editor.wrapSelection('mark', { class: 'highlight' });
368
+ editor.unwrapSelection('mark');
369
+
370
+ // Control
371
+ editor.focus();
372
+ editor.blur();
373
+ editor.enable();
374
+ editor.disable();
375
+ editor.destroy(); // remove editor, restore original element
376
+ editor.toggleFullscreen();
377
+ editor.triggerSave(); // trigger onSave
378
+ editor.previewContent(); // open preview modal
379
+ editor.downloadContent(); // download as .html
380
+
381
+ // Theme
382
+ editor.setTheme('dark');
383
+ editor.toggleTheme();
384
+
385
+ // Commands
386
+ editor.execCommand('bold');
387
+ editor.execCommand('foreColor', '#ff0000');
388
+
389
+ // Static (plugins)
390
+ NeikiEditor.registerPlugin({ /* ... */ });
391
+ NeikiEditor.getPlugins(); // array of registered plugins
392
+ NeikiEditor.addTranslation('de', { /* ... */ });
393
+ ```
394
+
395
+ > In SPA frameworks, always call `editor.destroy()` when the component unmounts to clean up listeners and avoid memory leaks.
396
+
397
+ Useful instance properties include `editor.contentArea` (the `contenteditable` element) and `editor.toolbar` (the toolbar element), which are handy inside plugin `init` hooks. See the full [API Reference](https://github.com/neikiri/neiki-editor/wiki/API-Reference).
398
+
399
+ ### Plugin API
400
+
401
+ Register a plugin globally to add a custom toolbar button and/or run code when the editor initializes. Reference the plugin by its `name` in the `toolbar` array.
402
+
403
+ ```javascript
404
+ NeikiEditor.registerPlugin({
405
+ name: 'word-counter', // required, unique
406
+ icon: '<svg viewBox="0 0 24 24">...</svg>', // optional toolbar icon
407
+ tooltip: 'Show Word Count', // optional
408
+ action: function (editor) { // optional, on click
409
+ const words = editor.getText().trim().split(/\s+/).filter(Boolean).length;
410
+ alert('Word count: ' + words);
411
+ },
412
+ init: function (editor) { // optional, runs once
413
+ console.log('Plugin initialized!');
414
+ }
415
+ });
416
+
417
+ new NeikiEditor('#editor', {
418
+ toolbar: ['bold', 'italic', '|', 'word-counter', '|', 'moreMenu']
419
+ });
420
+ ```
421
+
422
+ | Property | Type | Required | Description |
423
+ |----------|------|----------|-------------|
424
+ | `name` | `string` | Yes | Unique identifier, referenced in the toolbar array |
425
+ | `icon` | `string` | No | SVG markup for the toolbar button |
426
+ | `tooltip` | `string` | No | Hover tooltip text |
427
+ | `action` | `function(editor)` | No | Called when the button is clicked |
428
+ | `init` | `function(editor)` | No | Called once when the editor initializes |
429
+
430
+ ---
431
+
432
+ ## Keyboard shortcuts
433
+
434
+ | Shortcut | Action |
435
+ |----------|--------|
436
+ | Ctrl+B | Bold |
437
+ | Ctrl+I | Italic |
438
+ | Ctrl+U | Underline |
439
+ | Ctrl+K | Insert link |
440
+ | Ctrl+S | Save (triggers `onSave`) |
441
+ | Ctrl+Z | Undo |
442
+ | Ctrl+Y / Ctrl+Shift+Z | Redo |
443
+ | Tab / Shift+Tab | Indent / Outdent |
444
+
445
+ ---
446
+
447
+ ## Integration notes
448
+
449
+ ### PHP helper (recommended for PHP apps)
450
+
451
+ The repository includes `php/neiki-editor.php`, a helper for asset loading, rendering, and sanitization.
452
+
453
+ ```php
454
+ <?php require_once 'php/neiki-editor.php'; ?>
455
+ <head>
456
+ <?= NeikiEditor::assets() ?>
457
+ </head>
458
+ <body>
459
+ <form method="POST" action="save.php">
460
+ <?= NeikiEditor::render('content', $article->body, [
461
+ 'minHeight' => 400,
462
+ 'placeholder' => 'Write your article...'
463
+ ]) ?>
464
+ <button type="submit">Save</button>
465
+ </form>
466
+ </body>
467
+ ```
468
+
469
+ ```php
470
+ // save.php โ€” sanitize before saving to the database
471
+ require_once 'php/neiki-editor.php';
472
+ $clean = NeikiEditor::sanitize($_POST['content']);
473
+ $db->save($clean);
474
+ ```
475
+
476
+ | Method | Description |
477
+ |--------|-------------|
478
+ | `NeikiEditor::assets()` | Output CDN CSS & JS tags. Call once per page. |
479
+ | `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of the CDN. |
480
+ | `NeikiEditor::render($id, $content, $options)` | Render the textarea plus initialization script. |
481
+ | `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before saving. |
482
+
483
+ ### PHP form (manual)
484
+
485
+ If you prefer not to use the helper, render a plain `<textarea>` and initialize the editor yourself:
486
+
487
+ ```php
488
+ <form method="POST" action="save.php">
489
+ <textarea id="editor" name="content"><?= htmlspecialchars($article->content) ?></textarea>
490
+ <button type="submit">Save</button>
491
+ </form>
492
+
493
+ <script>
494
+ const editor = new NeikiEditor('#editor');
495
+ </script>
496
+ ```
497
+
498
+ ### React
499
+
500
+ ```jsx
501
+ import { useEffect, useRef } from 'react';
502
+
503
+ function NeikiEditorComponent({ value, onChange }) {
504
+ const ref = useRef(null);
505
+ const editorRef = useRef(null);
506
+
507
+ useEffect(() => {
508
+ editorRef.current = new NeikiEditor(ref.current, {
509
+ onChange: (content) => onChange?.(content)
510
+ });
511
+ if (value) editorRef.current.setContent(value);
512
+ return () => editorRef.current?.destroy();
513
+ }, []); // initialize once
514
+
515
+ return <textarea ref={ref} defaultValue={value} />;
516
+ }
517
+ ```
518
+
519
+ > Do not include `value` in the effect dependency array โ€” that would recreate the editor on every keystroke. Update content imperatively with `setContent()` instead.
520
+
521
+ ### Vue 3 (Composition API)
522
+
523
+ ```vue
524
+ <template>
525
+ <textarea ref="editorEl"></textarea>
526
+ </template>
527
+
528
+ <script setup>
529
+ import { ref, onMounted, onBeforeUnmount } from 'vue';
530
+
531
+ const props = defineProps({ modelValue: { type: String, default: '' } });
532
+ const emit = defineEmits(['update:modelValue']);
533
+ const editorEl = ref(null);
534
+ let editor = null;
535
+
536
+ onMounted(() => {
537
+ editor = new NeikiEditor(editorEl.value, {
538
+ onChange: (content) => emit('update:modelValue', content)
539
+ });
540
+ if (props.modelValue) editor.setContent(props.modelValue);
541
+ });
542
+
543
+ onBeforeUnmount(() => editor?.destroy());
544
+ </script>
545
+ ```
546
+
547
+ ### AJAX auto-save
548
+
549
+ ```javascript
550
+ const editor = new NeikiEditor('#editor', {
551
+ onChange: debounce(function (content) {
552
+ fetch('/api/save', {
553
+ method: 'POST',
554
+ headers: { 'Content-Type': 'application/json' },
555
+ body: JSON.stringify({ content })
556
+ });
557
+ }, 2000)
558
+ });
559
+ ```
560
+
561
+ The wiki also includes a [Laravel Blade](https://github.com/neikiri/neiki-editor/wiki/Integration-Guide) example and a full integration checklist.
562
+
563
+ ---
564
+
565
+ ## Security notes
566
+
567
+ Neiki's Editor sanitizes all HTML that enters the editor (textarea content, `setContent()`, `insertHTML()`, and autosave restoration). The client-side sanitizer strips dangerous tags such as `<script>` and `<iframe>`, removes event-handler attributes like `onclick` and `onerror`, and blocks `javascript:` protocol URLs.
568
+
569
+ > Client-side sanitization is defense-in-depth โ€” it does **not** replace server-side sanitization. Always validate and sanitize submitted HTML on the server before storing it or showing it to other users. The bundled PHP helper provides `NeikiEditor::sanitize()` for this purpose.
570
+
571
+ A few additional considerations:
572
+
573
+ - **Autosave is not encrypted.** Content saved to `localStorage` is readable by any JavaScript on the same origin. Disable autosave for sensitive content and save to your backend instead.
574
+ - **Content Security Policy.** The editor applies inline styles for font sizes, colors, and image dimensions, so a strict CSP will need to allow `'unsafe-inline'` for `style-src` for those features to work. See the [Security](https://github.com/neikiri/neiki-editor/wiki/Security) page for example CSP directives.
575
+
576
+ ---
577
+
578
+ ## Documentation
579
+
580
+ Full documentation lives in the project wiki:
581
+
582
+ - [Home](https://github.com/neikiri/neiki-editor/wiki/Home)
583
+ - [Getting Started](https://github.com/neikiri/neiki-editor/wiki/Getting-Started)
584
+ - [Configuration](https://github.com/neikiri/neiki-editor/wiki/Configuration)
585
+ - [Toolbar Reference](https://github.com/neikiri/neiki-editor/wiki/Toolbar-Reference)
586
+ - [API Reference](https://github.com/neikiri/neiki-editor/wiki/API-Reference)
587
+ - [Plugin API](https://github.com/neikiri/neiki-editor/wiki/Plugin-API)
588
+ - [Integration Guide](https://github.com/neikiri/neiki-editor/wiki/Integration-Guide)
589
+ - [Advanced Features](https://github.com/neikiri/neiki-editor/wiki/Advanced-Features)
590
+ - [Themes & Styling](https://github.com/neikiri/neiki-editor/wiki/Themes-and-Styling)
591
+ - [Security](https://github.com/neikiri/neiki-editor/wiki/Security)
592
+
593
+ **Live demo:** [https://neikiri.dev/editor](https://neikiri.dev/editor)
594
+
595
+ ---
596
+
597
+ ## Browser support
598
+
599
+ Neiki's Editor uses `contentEditable` and standard DOM APIs and targets current versions of modern browsers.
600
+
601
+ | Browser | Support |
602
+ |---------|---------|
603
+ | Chrome | Latest |
604
+ | Firefox | Latest |
605
+ | Safari | Latest |
606
+ | Edge | Latest |
607
+ | Opera | Latest |
608
+
609
+ > Internet Explorer is not supported.
610
+
611
+ ---
612
+
613
+ ## File structure
614
+
615
+ ```
616
+ neiki-editor/
617
+ โ”œโ”€โ”€ .github/
618
+ โ”‚ โ””โ”€โ”€ workflows/
619
+ โ”‚ โ”œโ”€โ”€ ci.yml # Continuous integration workflow
620
+ โ”‚ โ”œโ”€โ”€ codeql.yml # CodeQL security analysis
621
+ โ”‚ โ””โ”€โ”€ publish.yml # NPM/CDN publish workflow
622
+ โ”œโ”€โ”€ assets/
623
+ โ”‚ โ”œโ”€โ”€ logo.png # Project logo
624
+ โ”‚ โ””โ”€โ”€ preview.png # Editor preview screenshot
625
+ โ”œโ”€โ”€ demo/
626
+ โ”‚ โ”œโ”€โ”€ index.html # Interactive demo page
627
+ โ”‚ โ””โ”€โ”€ logo.png # Demo page logo
628
+ โ”œโ”€โ”€ dist/
629
+ โ”‚ โ”œโ”€โ”€ neiki-editor.css # Editor styles (unminified)
630
+ โ”‚ โ”œโ”€โ”€ neiki-editor.js # Editor core (unminified)
631
+ โ”‚ โ”œโ”€โ”€ neiki-editor.min.css # Minified styles
632
+ โ”‚ โ””โ”€โ”€ neiki-editor.min.js # Minified editor + embedded CSS (recommended)
633
+ โ”œโ”€โ”€ php/
634
+ โ”‚ โ””โ”€โ”€ neiki-editor.php # PHP integration helper
635
+ โ”œโ”€โ”€ src/
636
+ โ”‚ โ”œโ”€โ”€ neiki-editor.css # Source CSS styles
637
+ โ”‚ โ””โ”€โ”€ neiki-editor.js # Source JavaScript
638
+ โ”œโ”€โ”€ .gitattributes
639
+ โ”œโ”€โ”€ .gitignore
640
+ โ”œโ”€โ”€ CHANGELOG.md
641
+ โ”œโ”€โ”€ CODE_OF_CONDUCT.md
642
+ โ”œโ”€โ”€ CONTRIBUTING.md
643
+ โ”œโ”€โ”€ LICENSE
644
+ โ”œโ”€โ”€ README.md
645
+ โ”œโ”€โ”€ SECURITY.md
646
+ โ”œโ”€โ”€ package-lock.json
647
+ โ””โ”€โ”€ package.json
648
+ ```
649
+
650
+ ---
651
+
652
+ ## Contributing
653
+
654
+ Contributions are welcome. Please review [CONTRIBUTING.md](CONTRIBUTING.md) and the [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) before opening an issue or pull request. Security-related reports should follow [SECURITY.md](SECURITY.md).
655
+
656
+ The editor source lives in `src/` (`neiki-editor.js`, `neiki-editor.css`); the distributable builds are in `dist/`.
657
+
658
+ ---
659
+
660
+ ## License
661
+
662
+ Released under the **GNU Affero General Public License v3.0** (`AGPL-3.0-or-later`). See the [LICENSE](LICENSE) file for details.
663
+
664
+ ---
665
+
666
+ <p align="center">
667
+ Made with โค๏ธ for the web community
668
+ </p>