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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * NeikiEditor - A Modern WYSIWYG Editor
3
- * Version: 3.0.3
3
+ * Version: 3.2.0
4
4
  *
5
5
  * A lightweight, feature-rich text editor with support for:
6
6
  * - Rich text formatting (bold, italic, underline, etc.)
@@ -21,6 +21,10 @@
21
21
 
22
22
  let EDITOR_INSTANCE_COUNTER = 0;
23
23
 
24
+ // ============================================
25
+ // CSS_INJECT_POINT
26
+ // ============================================
27
+
24
28
  // ============================================
25
29
  // TRANSLATIONS / i18n
26
30
  // ============================================
@@ -107,6 +111,8 @@
107
111
  'theme.dark': 'Dark',
108
112
  'theme.blue': 'Blue',
109
113
  'theme.darkBlue': 'Dark Blue',
114
+ 'theme.midnight': 'Midnight',
115
+ 'theme.void': 'Void',
110
116
 
111
117
  // Help modal
112
118
  'help.author': 'Author',
@@ -200,6 +206,25 @@
200
206
  'blockToolbar.moveDown': 'Move block down',
201
207
  'blockToolbar.dragToReorder': 'Drag to reorder',
202
208
 
209
+ // Context menu
210
+ 'contextMenu.undo': 'Undo',
211
+ 'contextMenu.redo': 'Redo',
212
+ 'contextMenu.cut': 'Cut',
213
+ 'contextMenu.copy': 'Copy',
214
+ 'contextMenu.paste': 'Paste',
215
+ 'contextMenu.pasteAsPlainText': 'Paste as Plain Text',
216
+ 'contextMenu.selectAll': 'Select All',
217
+ 'contextMenu.removeFormat': 'Remove Formatting',
218
+ 'tableContextMenu.insertRowAbove': 'Insert Row Above',
219
+ 'tableContextMenu.insertRowBelow': 'Insert Row Below',
220
+ 'tableContextMenu.insertColLeft': 'Insert Column Left',
221
+ 'tableContextMenu.insertColRight': 'Insert Column Right',
222
+ 'tableContextMenu.deleteRow': 'Delete Row',
223
+ 'tableContextMenu.deleteCol': 'Delete Column',
224
+ 'tableContextMenu.deleteTable': 'Delete Table',
225
+ 'tableContextMenu.mergeCells': 'Merge Cells',
226
+ 'tableContextMenu.splitCell': 'Split Cell',
227
+
203
228
  // Placeholder
204
229
  'placeholder': 'Start typing...'
205
230
  },
@@ -284,6 +309,8 @@
284
309
  'theme.dark': 'Tmavý',
285
310
  'theme.blue': 'Modrý',
286
311
  'theme.darkBlue': 'Tmavě modrý',
312
+ 'theme.midnight': 'Půlnoc',
313
+ 'theme.void': 'Prázdnota',
287
314
  'help.author': 'Autor',
288
315
  'help.version': 'Verze',
289
316
  'help.github': 'GitHub',
@@ -372,6 +399,24 @@
372
399
  'blockToolbar.moveDown': 'Přesunout blok dolů',
373
400
  'blockToolbar.dragToReorder': 'Přetažením změnit pořadí',
374
401
 
402
+ 'contextMenu.undo': 'Zpět',
403
+ 'contextMenu.redo': 'Znovu',
404
+ 'contextMenu.cut': 'Vyjmout',
405
+ 'contextMenu.copy': 'Kopírovat',
406
+ 'contextMenu.paste': 'Vložit',
407
+ 'contextMenu.pasteAsPlainText': 'Vložit jako čistý text',
408
+ 'contextMenu.selectAll': 'Vybrat vše',
409
+ 'contextMenu.removeFormat': 'Odstranit formátování',
410
+ 'tableContextMenu.insertRowAbove': 'Vložit řádek nad',
411
+ 'tableContextMenu.insertRowBelow': 'Vložit řádek pod',
412
+ 'tableContextMenu.insertColLeft': 'Vložit sloupec vlevo',
413
+ 'tableContextMenu.insertColRight': 'Vložit sloupec vpravo',
414
+ 'tableContextMenu.deleteRow': 'Smazat řádek',
415
+ 'tableContextMenu.deleteCol': 'Smazat sloupec',
416
+ 'tableContextMenu.deleteTable': 'Smazat tabulku',
417
+ 'tableContextMenu.mergeCells': 'Sloučit buňky',
418
+ 'tableContextMenu.splitCell': 'Rozdělit buňku',
419
+
375
420
  'placeholder': 'Začněte psát...'
376
421
  },
