neiki-editor 3.4.0 → 3.6.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 +29 -7
- package/dist/neiki-editor.css +28 -28
- package/dist/neiki-editor.js +155 -9
- 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.6.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.6.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.6.0/neiki-editor.css">
|
|
155
|
+
<script src="https://cdn.neikiri.dev/neiki-editor/3.6.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.6.0/dist/neiki-editor.min.js"></script>
|
|
164
164
|
```
|
|
165
165
|
|
|
166
166
|
**Package manager**
|
|
@@ -263,7 +263,7 @@ const editor = new NeikiEditor('#editor', {
|
|
|
263
263
|
| `translations` | `object \| null` | `null` | Custom translation keys, merged with built-ins |
|
|
264
264
|
| `contextMenu` | `boolean` | `true` | Enable the custom right-click context menu. Set to `false` to fall back to the browser's native menu |
|
|
265
265
|
| `autosaveKey` | `string \| null` | `null` | Custom `localStorage` scope for autosave |
|
|
266
|
-
| `customClass` | `string \| null` | `null` |
|
|
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
268
|
| `showHelp` | `boolean` | `true` | Show the Help item in the More menu (⋯) |
|
|
269
269
|
| `imageUploadHandler` | `function \| null` | `null` | Async `(file) => Promise<url>` for server/CDN image uploads instead of base64 |
|
|
@@ -309,12 +309,14 @@ editor.toggleTheme(); // cycle: light → dark → blue → dark-blue →
|
|
|
309
309
|
|
|
310
310
|
### Custom content styling
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
By default, the editable area uses the internal `neiki-content` class plus the default typography class `neiki-content-default-style`. Use `customClass` to replace only the default typography class while keeping the internal editor class:
|
|
313
313
|
|
|
314
314
|
```javascript
|
|
315
315
|
new NeikiEditor('#editor', { customClass: 'article-content' });
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
+
That renders the content area with `class="neiki-content article-content"` instead of `class="neiki-content neiki-content-default-style"`.
|
|
319
|
+
|
|
318
320
|
```css
|
|
319
321
|
.article-content {
|
|
320
322
|
font-family: Georgia, serif;
|
|
@@ -351,6 +353,18 @@ Autosave is toggled from the More menu (⋯). When enabled, content is written t
|
|
|
351
353
|
new NeikiEditor('#editor', { autosaveKey: 'article-42' });
|
|
352
354
|
```
|
|
353
355
|
|
|
356
|
+
You can clear saved autosave data for the current editor, remove a specific scoped storage key, or remove all scoped keys matching a prefix:
|
|
357
|
+
|
|
358
|
+
```javascript
|
|
359
|
+
editor.clearAutosaveStorage(); // current editor's autosave flags/content
|
|
360
|
+
editor.removeStorage('autosave_content'); // current editor storage key
|
|
361
|
+
editor.removeStorageByPrefix('autosave_'); // current editor storage keys by prefix
|
|
362
|
+
|
|
363
|
+
NeikiEditor.removeStorageKey('neiki_autosave_article-42_autosave_content');
|
|
364
|
+
NeikiEditor.removeStorageByPrefix('neiki_autosave_');
|
|
365
|
+
NeikiEditor.clearAutosaveStorage(); // shortcut for the default autosave prefix
|
|
366
|
+
```
|
|
367
|
+
|
|
354
368
|
> Autosave is intended for drafts and recovery. For production persistence, use the `onSave` or `onChange` callbacks to save to your backend.
|
|
355
369
|
|
|
356
370
|
---
|
|
@@ -386,6 +400,11 @@ editor.triggerSave(); // trigger onSave
|
|
|
386
400
|
editor.previewContent(); // open preview modal
|
|
387
401
|
editor.downloadContent(); // download as .html
|
|
388
402
|
|
|
403
|
+
// Storage cleanup
|
|
404
|
+
editor.clearAutosaveStorage();
|
|
405
|
+
editor.removeStorage('autosave_content');
|
|
406
|
+
editor.removeStorageByPrefix('autosave_');
|
|
407
|
+
|
|
389
408
|
// Theme
|
|
390
409
|
editor.setTheme('dark');
|
|
391
410
|
editor.toggleTheme();
|
|
@@ -398,6 +417,9 @@ editor.execCommand('foreColor', '#ff0000');
|
|
|
398
417
|
NeikiEditor.registerPlugin({ /* ... */ });
|
|
399
418
|
NeikiEditor.getPlugins(); // array of registered plugins
|
|
400
419
|
NeikiEditor.addTranslation('de', { /* ... */ });
|
|
420
|
+
NeikiEditor.removeStorageKey('neiki_autosave_article-42_autosave_content');
|
|
421
|
+
NeikiEditor.removeStorageByPrefix('neiki_autosave_');
|
|
422
|
+
NeikiEditor.clearAutosaveStorage();
|
|
401
423
|
```
|
|
402
424
|
|
|
403
425
|
> In SPA frameworks, always call `editor.destroy()` when the component unmounts to clean up listeners and avoid memory leaks.
|
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.6.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
/* ============================================
|
|
@@ -668,58 +668,58 @@
|
|
|
668
668
|
background: var(--neiki-border-color-dark);
|
|
669
669
|
}
|
|
670
670
|
|
|
671
|
-
/* Content Typography */
|
|
672
|
-
.neiki-content p {
|
|
671
|
+
/* Default Content Typography */
|
|
672
|
+
.neiki-content-default-style p {
|
|
673
673
|
margin: 0 0 1em 0;
|
|
674
674
|
}
|
|
675
675
|
|
|
676
|
-
.neiki-content h1 {
|
|
676
|
+
.neiki-content-default-style h1 {
|
|
677
677
|
font-size: 2em;
|
|
678
678
|
font-weight: 700;
|
|
679
679
|
margin: 0 0 0.5em 0;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
-
.neiki-content h2 {
|
|
682
|
+
.neiki-content-default-style h2 {
|
|
683
683
|
font-size: 1.5em;
|
|
684
684
|
font-weight: 600;
|
|
685
685
|
margin: 0 0 0.5em 0;
|
|
686
686
|
}
|
|
687
687
|
|
|
688
|
-
.neiki-content h3 {
|
|
688
|
+
.neiki-content-default-style h3 {
|
|
689
689
|
font-size: 1.25em;
|
|
690
690
|
font-weight: 600;
|
|
691
691
|
margin: 0 0 0.5em 0;
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
-
.neiki-content h4 {
|
|
694
|
+
.neiki-content-default-style h4 {
|
|
695
695
|
font-size: 1.1em;
|
|
696
696
|
font-weight: 600;
|
|
697
697
|
margin: 0 0 0.5em 0;
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
-
.neiki-content h5 {
|
|
700
|
+
.neiki-content-default-style h5 {
|
|
701
701
|
font-size: 1em;
|
|
702
702
|
font-weight: 600;
|
|
703
703
|
margin: 0 0 0.5em 0;
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
-
.neiki-content h6 {
|
|
706
|
+
.neiki-content-default-style h6 {
|
|
707
707
|
font-size: 0.9em;
|
|
708
708
|
font-weight: 600;
|
|
709
709
|
margin: 0 0 0.5em 0;
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
-
.neiki-content ul,
|
|
713
|
-
.neiki-content ol {
|
|
712
|
+
.neiki-content-default-style ul,
|
|
713
|
+
.neiki-content-default-style ol {
|
|
714
714
|
margin: 0 0 1em 0;
|
|
715
715
|
padding-left: 2em;
|
|
716
716
|
}
|
|
717
717
|
|
|
718
|
-
.neiki-content li {
|
|
718
|
+
.neiki-content-default-style li {
|
|
719
719
|
margin-bottom: 0.25em;
|
|
720
720
|
}
|
|
721
721
|
|
|
722
|
-
.neiki-content blockquote {
|
|
722
|
+
.neiki-content-default-style blockquote {
|
|
723
723
|
margin: 0 0 1em 0;
|
|
724
724
|
padding: 10px 20px;
|
|
725
725
|
border-left: 4px solid var(--neiki-accent-color);
|
|
@@ -727,7 +727,7 @@
|
|
|
727
727
|
font-style: italic;
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
.neiki-content pre {
|
|
730
|
+
.neiki-content-default-style pre {
|
|
731
731
|
margin: 0 0 1em 0;
|
|
732
732
|
padding: 16px;
|
|
733
733
|
background: var(--neiki-bg-tertiary);
|
|
@@ -737,7 +737,7 @@
|
|
|
737
737
|
font-size: 13px;
|
|
738
738
|
}
|
|
739
739
|
|
|
740
|
-
.neiki-content code {
|
|
740
|
+
.neiki-content-default-style code {
|
|
741
741
|
padding: 2px 6px;
|
|
742
742
|
background: var(--neiki-bg-tertiary);
|
|
743
743
|
border-radius: 3px;
|
|
@@ -745,16 +745,16 @@
|
|
|
745
745
|
font-size: 0.9em;
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
-
.neiki-content a {
|
|
748
|
+
.neiki-content-default-style a {
|
|
749
749
|
color: var(--neiki-accent-color);
|
|
750
750
|
text-decoration: underline;
|
|
751
751
|
}
|
|
752
752
|
|
|
753
|
-
.neiki-content a:hover {
|
|
753
|
+
.neiki-content-default-style a:hover {
|
|
754
754
|
color: var(--neiki-accent-hover);
|
|
755
755
|
}
|
|
756
756
|
|
|
757
|
-
.neiki-content img {
|
|
757
|
+
.neiki-content-default-style img {
|
|
758
758
|
display: inline-block;
|
|
759
759
|
max-width: 100%;
|
|
760
760
|
height: auto;
|
|
@@ -762,12 +762,12 @@
|
|
|
762
762
|
cursor: pointer;
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
-
.neiki-content img.neiki-selected {
|
|
765
|
+
.neiki-content-default-style img.neiki-selected {
|
|
766
766
|
outline: 2px solid var(--neiki-accent-color);
|
|
767
767
|
outline-offset: 2px;
|
|
768
768
|
}
|
|
769
769
|
|
|
770
|
-
.neiki-content video {
|
|
770
|
+
.neiki-content-default-style video {
|
|
771
771
|
display: block;
|
|
772
772
|
max-width: 100%;
|
|
773
773
|
height: auto;
|
|
@@ -790,7 +790,7 @@
|
|
|
790
790
|
/* ============================================
|
|
791
791
|
Tables
|
|
792
792
|
============================================ */
|
|
793
|
-
.neiki-content table,
|
|
793
|
+
.neiki-content-default-style table,
|
|
794
794
|
.neiki-table {
|
|
795
795
|
width: 100%;
|
|
796
796
|
border-collapse: collapse;
|
|
@@ -798,8 +798,8 @@
|
|
|
798
798
|
table-layout: fixed;
|
|
799
799
|
}
|
|
800
800
|
|
|
801
|
-
.neiki-content table td,
|
|
802
|
-
.neiki-content table th {
|
|
801
|
+
.neiki-content-default-style table td,
|
|
802
|
+
.neiki-content-default-style table th {
|
|
803
803
|
border: 1px solid var(--neiki-table-border);
|
|
804
804
|
padding: 8px 12px;
|
|
805
805
|
min-width: 50px;
|
|
@@ -807,18 +807,18 @@
|
|
|
807
807
|
position: relative;
|
|
808
808
|
}
|
|
809
809
|
|
|
810
|
-
.neiki-content table th {
|
|
810
|
+
.neiki-content-default-style table th {
|
|
811
811
|
background: var(--neiki-bg-secondary);
|
|
812
812
|
font-weight: 600;
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
-
.neiki-content table td:hover,
|
|
816
|
-
.neiki-content table th:hover {
|
|
815
|
+
.neiki-content-default-style table td:hover,
|
|
816
|
+
.neiki-content-default-style table th:hover {
|
|
817
817
|
background: var(--neiki-bg-hover);
|
|
818
818
|
}
|
|
819
819
|
|
|
820
|
-
.neiki-content table td.neiki-cell-selected,
|
|
821
|
-
.neiki-content table th.neiki-cell-selected {
|
|
820
|
+
.neiki-content-default-style table td.neiki-cell-selected,
|
|
821
|
+
.neiki-content-default-style table th.neiki-cell-selected {
|
|
822
822
|
background: var(--neiki-selection-bg);
|
|
823
823
|
}
|
|
824
824
|
|
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.6.0
|
|
4
4
|
*
|
|
5
5
|
* A lightweight, feature-rich text editor with support for:
|
|
6
6
|
* - Rich text formatting (bold, italic, underline, etc.)
|
|
@@ -2160,6 +2160,38 @@
|
|
|
2160
2160
|
} catch (e) { }
|
|
2161
2161
|
}
|
|
2162
2162
|
|
|
2163
|
+
removeByPrefix(prefix = '') {
|
|
2164
|
+
return StorageManager.removeByPrefix(this.prefix + prefix);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
static removeKey(key) {
|
|
2168
|
+
try {
|
|
2169
|
+
localStorage.removeItem(key);
|
|
2170
|
+
return true;
|
|
2171
|
+
} catch (e) {
|
|
2172
|
+
return false;
|
|
2173
|
+
}
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
static removeByPrefix(prefix) {
|
|
2177
|
+
if (typeof prefix !== 'string') return 0;
|
|
2178
|
+
|
|
2179
|
+
try {
|
|
2180
|
+
const keys = [];
|
|
2181
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
2182
|
+
const key = localStorage.key(i);
|
|
2183
|
+
if (key && key.indexOf(prefix) === 0) {
|
|
2184
|
+
keys.push(key);
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
keys.forEach(key => localStorage.removeItem(key));
|
|
2189
|
+
return keys.length;
|
|
2190
|
+
} catch (e) {
|
|
2191
|
+
return 0;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2163
2195
|
// Global storage (shared across all editors)
|
|
2164
2196
|
static setGlobal(key, value) {
|
|
2165
2197
|
try {
|
|
@@ -3082,7 +3114,7 @@
|
|
|
3082
3114
|
<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;">
|
|
3083
3115
|
<div style="font-size: 14px; line-height: 2; color: var(--neiki-text-primary);">
|
|
3084
3116
|
<div><strong>${Utils.escapeHTML(t('help.author'))}:</strong> neikiri (Jindřich Stoklasa)</div>
|
|
3085
|
-
<div><strong>${Utils.escapeHTML(t('help.version'))}:</strong> 3.
|
|
3117
|
+
<div><strong>${Utils.escapeHTML(t('help.version'))}:</strong> 3.6.0</div>
|
|
3086
3118
|
<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>
|
|
3087
3119
|
<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>
|
|
3088
3120
|
</div>
|
|
@@ -3868,6 +3900,24 @@
|
|
|
3868
3900
|
if (!Utils.isSafeUrl(url)) return;
|
|
3869
3901
|
const selection = Utils.getSelection();
|
|
3870
3902
|
const range = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
|
|
3903
|
+
const existingLink = this.editor.getLinkAtSelection();
|
|
3904
|
+
|
|
3905
|
+
if (existingLink) {
|
|
3906
|
+
existingLink.setAttribute('href', url);
|
|
3907
|
+
existingLink.textContent = text || url;
|
|
3908
|
+
if (newTab) {
|
|
3909
|
+
existingLink.setAttribute('target', '_blank');
|
|
3910
|
+
existingLink.setAttribute('rel', 'noopener noreferrer');
|
|
3911
|
+
} else {
|
|
3912
|
+
existingLink.removeAttribute('target');
|
|
3913
|
+
existingLink.removeAttribute('rel');
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
this.editor.history.record();
|
|
3917
|
+
this.editor.syncToOriginal();
|
|
3918
|
+
this.editor.triggerChange();
|
|
3919
|
+
return;
|
|
3920
|
+
}
|
|
3871
3921
|
|
|
3872
3922
|
if (range && !range.collapsed) {
|
|
3873
3923
|
this.exec('createLink', url);
|
|
@@ -4467,7 +4517,7 @@
|
|
|
4467
4517
|
const insertItems = [
|
|
4468
4518
|
{
|
|
4469
4519
|
key: 'link', icon: Icons.link, labelKey: 'insert.link',
|
|
4470
|
-
action: () => { this.saveCurrentSelection(); this.modal.open('link',
|
|
4520
|
+
action: () => { this.saveCurrentSelection(); this.modal.open('link', this.getLinkModalData()); }
|
|
4471
4521
|
},
|
|
4472
4522
|
{
|
|
4473
4523
|
key: 'image', icon: Icons.image, labelKey: 'insert.image',
|
|
@@ -4670,7 +4720,7 @@
|
|
|
4670
4720
|
|
|
4671
4721
|
const contentClasses = this.config.customClass
|
|
4672
4722
|
? `neiki-content ${this.config.customClass}`
|
|
4673
|
-
: 'neiki-content';
|
|
4723
|
+
: 'neiki-content neiki-content-default-style';
|
|
4674
4724
|
|
|
4675
4725
|
this.contentArea = Utils.createElement('div', {
|
|
4676
4726
|
className: contentClasses,
|
|
@@ -4847,8 +4897,7 @@
|
|
|
4847
4897
|
let data = {};
|
|
4848
4898
|
|
|
4849
4899
|
if (item === 'link') {
|
|
4850
|
-
|
|
4851
|
-
data.text = sel.toString();
|
|
4900
|
+
data = this.getLinkModalData();
|
|
4852
4901
|
}
|
|
4853
4902
|
|
|
4854
4903
|
this.modal.open(item, data);
|
|
@@ -4878,6 +4927,13 @@
|
|
|
4878
4927
|
}
|
|
4879
4928
|
|
|
4880
4929
|
handleKeydown(e) {
|
|
4930
|
+
if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') &&
|
|
4931
|
+
this.imageResizer &&
|
|
4932
|
+
this.imageResizer.moveCaretFromSelectedImage(e.key === 'ArrowUp' ? 'before' : 'after')) {
|
|
4933
|
+
e.preventDefault();
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4936
|
+
|
|
4881
4937
|
if ((e.key === 'Backspace' || e.key === 'Delete') &&
|
|
4882
4938
|
this.imageResizer &&
|
|
4883
4939
|
this.imageResizer.deleteSelectedImage()) {
|
|
@@ -4903,7 +4959,7 @@
|
|
|
4903
4959
|
case 'k':
|
|
4904
4960
|
e.preventDefault();
|
|
4905
4961
|
this.saveCurrentSelection();
|
|
4906
|
-
this.modal.open('link',
|
|
4962
|
+
this.modal.open('link', this.getLinkModalData());
|
|
4907
4963
|
break;
|
|
4908
4964
|
case 'z':
|
|
4909
4965
|
e.preventDefault();
|
|
@@ -4965,6 +5021,37 @@
|
|
|
4965
5021
|
}
|
|
4966
5022
|
}
|
|
4967
5023
|
|
|
5024
|
+
getLinkAtSelection() {
|
|
5025
|
+
const selection = window.getSelection();
|
|
5026
|
+
if (!selection || !selection.rangeCount) return null;
|
|
5027
|
+
|
|
5028
|
+
const range = selection.getRangeAt(0);
|
|
5029
|
+
if (!Utils.isRangeInside(range, this.contentArea)) return null;
|
|
5030
|
+
|
|
5031
|
+
const getClosestLink = (node) => {
|
|
5032
|
+
const element = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
|
|
5033
|
+
const link = element && element.closest ? element.closest('a') : null;
|
|
5034
|
+
return link && this.contentArea.contains(link) ? link : null;
|
|
5035
|
+
};
|
|
5036
|
+
|
|
5037
|
+
const startLink = getClosestLink(range.startContainer);
|
|
5038
|
+
const endLink = getClosestLink(range.endContainer);
|
|
5039
|
+
return startLink && startLink === endLink ? startLink : null;
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
getLinkModalData() {
|
|
5043
|
+
const link = this.getLinkAtSelection();
|
|
5044
|
+
if (link) {
|
|
5045
|
+
return {
|
|
5046
|
+
url: link.getAttribute('href') || '',
|
|
5047
|
+
text: link.textContent || '',
|
|
5048
|
+
newTab: link.getAttribute('target') === '_blank'
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5052
|
+
return { text: Utils.getSelection().toString() };
|
|
5053
|
+
}
|
|
5054
|
+
|
|
4968
5055
|
handlePaste(e) {
|
|
4969
5056
|
// Check for pasted images first
|
|
4970
5057
|
if (e.clipboardData && e.clipboardData.files.length > 0) {
|
|
@@ -5216,6 +5303,30 @@
|
|
|
5216
5303
|
this.updateStatusBar();
|
|
5217
5304
|
}
|
|
5218
5305
|
|
|
5306
|
+
removeStorage(key) {
|
|
5307
|
+
this.storage.remove(key);
|
|
5308
|
+
}
|
|
5309
|
+
|
|
5310
|
+
removeStorageByPrefix(prefix = '') {
|
|
5311
|
+
return this.storage.removeByPrefix(prefix);
|
|
5312
|
+
}
|
|
5313
|
+
|
|
5314
|
+
clearAutosaveStorage() {
|
|
5315
|
+
this.isAutosaveEnabled = false;
|
|
5316
|
+
if (this.autosaveInterval) {
|
|
5317
|
+
clearInterval(this.autosaveInterval);
|
|
5318
|
+
this.autosaveInterval = null;
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5321
|
+
this.storage.remove('autosave_enabled');
|
|
5322
|
+
this.storage.remove('autosave_content');
|
|
5323
|
+
|
|
5324
|
+
if (this.statusAutosave) {
|
|
5325
|
+
this.statusAutosave.style.display = 'none';
|
|
5326
|
+
}
|
|
5327
|
+
this.updateToolbar();
|
|
5328
|
+
}
|
|
5329
|
+
|
|
5219
5330
|
_ensureDefaultBlock() {
|
|
5220
5331
|
// Ensure the content area always has at least one <p> block
|
|
5221
5332
|
if (!this.contentArea) return;
|
|
@@ -6257,6 +6368,9 @@
|
|
|
6257
6368
|
this.currentImg = img;
|
|
6258
6369
|
const isVideo = img.tagName === 'VIDEO';
|
|
6259
6370
|
|
|
6371
|
+
// Keep keyboard events in the editor while the media selection is active.
|
|
6372
|
+
this.editor.contentArea.focus({ preventScroll: true });
|
|
6373
|
+
|
|
6260
6374
|
// Create wrapper around image
|
|
6261
6375
|
this.wrapper = document.createElement('span');
|
|
6262
6376
|
this.wrapper.className = 'neiki-img-resizable';
|
|
@@ -6589,6 +6703,30 @@
|
|
|
6589
6703
|
if (sel) sel.removeAllRanges();
|
|
6590
6704
|
}
|
|
6591
6705
|
|
|
6706
|
+
moveCaretFromSelectedImage(position) {
|
|
6707
|
+
if (!this.currentImg || !this.wrapper || !this.editor.contentArea.contains(this.wrapper)) {
|
|
6708
|
+
return false;
|
|
6709
|
+
}
|
|
6710
|
+
|
|
6711
|
+
const img = this.currentImg;
|
|
6712
|
+
this.deselect();
|
|
6713
|
+
|
|
6714
|
+
const range = document.createRange();
|
|
6715
|
+
if (position === 'before') {
|
|
6716
|
+
range.setStartBefore(img);
|
|
6717
|
+
} else {
|
|
6718
|
+
range.setStartAfter(img);
|
|
6719
|
+
}
|
|
6720
|
+
range.collapse(true);
|
|
6721
|
+
|
|
6722
|
+
const selection = window.getSelection();
|
|
6723
|
+
selection.removeAllRanges();
|
|
6724
|
+
selection.addRange(range);
|
|
6725
|
+
this.editor.savedSelectionRange = range.cloneRange();
|
|
6726
|
+
this.editor.updateToolbar();
|
|
6727
|
+
return true;
|
|
6728
|
+
}
|
|
6729
|
+
|
|
6592
6730
|
getSelectedImageClipboardData() {
|
|
6593
6731
|
if (!this.currentImg || !this.wrapper || !this.editor.contentArea.contains(this.wrapper)) {
|
|
6594
6732
|
return null;
|
|
@@ -7048,7 +7186,9 @@
|
|
|
7048
7186
|
if (prev && prev.parentNode === this.editor.contentArea) {
|
|
7049
7187
|
prev.parentNode.insertBefore(block, prev);
|
|
7050
7188
|
this.editor.history.record();
|
|
7189
|
+
this.editor.syncToOriginal();
|
|
7051
7190
|
this.editor.triggerChange();
|
|
7191
|
+
this.editor.updateStatusBar();
|
|
7052
7192
|
}
|
|
7053
7193
|
}
|
|
7054
7194
|
|
|
@@ -7059,7 +7199,9 @@
|
|
|
7059
7199
|
if (next && next.parentNode === this.editor.contentArea) {
|
|
7060
7200
|
next.parentNode.insertBefore(block, next.nextSibling);
|
|
7061
7201
|
this.editor.history.record();
|
|
7202
|
+
this.editor.syncToOriginal();
|
|
7062
7203
|
this.editor.triggerChange();
|
|
7204
|
+
this.editor.updateStatusBar();
|
|
7063
7205
|
}
|
|
7064
7206
|
}
|
|
7065
7207
|
|
|
@@ -7225,8 +7367,7 @@
|
|
|
7225
7367
|
|
|
7226
7368
|
handleButtonClick(item) {
|
|
7227
7369
|
if (item === 'link') {
|
|
7228
|
-
|
|
7229
|
-
this.editor.modal.open('link', { text: sel.toString() });
|
|
7370
|
+
this.editor.modal.open('link', this.editor.getLinkModalData());
|
|
7230
7371
|
} else if (item === 'moveUp') {
|
|
7231
7372
|
if (this.editor.blockDragDrop) this.editor.blockDragDrop.moveBlockUp();
|
|
7232
7373
|
} else if (item === 'moveDown') {
|
|
@@ -7676,6 +7817,11 @@
|
|
|
7676
7817
|
|
|
7677
7818
|
// Static methods
|
|
7678
7819
|
NeikiEditor.addTranslation = addTranslation;
|
|
7820
|
+
NeikiEditor.removeStorageKey = StorageManager.removeKey;
|
|
7821
|
+
NeikiEditor.removeStorageByPrefix = StorageManager.removeByPrefix;
|
|
7822
|
+
NeikiEditor.clearAutosaveStorage = function (prefix = 'neiki_autosave_') {
|
|
7823
|
+
return StorageManager.removeByPrefix(prefix);
|
|
7824
|
+
};
|
|
7679
7825
|
|
|
7680
7826
|
// Export
|
|
7681
7827
|
global.NeikiEditor = NeikiEditor;
|