@akilli/editor-src 5.2.7 → 5.3.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/base/Base.js CHANGED
@@ -16,8 +16,8 @@ import SortableListener from './SortableListener.js';
16
16
  import Sorting from './Sorting.js';
17
17
  import TagGroup from './TagGroup.js';
18
18
  import TagListener from './TagListener.js';
19
- import ToolbarListener from './ToolbarListener.js';
20
19
  import TagName from './TagName.js';
20
+ import ToolbarListener from './ToolbarListener.js';
21
21
 
22
22
  export default class Base extends Plugin {
23
23
  /**
package/base/Dom.js CHANGED
@@ -116,6 +116,13 @@ export default class Dom {
116
116
  return element;
117
117
  }
118
118
 
119
+ /**
120
+ * @return {DocumentFragment}
121
+ */
122
+ createFragment() {
123
+ return this.document.createDocumentFragment();
124
+ }
125
+
119
126
  /**
120
127
  * Inserts element
121
128
  *
@@ -154,14 +161,50 @@ export default class Dom {
154
161
  */
155
162
  insertText(text) {
156
163
  const editable = this.getSelectedEditable();
164
+ const range = this.getRange();
165
+
166
+ if (!editable || !range || !text) {
167
+ return;
168
+ }
169
+
170
+ range.deleteContents();
171
+ range.insertNode(this.createText(text));
172
+ range.collapse();
173
+ editable.normalize();
174
+ }
175
+
176
+ /**
177
+ * @param {string} html
178
+ * @return {void}
179
+ */
180
+ insertHtml(html) {
181
+ const editable = this.getSelectedEditable();
182
+ const range = this.getRange();
183
+
184
+ if (!editable || !range || !html) {
185
+ return;
186
+ }
187
+
188
+ const element = this.createElement(editable.localName, { html });
189
+ this.editor.filters.filter(element);
190
+ const fragment = this.createFragment();
157
191
 
158
- if (editable) {
159
- const range = this.getRange();
160
- range.deleteContents();
161
- range.insertNode(this.createText(text));
162
- range.collapse();
163
- editable.normalize();
192
+ while (element.firstChild) {
193
+ fragment.appendChild(element.firstChild);
164
194
  }
195
+
196
+ range.deleteContents();
197
+ range.insertNode(fragment);
198
+
199
+ const lastChild = fragment.lastChild;
200
+
201
+ if (lastChild) {
202
+ range.setStartAfter(lastChild);
203
+ range.setEndAfter(lastChild);
204
+ this.setRange(range);
205
+ }
206
+
207
+ editable.normalize();
165
208
  }
166
209
 
167
210
  /**
@@ -502,7 +545,7 @@ export default class Dom {
502
545
  return true;
503
546
  }
504
547
 
505
- const root = element.closest(this.editor.root.localName);
548
+ const root = element.closest(TagName.ROOT);
506
549
 
507
550
  return root && !root.parentElement;
508
551
  }
@@ -9,6 +9,7 @@ export default class EditableListener extends Listener {
9
9
  constructor(editor) {
10
10
  super(editor);
11
11
  this.editor.root.addEventListener('insert', this);
12
+ this.editor.root.addEventListener('beforeinput', this);
12
13
  }
13
14
 
14
15
  /**
@@ -79,4 +80,27 @@ export default class EditableListener extends Listener {
79
80
  this.editor.dom.selectContents(target);
80
81
  this.editor.commands.findByTagName(target.localName)?.execute();
81
82
  }
83
+
84
+ /**
85
+ * @param {InputEvent} event
86
+ * @return {void}
87
+ */
88
+ beforeinput(event) {
89
+ if (event.inputType !== 'insertFromPaste' || !event.cancelable) {
90
+ return;
91
+ }
92
+
93
+ const html = event.dataTransfer?.getData('text/html');
94
+ const text = event.dataTransfer?.getData('text/plain');
95
+
96
+ if (html) {
97
+ event.preventDefault();
98
+ event.stopPropagation();
99
+ this.editor.dom.insertHtml(html);
100
+ } else if (text) {
101
+ event.preventDefault();
102
+ event.stopPropagation();
103
+ this.editor.dom.insertText(text);
104
+ }
105
+ }
82
106
  }
package/base/Editor.js CHANGED
@@ -373,7 +373,7 @@ export default class Editor {
373
373
  * @return {string}
374
374
  */
375
375
  getHtml() {
376
- const root = this.dom.createElement(this.root.localName, { html: this.root.innerHTML });
376
+ const root = this.dom.createElement(TagName.ROOT, { html: this.root.innerHTML });
377
377
  this.filters.filter(root);
378
378
  this.rootDispatcher.dispatch('gethtml', root);
379
379
 
@@ -387,7 +387,7 @@ export default class Editor {
387
387
  * @return {void}
388
388
  */
389
389
  setHtml(html) {
390
- const root = this.dom.createElement(this.root.localName, { html });
390
+ const root = this.dom.createElement(TagName.ROOT, { html });
391
391
  this.rootDispatcher.dispatch('sethtml', root);
392
392
  this.filters.filter(root);
393
393
  this.root.innerHTML = root.innerHTML;
package/base/Listener.js CHANGED
@@ -3,7 +3,8 @@ import { isFunction } from './util.js';
3
3
 
4
4
  /**
5
5
  * @abstract
6
- * @implements EventListener
6
+ * @implements {EventListener}
7
+ * @borrows EventListener#handleEvent as handleEvent
7
8
  */
8
9
  export default class Listener {
9
10
  /**
@@ -19,7 +20,6 @@ export default class Listener {
19
20
  }
20
21
 
21
22
  /**
22
- * @borrows handleEvent
23
23
  * @param {Editor} editor
24
24
  */
25
25
  constructor(editor) {
@@ -3,6 +3,7 @@ import DetailsDialog from './DetailsDialog.js';
3
3
  import DetailsFilter from './DetailsFilter.js';
4
4
  import DetailsListener from './DetailsListener.js';
5
5
  import Plugin from '../base/Plugin.js';
6
+ import SummaryListener from './SummaryListener.js';
6
7
  import TagGroup from '../base/TagGroup.js';
7
8
  import TagName from '../base/TagName.js';
8
9
 
@@ -58,6 +59,7 @@ export default class Details extends Plugin {
58
59
  enter: TagName.P,
59
60
  });
60
61
  new DetailsListener(this.editor);
62
+ new SummaryListener(this.editor);
61
63
  this.editor.dialogs.set(new DetailsDialog(this.editor, this.constructor.name));
62
64
  this._command(TagName.DETAILS);
63
65
  this._toolbar('Details');
@@ -1,4 +1,3 @@
1
- import Key, { isKey } from '../base/Key.js';
2
1
  import Listener from '../base/Listener.js';
3
2
  import TagName from '../base/TagName.js';
4
3
 
@@ -9,7 +8,6 @@ export default class DetailsListener extends Listener {
9
8
  constructor(editor) {
10
9
  super(editor);
11
10
  this.editor.root.addEventListener('insertdetails', this);
12
- this.editor.root.addEventListener('insertsummary', this);
13
11
  }
14
12
 
15
13
  /**
@@ -24,79 +22,4 @@ export default class DetailsListener extends Listener {
24
22
  this.editor.dom.insertFirstChild(this.editor.dom.createElement(TagName.SUMMARY), element);
25
23
  }
26
24
  }
27
-
28
- /**
29
- * @param {CustomEvent} event
30
- * @param {HTMLElement} event.detail.element
31
- * @return {void}
32
- */
33
- insertsummary({ detail: { element } }) {
34
- this.#empty(element);
35
- element.addEventListener('click', this);
36
- element.addEventListener('blur', this);
37
- element.addEventListener('keydown', this);
38
- }
39
-
40
- /**
41
- * Prevents toggling details open state
42
- *
43
- * @param {MouseEvent} event
44
- * @param {HTMLElement} event.target
45
- * @return {void}
46
- */
47
- click(event) {
48
- event.preventDefault();
49
- event.stopPropagation();
50
- event.target.parentElement.open = true;
51
- }
52
-
53
- /**
54
- * Sets default summary text content if it's empty
55
- *
56
- * @param {FocusEvent} event
57
- * @param {HTMLElement} event.target
58
- * @return {void}
59
- */
60
- blur({ target }) {
61
- this.#empty(target);
62
- }
63
-
64
- /**
65
- * Fixes space and enter key handling for editable summary elements
66
- *
67
- * @param {KeyboardEvent} event
68
- * @param {HTMLElement} event.target
69
- * @return {void}
70
- */
71
- keydown(event) {
72
- if (isKey(event, Key.SPACE)) {
73
- event.preventDefault();
74
- event.stopPropagation();
75
- this.editor.dom.insertText(' ');
76
- } else if (isKey(event, Key.ENTER)) {
77
- event.preventDefault();
78
- event.stopPropagation();
79
- event.target.parentElement.open = true;
80
- }
81
- }
82
-
83
- /**
84
- * Ensures summary element is not empty to avoid strange browser behaviour
85
- *
86
- * @param {HTMLElement} element
87
- * @return {void}
88
- */
89
- #empty(element) {
90
- if (!element.textContent.trim()) {
91
- element.textContent = this._('Details');
92
- } else {
93
- element
94
- .querySelectorAll(TagName.BR + ':not(:last-child)')
95
- .forEach((item) => item.parentElement.removeChild(item));
96
- }
97
-
98
- if (!(element.lastElementChild instanceof HTMLBRElement)) {
99
- this.editor.dom.insertLastChild(this.editor.dom.createElement(TagName.BR), element);
100
- }
101
- }
102
25
  }
@@ -0,0 +1,88 @@
1
+ import Key, { isKey } from '../base/Key.js';
2
+ import Listener from '../base/Listener.js';
3
+ import TagName from '../base/TagName.js';
4
+
5
+ export default class SummaryListener extends Listener {
6
+ /**
7
+ * @param {Editor} editor
8
+ */
9
+ constructor(editor) {
10
+ super(editor);
11
+ this.editor.root.addEventListener('insertsummary', this);
12
+ }
13
+
14
+ /**
15
+ * @param {CustomEvent} event
16
+ * @param {HTMLElement} event.detail.element
17
+ * @return {void}
18
+ */
19
+ insertsummary({ detail: { element } }) {
20
+ this.#empty(element);
21
+ element.addEventListener('click', this);
22
+ element.addEventListener('blur', this);
23
+ element.addEventListener('keydown', this);
24
+ }
25
+
26
+ /**
27
+ * Prevents toggling details open state
28
+ *
29
+ * @param {MouseEvent} event
30
+ * @param {HTMLElement} event.target
31
+ * @return {void}
32
+ */
33
+ click(event) {
34
+ event.preventDefault();
35
+ event.stopPropagation();
36
+ event.target.parentElement.open = true;
37
+ }
38
+
39
+ /**
40
+ * Sets default summary text content if it's empty
41
+ *
42
+ * @param {FocusEvent} event
43
+ * @param {HTMLElement} event.target
44
+ * @return {void}
45
+ */
46
+ blur({ target }) {
47
+ this.#empty(target);
48
+ }
49
+
50
+ /**
51
+ * Fixes space and enter key handling for editable summary elements
52
+ *
53
+ * @param {KeyboardEvent} event
54
+ * @param {HTMLElement} event.target
55
+ * @return {void}
56
+ */
57
+ keydown(event) {
58
+ if (isKey(event, Key.SPACE)) {
59
+ event.preventDefault();
60
+ event.stopPropagation();
61
+ this.editor.dom.insertText(' ');
62
+ } else if (isKey(event, Key.ENTER)) {
63
+ event.preventDefault();
64
+ event.stopPropagation();
65
+ event.target.parentElement.open = true;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Ensures summary element is not empty to avoid strange browser behaviour
71
+ *
72
+ * @param {HTMLElement} element
73
+ * @return {void}
74
+ */
75
+ #empty(element) {
76
+ if (!element.textContent.trim()) {
77
+ element.textContent = this._('Details');
78
+ } else {
79
+ element
80
+ .querySelectorAll(TagName.BR + ':not(:last-child)')
81
+ .forEach((item) => item.parentElement.removeChild(item));
82
+ }
83
+
84
+ if (!(element.lastElementChild instanceof HTMLBRElement)) {
85
+ this.editor.dom.insertLastChild(this.editor.dom.createElement(TagName.BR), element);
86
+ }
87
+ }
88
+ }
package/license.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2024 Ayhan Akilli
1
+ Copyright (c) 2026 Ayhan Akilli
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
4
4
  documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akilli/editor-src",
3
- "version": "5.2.7",
3
+ "version": "5.3.0",
4
4
  "description": "Source files for @akilli/editor",
5
5
  "keywords": [
6
6
  "contenteditable",
@@ -1,6 +1,5 @@
1
1
  import Base from '../base/Base.js';
2
2
  import Break from '../break/Break.js';
3
- import ParagraphListener from './ParagraphListener.js';
4
3
  import Plugin from '../base/Plugin.js';
5
4
  import TagGroup from '../base/TagGroup.js';
6
5
  import TagName from '../base/TagName.js';
@@ -35,7 +34,6 @@ export default class Paragraph extends Plugin {
35
34
  sortable: true,
36
35
  enter: TagName.P,
37
36
  });
38
- new ParagraphListener(this.editor);
39
37
  this._command(TagName.P);
40
38
  this._toolbar('Paragraph');
41
39
  }
@@ -1,40 +0,0 @@
1
- import Listener from '../base/Listener.js';
2
- import TagName from '../base/TagName.js';
3
-
4
- export default class ParagraphListener extends Listener {
5
- /**
6
- * @param {Editor} editor
7
- */
8
- constructor(editor) {
9
- super(editor);
10
- this.editor.root.addEventListener('sethtml', this);
11
- this.editor.root.addEventListener('insertp', this);
12
- }
13
-
14
- /**
15
- * @param {CustomEvent} event
16
- * @param {HTMLElement} event.detail.element
17
- * @return {void}
18
- */
19
- sethtml({ detail: { element } }) {
20
- Array.from(element.getElementsByTagName(TagName.P)).forEach((item) => item.addEventListener('paste', this));
21
- }
22
-
23
- /**
24
- * @param {CustomEvent} event
25
- * @param {HTMLParagraphElement} event.detail.element
26
- * @return {void}
27
- */
28
- insertp({ detail: { element } }) {
29
- element.addEventListener('paste', this);
30
- }
31
-
32
- /**
33
- * @param {ClipboardEvent} event
34
- * @param {HTMLParagraphElement} event.target
35
- * @return {void}
36
- */
37
- paste({ target }) {
38
- this.editor.dom.window.setTimeout(() => this.editor.filters.filter(target));
39
- }
40
- }