377
422
  zh: {
@@ -447,6 +492,8 @@
447
492
  'theme.dark': '深色',
448
493
  'theme.blue': '蓝色',
449
494
  'theme.darkBlue': '深蓝色',
495
+ 'theme.midnight': '午夜',
496
+ 'theme.void': '虚空',
450
497
  'help.author': '作者',
451
498
  'help.version': '版本',
452
499
  'help.github': 'GitHub',
@@ -519,6 +566,24 @@
519
566
  'blockToolbar.moveDown': '向下移动块',
520
567
  'blockToolbar.dragToReorder': '拖动以重新排序',
521
568
 
569
+ 'contextMenu.undo': '撤销',
570
+ 'contextMenu.redo': '重做',
571
+ 'contextMenu.cut': '剪切',
572
+ 'contextMenu.copy': '复制',
573
+ 'contextMenu.paste': '粘贴',
574
+ 'contextMenu.pasteAsPlainText': '粘贴为纯文本',
575
+ 'contextMenu.selectAll': '全选',
576
+ 'contextMenu.removeFormat': '清除格式',
577
+ 'tableContextMenu.insertRowAbove': '在上方插入行',
578
+ 'tableContextMenu.insertRowBelow': '在下方插入行',
579
+ 'tableContextMenu.insertColLeft': '在左侧插入列',
580
+ 'tableContextMenu.insertColRight': '在右侧插入列',
581
+ 'tableContextMenu.deleteRow': '删除行',
582
+ 'tableContextMenu.deleteCol': '删除列',
583
+ 'tableContextMenu.deleteTable': '删除表格',
584
+ 'tableContextMenu.mergeCells': '合并单元格',
585
+ 'tableContextMenu.splitCell': '拆分单元格',
586
+
522
587
  'placeholder': '开始输入...'
523
588
  },
524
589
  es: {
@@ -593,6 +658,8 @@
593
658
  'theme.dark': 'Oscuro',
594
659
  'theme.blue': 'Azul',
595
660
  'theme.darkBlue': 'Azul oscuro',
661
+ 'theme.midnight': 'Medianoche',
662
+ 'theme.void': 'Vacío',
596
663
  'help.author': 'Autor',
597
664
  'help.version': 'Versión',
598
665
  'help.github': 'GitHub',
@@ -665,6 +732,24 @@
665
732
  'blockToolbar.moveDown': 'Mover bloque abajo',
666
733
  'blockToolbar.dragToReorder': 'Arrastrar para reordenar',
667
734
 
735
+ 'contextMenu.undo': 'Deshacer',
736
+ 'contextMenu.redo': 'Rehacer',
737
+ 'contextMenu.cut': 'Cortar',
738
+ 'contextMenu.copy': 'Copiar',
739
+ 'contextMenu.paste': 'Pegar',
740
+ 'contextMenu.pasteAsPlainText': 'Pegar como texto sin formato',
741
+ 'contextMenu.selectAll': 'Seleccionar todo',
742
+ 'contextMenu.removeFormat': 'Quitar formato',
743
+ 'tableContextMenu.insertRowAbove': 'Insertar fila arriba',
744
+ 'tableContextMenu.insertRowBelow': 'Insertar fila abajo',
745
+ 'tableContextMenu.insertColLeft': 'Insertar columna a la izquierda',
746
+ 'tableContextMenu.insertColRight': 'Insertar columna a la derecha',
747
+ 'tableContextMenu.deleteRow': 'Eliminar fila',
748
+ 'tableContextMenu.deleteCol': 'Eliminar columna',
749
+ 'tableContextMenu.deleteTable': 'Eliminar tabla',
750
+ 'tableContextMenu.mergeCells': 'Combinar celdas',
751
+ 'tableContextMenu.splitCell': 'Dividir celda',
752
+
668
753
  'placeholder': 'Empiece a escribir...'
669
754
  },
