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 +387 -515
- package/dist/neiki-editor.css +110 -4
- package/dist/neiki-editor.js +393 -19
- package/dist/neiki-editor.min.css +1 -1
- package/dist/neiki-editor.min.js +1 -1
- package/package.json +1 -1
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
|
|
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://
|
|
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'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
|
-
##
|
|
44
|
+
## Overview
|
|
46
45
|
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
126
|
+
<summary><b>Other installation options</b> (pinned version, separate CSS/JS, jsDelivr, npm, self-hosted)</summary>
|
|
60
127
|
<br>
|
|
61
128
|
|
|
62
|
-
|
|
129
|
+
**Pin a specific version**
|
|
63
130
|
|
|
64
131
|
```html
|
|
65
|
-
<script src="https://cdn.neikiri.dev/neiki-editor/3.0
|
|
132
|
+
<script src="https://cdn.neikiri.dev/neiki-editor/3.2.0/neiki-editor.min.js"></script>
|
|
66
133
|
```
|
|
67
134
|
|
|
68
|
-
|
|
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
|
|
77
|
-
<script src="https://cdn.neikiri.dev/neiki-editor/3.0
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|
140
|
-
|
|
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
|
-
###
|
|
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
|
|
295
|
-
|
|
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
|
-
###
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
Add your own language or override existing translations using the static method:
|
|
221
|
+
---
|
|
339
222
|
|
|
340
|
-
|
|
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
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## 💾 Autosave
|
|
240
|
+
### Options
|
|
371
241
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
274
|
+
toolbar: [
|
|
275
|
+
'bold', 'italic', 'underline', '|',
|
|
276
|
+
'heading', 'fontSize', '|',
|
|
277
|
+
'bulletList', 'numberedList', '|',
|
|
278
|
+
'insertDropdown', '|',
|
|
279
|
+
'moreMenu'
|
|
280
|
+
]
|
|
384
281
|
});
|
|
385
282
|
```
|
|
386
283
|
|
|
387
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
405
|
-
editor.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
301
|
+
Use `customClass` to add your own class to the content area without overriding the defaults:
|
|
432
302
|
|
|
433
303
|
```javascript
|
|
434
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
329
|
+
// Or register globally
|
|
330
|
+
NeikiEditor.addTranslation('de', {
|
|
331
|
+
'toolbar.bold': 'Fett (Strg+B)'
|
|
332
|
+
});
|
|
333
|
+
```
|
|
470
334
|
|
|
471
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
349
|
+
Methods are called on the editor instance unless noted as static.
|
|
520
350
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
|
|
382
|
+
// Commands
|
|
383
|
+
editor.execCommand('bold');
|
|
384
|
+
editor.execCommand('foreColor', '#ff0000');
|
|
530
385
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
386
|
+
// Static (plugins)
|
|
387
|
+
NeikiEditor.registerPlugin({ /* ... */ });
|
|
388
|
+
NeikiEditor.getPlugins(); // array of registered plugins
|
|
389
|
+
NeikiEditor.addTranslation('de', { /* ... */ });
|
|
390
|
+
```
|
|
534
391
|
|
|
535
|
-
|
|
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
|
-
|
|
396
|
+
### Plugin API
|
|
540
397
|
|
|
541
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
429
|
+
## Keyboard shortcuts
|
|
597
430
|
|
|
598
431
|
| Shortcut | Action |
|
|
599
432
|
|----------|--------|
|
|
600
|
-
|
|
|
601
|
-
|
|
|
602
|
-
|
|
|
603
|
-
|
|
|
604
|
-
|
|
|
605
|
-
|
|
|
606
|
-
|
|
|
607
|
-
|
|
|
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
|
-
##
|
|
444
|
+
## Integration notes
|
|
622
445
|
|
|
623
|
-
### PHP
|
|
446
|
+
### PHP helper (recommended for PHP apps)
|
|
624
447
|
|
|
625
|
-
|
|
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->
|
|
637
|
-
'minHeight'
|
|
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
|
|
658
|
-
| `NeikiEditor::assets(true, '/path/to/dist')` | Use local files instead of CDN. |
|
|
659
|
-
| `NeikiEditor::render($id, $content, $options)` | Render textarea
|
|
660
|
-
| `NeikiEditor::sanitize($html)` | Strip dangerous tags/attributes before
|
|
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
|
-
|
|
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
|
-
###
|
|
495
|
+
### React
|
|
676
496
|
|
|
677
|
-
```
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
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
|
-
|
|
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="
|
|
522
|
+
<textarea ref="editorEl"></textarea>
|
|
694
523
|
</template>
|
|
695
524
|
|
|
696
|
-
<script>
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
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
|
-
###
|
|
544
|
+
### AJAX auto-save
|
|
713
545
|
|
|
714
|
-
```
|
|
715
|
-
|
|
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
|
-
|
|
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
|
-
|
|
722
|
-
editorRef.current = new NeikiEditor(ref.current, {
|
|
723
|
-
onChange: (content) => onChange?.(content)
|
|
724
|
-
});
|
|
725
|
-
return () => editorRef.current?.destroy();
|
|
726
|
-
}, []);
|
|
560
|
+
---
|
|
727
561
|
|
|
728
|
-
|
|
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
|
-
##
|
|
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 |
|
|
739
|
-
| Firefox |
|
|
740
|
-
| Safari |
|
|
741
|
-
| Edge |
|
|
742
|
-
| Opera |
|
|
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
|
-
##
|
|
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
|
|
772
|
-
├── .gitignore
|
|
773
|
-
├── CHANGELOG.md
|
|
774
|
-
├── CODE_OF_CONDUCT.md
|
|
775
|
-
├── CONTRIBUTING.md
|
|
776
|
-
├── LICENSE
|
|
777
|
-
├── README.md
|
|
778
|
-
├── SECURITY.md
|
|
779
|
-
├── package-lock.json
|
|
780
|
-
└── package.json
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
|