neiki-editor 3.0.3 → 3.2.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
@@ -11,7 +11,7 @@
11
11
  <img src="https://img.shields.io/badge/css-%23663399.svg?style=for-the-badge&logo=css&logoColor=white" alt="CSS">
12
12
  <br>
13
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">
14
+ <img src="https://img.shields.io/badge/Version-3.2.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
15
15
  </p>
16
16
 
17
17
  <p align="center">
@@ -21,14 +21,13 @@
21
21
 
22
22
  <p align="center">
23
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" />
24
+ <img src="https://img.shields.io/badge/Themes-Light%20%26%20Dark%20%26%20More-8b5cf6?style=flat&labelColor=383C43" />
25
25
  <img src="https://img.shields.io/badge/Setup-Zero%20Config-22c55e?style=flat&labelColor=383C43" />
26
26
  <img src="https://img.shields.io/badge/Size-Lightweight-f97316?style=flat&labelColor=383C43" />
27
27
  </p>
28
28
 
29
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>
30
+ <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>
32
31
  </p>
33
32
 
34
33
  ---
@@ -42,30 +41,98 @@
42
41
 
43
42
  ---
44
43
 
45
- ## 💡 Why Neiki's Editor?
44
+ ## Overview
46
45
 
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.
46
+ 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.
47
+
48
+ ```html
49
+ <textarea id="editor"></textarea>
50
+ <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
51
+ <script>
52
+ const editor = new NeikiEditor('#editor');
53
+ </script>
54
+ ```
55
+
56
+ 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.
57
+
58
+ ---
59
+
60
+ ## Why Neiki's Editor?
61
+
62
+ 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.
63
+
64
+ - **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.
65
+ - **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.
66
+ - **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.
67
+ - **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)).
68
+ - **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.
69
+ - **Localized out of the box.** Eight UI languages are bundled, and you can override or add translations with your own keys.
70
+
71
+ 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.
72
+
73
+ ---
74
+
75
+ ## Features
76
+
77
+ ### Text formatting
78
+
79
+ - Bold, italic, underline, strikethrough, subscript, superscript
80
+ - Inline `<code>` and `<pre><code>` code blocks
81
+ - Remove formatting
82
+ - Headings (Paragraph, H1–H6), font family, and a font-size widget with `−` / `+` controls and presets
83
+ - Text color and background color pickers (preset palette, native color input, hex input)
84
+
85
+ > When no text is selected, formatting commands automatically apply to the word at the cursor.
86
+
87
+ ### Structure and content blocks
88
+
89
+ - Bulleted and numbered lists, indent / outdent
90
+ - Left, center, right, and justify alignment
91
+ - Blockquotes and horizontal rules
92
+ - **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
93
+ - **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
94
+ - **Video** — insert by URL, file upload, or drag & drop; rendered as `<video controls>` and resizable like images
95
+
96
+ ### Editing experience
97
+
98
+ - **Right-click context menu** — Undo, Redo, Cut, Copy, Paste, **Paste as Plain Text**, Select All, and Remove Formatting, themed to match the active editor theme; disable it entirely with `contextMenu: false`
99
+ - **Floating toolbar** that appears on text selection (move block up/down, bold, italic, underline, strikethrough, insert link)
100
+ - **Block drag & drop** reordering using a grip handle, with ghost preview and drop placeholder
101
+ - **Find & Replace** with case-sensitive and regular-expression modes
102
+ - **HTML source view** with syntax highlighting
103
+ - Undo / redo, autosave to `localStorage`, fullscreen mode, content preview, download as HTML, and print
104
+ - Status bar showing word count, character count, and the current block type
105
+
106
+ ### Developer features
107
+
108
+ - Zero dependencies, single-file drop-in
109
+ - Plugin API for custom toolbar buttons and init hooks
110
+ - PHP integration helper with asset loading, rendering, and HTML sanitization
111
+ - Eight built-in UI languages: `en`, `cs`, `zh`, `es`, `de`, `fr`, `pt`, `ja`
112
+ - Six built-in themes: Light, Dark, Blue, Dark Blue, Midnight, Void
113
+ - Lifecycle callbacks: `onReady`, `onChange`, `onSave`, `onFocus`, `onBlur`
48
114
 