670
755
  de: {
@@ -739,6 +824,8 @@
739
824
  'theme.dark': 'Dunkel',
740
825
  'theme.blue': 'Blau',
741
826
  'theme.darkBlue': 'Dunkelblau',
827
+ 'theme.midnight': 'Mitternacht',
828
+ 'theme.void': 'Leere',
742
829
  'help.author': 'Autor',
743
830
  'help.version': 'Version',
744
831
  'help.github': 'GitHub',
@@ -811,6 +898,24 @@
811
898
  'blockToolbar.moveDown': 'Block nach unten verschieben',
812
899
  'blockToolbar.dragToReorder': 'Zum Neuordnen ziehen',
813
900
 
901
+ 'contextMenu.undo': 'Rückgängig',
902
+ 'contextMenu.redo': 'Wiederholen',
903
+ 'contextMenu.cut': 'Ausschneiden',
904
+ 'contextMenu.copy': 'Kopieren',
905
+ 'contextMenu.paste': 'Einfügen',
906
+ 'contextMenu.pasteAsPlainText': 'Als reinen Text einfügen',
907
+ 'contextMenu.selectAll': 'Alles auswählen',
908
+ 'contextMenu.removeFormat': 'Formatierung entfernen',
909
+ 'tableContextMenu.insertRowAbove': 'Zeile oberhalb einfügen',
910
+ 'tableContextMenu.insertRowBelow': 'Zeile unterhalb einfügen',
911
+ 'tableContextMenu.insertColLeft': 'Spalte links einfügen',
912
+ 'tableContextMenu.insertColRight': 'Spalte rechts einfügen',
913
+ 'tableContextMenu.deleteRow': 'Zeile löschen',
914
+ 'tableContextMenu.deleteCol': 'Spalte löschen',
915
+ 'tableContextMenu.deleteTable': 'Tabelle löschen',
916
+ 'tableContextMenu.mergeCells': 'Zellen verbinden',
917
+ 'tableContextMenu.splitCell': 'Zelle teilen',
918
+
814
919
  'placeholder': 'Hier schreiben...'
815
920
  },
816
921
  fr: {
@@ -885,6 +990,8 @@
885
990
  'theme.dark': 'Sombre',
886
991
  'theme.blue': 'Bleu',
887
992
  'theme.darkBlue': 'Bleu foncé',
993
+ 'theme.midnight': 'Minuit',
994
+ 'theme.void': 'Néant',
888
995
  'help.author': 'Auteur',
889
996
  'help.version': 'Version',
890
997
  'help.github': 'GitHub',
@@ -957,6 +1064,24 @@
957
1064
  'blockToolbar.moveDown': 'Déplacer le bloc vers le bas',
958
1065
  'blockToolbar.dragToReorder': 'Glisser pour réorganiser',
959
1066
 
1067
+ 'contextMenu.undo': 'Annuler',
1068
+ 'contextMenu.redo': 'Rétablir',
1069
+ 'contextMenu.cut': 'Couper',
1070
+ 'contextMenu.copy': 'Copier',
1071
+ 'contextMenu.paste': 'Coller',
1072
+ 'contextMenu.pasteAsPlainText': 'Coller comme texte brut',
1073
+ 'contextMenu.selectAll': 'Tout sélectionner',
1074
+ 'contextMenu.removeFormat': 'Effacer la mise en forme',
1075
+ 'tableContextMenu.insertRowAbove': 'Insérer une ligne au-dessus',
1076
+ 'tableContextMenu.insertRowBelow': 'Insérer une ligne en dessous',
1077
+ 'tableContextMenu.insertColLeft': 'Insérer une colonne à gauche',
1078
+ 'tableContextMenu.insertColRight': 'Insérer une colonne à droite',
1079
+ 'tableContextMenu.deleteRow': 'Supprimer la ligne',
1080
+ 'tableContextMenu.deleteCol': 'Supprimer la colonne',
1081
+ 'tableContextMenu.deleteTable': 'Supprimer le tableau',
1082
+ 'tableContextMenu.mergeCells': 'Fusionner les cellules',
1083
+ 'tableContextMenu.splitCell': 'Diviser la cellule',
1084
+
960
1085
  'placeholder': 'Commencez à écrire...'
961
1086
  },
962
1087
  pt: {
@@ -1031,6 +1156,8 @@
1031
1156
  'theme.dark': 'Escuro',
1032
1157
  'theme.blue': 'Azul',
1033
1158
  'theme.darkBlue': 'Azul escuro',
1159
+ 'theme.midnight': 'Meia-noite',
1160
+ 'theme.void': 'Vazio',
1034
1161
  'help.author': 'Autor',
1035
1162
  'help.version': 'Versão',
1036
1163
  'help.github': 'GitHub',
@@ -1103,6 +1230,24 @@
1103
1230
  'blockToolbar.moveDown': 'Mover bloco para baixo',
1104
1231
  'blockToolbar.dragToReorder': 'Arraste para reordenar',
1105
1232
 
1233
+ 'contextMenu.undo': 'Desfazer',
1234
+ 'contextMenu.redo': 'Refazer',
1235
+ 'contextMenu.cut': 'Recortar',
1236
+ 'contextMenu.copy': 'Copiar',
1237
+ 'contextMenu.paste': 'Colar',
1238
+ 'contextMenu.pasteAsPlainText': 'Colar como texto simples',
1239
+ 'contextMenu.selectAll': 'Selecionar tudo',
1240
+ 'contextMenu.removeFormat': 'Remover formatação',
1241
+ 'tableContextMenu.insertRowAbove': 'Inserir linha acima',
1242
+ 'tableContextMenu.insertRowBelow': 'Inserir linha abaixo',
1243
+ 'tableContextMenu.insertColLeft': 'Inserir coluna à esquerda',
1244
+ 'tableContextMenu.insertColRight': 'Inserir coluna à direita',
1245
+ 'tableContextMenu.deleteRow': 'Excluir linha',
1246
+ 'tableContextMenu.deleteCol': 'Excluir coluna',
1247
+ 'tableContextMenu.deleteTable': 'Excluir tabela',
1248
+ 'tableContextMenu.mergeCells': 'Mesclar células',
1249
+ 'tableContextMenu.splitCell': 'Dividir célula',
1250
+
1106
1251
  'placeholder': 'Comece a digitar...'
1107
1252
  },
