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