49
115
  ---
50
- ## 📦 Installation
51
116
 
52
- Add this single line — CSS is included automatically, always the **latest version**:
117
+ ## Getting started
118
+
119
+ The recommended install is the single bundled script from the CDN. CSS is included automatically.
53
120
 
54
121
  ```html
55
122
  <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.min.js"></script>
56
123
  ```
57
124
 
58
125
  <details>
59
- <summary><b>📋 More installation options</b> (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)</summary>
126
+ <summary><b>Other installation options</b> (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)</summary>
60
127
  <br>
61
128
 
62
- #### Pin a specific version
129
+ **Pin a specific version**
63
130
 
64
131
  ```html
65
- <script src="https://cdn.neikiri.dev/neiki-editor/3.0.3/neiki-editor.min.js"></script>
132
+ <script src="https://cdn.neikiri.dev/neiki-editor/3.2.0/neiki-editor.min.js"></script>
66
133
  ```
67
134
 
68
- #### Load CSS and JS separately
135
+ **Load CSS and JS separately**
69
136
 
70
137
  ```html
71
138
  <!-- Latest -->
@@ -73,29 +140,19 @@ Add this single line — CSS is included automatically, always the **latest vers
73
140
  <script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.js"></script>
74
141
 
75
142
  <!-- 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>
143
+ <link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/3.2.0/neiki-editor.css">
144
+ <script src="https://cdn.neikiri.dev/neiki-editor/3.2.0/neiki-editor.js"></script>
78
145
  ```
79
146
 
80
- #### Alternative CDN — jsDelivr
147
+ **Alternative CDN — jsDelivr**
81
148
 
82
149
  ```html
83
- <!-- Latest -->
84
150
  <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.min.js"></script>
85
-
86
151
  <!-- 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>
152
+ <script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@3.2.0/dist/neiki-editor.min.js"></script>
96
153
  ```
97
154
 
98
- #### Package Manager
155
+ **Package manager**
99
156
 
100
157
  ```bash
101
158
  npm install neiki-editor
@@ -105,7 +162,7 @@ yarn add neiki-editor
105
162
  pnpm add neiki-editor
106
163
  ```
107
164
 
108
- #### Self-hosted
165
+ **Self-hosted**
109
166
 
110
167
  ```html
111
168
  <script src="path/to/neiki-editor.min.js"></script>
@@ -117,549 +174,312 @@ pnpm add neiki-editor
117
174
 
118
175
  </details>
119
176
 
177
+ > **Note:** When using separate CSS and JS files, load the CSS **before** the JS so the editor renders correctly during initialization.
178
+
120
179
  ---
121
180
 
122
- ## 🚀 Quick Start
181
+ ## Basic usage
182
+
183
+ Attach the editor to a `<textarea>`. Any HTML already inside the element is loaded automatically.
123
184
 
124
185
  ```html
125
- <textarea id="editor"></textarea>
186
+ <textarea id="editor"><p>Hello, world!</p></textarea>
126
187
 
127
188
  <script>
128
189
  const editor = new NeikiEditor('#editor');
129
190
  </script>
130
191
  ```
131
192
 
132
- That's it zero config required. The editor replaces the `<textarea>` with a full-featured WYSIWYG editor.
133
-
134
- ---
135
-
136
- ## ⚙️ Configuration
193
+ 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:
137
194
 
138
195
  ```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
- });
196
+ const editor1 = new NeikiEditor('#editor-1', { theme: 'light' });
197
+ const editor2 = new NeikiEditor('#editor-2', { theme: 'dark', minHeight: 200 });
172
198
  ```
173
199
 
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:
200
+ ### Getting content back
292
201
 
293
202
  ```javascript
294
- const editor = new NeikiEditor('#editor', {
295
- theme: 'dark'
296
- });
203
+ const html = editor.getContent(); // HTML string
204
+ const text = editor.getText(); // plain text, tags stripped
205
+ const empty = editor.isEmpty(); // boolean
206
+ const json = editor.getJSON(); // structured JSON
297
207
  ```
298
208
 
299
- ### Change theme at runtime:
300
-
301
- Use the **Change theme** select in the More menu (⋯), or change themes programmatically:
209
+ ### Saving on form submit
302
210
 
303
211
  ```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`.
212
+ const editor = new NeikiEditor('#editor');
312
213
 
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
214
+ document.getElementById('my-form').addEventListener('submit', function (e) {
215
+ e.preventDefault();
216
+ const content = editor.getContent();
217
+ // send `content` to your backend...
333
218
  });
334
219
  ```