1108
1253
  ja: {
@@ -1177,6 +1322,8 @@
1177
1322
  'theme.dark': 'ダーク',
1178
1323
  'theme.blue': 'ブルー',
1179
1324
  'theme.darkBlue': 'ダークブルー',
1325
+ 'theme.midnight': 'ミッドナイト',
1326
+ 'theme.void': 'ヴォイド',
1180
1327
  'help.author': '作成者',
1181
1328
  'help.version': 'バージョン',
1182
1329
  'help.github': 'GitHub',
@@ -1249,6 +1396,24 @@
1249
1396
  'blockToolbar.moveDown': 'ブロックを下へ移動',
1250
1397
  'blockToolbar.dragToReorder': 'ドラッグして並べ替え',
1251
1398
 
1399
+ 'contextMenu.undo': '元に戻す',
1400
+ 'contextMenu.redo': 'やり直す',
1401
+ 'contextMenu.cut': '切り取り',
1402
+ 'contextMenu.copy': 'コピー',
1403
+ 'contextMenu.paste': '貼り付け',
1404
+ 'contextMenu.pasteAsPlainText': 'プレーンテキストとして貼り付け',
1405
+ 'contextMenu.selectAll': 'すべて選択',
1406
+ 'contextMenu.removeFormat': '書式をクリア',
1407
+ 'tableContextMenu.insertRowAbove': '上に行を挿入',
1408
+ 'tableContextMenu.insertRowBelow': '下に行を挿入',
1409
+ 'tableContextMenu.insertColLeft': '左に列を挿入',
1410
+ 'tableContextMenu.insertColRight': '右に列を挿入',
1411
+ 'tableContextMenu.deleteRow': '行を削除',
1412
+ 'tableContextMenu.deleteCol': '列を削除',
1413
+ 'tableContextMenu.deleteTable': 'テーブルを削除',
1414
+ 'tableContextMenu.mergeCells': 'セルを結合',
1415
+ 'tableContextMenu.splitCell': 'セルを分割',
1416
+
1252
1417
  'placeholder': '入力してください...'
1253
1418
  }
1254
1419
  };
@@ -1277,12 +1442,14 @@
1277
1442
  return text;
1278
1443
  }
1279
1444
 
1280
- const THEMES = ['light', 'dark', 'blue', 'dark-blue'];
1445
+ const THEMES = ['light', 'dark', 'blue', 'dark-blue', 'midnight', 'void'];
1281
1446
  const THEME_OPTIONS = [
1282
1447
  { value: 'light', labelKey: 'theme.light' },
1283
1448
  { value: 'dark', labelKey: 'theme.dark' },
1284
1449
  { value: 'blue', labelKey: 'theme.blue' },
1285
- { value: 'dark-blue', labelKey: 'theme.darkBlue' }
1450
+ { value: 'dark-blue', labelKey: 'theme.darkBlue' },
1451
+ { value: 'midnight', labelKey: 'theme.midnight' },
1452
+ { value: 'void', labelKey: 'theme.void' }
1286
1453
  ];
1287
1454
 
