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 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.4.0-2563EB?style=for-the-badge&logo=semantic-release&logoColor=white&labelColor=000F15&logoWidth=20" alt="Version">
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.4.0/neiki-editor.min.js"></script>
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.4.0/neiki-editor.css">
155
- <script src="https://cdn.neikiri.dev/neiki-editor/3.4.0/neiki-editor.js"></script>
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.4.0/dist/neiki-editor.min.js"></script>
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` | Extra CSS class appended to the content area (`neiki-content`) |
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
- Use `customClass` to add your own class to the content area without overriding the defaults:
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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * NeikiEditor - Production-Ready WYSIWYG Rich Text Editor
3
3
  * CSS Stylesheet
4
- * Version: 3.4.0
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
 
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * NeikiEditor - A Modern WYSIWYG Editor
3
- * Version: 3.4.0
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.4.0</div>
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', { text: Utils.getSelection().toString() }); }
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
- const sel = Utils.getSelection();
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', { text: Utils.getSelection().toString() });
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
- const sel = Utils.getSelection();
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;