335
220
 
336
- ### Custom translations
337
-
338
- Add your own language or override existing translations using the static method:
221
+ ---
339
222
 
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
- });
223
+ ## Configuration
347
224
 
348
- const editor = new NeikiEditor('#editor', { language: 'de' });
349
- ```
350
-
351
- Or pass translations directly in config:
225
+ All options are optional. Pass them as the second argument to the constructor.
352
226
 
353
227
  ```javascript
354
228
  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
- }
229
+ placeholder: 'Start typing...',
230
+ minHeight: 300,
231
+ maxHeight: 600,
232
+ theme: 'light', // 'light' | 'dark' | 'blue' | 'dark-blue'
233
+ language: 'en', // 'en' | 'cs' | 'zh' | 'es' | 'de' | 'fr' | 'pt' | 'ja'
234
+ onChange: function (content, editor) {
235
+ console.log('Content changed:', content);
236
+ }
363
237
  });
364
238
  ```
365
239
 
366
- All toolbar tooltips, modal dialogs, status bar texts, and system messages are translatable.
367
-
368
- ---
369
-
370
- ## 💾 Autosave
240
+ ### Options
371
241
 
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`:
242
+ | Option | Type | Default | Description |
243
+ |--------|------|---------|-------------|
244
+ | `placeholder` | `string` | `'Start typing...'` | Ghost text shown when the editor is empty |
245
+ | `minHeight` | `number` | `300` | Minimum height in pixels |
246
+ | `maxHeight` | `number \| null` | `null` | Maximum height in pixels (enables scroll). When `null`, the toolbar uses `position: sticky` while scrolling |
247
+ | `autofocus` | `boolean` | `false` | Focus the editor on initialization |
248
+ | `spellcheck` | `boolean` | `true` | Enable browser spellcheck |
249
+ | `readonly` | `boolean` | `false` | Make the editor read-only |
250
+ | `theme` | `string` | `'light'` | `'light'`, `'dark'`, `'blue'`, `'dark-blue'`, `'midnight'`, or `'void'` |
251
+ | `language` | `string` | `'en'` | UI language (see list above) |
252
+ | `translations` | `object \| null` | `null` | Custom translation keys, merged with built-ins |
253
+ | `contextMenu` | `boolean` | `true` | Enable the custom right-click context menu. Set to `false` to fall back to the browser's native menu |
254
+ | `autosaveKey` | `string \| null` | `null` | Custom `localStorage` scope for autosave |
255
+ | `customClass` | `string \| null` | `null` | Extra CSS class appended to the content area (`neiki-content`) |
256
+ | `toolbar` | `array` | *(full set)* | Toolbar button configuration |
257
+ | `showHelp` | `boolean` | `true` | Show the Help item in the More menu (⋯) |
258
+ | `imageUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN image uploads instead of base64 |
259
+ | `videoUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN video uploads instead of base64 |
260
+ | `onChange` | `function \| null` | `null` | Fired on every content change |
261
+ | `onSave` | `function \| null` | `null` | Fired on save (Ctrl+S or More → Save) |
262
+ | `onFocus` | `function \| null` | `null` | Fired when the editor gains focus |
263
+ | `onBlur` | `function \| null` | `null` | Fired when the editor loses focus |
264
+ | `onReady` | `function \| null` | `null` | Fired once the editor is fully initialized |
265
+
266
+ > The `language` option is read at initialization. Changing the UI language at runtime requires re-initializing the editor.
267
+
268
+ ### Customizing the toolbar
269
+
270
+ 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.
380
271
 
381
272
  ```javascript
382
273
  new NeikiEditor('#editor', {
383
- autosaveKey: 'article-42'
274
+ toolbar: [
275
+ 'bold', 'italic', 'underline', '|',
276
+ 'heading', 'fontSize', '|',
277
+ 'bulletList', 'numberedList', '|',
278
+ 'insertDropdown', '|',
279
+ 'moreMenu'
280
+ ]
384
281
  });
385
282
  ```
