neiki-editor 3.6.0 → 3.8.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 +17 -6
- package/dist/neiki-editor.css +8 -1
- package/dist/neiki-editor.js +190 -61
- 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/web%20components-29ABE2.svg?style=for-the-badge&logo=webcomponentsdotorg&logoColor=white" alt="Web Components">
|
|
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.
|
|
14
|
+
<img src="https://img.shields.io/badge/Version-3.8.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">
|
|
@@ -140,7 +140,7 @@ The recommended install is the single bundled script from the CDN. CSS is includ
|
|
|
140
140
|
**Pin a specific version**
|
|
141
141
|
|
|
142
142
|
```html
|
|
143
|
-
<script src="https://cdn.neikiri.dev/neiki-editor/3.
|
|
143
|
+
<script src="https://cdn.neikiri.dev/neiki-editor/3.8.0/neiki-editor.min.js"></script>
|
|
144
144
|
```
|
|
145
145
|
|
|
146
146
|
**Load CSS and JS separately**
|
|
@@ -151,8 +151,8 @@ The recommended install is the single bundled script from the CDN. CSS is includ
|
|
|
151
151
|
<script src="https://cdn.neikiri.dev/neiki-editor/neiki-editor.js"></script>
|
|
152
152
|
|
|
153
153
|
<!-- Or pinned -->
|
|
154
|
-
<link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/3.
|
|
155
|
-
<script src="https://cdn.neikiri.dev/neiki-editor/3.
|
|
154
|
+
<link rel="stylesheet" href="https://cdn.neikiri.dev/neiki-editor/3.8.0/neiki-editor.css">
|
|
155
|
+
<script src="https://cdn.neikiri.dev/neiki-editor/3.8.0/neiki-editor.js"></script>
|
|
156
156
|
```
|
|
157
157
|
|
|
158
158
|
**Alternative CDN — jsDelivr**
|
|
@@ -160,7 +160,7 @@ The recommended install is the single bundled script from the CDN. CSS is includ
|
|
|
160
160
|
```html
|
|
161
161
|
<script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@latest/dist/neiki-editor.min.js"></script>
|
|
162
162
|
<!-- Pinned -->
|
|
163
|
-
<script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@3.
|
|
163
|
+
<script src="https://cdn.jsdelivr.net/gh/neikiri/neiki-editor@3.8.0/dist/neiki-editor.min.js"></script>
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
**Package manager**
|
|
@@ -265,6 +265,7 @@ const editor = new NeikiEditor('#editor', {
|
|
|
265
265
|
| `autosaveKey` | `string \| null` | `null` | Custom `localStorage` scope for autosave |
|
|
266
266
|
| `customClass` | `string \| null` | `null` | Custom content style class that replaces the default `neiki-content-default-style` class |
|
|
267
267
|
| `toolbar` | `array` | *(full set)* | Toolbar button configuration |
|
|
268
|
+
| `floatingToolbar` | `array \| false` | *(full set)* | Floating selection toolbar button configuration; set to `false` to disable it |
|
|
268
269
|
| `showHelp` | `boolean` | `true` | Show the Help item in the More menu (⋯) |
|
|
269
270
|
| `imageUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN image uploads instead of base64 |
|
|
270
271
|
| `videoUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN video uploads instead of base64 |
|
|
@@ -292,7 +293,17 @@ new NeikiEditor('#editor', {
|
|
|
292
293
|
});
|
|
293
294
|
```
|
|
294
295
|
|
|
295
|
-
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.
|
|
296
|
+
Available identifiers include text formatting (`bold`, `italic`, `underline`, `strikethrough`, `subscript`, `superscript`, `code`, `formatPainter`, `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.
|
|
297
|
+
|
|
298
|
+
### Customizing the floating toolbar
|
|
299
|
+
|
|
300
|
+
The `floatingToolbar` option controls the toolbar shown for desktop text selections. It accepts `moveUp`, `moveDown`, `bold`, `italic`, `underline`, `strikethrough`, `link`, and `'|'` separators. Omit an item to remove it, or set the option to `false` to disable the floating toolbar completely. On touch-first devices, the editor automatically leaves selection controls to the browser so the floating toolbar does not overlap the native long-press menu.
|
|
301
|
+
|
|
302
|
+
```javascript
|
|
303
|
+
new NeikiEditor('#editor', {
|
|
304
|
+
floatingToolbar: ['bold', 'italic', 'underline', 'strikethrough']
|
|
305
|
+
});
|
|
306
|
+
```
|
|
296
307
|
|
|
297
308
|
### Themes
|
|
298
309
|
|
package/dist/neiki-editor.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NeikiEditor - Production-Ready WYSIWYG Rich Text Editor
|
|
3
3
|
* CSS Stylesheet
|
|
4
|
-
* Version: 3.
|
|
4
|
+
* Version: 3.8.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/* ============================================
|
|
@@ -2550,6 +2550,13 @@
|
|
|
2550
2550
|
}
|
|
2551
2551
|
}
|
|
2552
2552
|
|
|
2553
|
+
/* Mobile browsers provide native long-press selection controls. */
|
|
2554
|
+
@media (pointer: coarse) {
|
|
2555
|
+
.neiki-floating-toolbar {
|
|
2556
|
+
display: none !important;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
|
|
2553
2560
|
/* ============================================
|
|
2554
2561
|
Animations
|
|
2555
2562
|
============================================ */
|
package/dist/neiki-editor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* NeikiEditor - A Modern WYSIWYG Editor
|
|
3
|
-
* Version: 3.
|
|
3
|
+
* Version: 3.8.0
|
|
4
4
|
*
|
|
5
5
|
* A lightweight, feature-rich text editor with support for:
|
|
6
6
|
* - Rich text formatting (bold, italic, underline, etc.)
|
|
@@ -60,6 +60,7 @@
|
|
|
60
60
|
'toolbar.horizontalRule': 'Horizontal Line',
|
|
61
61
|
'toolbar.subscript': 'Subscript',
|
|
62
62
|
'toolbar.superscript': 'Superscript',
|
|
63
|
+
'toolbar.formatPainter': 'Copy Formatting',
|
|
63
64
|
'toolbar.removeFormat': 'Remove Formatting',
|
|
64
65
|
'toolbar.findReplace': 'Find & Replace',
|
|
65
66
|
'toolbar.emoji': 'Insert Emoji',
|
|
@@ -260,6 +261,7 @@
|
|
|
260
261
|
'toolbar.horizontalRule': 'Vodorovná čára',
|
|
261
262
|
'toolbar.subscript': 'Dolní index',
|
|
262
263
|
'toolbar.superscript': 'Horní index',
|
|
264
|
+
'toolbar.formatPainter': 'Kopírovat formátování',
|
|
263
265
|
'toolbar.removeFormat': 'Odstranit formátování',
|
|
264
266
|
'toolbar.findReplace': 'Najít a nahradit',
|
|
265
267
|
'toolbar.emoji': 'Vložit emoji',
|
|
@@ -453,6 +455,7 @@
|
|
|
453
455
|
'toolbar.horizontalRule': '水平线',
|
|
454
456
|
'toolbar.subscript': '下标',
|
|
455
457
|
'toolbar.superscript': '上标',
|
|
458
|
+
'toolbar.formatPainter': '复制格式',
|
|
456
459
|
'toolbar.removeFormat': '清除格式',
|
|
457
460
|
'toolbar.findReplace': '查找和替换',
|
|
458
461
|
'toolbar.emoji': '插入表情',
|
|
@@ -621,6 +624,7 @@
|
|
|
621
624
|
'toolbar.horizontalRule': 'Línea horizontal',
|
|
622
625
|
'toolbar.subscript': 'Subíndice',
|
|
623
626
|
'toolbar.superscript': 'Superíndice',
|
|
627
|
+
'toolbar.formatPainter': 'Copiar formato',
|
|
624
628
|
'toolbar.removeFormat': 'Eliminar formato',
|
|
625
629
|
'toolbar.findReplace': 'Buscar y reemplazar',
|
|
626
630
|
'toolbar.emoji': 'Insertar emoji',
|
|
@@ -789,6 +793,7 @@
|
|
|
789
793
|
'toolbar.horizontalRule': 'Horizontale Linie',
|
|
790
794
|
'toolbar.subscript': 'Tiefgestellt',
|
|
791
795
|
'toolbar.superscript': 'Hochgestellt',
|
|
796
|
+
'toolbar.formatPainter': 'Format kopieren',
|
|
792
797
|
'toolbar.removeFormat': 'Formatierung entfernen',
|
|
793
798
|
'toolbar.findReplace': 'Suchen und Ersetzen',
|
|
794
799
|
'toolbar.emoji': 'Emoji einfügen',
|
|
@@ -957,6 +962,7 @@
|
|
|
957
962
|
'toolbar.horizontalRule': 'Ligne horizontale',
|
|
958
963
|
'toolbar.subscript': 'Indice',
|
|
959
964
|
'toolbar.superscript': 'Exposant',
|
|
965
|
+
'toolbar.formatPainter': 'Copier la mise en forme',
|
|
960
966
|
'toolbar.removeFormat': 'Supprimer la mise en forme',
|
|
961
967
|
'toolbar.findReplace': 'Rechercher et remplacer',
|
|
962
968
|
'toolbar.emoji': 'Insérer un emoji',
|
|
@@ -1125,6 +1131,7 @@
|
|
|
1125
1131
|
'toolbar.horizontalRule': 'Linha horizontal',
|
|
1126
1132
|
'toolbar.subscript': 'Subscrito',
|
|
1127
1133
|
'toolbar.superscript': 'Sobrescrito',
|
|
1134
|
+
'toolbar.formatPainter': 'Copiar formatação',
|
|
1128
1135
|
'toolbar.removeFormat': 'Remover formatação',
|
|
1129
1136
|
'toolbar.findReplace': 'Localizar e substituir',
|
|
1130
1137
|
'toolbar.emoji': 'Inserir emoji',
|
|
@@ -1293,6 +1300,7 @@
|
|
|
1293
1300
|
'toolbar.horizontalRule': '水平線',
|
|
1294
1301
|
'toolbar.subscript': '下付き',
|
|
1295
1302
|
'toolbar.superscript': '上付き',
|
|
1303
|
+
'toolbar.formatPainter': '書式のコピー',
|
|
1296
1304
|
'toolbar.removeFormat': '書式解除',
|
|
1297
1305
|
'toolbar.findReplace': '検索と置換',
|
|
1298
1306
|
'toolbar.emoji': '絵文字挿入',
|
|
@@ -1473,7 +1481,7 @@
|
|
|
1473
1481
|
const DEFAULT_CONFIG = {
|
|
1474
1482
|
toolbar: [
|
|
1475
1483
|
'viewCode', 'undo', 'redo', 'findReplace', '|',
|
|
1476
|
-
'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'code', 'removeFormat', '|',
|
|
1484
|
+
'bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'code', 'formatPainter', 'removeFormat', '|',
|
|
1477
1485
|
'heading', 'fontFamily', 'fontSize', '|',
|
|
1478
1486
|
'foreColor', 'backColor', '|',
|
|
1479
1487
|
'alignLeft', 'alignCenter', 'alignRight', 'alignJustify', '|',
|
|
@@ -1482,6 +1490,7 @@
|
|
|
1482
1490
|
'insertDropdown', '|',
|
|
1483
1491
|
'moreMenu'
|
|
1484
1492
|
],
|
|
1493
|
+
floatingToolbar: ['moveUp', 'moveDown', '|', 'bold', 'italic', 'underline', 'strikethrough', 'link'],
|
|
1485
1494
|
placeholder: 'Start typing...',
|
|
1486
1495
|
minHeight: 300,
|
|
1487
1496
|
maxHeight: null,
|
|
@@ -1535,6 +1544,7 @@
|
|
|
1535
1544
|
subscript: { icon: 'subscript', titleKey: 'toolbar.subscript', command: 'subscript' },
|
|
1536
1545
|
superscript: { icon: 'superscript', titleKey: 'toolbar.superscript', command: 'superscript' },
|
|
1537
1546
|
code: { icon: 'code-inline', titleKey: 'toolbar.code', command: 'toggleCode' },
|
|
1547
|
+
formatPainter: { icon: 'format-painter', titleKey: 'toolbar.formatPainter', command: 'copyFormat' },
|
|
1538
1548
|
removeFormat: { icon: 'eraser', titleKey: 'toolbar.removeFormat', command: 'removeFormat' },
|
|
1539
1549
|
findReplace: { icon: 'search', titleKey: 'toolbar.findReplace', command: 'findReplace', modal: true },
|
|
1540
1550
|
emoji: { icon: 'emoji', titleKey: 'toolbar.emoji', command: 'emoji', picker: 'emoji' },
|
|
@@ -2096,6 +2106,7 @@
|
|
|
2096
2106
|
quote: '<svg viewBox="0 0 24 24"><path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/></svg>',
|
|
2097
2107
|
code: '<svg viewBox="0 0 24 24"><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>',
|
|
2098
2108
|
'code-inline': '<svg viewBox="0 0 256 256"><path d="M0 0h256v256H0z" fill="none"/><path fill="currentColor" d="M71.68 97.22L34.74 128l36.94 30.78a12 12 0 1 1-15.36 18.44l-48-40a12 12 0 0 1 0-18.44l48-40a12 12 0 0 1 15.36 18.44m176 21.56l-48-40a12 12 0 1 0-15.36 18.44L221.26 128l-36.94 30.78a12 12 0 1 0 15.36 18.44l48-40a12 12 0 0 0 0-18.44M164.1 28.72a12 12 0 0 0-15.38 7.18l-64 176a12 12 0 0 0 7.18 15.37a11.8 11.8 0 0 0 4.1.73a12 12 0 0 0 11.28-7.9l64-176a12 12 0 0 0-7.18-15.38"/></svg>',
|
|
2109
|
+
'format-painter': '<svg width="1em" height="1em" viewBox="0 0 32 32"><g transform="translate(0 32) scale(1 -1)"><path d="M0 0h32v32H0z" fill="none"/><path fill="currentColor" d="M28.83 23.17L23 17.33V13a1 1 0 0 0-.29-.71l-10-10a1 1 0 0 0-1.42 0l-9 9a1 1 0 0 0 0 1.42l10 10A1 1 0 0 0 13 23h4.34l5.83 5.84a4 4 0 0 0 5.66-5.66ZM6 10.41l2.29 2.3l1.42-1.42L7.41 9L9 7.41l4.29 4.3l1.42-1.42L10.41 6L12 4.41L18.59 11L11 18.59L4.41 12Zm21.41 17a2 2 0 0 1-2.82 0l-6.13-6.12a1.8 1.8 0 0 0-.71-.29h-4.34l-1-1L20 12.41l1 1v4.34a1 1 0 0 0 .29.7l6.12 6.14a2 2 0 0 1 0 2.82" stroke-width="0.45" stroke="currentColor"/></g></svg>',
|
|
2099
2110
|
minus: '<svg viewBox="0 0 24 24"><path d="M19 13H5v-2h14v2z"/></svg>',
|
|
2100
2111
|
eraser: '<svg viewBox="0 0 24 24"><path d="M16.24 3.56l4.95 4.94c.78.79.78 2.05 0 2.84L12 20.53a4.008 4.008 0 01-5.66 0L2.81 17c-.78-.79-.78-2.05 0-2.84l10.6-10.6c.79-.78 2.05-.78 2.83 0zm-1.41 1.42L6.93 12.9l4.24 4.24 7.9-7.9-4.24-4.26z"/></svg>',
|
|
2101
2112
|
fullscreen: '<svg viewBox="0 0 24 24"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z"/></svg>',
|
|
@@ -3114,7 +3125,7 @@
|
|
|
3114
3125
|
<img src="https://github.com/neikiri/neiki-editor/raw/main/assets/logo.svg" alt="Neiki's Editor" style="width: 240px; height: auto; margin: 0 auto 16px; display: block;">
|
|
3115
3126
|
<div style="font-size: 14px; line-height: 2; color: var(--neiki-text-primary);">
|
|
3116
3127
|
<div><strong>${Utils.escapeHTML(t('help.author'))}:</strong> neikiri (Jindřich Stoklasa)</div>
|
|
3117
|
-
<div><strong>${Utils.escapeHTML(t('help.version'))}:</strong> 3.
|
|
3128
|
+
<div><strong>${Utils.escapeHTML(t('help.version'))}:</strong> 3.8.0</div>
|
|
3118
3129
|
<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>
|
|
3119
3130
|
<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>
|
|
3120
3131
|
</div>
|
|
@@ -3532,6 +3543,7 @@
|
|
|
3532
3543
|
class Commands {
|
|
3533
3544
|
constructor(editor) {
|
|
3534
3545
|
this.editor = editor;
|
|
3546
|
+
this.formatPainter = null;
|
|
3535
3547
|
}
|
|
3536
3548
|
|
|
3537
3549
|
exec(command, value = null) {
|
|
@@ -3692,6 +3704,102 @@
|
|
|
3692
3704
|
|
|
3693
3705
|
removeFormat() { this.exec('removeFormat'); }
|
|
3694
3706
|
|
|
3707
|
+
isFormatPainterActive() {
|
|
3708
|
+
return !!this.formatPainter;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
copyFormat() {
|
|
3712
|
+
if (this.formatPainter) {
|
|
3713
|
+
this.formatPainter = null;
|
|
3714
|
+
this.editor.updateToolbar();
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
if (!this.editor.restoreSavedSelection()) return;
|
|
3719
|
+
this._expandToWordIfCollapsed();
|
|
3720
|
+
|
|
3721
|
+
const selection = window.getSelection();
|
|
3722
|
+
if (!selection || !selection.rangeCount) return;
|
|
3723
|
+
const range = selection.getRangeAt(0);
|
|
3724
|
+
if (!Utils.isRangeInside(range, this.editor.contentArea) || range.collapsed) return;
|
|
3725
|
+
|
|
3726
|
+
let sourceNode = range.startContainer;
|
|
3727
|
+
if (sourceNode.nodeType === Node.TEXT_NODE) sourceNode = sourceNode.parentNode;
|
|
3728
|
+
const sourceStyle = window.getComputedStyle(sourceNode);
|
|
3729
|
+
const inlineCommands = ['bold', 'italic', 'underline', 'strikeThrough', 'subscript', 'superscript'];
|
|
3730
|
+
const states = {};
|
|
3731
|
+
|
|
3732
|
+
inlineCommands.forEach(command => {
|
|
3733
|
+
try {
|
|
3734
|
+
states[command] = document.queryCommandState(command);
|
|
3735
|
+
} catch (e) {
|
|
3736
|
+
states[command] = false;
|
|
3737
|
+
}
|
|
3738
|
+
});
|
|
3739
|
+
|
|
3740
|
+
this.formatPainter = {
|
|
3741
|
+
sourceRange: range.cloneRange(),
|
|
3742
|
+
states,
|
|
3743
|
+
fontFamily: sourceStyle.fontFamily,
|
|
3744
|
+
fontSize: sourceStyle.fontSize,
|
|
3745
|
+
color: sourceStyle.color,
|
|
3746
|
+
backgroundColor: sourceStyle.backgroundColor
|
|
3747
|
+
};
|
|
3748
|
+
this.editor.saveCurrentSelection();
|
|
3749
|
+
this.editor.updateToolbar();
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
applyCopiedFormat() {
|
|
3753
|
+
if (!this.formatPainter) return false;
|
|
3754
|
+
|
|
3755
|
+
const selection = window.getSelection();
|
|
3756
|
+
if (!selection || !selection.rangeCount) return false;
|
|
3757
|
+
const range = selection.getRangeAt(0);
|
|
3758
|
+
if (!Utils.isRangeInside(range, this.editor.contentArea) || this._areRangesEqual(range, this.formatPainter.sourceRange)) {
|
|
3759
|
+
return false;
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
this._expandToWordIfCollapsed();
|
|
3763
|
+
const style = this.formatPainter;
|
|
3764
|
+
this.formatPainter = null;
|
|
3765
|
+
|
|
3766
|
+
Object.entries(style.states).forEach(([command, shouldBeActive]) => {
|
|
3767
|
+
try {
|
|
3768
|
+
if (document.queryCommandState(command) !== shouldBeActive) {
|
|
3769
|
+
document.execCommand(command, false, null);
|
|
3770
|
+
}
|
|
3771
|
+
} catch (e) { /* Ignore unsupported command states. */ }
|
|
3772
|
+
});
|
|
3773
|
+
|
|
3774
|
+
if (style.fontFamily) document.execCommand('fontName', false, style.fontFamily);
|
|
3775
|
+
if (style.fontSize) this._applyFontSize(style.fontSize);
|
|
3776
|
+
if (style.color) document.execCommand('foreColor', false, style.color);
|
|
3777
|
+
if (this._isTransparentColor(style.backgroundColor)) {
|
|
3778
|
+
this._resetColorProperty('backgroundColor', false);
|
|
3779
|
+
} else if (style.backgroundColor) {
|
|
3780
|
+
document.execCommand('backColor', false, style.backgroundColor);
|
|
3781
|
+
}
|
|
3782
|
+
|
|
3783
|
+
this.editor.history.record();
|
|
3784
|
+
this.editor.updateToolbar();
|
|
3785
|
+
this.editor.triggerChange();
|
|
3786
|
+
return true;
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
_areRangesEqual(first, second) {
|
|
3790
|
+
if (!first || !second) return false;
|
|
3791
|
+
try {
|
|
3792
|
+
return first.compareBoundaryPoints(Range.START_TO_START, second) === 0 &&
|
|
3793
|
+
first.compareBoundaryPoints(Range.END_TO_END, second) === 0;
|
|
3794
|
+
} catch (e) {
|
|
3795
|
+
return false;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
_isTransparentColor(color) {
|
|
3800
|
+
return !color || color === 'transparent' || /^rgba\(0,\s*0,\s*0,\s*0\)$/.test(color);
|
|
3801
|
+
}
|
|
3802
|
+
|
|
3695
3803
|
insertHorizontalRule() { this.exec('insertHorizontalRule'); }
|
|
3696
3804
|
|
|
3697
3805
|
formatBlock(tag) {
|
|
@@ -3773,9 +3881,7 @@
|
|
|
3773
3881
|
return ['ADDRESS', 'ARTICLE', 'ASIDE', 'BLOCKQUOTE', 'DIV', 'DL', 'FIGURE', 'FOOTER', 'FORM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', 'HR', 'MAIN', 'OL', 'P', 'PRE', 'SECTION', 'TABLE', 'UL'].includes(node.tagName);
|
|
3774
3882
|
}
|
|
3775
3883
|
|
|
3776
|
-
|
|
3777
|
-
this.editor.focus();
|
|
3778
|
-
this._expandToWordIfCollapsed();
|
|
3884
|
+
_applyFontSize(sizeStr) {
|
|
3779
3885
|
document.execCommand('fontSize', false, '7');
|
|
3780
3886
|
const marked = this.editor.contentArea.querySelectorAll('font[size="7"]');
|
|
3781
3887
|
const spans = [];
|
|
@@ -3798,6 +3904,12 @@
|
|
|
3798
3904
|
sel.addRange(range);
|
|
3799
3905
|
} catch (e) { /* ignore */ }
|
|
3800
3906
|
}
|
|
3907
|
+
}
|
|
3908
|
+
|
|
3909
|
+
fontSize(sizeStr) {
|
|
3910
|
+
this.editor.focus();
|
|
3911
|
+
this._expandToWordIfCollapsed();
|
|
3912
|
+
this._applyFontSize(sizeStr);
|
|
3801
3913
|
this.editor.history.record();
|
|
3802
3914
|
this.editor.updateToolbar();
|
|
3803
3915
|
this.editor.triggerChange();
|
|
@@ -3823,7 +3935,7 @@
|
|
|
3823
3935
|
this._resetColorProperty('backgroundColor');
|
|
3824
3936
|
}
|
|
3825
3937
|
|
|
3826
|
-
_resetColorProperty(cssProp) {
|
|
3938
|
+
_resetColorProperty(cssProp, shouldRecord = true) {
|
|
3827
3939
|
this.editor.focus();
|
|
3828
3940
|
const sel = window.getSelection();
|
|
3829
3941
|
if (!sel || !sel.rangeCount) return;
|
|
@@ -3872,9 +3984,11 @@
|
|
|
3872
3984
|
sel.removeAllRanges();
|
|
3873
3985
|
sel.addRange(range);
|
|
3874
3986
|
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3987
|
+
if (shouldRecord) {
|
|
3988
|
+
this.editor.history.record();
|
|
3989
|
+
this.editor.updateToolbar();
|
|
3990
|
+
this.editor.triggerChange();
|
|
3991
|
+
}
|
|
3878
3992
|
}
|
|
3879
3993
|
|
|
3880
3994
|
viewCode() {
|
|
@@ -4830,6 +4944,13 @@
|
|
|
4830
4944
|
}
|
|
4831
4945
|
});
|
|
4832
4946
|
|
|
4947
|
+
// Format Painter applies its copied inline formatting once the target selection is complete.
|
|
4948
|
+
this.contentArea.addEventListener('mouseup', () => {
|
|
4949
|
+
if (this.commands && this.commands.isFormatPainterActive()) {
|
|
4950
|
+
this.commands.applyCopiedFormat();
|
|
4951
|
+
}
|
|
4952
|
+
});
|
|
4953
|
+
|
|
4833
4954
|
// Focus/Blur
|
|
4834
4955
|
this.contentArea.addEventListener('focus', () => {
|
|
4835
4956
|
if (this.config.onFocus) this.config.onFocus(this);
|
|
@@ -5175,6 +5296,10 @@
|
|
|
5175
5296
|
if (this.toolbarButtons.viewCode) {
|
|
5176
5297
|
this.toolbarButtons.viewCode.classList.toggle('active', this.isCodeViewOpen);
|
|
5177
5298
|
}
|
|
5299
|
+
if (this.toolbarButtons.formatPainter) {
|
|
5300
|
+
this.toolbarButtons.formatPainter.classList.toggle('active',
|
|
5301
|
+
!!(this.commands && this.commands.isFormatPainterActive()));
|
|
5302
|
+
}
|
|
5178
5303
|
|
|
5179
5304
|
// Sync heading select
|
|
5180
5305
|
if (this.toolbarSelects.heading) {
|
|
@@ -7241,61 +7366,53 @@
|
|
|
7241
7366
|
|
|
7242
7367
|
createToolbar() {
|
|
7243
7368
|
this.toolbar = Utils.createElement('div', { className: 'neiki-floating-toolbar' });
|
|
7369
|
+
const items = {
|
|
7370
|
+
moveUp: { icon: Icons.moveUp, title: t('blockToolbar.moveUp'), move: true },
|
|
7371
|
+
moveDown: { icon: Icons.moveDown, title: t('blockToolbar.moveDown'), move: true },
|
|
7372
|
+
bold: { icon: Icons.bold, title: t('toolbar.bold') },
|
|
7373
|
+
italic: { icon: Icons.italic, title: t('toolbar.italic') },
|
|
7374
|
+
underline: { icon: Icons.underline, title: t('toolbar.underline') },
|
|
7375
|
+
strikethrough: { icon: Icons.strikethrough, title: t('toolbar.strikethrough') },
|
|
7376
|
+
link: { icon: Icons.link, title: t('toolbar.link') }
|
|
7377
|
+
};
|
|
7378
|
+
let lastWasDivider = true;
|
|
7379
|
+
|
|
7380
|
+
if (Array.isArray(this.editor.config.floatingToolbar)) {
|
|
7381
|
+
this.editor.config.floatingToolbar.forEach(item => {
|
|
7382
|
+
if (item === '|') {
|
|
7383
|
+
if (!lastWasDivider) {
|
|
7384
|
+
this.toolbar.appendChild(Utils.createElement('span', { className: 'neiki-floating-divider' }));
|
|
7385
|
+
lastWasDivider = true;
|
|
7386
|
+
}
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7244
7389
|
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7248
|
-
|
|
7249
|
-
|
|
7250
|
-
|
|
7251
|
-
|
|
7252
|
-
|
|
7253
|
-
|
|
7254
|
-
|
|
7255
|
-
type: 'button',
|
|
7256
|
-
innerHTML: icon,
|
|
7257
|
-
'data-command': item
|
|
7258
|
-
});
|
|
7259
|
-
|
|
7260
|
-
button.addEventListener('click', (e) => {
|
|
7261
|
-
e.preventDefault();
|
|
7262
|
-
e.stopPropagation();
|
|
7263
|
-
this.handleButtonClick(item);
|
|
7264
|
-
});
|
|
7265
|
-
|
|
7266
|
-
this.toolbar.appendChild(button);
|
|
7267
|
-
});
|
|
7268
|
-
|
|
7269
|
-
// Divider
|
|
7270
|
-
const divider = Utils.createElement('span', { className: 'neiki-floating-divider' });
|
|
7271
|
-
this.toolbar.appendChild(divider);
|
|
7272
|
-
|
|
7273
|
-
// Formatting buttons
|
|
7274
|
-
const buttons = [
|
|
7275
|
-
{ item: 'bold', icon: Icons.bold, title: 'Bold' },
|
|
7276
|
-
{ item: 'italic', icon: Icons.italic, title: 'Italic' },
|
|
7277
|
-
{ item: 'underline', icon: Icons.underline, title: 'Underline' },
|
|
7278
|
-
{ item: 'strikeThrough', icon: Icons.strikethrough, title: 'Strikethrough' },
|
|
7279
|
-
{ item: 'link', icon: Icons.link, title: 'Link' }
|
|
7280
|
-
];
|
|
7390
|
+
const config = items[item];
|
|
7391
|
+
if (!config) return;
|
|
7392
|
+
const button = Utils.createElement('button', {
|
|
7393
|
+
className: `neiki-toolbar-btn neiki-floating-btn${config.move ? ' neiki-floating-move-btn' : ''}`,
|
|
7394
|
+
title: config.title,
|
|
7395
|
+
'aria-label': config.title,
|
|
7396
|
+
type: 'button',
|
|
7397
|
+
innerHTML: config.icon,
|
|
7398
|
+
'data-command': item
|
|
7399
|
+
});
|
|
7281
7400
|
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
|
|
7287
|
-
innerHTML: icon,
|
|
7288
|
-
'data-command': item
|
|
7289
|
-
});
|
|
7401
|
+
button.addEventListener('click', (e) => {
|
|
7402
|
+
e.preventDefault();
|
|
7403
|
+
e.stopPropagation();
|
|
7404
|
+
this.handleButtonClick(item);
|
|
7405
|
+
});
|
|
7290
7406
|
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
e.stopPropagation();
|
|
7294
|
-
this.handleButtonClick(item);
|
|
7407
|
+
this.toolbar.appendChild(button);
|
|
7408
|
+
lastWasDivider = false;
|
|
7295
7409
|
});
|
|
7410
|
+
}
|
|
7296
7411
|
|
|
7297
|
-
|
|
7298
|
-
|
|
7412
|
+
if (lastWasDivider && this.toolbar.lastElementChild) {
|
|
7413
|
+
this.toolbar.lastElementChild.remove();
|
|
7414
|
+
}
|
|
7415
|
+
this.isEnabled = this.toolbar.childElementCount > 0;
|
|
7299
7416
|
|
|
7300
7417
|
document.body.appendChild(this.toolbar);
|
|
7301
7418
|
}
|
|
@@ -7316,6 +7433,13 @@
|
|
|
7316
7433
|
}
|
|
7317
7434
|
|
|
7318
7435
|
updatePosition() {
|
|
7436
|
+
// Mobile browsers provide their own selection controls after a long press.
|
|
7437
|
+
// Do not place a second toolbar underneath those native controls.
|
|
7438
|
+
if (!this.isEnabled || this.usesNativeTouchSelectionUI()) {
|
|
7439
|
+
this.hide();
|
|
7440
|
+
return;
|
|
7441
|
+
}
|
|
7442
|
+
|
|
7319
7443
|
// Hide when an image is selected (image toolbar takes over)
|
|
7320
7444
|
if (this.editor.imageResizer && this.editor.imageResizer.currentImg) {
|
|
7321
7445
|
this.hide();
|
|
@@ -7351,6 +7475,10 @@
|
|
|
7351
7475
|
this.toolbar.style.top = finalY + 'px';
|
|
7352
7476
|
}
|
|
7353
7477
|
|
|
7478
|
+
usesNativeTouchSelectionUI() {
|
|
7479
|
+
return typeof window.matchMedia === 'function' && window.matchMedia('(pointer: coarse)').matches;
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7354
7482
|
show() {
|
|
7355
7483
|
if (!this.isVisible) {
|
|
7356
7484
|
this.toolbar.classList.add('show');
|
|
@@ -7373,7 +7501,8 @@
|
|
|
7373
7501
|
} else if (item === 'moveDown') {
|
|
7374
7502
|
if (this.editor.blockDragDrop) this.editor.blockDragDrop.moveBlockDown();
|
|
7375
7503
|
} else {
|
|
7376
|
-
|
|
7504
|
+
const command = item === 'strikethrough' ? 'strikeThrough' : item;
|
|
7505
|
+
this.editor.commands[command]();
|
|
7377
7506
|
}
|
|
7378
7507
|
this.hide();
|
|
7379
7508
|
}
|