1288
1455
  const DEFAULT_CONFIG = {
@@ -1306,6 +1473,7 @@
1306
1473
  theme: 'light',
1307
1474
  language: 'en',
1308
1475
  translations: null,
1476
+ contextMenu: true,
1309
1477
  autosaveKey: null,
1310
1478
  plugins: [],
1311
1479
  onChange: null,
@@ -1934,7 +2102,12 @@
1934
2102
  grip: '<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path d="M0 0h24v24H0z" fill="none"/><path fill="none" stroke="currentColor" stroke-linecap="square" stroke-width="2" d="m15 5l-3-3l-3 3m0 14l3 3l3-3m4-4l3-3l-3-3M5 9l-3 3l3 3m7-12v9m0 0v9m0-9h9m-9 0H3"/></svg>',
1935
2103
  moveUp: '<svg viewBox="0 0 24 24"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z"/></svg>',
1936
2104
  moveDown: '<svg viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6z"/></svg>',
1937
- replaceImage: '<svg viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/><path d="M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>'
2105
+ replaceImage: '<svg viewBox="0 0 24 24"><path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/><path d="M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04" fill="none" stroke="currentColor" stroke-width="1.5"/></svg>',
2106
+ cut: '<svg viewBox="0 0 24 24"><path d="M9.64 7.64c.23-.5.36-1.05.36-1.64 0-2.21-1.79-4-4-4S2 3.79 2 6s1.79 4 4 4c.59 0 1.14-.13 1.64-.36L10 12l-2.36 2.36C7.14 14.13 6.59 14 6 14c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4c0-.59-.13-1.14-.36-1.64L12 14l7 7h3v-1L9.64 7.64zM6 8c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm0 12c-1.1 0-2-.89-2-2s.9-2 2-2 2 .89 2 2-.9 2-2 2zm6-7.5c-.28 0-.5-.22-.5-.5s.22-.5.5-.5.5.22.5.5-.22.5-.5.5zM19 3l-6 6 2 2 7-7V3z"/></svg>',
2107
+ copy: '<svg viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>',
2108
+ paste: '<svg viewBox="0 0 24 24"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0S9.6.84 9.18 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm7 18H5V4h2v3h10V4h2v16z"/></svg>',
2109
+ pasteText: '<svg viewBox="0 0 24 24"><path d="M19 2h-4.18C14.4.84 13.3 0 12 0S9.6.84 9.18 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zm5 16H7v-2h10v2zm0-4H7v-2h10v2zm-3-4H7V8h7v2z"/></svg>',
2110
+ selectAll: '<svg viewBox="0 0 24 24"><path d="M3 3v6h2V5h4V3H3zm16 0h-4v2h4v4h2V3h-2zM5 15H3v6h6v-2H5v-4zm14 4h-4v2h6v-6h-2v4zM9 9h6v6H9V9z"/></svg>'
1938
2111
  };
1939
2112
 
1940
2113
  // ============================================
@@ -2109,7 +2282,7 @@
2109
2282
 