386
283
 
387
- > **Note:** For production use (CMS, blog, etc.), use the `onSave` callback or `onChange` callback to save content to your database instead.
284
+ 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.
388
285
 
389
- ---
390
-
391
- ## 📋 API Methods
286
+ ### Themes
392
287
 
393
- ### Content
288
+ Six themes ship by default: `light`, `dark`, `blue`, `dark-blue`, `midnight`, and `void` (a dark purple cyberpunk theme with neon-purple glow accents). Set one at init or change it at runtime:
394
289
 
395
290
  ```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()
291
+ const editor = new NeikiEditor('#editor', { theme: 'dark' });
403
292
 
404
- editor.getJSON(); // Get structured JSON representation
405
- editor.setJSON(json); // Set content from JSON
293
+ editor.setTheme('void'); // set a specific theme
294
+ editor.toggleTheme(); // cycle: light dark → blue → dark-blue → midnight → void → light
406
295
  ```
407
296
 
408
- ### Editor Control
297
+ > 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.
409
298
 
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
- ```
299
+ ### Custom content styling
430
300
 
431
- ### Command Execution
301
+ Use `customClass` to add your own class to the content area without overriding the defaults:
432
302
 
433
303
  ```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
304
+ new NeikiEditor('#editor', { customClass: 'article-content' });
438
305
  ```
439
306
 
440
- ### Selection
441
-
442
- ```javascript
443
- editor.getSelection(); // Get current Selection object
307
+ ```css
308
+ .article-content {
309
+ font-family: Georgia, serif;
310
+ font-size: 18px;
311
+ line-height: 1.8;
312
+ }
444
313
  ```
445
314
 
446
- ---
447
-
448
- ## 🔌 Plugin API
315
+ ### Localization
449
316
 
450
- Extend the editor with custom plugins:
317
+ 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:
451
318
 
452
319
  ```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
- }
320
+ // Inline overrides (merged with built-ins)
321
+ new NeikiEditor('#editor', {
322
+ language: 'en',
323
+ translations: {
324
+ 'toolbar.bold': 'Make it bold',
325
+ 'placeholder': 'Start your story...'
326
+ }
466
327
  });
467
- ```
468
328
 
469
- ### Plugin Properties
329
+ // Or register globally
330
+ NeikiEditor.addTranslation('de', {
331
+ 'toolbar.bold': 'Fett (Strg+B)'
332
+ });
333
+ ```
470
334
 
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 |
335
+ ### Autosave
478
336
 
479
- ### List Registered Plugins
337
+ 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:
480
338
 
481
339
  ```javascript
482
- NeikiEditor.getPlugins(); // Returns array of registered plugins
340
+ new NeikiEditor('#editor', { autosaveKey: 'article-42' });
483
341
  ```
484
342
 
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.
343
+ > Autosave is intended for drafts and recovery. For production persistence, use the `onSave` or `onChange` callbacks to save to your backend.
504
344
 
505
345
  ---
506
346
 
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
347
+ ## API
518
348
 
519
- Drag image files directly into the editor content area. Images are automatically converted to base64 and inserted at the drop position.
349
+ Methods are called on the editor instance unless noted as static.
520
350
 
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
351
+ ```javascript
352
+ // Content
353
+ editor.getContent(); // HTML string (alias: getHTML)
354
+ editor.setContent('<p>Hello</p>'); // (alias: setHTML)
355
+ editor.getText(); // plain text
356
+ editor.isEmpty(); // boolean
357
+ editor.getJSON(); // structured JSON
358
+ editor.setJSON(json);
359
+ editor.insertHTML('<mark>hi</mark>');// insert at cursor
360
+ editor.clearAll();
361
+
362
+ // Selection
363
+ editor.getSelection();
364
+ editor.wrapSelection('mark', { class: 'highlight' });
365
+ editor.unwrapSelection('mark');
366
+
367
+ // Control
368
+ editor.focus();
369
+ editor.blur();
370
+ editor.enable();
371
+ editor.disable();
372
+ editor.destroy(); // remove editor, restore original element
373
+ editor.toggleFullscreen();
374
+ editor.triggerSave(); // trigger onSave
375
+ editor.previewContent(); // open preview modal
376
+ editor.downloadContent(); // download as .html
377
+
378
+ // Theme
379
+ editor.setTheme('dark');
380
+ editor.toggleTheme();
528
381
 
