@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 +1 -1
- package/base/Dom.js +50 -7
- package/base/EditableListener.js +24 -0
- package/base/Editor.js +2 -2
- package/base/Listener.js +2 -2
- package/details/Details.js +2 -0
- package/details/DetailsListener.js +0 -77
- package/details/SummaryListener.js +88 -0
- package/license.md +1 -1
- package/package.json +1 -1
- package/paragraph/Paragraph.js +0 -2
- package/paragraph/ParagraphListener.js +0 -40
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
|
-
|
|
159
|
-
|
|
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(
|
|
548
|
+
const root = element.closest(TagName.ROOT);
|
|
506
549
|
|
|
507
550
|
return root && !root.parentElement;
|
|
508
551
|
}
|
package/base/EditableListener.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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) {
|
package/details/Details.js
CHANGED
|
@@ -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)
|
|
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
package/paragraph/Paragraph.js
CHANGED
|
@@ -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
|
-
}
|