2110
2283
  syncThemeClasses() {
2111
2284
  if (!this.overlay || !this.editor.getThemeClasses) return;
2112
- this.overlay.classList.remove('neiki-dark', 'neiki-theme-blue', 'neiki-theme-dark-blue');
2285
+ this.overlay.classList.remove('neiki-dark', 'neiki-theme-blue', 'neiki-theme-dark-blue', 'neiki-theme-midnight', 'neiki-theme-void');
2113
2286
  this.editor.getThemeClasses(this.editor.config.theme).split(' ').filter(Boolean).forEach(className => {
2114
2287
  this.overlay.classList.add(className);
2115
2288
  });
@@ -2891,7 +3064,7 @@
2891
3064
  <img src="https://github.com/neikiri/neiki-editor/raw/main/assets/logo.png" alt="Neiki's Editor" style="width: 240px; height: auto; margin: 0 auto 16px; display: block;">
2892
3065
  <div style="font-size: 14px; line-height: 2; color: var(--neiki-text-primary);">
2893
3066
  <div><strong>${Utils.escapeHTML(t('help.author'))}:</strong> neikiri (Jindřich Stoklasa)</div>
2894
- <div><strong>${Utils.escapeHTML(t('help.version'))}:</strong> 3.0.3</div>
3067
+ <div><strong>${Utils.escapeHTML(t('help.version'))}:</strong> 3.2.0</div>
2895
3068
  <div><strong>${Utils.escapeHTML(t('help.github'))}:</strong> <a href="https://github.com/neikiri/neiki-editor" target="_blank" rel="noopener noreferrer" style="color: var(--neiki-accent);">github.com/neikiri/neiki-editor</a></div>
2896
3069
  <div><strong>${Utils.escapeHTML(t('help.documentation'))}:</strong> <a href="https://github.com/neikiri/neiki-editor/wiki" target="_blank" rel="noopener noreferrer" style="color: var(--neiki-accent);">Wiki</a></div>
2897
3070
  </div>
@@ -3834,7 +4007,10 @@
3834
4007
  this.emojiPicker = new EmojiPicker(this);
3835
4008
  this.specialCharsPicker = new SpecialCharsPicker(this);
3836
4009
  this.commands = new Commands(this);
3837
- this.tableContextMenu = new TableContextMenu(this);
4010
+ if (this.config.contextMenu !== false) {
4011
+ this.tableContextMenu = new TableContextMenu(this);
4012
+ this.editorContextMenu = new EditorContextMenu(this);
4013
+ }
3838
4014
  this.floatingToolbar = new FloatingToolbar(this);
3839
4015
  this.imageResizer = new ImageResizer(this);
3840
4016
  this.tableColumnResizer = new TableColumnResizer(this);
@@ -3884,7 +4060,7 @@
3884
4060
  const normalizedTheme = THEMES.includes(theme) ? theme : 'light';
3885
4061
  const classes = [];
3886
4062
 
3887
- if (normalizedTheme === 'dark' || normalizedTheme === 'dark-blue') {
4063
+ if (normalizedTheme === 'dark' || normalizedTheme === 'dark-blue' || normalizedTheme === 'midnight' || normalizedTheme === 'void') {
3888
4064
  classes.push('neiki-dark');
3889
4065
  }
3890
4066
 
@@ -3898,7 +4074,7 @@
3898
4074
  applyTheme(theme) {
3899
4075
  const normalizedTheme = THEMES.includes(theme) ? theme : 'light';
3900
4076
  this.config.theme = normalizedTheme;
3901
- this.container.classList.remove('neiki-dark', 'neiki-theme-blue', 'neiki-theme-dark-blue');
4077
+ this.container.classList.remove('neiki-dark', 'neiki-theme-blue', 'neiki-theme-dark-blue', 'neiki-theme-midnight', 'neiki-theme-void');
3902
4078
  this.getThemeClasses(normalizedTheme).split(' ').filter(Boolean).forEach(className => {
3903
4079
  this.container.classList.add(className);
3904
4080
  });
@@ -4873,7 +5049,7 @@
4873
5049
  }
4874
5050
  break;
4875
5051
  case 'themeToggle':
4876
- isActive = this.config.theme === 'dark' || this.config.theme === 'dark-blue';
5052
+ isActive = this.config.theme === 'dark' || this.config.theme === 'dark-blue' || this.config.theme === 'midnight' || this.config.theme === 'void';
4877
5053
  break;
4878
5054
  }
4879
5055
  } catch (e) {
@@ -7079,17 +7255,17 @@
7079
7255
  createMenu() {
7080
7256
  this.menu = Utils.createElement('div', { className: 'neiki-context-menu' });
7081
7257
  this.menu.innerHTML = `
7082
- <div class="neiki-context-item" data-action="insertRowAbove">${Icons.table} Insert Row Above</div>
7083
- <div class="neiki-context-item" data-action="insertRowBelow">${Icons.table} Insert Row Below</div>
7084
- <div class="neiki-context-item" data-action="insertColLeft">${Icons.table} Insert Column Left</div>
7085
- <div class="neiki-context-item" data-action="insertColRight">${Icons.table} Insert Column Right</div>
7258
+ <div class="neiki-context-item" data-action="insertRowAbove">${Icons.table} ${Utils.escapeHTML(t('tableContextMenu.insertRowAbove'))}</div>
7259
+ <div class="neiki-context-item" data-action="insertRowBelow">${Icons.table} ${Utils.escapeHTML(t('tableContextMenu.insertRowBelow'))}</div>
7260
+ <div class="neiki-context-item" data-action="insertColLeft">${Icons.table} ${Utils.escapeHTML(t('tableContextMenu.insertColLeft'))}</div>
7261
+ <div class="neiki-context-item" data-action="insertColRight">${Icons.table} ${Utils.escapeHTML(t('tableContextMenu.insertColRight'))}</div>
7086
7262
  <div class="neiki-context-divider"></div>
7087
- <div class="neiki-context-item" data-action="deleteRow">${Icons.eraser} Delete Row</div>
7088
- <div class="neiki-context-item" data-action="deleteCol">${Icons.eraser} Delete Column</div>
7089
- <div class="neiki-context-item neiki-context-danger" data-action="deleteTable">${Icons.eraser} Delete Table</div>
7263
+ <div class="neiki-context-item" data-action="deleteRow">${Icons.eraser} ${Utils.escapeHTML(t('tableContextMenu.deleteRow'))}</div>
7264
+ <div class="neiki-context-item" data-action="deleteCol">${Icons.eraser} ${Utils.escapeHTML(t('tableContextMenu.deleteCol'))}</div>
7265
+ <div class="neiki-context-item neiki-context-danger" data-action="deleteTable">${Icons.eraser} ${Utils.escapeHTML(t('tableContextMenu.deleteTable'))}</div>
7090
7266
  <div class="neiki-context-divider"></div>
7091
- <div class="neiki-context-item" data-action="mergeCells">${Icons.table} Merge Cells</div>
7092
- <div class="neiki-context-item" data-action="splitCell">${Icons.table} Split Cell</div>
7267
+ <div class="neiki-context-item" data-action="mergeCells">${Icons.table} ${Utils.escapeHTML(t('tableContextMenu.mergeCells'))}</div>
7268
+ <div class="neiki-context-item" data-action="splitCell">${Icons.table} ${Utils.escapeHTML(t('tableContextMenu.splitCell'))}</div>
7093
7269
  `;
7094
7270
  document.body.appendChild(this.menu);
7095
7271
 
@@ -7123,7 +7299,16 @@
7123
7299
  });