529
- When an image is selected, a contextual toolbar appears above it with image-specific actions:
382
+ // Commands
383
+ editor.execCommand('bold');
384
+ editor.execCommand('foreColor', '#ff0000');
530
385
 
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
386
+ // Static (plugins)
387
+ NeikiEditor.registerPlugin({ /* ... */ });
388
+ NeikiEditor.getPlugins(); // array of registered plugins
389
+ NeikiEditor.addTranslation('de', { /* ... */ });
390
+ ```
534
391
 
535
- The floating text-formatting toolbar is automatically hidden when an image is selected.
392
+ > In SPA frameworks, always call `editor.destroy()` when the component unmounts to clean up listeners and avoid memory leaks.
536
393
 
537
- ---
394
+ 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).
538
395
 
539
- ## 🎬 Video Support
396
+ ### Plugin API
540
397
 
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.
398
+ 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.
542
399
 
543
400
  ```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
- }
401
+ NeikiEditor.registerPlugin({
402
+ name: 'word-counter', // required, unique
403
+ icon: '<svg viewBox="0 0 24 24">...</svg>', // optional toolbar icon
404
+ tooltip: 'Show Word Count', // optional
405
+ action: function (editor) { // optional, on click
406
+ const words = editor.getText().trim().split(/\s+/).filter(Boolean).length;
407
+ alert('Word count: ' + words);
408
+ },
409
+ init: function (editor) { // optional, runs once
410
+ console.log('Plugin initialized!');
411
+ }
552
412
  });
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
413
 
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
414
+ new NeikiEditor('#editor', {
415
+ toolbar: ['bold', 'italic', '|', 'word-counter', '|', 'moreMenu']
416
+ });
417
+ ```
591
418
 
592
- Click the **Print** button to open the browser print dialog with the editor content formatted for printing.
419
+ | Property | Type | Required | Description |
420
+ |----------|------|----------|-------------|
421
+ | `name` | `string` | Yes | Unique identifier, referenced in the toolbar array |
422
+ | `icon` | `string` | No | SVG markup for the toolbar button |
423
+ | `tooltip` | `string` | No | Hover tooltip text |
424
+ | `action` | `function(editor)` | No | Called when the button is clicked |
425
+ | `init` | `function(editor)` | No | Called once when the editor initializes |
593
426
 
594
427
  ---
595
428
 
596
- ## ⌨️ Keyboard Shortcuts
429
+ ## Keyboard shortcuts
597
430
 
598
431
  | Shortcut | Action |
599
432
  |----------|--------|
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.)
433
+ | Ctrl+B | Bold |
434
+ | Ctrl+I | Italic |
435
+ | Ctrl+U | Underline |
436
+ | Ctrl+K | Insert link |
437
+ | Ctrl+S | Save (triggers `onSave`) |
438
+ | Ctrl+Z | Undo |
439
+ | Ctrl+Y / Ctrl+Shift+Z | Redo |
440
+ | Tab / Shift+Tab | Indent / Outdent |
618
441
 
619
442
  ---
620
443
 
621
- ## 🔗 Integration Examples
444
+ ## Integration notes
622
445
 
623
- ### PHP Helper (Recommended)
446
+ ### PHP helper (recommended for PHP apps)
624
447
 
625
- Neiki's Editor includes a PHP integration helper (`php/neiki-editor.php`) that provides asset loading, editor rendering, and HTML sanitization:
448
+ The repository includes `php/neiki-editor.php`, a helper for asset loading, rendering, and sanitization.
626
449
 
627
450
  ```php
628
451
  <?php require_once 'php/neiki-editor.php'; ?>
629
- <!DOCTYPE html>
630
- <html>
631
452
  <head>
632
453
  <?= NeikiEditor::assets() ?>
633
454
  </head>
