@akilli/editor-src 5.1.5
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/abbreviation/Abbreviation.js +32 -0
- package/abbreviation/AbbreviationDialog.js +21 -0
- package/audio/Audio.js +47 -0
- package/audio/AudioDialog.js +18 -0
- package/audio/AudioListener.js +50 -0
- package/base/AlignCommand.js +34 -0
- package/base/AlignableListener.js +45 -0
- package/base/Alignment.js +36 -0
- package/base/BarListener.js +95 -0
- package/base/Base.js +127 -0
- package/base/Command.js +139 -0
- package/base/CommandManager.js +60 -0
- package/base/ContentFilter.js +109 -0
- package/base/DeletableListener.js +36 -0
- package/base/DeleteCommand.js +18 -0
- package/base/Dialog.js +153 -0
- package/base/DialogManager.js +44 -0
- package/base/Dispatcher.js +88 -0
- package/base/Dom.js +790 -0
- package/base/EditableListener.js +82 -0
- package/base/Editor.js +448 -0
- package/base/Filter.js +35 -0
- package/base/FilterManager.js +44 -0
- package/base/FocusableListener.js +22 -0
- package/base/FocusbarListener.js +99 -0
- package/base/FormCreator.js +162 -0
- package/base/FormatbarListener.js +32 -0
- package/base/Key.js +258 -0
- package/base/Listener.js +51 -0
- package/base/NavigableListener.js +81 -0
- package/base/Plugin.js +176 -0
- package/base/PluginManager.js +51 -0
- package/base/SlotableListener.js +40 -0
- package/base/SortCommand.js +30 -0
- package/base/SortableListener.js +135 -0
- package/base/Sorting.js +36 -0
- package/base/Tag.js +113 -0
- package/base/TagGroup.js +183 -0
- package/base/TagListener.js +34 -0
- package/base/TagManager.js +61 -0
- package/base/TagName.js +470 -0
- package/base/ToolbarListener.js +11 -0
- package/base/util.js +59 -0
- package/block/Block.js +51 -0
- package/block/BlockDialog.js +11 -0
- package/block/BlockElement.js +21 -0
- package/block/BlockListener.js +60 -0
- package/blockquote/Blockquote.js +43 -0
- package/blockquote/BlockquoteFilter.js +22 -0
- package/blockquote/BlockquoteListener.js +34 -0
- package/bold/Bold.js +30 -0
- package/break/Break.js +33 -0
- package/break/BreakFilter.js +24 -0
- package/build/BuildEditor.js +97 -0
- package/build/editor.css +548 -0
- package/build/editor.woff2 +0 -0
- package/cite/Cite.js +30 -0
- package/code/Code.js +30 -0
- package/data/Data.js +32 -0
- package/data/DataDialog.js +13 -0
- package/definition/Definition.js +32 -0
- package/definition/DefinitionDialog.js +13 -0
- package/deletion/Deletion.js +30 -0
- package/details/Details.js +63 -0
- package/details/DetailsFilter.js +17 -0
- package/details/DetailsListener.js +102 -0
- package/division/Division.js +53 -0
- package/division/DivisionDialog.js +13 -0
- package/emphasis/Emphasis.js +30 -0
- package/figure/Figure.js +58 -0
- package/figure/FigureFilter.js +14 -0
- package/figure/FigureListener.js +23 -0
- package/heading/Heading.js +38 -0
- package/horizontalrule/HorizontalRule.js +37 -0
- package/i18n/I18n.js +26 -0
- package/i18n/de.js +167 -0
- package/iframe/Iframe.js +49 -0
- package/iframe/IframeDialog.js +20 -0
- package/iframe/IframeListener.js +48 -0
- package/image/Image.js +47 -0
- package/image/ImageDialog.js +23 -0
- package/image/ImageListener.js +47 -0
- package/insertion/Insertion.js +30 -0
- package/italic/Italic.js +30 -0
- package/keyboard/Keyboard.js +30 -0
- package/link/Link.js +34 -0
- package/link/LinkDialog.js +14 -0
- package/link/LinkListener.js +45 -0
- package/list/List.js +40 -0
- package/list/ListListener.js +91 -0
- package/mark/Mark.js +30 -0
- package/orderedlist/OrderedList.js +39 -0
- package/package.json +24 -0
- package/paragraph/Paragraph.js +42 -0
- package/paragraph/ParagraphListener.js +40 -0
- package/preformat/Preformat.js +43 -0
- package/preformat/PreformatFilter.js +22 -0
- package/preformat/PreformatListener.js +34 -0
- package/quote/Quote.js +30 -0
- package/sample/Sample.js +30 -0
- package/section/Section.js +55 -0
- package/section/SectionDialog.js +13 -0
- package/small/Small.js +30 -0
- package/strikethrough/Strikethrough.js +30 -0
- package/strong/Strong.js +30 -0
- package/subheading/Subheading.js +38 -0
- package/subscript/Subscript.js +30 -0
- package/superscript/Superscript.js +30 -0
- package/table/Table.js +113 -0
- package/table/TableCellListener.js +125 -0
- package/table/TableColumnAddCommand.js +19 -0
- package/table/TableColumnListener.js +34 -0
- package/table/TableCommand.js +23 -0
- package/table/TableDialog.js +14 -0
- package/table/TableFilter.js +35 -0
- package/table/TableListener.js +39 -0
- package/table/TableRowAddCommand.js +18 -0
- package/table/TableRowListener.js +34 -0
- package/time/Time.js +32 -0
- package/time/TimeDialog.js +13 -0
- package/underline/Underline.js +30 -0
- package/unorderedlist/UnorderedList.js +39 -0
- package/variable/Variable.js +30 -0
- package/video/Video.js +47 -0
- package/video/VideoDialog.js +20 -0
- package/video/VideoListener.js +48 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import Key from './Key.js';
|
|
2
|
+
import Listener from './Listener.js';
|
|
3
|
+
import TagName from './TagName.js';
|
|
4
|
+
|
|
5
|
+
export default class EditableListener extends Listener {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Editor} editor
|
|
8
|
+
*/
|
|
9
|
+
constructor(editor) {
|
|
10
|
+
super(editor);
|
|
11
|
+
this.editor.root.addEventListener('insert', this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {CustomEvent} event
|
|
16
|
+
* @param {HTMLElement} event.detail.element
|
|
17
|
+
* @return {void}
|
|
18
|
+
*/
|
|
19
|
+
insert({ detail: { element } }) {
|
|
20
|
+
if (element.contentEditable === 'true') {
|
|
21
|
+
element.addEventListener('keydown', this);
|
|
22
|
+
} else if (element.parentElement.contentEditable === 'true') {
|
|
23
|
+
element.addEventListener('dblclick', this);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {KeyboardEvent} event
|
|
29
|
+
* @param {HTMLElement} event.target
|
|
30
|
+
* @return {void}
|
|
31
|
+
*/
|
|
32
|
+
keydown(event) {
|
|
33
|
+
if (Key.isEventFor(event, Key.ENTER, { shift: true }) && !this.editor.tags.allowed(event.target, TagName.BR)) {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
event.stopPropagation();
|
|
36
|
+
} else if (Key.isEventFor(event, Key.ENTER)) {
|
|
37
|
+
event.preventDefault();
|
|
38
|
+
event.stopPropagation();
|
|
39
|
+
const enter = this.editor.tags.get(event.target)?.enter;
|
|
40
|
+
|
|
41
|
+
if (enter) {
|
|
42
|
+
if (event.target.textContent.trim() || !event.target.hasAttribute('data-deletable')) {
|
|
43
|
+
const sibling = this.editor.dom.closest(event.target, enter);
|
|
44
|
+
|
|
45
|
+
if (sibling) {
|
|
46
|
+
this.editor.dom.insertAfter(this.editor.dom.createElement(enter), sibling);
|
|
47
|
+
}
|
|
48
|
+
} else if (!(event.target instanceof HTMLParagraphElement)) {
|
|
49
|
+
const sibling = this.editor.dom.closest(event.target, TagName.P);
|
|
50
|
+
|
|
51
|
+
if (sibling) {
|
|
52
|
+
this.editor.dom.insertAfter(this.editor.dom.createElement(TagName.P), sibling);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
event.target.parentElement.removeChild(event.target);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} else if (
|
|
59
|
+
Key.isEventFor(event, Key.BACKSPACE) &&
|
|
60
|
+
!event.target.textContent &&
|
|
61
|
+
event.target.hasAttribute('data-deletable')
|
|
62
|
+
) {
|
|
63
|
+
this.editor.dom.delete(event.target);
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
event.stopPropagation();
|
|
66
|
+
} else if (/^[A-Z]$/.test(event.key) && Key.isEventFor(event, event.key, { alt: true, shift: true })) {
|
|
67
|
+
event.preventDefault();
|
|
68
|
+
event.stopPropagation();
|
|
69
|
+
this.editor.formatbar.querySelector(`${TagName.BUTTON}[data-key=${event.key.toLowerCase()}]`)?.click();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {MouseEvent} event
|
|
75
|
+
* @param {HTMLElement} event.target
|
|
76
|
+
* @return {void}
|
|
77
|
+
*/
|
|
78
|
+
dblclick({ target }) {
|
|
79
|
+
this.editor.dom.selectContents(target);
|
|
80
|
+
this.editor.commands.findByTagName(target.localName)?.execute();
|
|
81
|
+
}
|
|
82
|
+
}
|
package/base/Editor.js
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
import CommandManager from './CommandManager.js';
|
|
2
|
+
import DialogManager from './DialogManager.js';
|
|
3
|
+
import Dispatcher from './Dispatcher.js';
|
|
4
|
+
import Dom from './Dom.js';
|
|
5
|
+
import FilterManager from './FilterManager.js';
|
|
6
|
+
import PluginManager from './PluginManager.js';
|
|
7
|
+
import TagManager from './TagManager.js';
|
|
8
|
+
import TagName from './TagName.js';
|
|
9
|
+
|
|
10
|
+
export default class Editor {
|
|
11
|
+
/**
|
|
12
|
+
* @type {HTMLElement}
|
|
13
|
+
*/
|
|
14
|
+
#orig;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @return {HTMLElement}
|
|
18
|
+
*/
|
|
19
|
+
get orig() {
|
|
20
|
+
return this.#orig;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @type {Object}
|
|
25
|
+
*/
|
|
26
|
+
#config = {};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @return {Object}
|
|
30
|
+
*/
|
|
31
|
+
get config() {
|
|
32
|
+
return this.#config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @type {Dom}
|
|
37
|
+
*/
|
|
38
|
+
#dom;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @return {Dom}
|
|
42
|
+
*/
|
|
43
|
+
get dom() {
|
|
44
|
+
return this.#dom;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @type {HTMLElement}
|
|
49
|
+
*/
|
|
50
|
+
#element;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @return {HTMLElement}
|
|
54
|
+
*/
|
|
55
|
+
get element() {
|
|
56
|
+
return this.#element;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @type {HTMLElement}
|
|
61
|
+
*/
|
|
62
|
+
#toolbar;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @return {HTMLElement}
|
|
66
|
+
*/
|
|
67
|
+
get toolbar() {
|
|
68
|
+
return this.#toolbar;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @type {Dispatcher}
|
|
73
|
+
*/
|
|
74
|
+
#toolbarDispatcher;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @return {Dispatcher}
|
|
78
|
+
*/
|
|
79
|
+
get toolbarDispatcher() {
|
|
80
|
+
return this.#toolbarDispatcher;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @type {HTMLElement}
|
|
85
|
+
*/
|
|
86
|
+
#formatbar;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @return {HTMLElement}
|
|
90
|
+
*/
|
|
91
|
+
get formatbar() {
|
|
92
|
+
return this.#formatbar;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @type {Dispatcher}
|
|
97
|
+
*/
|
|
98
|
+
#formatbarDispatcher;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @return {Dispatcher}
|
|
102
|
+
*/
|
|
103
|
+
get formatbarDispatcher() {
|
|
104
|
+
return this.#formatbarDispatcher;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @type {HTMLElement}
|
|
109
|
+
*/
|
|
110
|
+
#focusbar;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @return {HTMLElement}
|
|
114
|
+
*/
|
|
115
|
+
get focusbar() {
|
|
116
|
+
return this.#focusbar;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @type {Dispatcher}
|
|
121
|
+
*/
|
|
122
|
+
#focusbarDispatcher;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @return {Dispatcher}
|
|
126
|
+
*/
|
|
127
|
+
get focusbarDispatcher() {
|
|
128
|
+
return this.#focusbarDispatcher;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @type {HTMLElement}
|
|
133
|
+
*/
|
|
134
|
+
#root;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @return {HTMLElement}
|
|
138
|
+
*/
|
|
139
|
+
get root() {
|
|
140
|
+
return this.#root;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @type {Dispatcher}
|
|
145
|
+
*/
|
|
146
|
+
#rootDispatcher;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @return {Dispatcher}
|
|
150
|
+
*/
|
|
151
|
+
get rootDispatcher() {
|
|
152
|
+
return this.#rootDispatcher;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @type {Object.<string, string>}
|
|
157
|
+
*/
|
|
158
|
+
#i18n = {};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @return {Object.<string, string>}
|
|
162
|
+
*/
|
|
163
|
+
get i18n() {
|
|
164
|
+
return this.#i18n;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @param {Object.<string, string>} i18n
|
|
169
|
+
* @return {void}
|
|
170
|
+
*/
|
|
171
|
+
set i18n(i18n) {
|
|
172
|
+
this.#i18n = { ...this.#i18n, ...(i18n || {}) };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @type {TagManager}
|
|
177
|
+
*/
|
|
178
|
+
#tags = new TagManager();
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @return {TagManager}
|
|
182
|
+
*/
|
|
183
|
+
get tags() {
|
|
184
|
+
return this.#tags;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @type {FilterManager}
|
|
189
|
+
*/
|
|
190
|
+
#filters = new FilterManager();
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* @return {FilterManager}
|
|
194
|
+
*/
|
|
195
|
+
get filters() {
|
|
196
|
+
return this.#filters;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @type {DialogManager}
|
|
201
|
+
*/
|
|
202
|
+
#dialogs = new DialogManager();
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* @type {DialogManager}
|
|
206
|
+
*/
|
|
207
|
+
get dialogs() {
|
|
208
|
+
return this.#dialogs;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @type {CommandManager}
|
|
213
|
+
*/
|
|
214
|
+
#commands = new CommandManager();
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* @return {CommandManager}
|
|
218
|
+
*/
|
|
219
|
+
get commands() {
|
|
220
|
+
return this.#commands;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @type {PluginManager}
|
|
225
|
+
*/
|
|
226
|
+
#plugins = new PluginManager();
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @return {PluginManager}
|
|
230
|
+
*/
|
|
231
|
+
get plugins() {
|
|
232
|
+
return this.#plugins;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @type {Object.<string, Object>}
|
|
237
|
+
*/
|
|
238
|
+
static get defaultConfig() {
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @param {HTMLElement} orig
|
|
244
|
+
* @param {Object} [config = {}]
|
|
245
|
+
*/
|
|
246
|
+
constructor(orig, config = {}) {
|
|
247
|
+
if (!(orig instanceof HTMLElement) || !(config instanceof Object)) {
|
|
248
|
+
throw new TypeError('Invalid argument');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.#orig = orig;
|
|
252
|
+
this.#config = config;
|
|
253
|
+
this.#dom = new Dom(this, this.orig.ownerDocument);
|
|
254
|
+
this.#element = this.dom.createElement(TagName.EDITOR);
|
|
255
|
+
|
|
256
|
+
this.#toolbar = this.dom.createElement(TagName.TOOLBAR, { attributes: { role: 'toolbar' } });
|
|
257
|
+
this.dom.insertLastChild(this.toolbar, this.element);
|
|
258
|
+
this.#toolbarDispatcher = new Dispatcher(this.toolbar);
|
|
259
|
+
|
|
260
|
+
this.#formatbar = this.dom.createElement(TagName.FORMATBAR, { attributes: { role: 'toolbar' } });
|
|
261
|
+
this.formatbar.hidden = true;
|
|
262
|
+
this.dom.insertLastChild(this.formatbar, this.element);
|
|
263
|
+
this.#formatbarDispatcher = new Dispatcher(this.formatbar);
|
|
264
|
+
|
|
265
|
+
this.#focusbar = this.dom.createElement(TagName.FOCUSBAR, { attributes: { role: 'toolbar' } });
|
|
266
|
+
this.focusbar.hidden = true;
|
|
267
|
+
this.dom.insertLastChild(this.focusbar, this.element);
|
|
268
|
+
this.#focusbarDispatcher = new Dispatcher(this.focusbar);
|
|
269
|
+
|
|
270
|
+
this.#root = this.dom.createElement(TagName.ROOT);
|
|
271
|
+
this.dom.insertLastChild(this.root, this.element);
|
|
272
|
+
this.#rootDispatcher = new Dispatcher(this.root);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Initializes plugins and configuration
|
|
277
|
+
*
|
|
278
|
+
* @return {void}
|
|
279
|
+
*/
|
|
280
|
+
init() {
|
|
281
|
+
const config = this.config;
|
|
282
|
+
this.#config = {};
|
|
283
|
+
const builtin = this.constructor.defaultConfig.base?.plugins || [];
|
|
284
|
+
let configured = builtin;
|
|
285
|
+
|
|
286
|
+
if (Array.isArray(config.base?.plugins) && config.base.plugins.length > 0) {
|
|
287
|
+
const p = config.base.plugins;
|
|
288
|
+
|
|
289
|
+
if (!config.base?.pluginsDisabled) {
|
|
290
|
+
configured = [];
|
|
291
|
+
p.forEach((item) => {
|
|
292
|
+
const b = builtin.find((i) => i.name === item);
|
|
293
|
+
b && configured.push(b);
|
|
294
|
+
});
|
|
295
|
+
} else {
|
|
296
|
+
configured = configured.filter((i) => !p.includes(i.name));
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const plugins = new Set();
|
|
301
|
+
const add = (item) => {
|
|
302
|
+
item.dependencies?.forEach(add);
|
|
303
|
+
plugins.add(item);
|
|
304
|
+
};
|
|
305
|
+
configured.map(add);
|
|
306
|
+
plugins.forEach((item) => {
|
|
307
|
+
Object.entries(item.config).forEach(([key, val]) => {
|
|
308
|
+
this.config[item.name] ??= {};
|
|
309
|
+
this.config[item.name][key] =
|
|
310
|
+
config[item.name]?.[key] || this.constructor.defaultConfig[item.name]?.[key] || val;
|
|
311
|
+
});
|
|
312
|
+
this.plugins.set(new item(this));
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (this.config.base.lang) {
|
|
316
|
+
this.element.lang = this.config.base.lang;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this.plugins.init();
|
|
320
|
+
this.toolbarDispatcher.dispatch('init');
|
|
321
|
+
this.formatbarDispatcher.dispatch('init');
|
|
322
|
+
this.focusbarDispatcher.dispatch('init');
|
|
323
|
+
this.rootDispatcher.dispatch('init');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Freezes the editor and its configuration
|
|
328
|
+
*
|
|
329
|
+
* @return {void}
|
|
330
|
+
*/
|
|
331
|
+
freeze() {
|
|
332
|
+
Object.freeze(this.config);
|
|
333
|
+
Object.freeze(this.i18n);
|
|
334
|
+
this.tags.freeze();
|
|
335
|
+
this.filters.freeze();
|
|
336
|
+
this.dialogs.freeze();
|
|
337
|
+
this.commands.freeze();
|
|
338
|
+
this.plugins.freeze();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Loads editor element into DOM
|
|
343
|
+
*
|
|
344
|
+
* @return {void}
|
|
345
|
+
*/
|
|
346
|
+
load() {
|
|
347
|
+
if (this.orig instanceof HTMLTextAreaElement) {
|
|
348
|
+
this.orig.form.addEventListener('submit', () => this.save());
|
|
349
|
+
this.setHtml(this.orig.value.replace('/ /g', ' '));
|
|
350
|
+
} else {
|
|
351
|
+
this.setHtml(this.orig.innerHTML);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.dom.insertAfter(this.element, this.orig);
|
|
355
|
+
this.orig.hidden = true;
|
|
356
|
+
this.rootDispatcher.dispatch('load');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Removes editor element from DOM
|
|
361
|
+
*
|
|
362
|
+
* @return {void}
|
|
363
|
+
*/
|
|
364
|
+
destroy() {
|
|
365
|
+
this.element.parentElement.removeChild(this.element);
|
|
366
|
+
this.orig.hidden = false;
|
|
367
|
+
this.rootDispatcher.dispatch('destroy');
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Returns editor content root element's innerHTML
|
|
372
|
+
*
|
|
373
|
+
* @return {string}
|
|
374
|
+
*/
|
|
375
|
+
getHtml() {
|
|
376
|
+
const root = this.dom.createElement(this.root.localName, { html: this.root.innerHTML });
|
|
377
|
+
this.filters.filter(root);
|
|
378
|
+
this.rootDispatcher.dispatch('gethtml', root);
|
|
379
|
+
|
|
380
|
+
return root.innerHTML;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Sets editor content root element's innerHTML
|
|
385
|
+
*
|
|
386
|
+
* @param {string} html
|
|
387
|
+
* @return {void}
|
|
388
|
+
*/
|
|
389
|
+
setHtml(html) {
|
|
390
|
+
const root = this.dom.createElement(this.root.localName, { html });
|
|
391
|
+
this.rootDispatcher.dispatch('sethtml', root);
|
|
392
|
+
this.filters.filter(root);
|
|
393
|
+
this.root.innerHTML = root.innerHTML;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Saves editor data to source element
|
|
398
|
+
*
|
|
399
|
+
* @return {void}
|
|
400
|
+
*/
|
|
401
|
+
save() {
|
|
402
|
+
if (this.orig instanceof HTMLTextAreaElement) {
|
|
403
|
+
this.orig.value = this.getHtml();
|
|
404
|
+
} else {
|
|
405
|
+
this.orig.innerHTML = this.getHtml();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
this.rootDispatcher.dispatch('save');
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* @param {string} key
|
|
413
|
+
* @return {string}
|
|
414
|
+
*/
|
|
415
|
+
translate(key) {
|
|
416
|
+
return this.i18n[key] || key;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Returns relative or absolute URL depending on its origin
|
|
421
|
+
*
|
|
422
|
+
* @param {string} url
|
|
423
|
+
* @return {string}
|
|
424
|
+
*/
|
|
425
|
+
url(url) {
|
|
426
|
+
const origin = this.dom.window.origin || this.dom.window.location.origin;
|
|
427
|
+
/** @type {HTMLAnchorElement} */
|
|
428
|
+
const a = this.dom.createElement(TagName.A, { attributes: { href: url } });
|
|
429
|
+
|
|
430
|
+
return origin === a.origin ? a.pathname : a.href;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Factory method to create a new editor instance with given configuration
|
|
435
|
+
*
|
|
436
|
+
* @param {HTMLElement} element
|
|
437
|
+
* @param {Object} [config = {}]
|
|
438
|
+
* @return {Editor}
|
|
439
|
+
*/
|
|
440
|
+
static create(element, config = {}) {
|
|
441
|
+
const editor = new this(element, config);
|
|
442
|
+
editor.init();
|
|
443
|
+
editor.freeze();
|
|
444
|
+
editor.load();
|
|
445
|
+
|
|
446
|
+
return editor;
|
|
447
|
+
}
|
|
448
|
+
}
|
package/base/Filter.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import Editor from './Editor.js';
|
|
2
|
+
|
|
3
|
+
export default class Filter {
|
|
4
|
+
/**
|
|
5
|
+
* @type {Editor}
|
|
6
|
+
*/
|
|
7
|
+
#editor;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @return {Editor}
|
|
11
|
+
*/
|
|
12
|
+
get editor() {
|
|
13
|
+
return this.#editor;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {Editor} editor
|
|
18
|
+
*/
|
|
19
|
+
constructor(editor) {
|
|
20
|
+
if (!(editor instanceof Editor)) {
|
|
21
|
+
throw new TypeError('Invalid argument');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.#editor = editor;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @abstract
|
|
29
|
+
* @param {HTMLElement} element
|
|
30
|
+
* @return {void}
|
|
31
|
+
*/
|
|
32
|
+
filter(element) {
|
|
33
|
+
throw new Error('Not implemented');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import Filter from './Filter.js';
|
|
2
|
+
|
|
3
|
+
export default class FilterManager {
|
|
4
|
+
/**
|
|
5
|
+
* @type {Set<Filter>}
|
|
6
|
+
*/
|
|
7
|
+
#items = new Set();
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Filter} filter
|
|
11
|
+
* @return {void}
|
|
12
|
+
*/
|
|
13
|
+
add(filter) {
|
|
14
|
+
if (!(filter instanceof Filter)) {
|
|
15
|
+
throw new TypeError('Invalid argument');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this.#items.add(filter);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {HTMLElement} element
|
|
23
|
+
* @return {void}
|
|
24
|
+
*/
|
|
25
|
+
filter(element) {
|
|
26
|
+
if (!(element instanceof HTMLElement)) {
|
|
27
|
+
throw new TypeError('Invalid argument');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.#items.forEach((filter) => {
|
|
31
|
+
element.normalize();
|
|
32
|
+
filter.filter(element);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @return {void}
|
|
38
|
+
*/
|
|
39
|
+
freeze() {
|
|
40
|
+
this.#items.forEach((filter) => Object.freeze(filter));
|
|
41
|
+
Object.freeze(this.#items);
|
|
42
|
+
Object.freeze(this);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Listener from './Listener.js';
|
|
2
|
+
|
|
3
|
+
export default class FocusableListener extends Listener {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Editor} editor
|
|
6
|
+
*/
|
|
7
|
+
constructor(editor) {
|
|
8
|
+
super(editor);
|
|
9
|
+
this.editor.root.addEventListener('insert', this);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {CustomEvent} event
|
|
14
|
+
* @param {HTMLElement} event.detail.element
|
|
15
|
+
* @return {void}
|
|
16
|
+
*/
|
|
17
|
+
insert({ detail: { element } }) {
|
|
18
|
+
if (element.hasAttribute('data-focusable')) {
|
|
19
|
+
element.focus();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|