7124
7300
  }
7125
7301
 
7302
+ syncThemeClasses() {
7303
+ if (!this.editor.getThemeClasses) return;
7304
+ this.menu.classList.remove('neiki-dark', 'neiki-theme-blue', 'neiki-theme-dark-blue', 'neiki-theme-midnight', 'neiki-theme-void');
7305
+ this.editor.getThemeClasses(this.editor.config.theme).split(' ').filter(Boolean).forEach(className => {
7306
+ this.menu.classList.add(className);
7307
+ });
7308
+ }
7309
+
7126
7310
  show(x, y) {
7311
+ this.syncThemeClasses();
7127
7312
  this.menu.style.display = 'block';
7128
7313
 
7129
7314
  // Adjust position to stay within viewport
@@ -7262,6 +7447,195 @@
7262
7447
  }
7263
7448
  }
7264
7449
 
7450
+ // ============================================
7451
+ // EDITOR CONTEXT MENU (general right-click menu)
7452
+ // ============================================
7453
+
7454
+ class EditorContextMenu {
7455
+ constructor(editor) {
7456
+ this.editor = editor;
7457
+ this.menu = null;
7458
+
7459
+ this.createMenu();
7460
+ this.bindEvents();
7461
+ }
7462
+
7463
+ createMenu() {
7464
+ this.menu = Utils.createElement('div', { className: 'neiki-context-menu' });
7465
+ this.menu.innerHTML = `
7466
+ <div class="neiki-context-item" data-action="undo">${Icons.undo} <span data-label="contextMenu.undo"></span></div>
7467
+ <div class="neiki-context-item" data-action="redo">${Icons.redo} <span data-label="contextMenu.redo"></span></div>
7468
+ <div class="neiki-context-divider"></div>
7469
+ <div class="neiki-context-item" data-action="cut">${Icons.cut} <span data-label="contextMenu.cut"></span></div>
7470
+ <div class="neiki-context-item" data-action="copy">${Icons.copy} <span data-label="contextMenu.copy"></span></div>
7471
+ <div class="neiki-context-item" data-action="paste">${Icons.paste} <span data-label="contextMenu.paste"></span></div>
7472
+ <div class="neiki-context-item" data-action="pasteAsPlainText">${Icons.pasteText} <span data-label="contextMenu.pasteAsPlainText"></span></div>
7473
+ <div class="neiki-context-divider"></div>
7474
+ <div class="neiki-context-item" data-action="selectAll">${Icons.selectAll} <span data-label="contextMenu.selectAll"></span></div>
7475
+ <div class="neiki-context-divider"></div>
7476
+ <div class="neiki-context-item" data-action="removeFormat">${Icons.eraser} <span data-label="contextMenu.removeFormat"></span></div>
7477
+ `;
7478
+ document.body.appendChild(this.menu);
7479
+ this.updateLabels();
7480
+
7481
+ this.menu.querySelectorAll('.neiki-context-item').forEach(item => {
7482
+ item.addEventListener('click', (e) => {
7483
+ if (item.classList.contains('neiki-context-disabled')) return;
7484
+ const action = item.dataset.action;
7485
+ this.executeAction(action);
7486
+ this.hide();
7487
+ });
7488
+ });
7489
+ }
7490
+
7491
+ updateLabels() {
7492
+ this.menu.querySelectorAll('[data-label]').forEach(span => {
7493
+ span.textContent = t(span.dataset.label);
7494
+ });
7495
+ }
7496
+
7497
+ syncThemeClasses() {
7498
+ if (!this.editor.getThemeClasses) return;
7499
+ this.menu.classList.remove('neiki-dark', 'neiki-theme-blue', 'neiki-theme-dark-blue', 'neiki-theme-midnight', 'neiki-theme-void');
7500
+ this.editor.getThemeClasses(this.editor.config.theme).split(' ').filter(Boolean).forEach(className => {
7501
+ this.menu.classList.add(className);
7502
+ });
7503
+ }
7504
+
7505
+ updateItemStates() {
7506
+ const sel = window.getSelection();
7507
+ const hasSelection = !!(sel && sel.rangeCount && !sel.isCollapsed && this.editor.contentArea.contains(sel.anchorNode));
7508
+
7509
+ ['cut', 'copy'].forEach(action => {
7510
+ const item = this.menu.querySelector(`[data-action="${action}"]`);
7511
+ if (item) item.classList.toggle('neiki-context-disabled', !hasSelection);
7512
+ });
7513
+ }
7514
+
7515
+ bindEvents() {
7516
+ this.editor.contentArea.addEventListener('contextmenu', (e) => {
7517
+ if (e.target.closest('td, th')) return; // handled by TableContextMenu
7518
+
7519
+ e.preventDefault();
7520
+ this.editor.saveCurrentSelection();
7521
+ this.updateLabels();
7522
+ this.updateItemStates();
7523
+ this.show(e.clientX, e.clientY);
7524
+ });
7525
+
7526
+ document.addEventListener('click', (e) => {
7527
+ if (!this.menu.contains(e.target)) {
7528
+ this.hide();
7529
+ }
7530
+ });
7531
+
7532
+ document.addEventListener('keydown', (e) => {
7533
+ if (e.key === 'Escape') this.hide();
7534
+ });
7535
+ }
7536
+
7537
+ show(x, y) {
7538
+ this.syncThemeClasses();
7539
+ this.menu.style.display = 'block';
7540
+
7541
+ const rect = this.menu.getBoundingClientRect();
7542
+ const viewportWidth = window.innerWidth;
7543
+ const viewportHeight = window.innerHeight;
7544
+
7545
+ if (x + rect.width > viewportWidth) {
7546
+ x = viewportWidth - rect.width - 10;
7547
+ }
7548
+ if (y + rect.height > viewportHeight) {
7549
+ y = viewportHeight - rect.height - 10;
7550
+ }
7551
+
7552
+ this.menu.style.left = x + 'px';
7553
+ this.menu.style.top = y + 'px';
7554
+ }
7555
+
7556
+ hide() {
7557
+ this.menu.style.display = 'none';
7558
+ }
7559
+
7560
+ async executeAction(action) {
7561
+ this.editor.restoreSavedSelection();
7562
+
7563
+ switch (action) {
7564
+ case 'undo':
7565
+ this.editor.commands.undo();
7566
+ break;
7567
+ case 'redo':
7568
+ this.editor.commands.redo();
7569
+ break;
7570
+ case 'cut':
7571
+ document.execCommand('cut');
7572
+ this.editor.history.record();
7573
+ this.editor.triggerChange();
7574
+ break;
7575
+ case 'copy':
7576
+ document.execCommand('copy');
7577
+ break;
7578
+ case 'paste':
7579
+ await this.pasteWithFormatting();
7580
+ break;
7581
+ case 'pasteAsPlainText':
7582
+ await this.pasteAsPlainText();
7583
+ break;
7584
+ case 'selectAll':
7585
+ document.execCommand('selectAll');
7586
+ break;
7587
+ case 'removeFormat':
7588
+ this.editor.commands.removeFormat();
7589
+ break;
7590
+ }
7591
+ }
7592
+
7593
+ async pasteWithFormatting() {
7594
+ try {
7595
+ if (navigator.clipboard && navigator.clipboard.read) {
7596
+ const items = await navigator.clipboard.read();
7597
+ for (const item of items) {
7598
+ if (item.types.includes('text/html')) {
7599
+ const blob = await item.getType('text/html');
7600
+ const html = await blob.text();
7601
+ document.execCommand('insertHTML', false, Utils.sanitizeHTML(html));
7602
+ this.editor.history.record();
7603
+ this.editor.triggerChange();
7604
+ return;
7605
+ }
7606
+ }
7607
+ for (const item of items) {
7608
+ if (item.types.includes('text/plain')) {
7609
+ const blob = await item.getType('text/plain');
7610
+ const text = await blob.text();
7611
+ document.execCommand('insertHTML', false, Utils.escapeHTML(text).replace(/\n/g, '<br>'));
7612
+ this.editor.history.record();
7613
+ this.editor.triggerChange();
7614
+ return;
7615
+ }
7616
+ }
7617
+ } else if (document.execCommand('paste')) {
7618
+ this.editor.history.record();
7619
+ this.editor.triggerChange();
7620
+ }
7621
+ } catch (err) {
7622
+ console.error('NeikiEditor: Paste failed', err);
7623
+ }
7624
+ }
7625
+
7626
+ async pasteAsPlainText() {
7627
+ try {
7628
+ if (!navigator.clipboard || !navigator.clipboard.readText) return;
7629
+ const text = await navigator.clipboard.readText();
7630
+ document.execCommand('insertHTML', false, Utils.escapeHTML(text).replace(/\n/g, '<br>'));
7631
+ this.editor.history.record();
7632
+ this.editor.triggerChange();
7633
+ } catch (err) {
7634
+ console.error('NeikiEditor: Paste as plain text failed', err);
7635
+ }
7636
+ }
7637
+ }
7638
+
7265
7639
  // ============================================
7266
7640
  // SECTION 12: EXPORT
7267
7641
  // ============================================