634
455
  <body>
635
456
  <form method="POST" action="save.php">
636
- <?= NeikiEditor::render('content', $article->content, [
637
- 'minHeight' => 400,
457
+ <?= NeikiEditor::render('content', $article->body, [
458
+ 'minHeight' => 400,
638
459
  'placeholder' => 'Write your article...'
639
460
  ]) ?>
640
461
  <button type="submit">Save</button>
641
462
  </form>
642
463
  </body>
643
- </html>
644
464
  ```
645
465
 
646
466
  ```php
647
- // save.php — sanitize before saving to database
467
+ // save.php — sanitize before saving to the database
648
468
  require_once 'php/neiki-editor.php';
649
469
  $clean = NeikiEditor::sanitize($_POST['content']);
650
470
  $db->save($clean);
651
471
  ```
652
472
 
653
- #### PHP Helper Methods
654
-
655
473
  | Method | Description |
656
474
  |--------|-------------|
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. |
475
+ | `NeikiEditor::assets()` | Output CDN CSS & JS tags. Call once per page. |
476
+ | `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of the CDN. |
477
+ | `NeikiEditor::render($id, $content, $options)` | Render the textarea plus initialization script. |
478
+ | `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before saving. |
479
+
480
+ ### PHP form (manual)
661
481
 
662
- ### PHP Form (Manual)
482
+ If you prefer not to use the helper, render a plain `<textarea>` and initialize the editor yourself:
663
483
 
664
484
  ```php
665
485
  <form method="POST" action="save.php">
@@ -672,78 +492,122 @@ $db->save($clean);
672
492
  </script>
673
493
  ```
674
494
 
675
- ### AJAX Save
495
+ ### React
676
496
 
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
- });
497
+ ```jsx
498
+ import { useEffect, useRef } from 'react';
499
+
500
+ function NeikiEditorComponent({ value, onChange }) {
501
+ const ref = useRef(null);
502
+ const editorRef = useRef(null);
503
+
504
+ useEffect(() => {
505
+ editorRef.current = new NeikiEditor(ref.current, {
506
+ onChange: (content) => onChange?.(content)
507
+ });
508
+ if (value) editorRef.current.setContent(value);
509
+ return () => editorRef.current?.destroy();
510
+ }, []); // initialize once
511
+
512
+ return <textarea ref={ref} defaultValue={value} />;
513
+ }
687
514
  ```
688
515
 
689
- ### Vue.js
516
+ > Do not include `value` in the effect dependency array — that would recreate the editor on every keystroke. Update content imperatively with `setContent()` instead.
517
+
518
+ ### Vue 3 (Composition API)
690
519
 
691
520
  ```vue
692
521
  <template>
693
- <textarea ref="editor"></textarea>
522
+ <textarea ref="editorEl"></textarea>
694
523
  </template>
695
524
 
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
- }
525
+ <script setup>
526
+ import { ref, onMounted, onBeforeUnmount } from 'vue';
527
+
528
+ const props = defineProps({ modelValue: { type: String, default: '' } });
529
+ const emit = defineEmits(['update:modelValue']);
530
+ const editorEl = ref(null);
531
+ let editor = null;
532
+
533
+ onMounted(() => {
534
+ editor = new NeikiEditor(editorEl.value, {
535
+ onChange: (content) => emit('update:modelValue', content)
536
+ });
537
+ if (props.modelValue) editor.setContent(props.modelValue);
538
+ });
539
+
540
+ onBeforeUnmount(() => editor?.destroy());
709
541
  </script>
710
542
  ```
711
543
 
712
- ### React
544
+ ### AJAX auto-save
713
545
 
714
- ```jsx
715
- import { useEffect, useRef } from 'react';
546
+ ```javascript
547
+ const editor = new NeikiEditor('#editor', {
548
+ onChange: debounce(function (content) {
549
+ fetch('/api/save', {
550
+ method: 'POST',
551
+ headers: { 'Content-Type': 'application/json' },
552
+ body: JSON.stringify({ content })
553
+ });
554
+ }, 2000)
555
+ });
556
+ ```
716
557
 
717
- function NeikiEditorComponent({ value, onChange }) {
718
- const ref = useRef(null);
719
- const editorRef = useRef(null);
558
+ The wiki also includes a [Laravel Blade](https://github.com/neikiri/neiki-editor/wiki/Integration-Guide) example and a full integration checklist.
720
559
 
721
- useEffect(() => {
722
- editorRef.current = new NeikiEditor(ref.current, {
723
- onChange: (content) => onChange?.(content)
724
- });
725
- return () => editorRef.current?.destroy();
726
- }, []);
560
+ ---
727
561
 
728
- return <textarea ref={ref} defaultValue={value} />;
729
- }
730
- ```
562
+ ## Security notes
563
+
564
+ 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.
565
+
566
+ > 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.
567
+
568
+ A few additional considerations:
569
+
570
+ - **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.
571
+ - **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.
731
572
 
732
573
  ---
733
574
 
734
- ## 🌐 Browser Support
575
+ ## Documentation
576
+
577
+ Full documentation lives in the project wiki:
578
+
579
+ - [Home](https://github.com/neikiri/neiki-editor/wiki/Home)
580
+ - [Getting Started](https://github.com/neikiri/neiki-editor/wiki/Getting-Started)
581
+ - [Configuration](https://github.com/neikiri/neiki-editor/wiki/Configuration)
582
+ - [Toolbar Reference](https://github.com/neikiri/neiki-editor/wiki/Toolbar-Reference)
583
+ - [API Reference](https://github.com/neikiri/neiki-editor/wiki/API-Reference)
584
+ - [Plugin API](https://github.com/neikiri/neiki-editor/wiki/Plugin-API)
585
+ - [Integration Guide](https://github.com/neikiri/neiki-editor/wiki/Integration-Guide)
586
+ - [Advanced Features](https://github.com/neikiri/neiki-editor/wiki/Advanced-Features)
587
+ - [Themes & Styling](https://github.com/neikiri/neiki-editor/wiki/Themes-and-Styling)
588
+ - [Security](https://github.com/neikiri/neiki-editor/wiki/Security)
589
+
590
+ **Live demo:** [https://neikiri.dev/editor](https://neikiri.dev/editor)
591
+
592
+ ---
593
+
594
+ ## Browser support
595
+
596
+ Neiki's Editor uses `contentEditable` and standard DOM APIs and targets current versions of modern browsers.
735
597
 
736
598
  | Browser | Support |
737
599
  |---------|---------|
738
- | Chrome | Latest |
739
- | Firefox | Latest |
740
- | Safari | Latest |
741
- | Edge | Latest |
742
- | Opera | Latest |
600
+ | Chrome | Latest |
601
+ | Firefox | Latest |
602
+ | Safari | Latest |
603
+ | Edge | Latest |
604
+ | Opera | Latest |
605
+
606
+ > Internet Explorer is not supported.
743
607
 
744
608
  ---
745
609
 
746
- ## 📁 File Structure
610
+ ## File structure
747
611
 
748
612
  ```
749
613
  neiki-editor/
@@ -768,23 +632,31 @@ neiki-editor/
768
632
  ├── src/
769
633
  │ ├── neiki-editor.css # Source CSS styles
770
634
  │ └── 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
635
+ ├── .gitattributes
636
+ ├── .gitignore
637
+ ├── CHANGELOG.md
638
+ ├── CODE_OF_CONDUCT.md
639
+ ├── CONTRIBUTING.md
640
+ ├── LICENSE
641
+ ├── README.md
642
+ ├── SECURITY.md
643
+ ├── package-lock.json
644
+ └── package.json
781
645
  ```
782
646
 
783
647
  ---
784
648
 
785
- ## 📄 License
649
+ ## Contributing
650
+
651
+ 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).
652
+
653
+ The editor source lives in `src/` (`neiki-editor.js`, `neiki-editor.css`); the distributable builds are in `dist/`.
654
+
655
+ ---
656
+
657
+ ## License
786
658
 
787
- This project is licensed under the GNU Affero General Public License v3 see the [LICENSE](LICENSE) file for details.
659
+ Released under the **GNU Affero General Public License v3.0** (`AGPL-3.0-or-later`). See the [LICENSE](LICENSE) file for details.
788
660
 
789
661
  ---
790
662