@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,99 @@
|
|
|
1
|
+
import BarListener from './BarListener.js';
|
|
2
|
+
|
|
3
|
+
export default class FocusbarListener extends BarListener {
|
|
4
|
+
/**
|
|
5
|
+
* @param {Editor} editor
|
|
6
|
+
*/
|
|
7
|
+
constructor(editor) {
|
|
8
|
+
super(editor);
|
|
9
|
+
this.editor.focusbar.addEventListener('insertbutton', this);
|
|
10
|
+
this.editor.root.addEventListener('focusin', this);
|
|
11
|
+
this.editor.root.addEventListener('focusout', this);
|
|
12
|
+
this.editor.dom.document.addEventListener('selectionchange', this);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {CustomEvent} event
|
|
17
|
+
* @param {HTMLButtonElement} event.detail.element
|
|
18
|
+
* @return {void}
|
|
19
|
+
*/
|
|
20
|
+
insertbutton(event) {
|
|
21
|
+
super.insertbutton(event);
|
|
22
|
+
|
|
23
|
+
if (event.detail.element.getAttribute('data-command')) {
|
|
24
|
+
event.detail.element.addEventListener('mousedown', this);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {MouseEvent} event
|
|
30
|
+
* @return {void}
|
|
31
|
+
*/
|
|
32
|
+
mousedown(event) {
|
|
33
|
+
event.preventDefault();
|
|
34
|
+
event.stopPropagation();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shows the focusbar on focusin if element is focusable
|
|
39
|
+
*
|
|
40
|
+
* @param {FocusEvent} event
|
|
41
|
+
* @param {HTMLElement} event.target
|
|
42
|
+
* @return {void}
|
|
43
|
+
*/
|
|
44
|
+
focusin({ target }) {
|
|
45
|
+
if (target instanceof HTMLElement && target.hasAttribute('data-focusable')) {
|
|
46
|
+
this.#show(target);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Hides the focusbar on focusout
|
|
52
|
+
*
|
|
53
|
+
* @return {void}
|
|
54
|
+
*/
|
|
55
|
+
focusout() {
|
|
56
|
+
this.#hide();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Hides focusbar if current selection is not collapsed
|
|
61
|
+
*
|
|
62
|
+
* @return {void}
|
|
63
|
+
*/
|
|
64
|
+
selectionchange() {
|
|
65
|
+
const element = this.editor.dom.getActiveElement();
|
|
66
|
+
|
|
67
|
+
if (!this.editor.dom.getSelection().isCollapsed || !element) {
|
|
68
|
+
this.#hide();
|
|
69
|
+
} else if (element instanceof HTMLElement && element.hasAttribute('data-focusable')) {
|
|
70
|
+
this.#show(element);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Shows the focusbar
|
|
76
|
+
*
|
|
77
|
+
* @param {HTMLElement} element
|
|
78
|
+
* @return {void}
|
|
79
|
+
*/
|
|
80
|
+
#show(element) {
|
|
81
|
+
if (!this.editor.focusbar.children.length) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Object.keys(element.dataset).forEach((key) => (this.editor.focusbar.dataset[key] = element.dataset[key]));
|
|
86
|
+
this.editor.focusbar.dataset.tag = element.localName;
|
|
87
|
+
this._show(this.editor.focusbar, element);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Hides the focusbar
|
|
92
|
+
*
|
|
93
|
+
* @return {void}
|
|
94
|
+
*/
|
|
95
|
+
#hide() {
|
|
96
|
+
this._hide(this.editor.focusbar);
|
|
97
|
+
Object.keys(this.editor.focusbar.dataset).forEach((key) => delete this.editor.focusbar.dataset[key]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import Editor from './Editor.js';
|
|
2
|
+
import TagName from './TagName.js';
|
|
3
|
+
import { isString } from './util.js';
|
|
4
|
+
|
|
5
|
+
export default class FormCreator {
|
|
6
|
+
/**
|
|
7
|
+
* @type {Editor}
|
|
8
|
+
*/
|
|
9
|
+
#editor;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @return {Editor}
|
|
13
|
+
*/
|
|
14
|
+
get editor() {
|
|
15
|
+
return this.#editor;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @type {HTMLFormElement}
|
|
20
|
+
*/
|
|
21
|
+
#form;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @return {HTMLFormElement}
|
|
25
|
+
*/
|
|
26
|
+
get form() {
|
|
27
|
+
return this.#form;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Current fieldset
|
|
32
|
+
*
|
|
33
|
+
* @type {HTMLFieldSetElement}
|
|
34
|
+
*/
|
|
35
|
+
#fieldset;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {Editor} editor
|
|
39
|
+
*/
|
|
40
|
+
constructor(editor) {
|
|
41
|
+
if (!(editor instanceof Editor)) {
|
|
42
|
+
throw new TypeError('Invalid argument');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.#editor = editor;
|
|
46
|
+
this.#form = this.editor.dom.createElement(TagName.FORM, { attributes: { method: 'dialog' } });
|
|
47
|
+
this.addFieldset().#addCancelButton().#addSubmitButton();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @return {this}
|
|
52
|
+
*/
|
|
53
|
+
addFieldset() {
|
|
54
|
+
const fieldset = this.editor.dom.createElement(TagName.FIELDSET);
|
|
55
|
+
|
|
56
|
+
if (this.#fieldset) {
|
|
57
|
+
this.editor.dom.insertAfter(fieldset, this.#fieldset);
|
|
58
|
+
} else {
|
|
59
|
+
this.editor.dom.insertFirstChild(fieldset, this.#form);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
this.#fieldset = fieldset;
|
|
63
|
+
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} html
|
|
69
|
+
* @return {this}
|
|
70
|
+
*/
|
|
71
|
+
addLegend(html) {
|
|
72
|
+
if (!isString(html)) {
|
|
73
|
+
throw new TypeError('Invalid argument');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.editor.dom.insertLastChild(this.editor.dom.createElement(TagName.LEGEND, { html }), this.#fieldset);
|
|
77
|
+
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Adds a wrapped text input element with label
|
|
83
|
+
*
|
|
84
|
+
* @param {string} name
|
|
85
|
+
* @param {string} label
|
|
86
|
+
* @param {Object.<string, string>} [attributes = {}]
|
|
87
|
+
* @return {this}
|
|
88
|
+
*/
|
|
89
|
+
addTextInput(name, label, attributes = {}) {
|
|
90
|
+
return this.addInput(name, label, attributes, 'text');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Adds a wrapped number input element with label
|
|
95
|
+
*
|
|
96
|
+
* @param {string} name
|
|
97
|
+
* @param {string} label
|
|
98
|
+
* @param {Object.<string, string>} [attributes = {}]
|
|
99
|
+
* @return {this}
|
|
100
|
+
*/
|
|
101
|
+
addNumberInput(name, label, attributes = {}) {
|
|
102
|
+
return this.addInput(name, label, attributes, 'number');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Adds a wrapped input element with label
|
|
107
|
+
*
|
|
108
|
+
* @param {string} name
|
|
109
|
+
* @param {string} label
|
|
110
|
+
* @param {Object.<string, string>} [attributes = {}]
|
|
111
|
+
* @param {string} [type = 'text']
|
|
112
|
+
* @return {this}
|
|
113
|
+
*/
|
|
114
|
+
addInput(name, label, attributes = {}, type = 'text') {
|
|
115
|
+
if (!isString(name) || !isString(label) || !isString(type)) {
|
|
116
|
+
throw new TypeError('Invalid argument');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
Object.assign(attributes, { id: `editor-${name}`, name, type });
|
|
120
|
+
const div = this.editor.dom.createElement(TagName.DIV);
|
|
121
|
+
this.editor.dom.insertLastChild(
|
|
122
|
+
this.editor.dom.createElement(TagName.LABEL, { attributes: { for: attributes.id }, html: label }),
|
|
123
|
+
div
|
|
124
|
+
);
|
|
125
|
+
this.editor.dom.insertLastChild(this.editor.dom.createElement(TagName.INPUT, { attributes }), div);
|
|
126
|
+
attributes.required && div.setAttribute('data-required', '');
|
|
127
|
+
this.editor.dom.insertLastChild(div, this.#fieldset);
|
|
128
|
+
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @return {this}
|
|
134
|
+
*/
|
|
135
|
+
#addCancelButton() {
|
|
136
|
+
const html = this.#t('Cancel');
|
|
137
|
+
const button = this.editor.dom.createElement(TagName.BUTTON, { attributes: { type: 'reset' }, html });
|
|
138
|
+
this.editor.dom.insertLastChild(button, this.#form);
|
|
139
|
+
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @return {this}
|
|
145
|
+
*/
|
|
146
|
+
#addSubmitButton() {
|
|
147
|
+
const button = this.editor.dom.createElement(TagName.BUTTON, { html: this.#t('Save') });
|
|
148
|
+
this.editor.dom.insertLastChild(button, this.#form);
|
|
149
|
+
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Translates given string
|
|
155
|
+
*
|
|
156
|
+
* @param {string} key
|
|
157
|
+
* @return {string}
|
|
158
|
+
*/
|
|
159
|
+
#t(key) {
|
|
160
|
+
return this.editor.translate(key);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import BarListener from './BarListener.js';
|
|
2
|
+
import TagGroup from './TagGroup.js';
|
|
3
|
+
|
|
4
|
+
export default class FormatbarListener extends BarListener {
|
|
5
|
+
/**
|
|
6
|
+
* @param {Editor} editor
|
|
7
|
+
*/
|
|
8
|
+
constructor(editor) {
|
|
9
|
+
super(editor);
|
|
10
|
+
this.editor.formatbar.addEventListener('insertbutton', this);
|
|
11
|
+
this.editor.dom.document.addEventListener('selectionchange', this);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Shows or hides formatbar depending on current selection
|
|
16
|
+
*
|
|
17
|
+
* @return {void}
|
|
18
|
+
*/
|
|
19
|
+
selectionchange() {
|
|
20
|
+
const editable = this.editor.dom.getSelectedEditable();
|
|
21
|
+
|
|
22
|
+
if (
|
|
23
|
+
editable &&
|
|
24
|
+
!this.editor.dom.getSelection().isCollapsed &&
|
|
25
|
+
this.editor.tags.allowed(editable, TagGroup.FORMAT, true)
|
|
26
|
+
) {
|
|
27
|
+
this._show(this.editor.formatbar, editable);
|
|
28
|
+
} else {
|
|
29
|
+
this._hide(this.editor.formatbar);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/base/Key.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
export default class Key {
|
|
2
|
+
/**
|
|
3
|
+
* @return {string}
|
|
4
|
+
*/
|
|
5
|
+
static get A() {
|
|
6
|
+
return 'a';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @return {string}
|
|
11
|
+
*/
|
|
12
|
+
static get ARROWDOWN() {
|
|
13
|
+
return 'ArrowDown';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @return {string}
|
|
18
|
+
*/
|
|
19
|
+
static get ARROWLEFT() {
|
|
20
|
+
return 'ArrowLeft';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @return {string}
|
|
25
|
+
*/
|
|
26
|
+
static get ARROWRIGHT() {
|
|
27
|
+
return 'ArrowRight';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @return {string}
|
|
32
|
+
*/
|
|
33
|
+
static get ARROWUP() {
|
|
34
|
+
return 'ArrowUp';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @return {string}
|
|
39
|
+
*/
|
|
40
|
+
static get B() {
|
|
41
|
+
return 'b';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @return {string}
|
|
46
|
+
*/
|
|
47
|
+
static get BACKSPACE() {
|
|
48
|
+
return 'Backspace';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @return {string}
|
|
53
|
+
*/
|
|
54
|
+
static get C() {
|
|
55
|
+
return 'c';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @return {string}
|
|
60
|
+
*/
|
|
61
|
+
static get D() {
|
|
62
|
+
return 'd';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @return {string}
|
|
67
|
+
*/
|
|
68
|
+
static get DEL() {
|
|
69
|
+
return 'Delete';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @return {string}
|
|
74
|
+
*/
|
|
75
|
+
static get E() {
|
|
76
|
+
return 'e';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @return {string}
|
|
81
|
+
*/
|
|
82
|
+
static get END() {
|
|
83
|
+
return 'End';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @return {string}
|
|
88
|
+
*/
|
|
89
|
+
static get ENTER() {
|
|
90
|
+
return 'Enter';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @return {string}
|
|
95
|
+
*/
|
|
96
|
+
static get F() {
|
|
97
|
+
return 'f';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @return {string}
|
|
102
|
+
*/
|
|
103
|
+
static get G() {
|
|
104
|
+
return 'g';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @return {string}
|
|
109
|
+
*/
|
|
110
|
+
static get HOME() {
|
|
111
|
+
return 'Home';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @return {string}
|
|
116
|
+
*/
|
|
117
|
+
static get I() {
|
|
118
|
+
return 'i';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @return {string}
|
|
123
|
+
*/
|
|
124
|
+
static get J() {
|
|
125
|
+
return 'j';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @return {string}
|
|
130
|
+
*/
|
|
131
|
+
static get K() {
|
|
132
|
+
return 'k';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @return {string}
|
|
137
|
+
*/
|
|
138
|
+
static get L() {
|
|
139
|
+
return 'l';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @return {string}
|
|
144
|
+
*/
|
|
145
|
+
static get M() {
|
|
146
|
+
return 'm';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* @return {string}
|
|
151
|
+
*/
|
|
152
|
+
static get O() {
|
|
153
|
+
return 'o';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* @return {string}
|
|
158
|
+
*/
|
|
159
|
+
static get Q() {
|
|
160
|
+
return 'q';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @return {string}
|
|
165
|
+
*/
|
|
166
|
+
static get R() {
|
|
167
|
+
return 'r';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @return {string}
|
|
172
|
+
*/
|
|
173
|
+
static get S() {
|
|
174
|
+
return 's';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @return {string}
|
|
179
|
+
*/
|
|
180
|
+
static get SPACE() {
|
|
181
|
+
return ' ';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* @return {string}
|
|
186
|
+
*/
|
|
187
|
+
static get T() {
|
|
188
|
+
return 't';
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @return {string}
|
|
193
|
+
*/
|
|
194
|
+
static get TAB() {
|
|
195
|
+
return 'Tab';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @return {string}
|
|
200
|
+
*/
|
|
201
|
+
static get U() {
|
|
202
|
+
return 'u';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @return {string}
|
|
207
|
+
*/
|
|
208
|
+
static get V() {
|
|
209
|
+
return 'v';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @return {string}
|
|
214
|
+
*/
|
|
215
|
+
static get W() {
|
|
216
|
+
return 'w';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @return {string}
|
|
221
|
+
*/
|
|
222
|
+
static get X() {
|
|
223
|
+
return 'x';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @return {string}
|
|
228
|
+
*/
|
|
229
|
+
static get Y() {
|
|
230
|
+
return 'y';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @return {string}
|
|
235
|
+
*/
|
|
236
|
+
static get Z() {
|
|
237
|
+
return 'z';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Indicates if keyboard event was triggered for given key combination
|
|
242
|
+
*
|
|
243
|
+
* @param {KeyboardEvent} event
|
|
244
|
+
* @param {string|string[]} key
|
|
245
|
+
* @param {boolean} alt
|
|
246
|
+
* @param {boolean} ctrl
|
|
247
|
+
* @param {boolean} shift
|
|
248
|
+
* @return {boolean}
|
|
249
|
+
*/
|
|
250
|
+
static isEventFor(event, key, { alt = false, ctrl = false, shift = false } = {}) {
|
|
251
|
+
return (
|
|
252
|
+
((Array.isArray(key) && key.includes(event.key)) || event.key === key) &&
|
|
253
|
+
event.altKey === alt &&
|
|
254
|
+
event.ctrlKey === ctrl &&
|
|
255
|
+
event.shiftKey === shift
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
package/base/Listener.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import Editor from './Editor.js';
|
|
2
|
+
import { isFunction } from './util.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @abstract
|
|
6
|
+
* @implements EventListener
|
|
7
|
+
*/
|
|
8
|
+
export default class Listener {
|
|
9
|
+
/**
|
|
10
|
+
* @type {Editor}
|
|
11
|
+
*/
|
|
12
|
+
#editor;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @return {Editor}
|
|
16
|
+
*/
|
|
17
|
+
get editor() {
|
|
18
|
+
return this.#editor;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @borrows handleEvent
|
|
23
|
+
* @param {Editor} editor
|
|
24
|
+
*/
|
|
25
|
+
constructor(editor) {
|
|
26
|
+
if (!(editor instanceof Editor)) {
|
|
27
|
+
throw new TypeError('Invalid argument');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.#editor = editor;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {Event} event
|
|
35
|
+
* @return {void}
|
|
36
|
+
*/
|
|
37
|
+
handleEvent(event) {
|
|
38
|
+
isFunction(this[event.type]) && this[event.type](event);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Translates given string
|
|
43
|
+
*
|
|
44
|
+
* @protected
|
|
45
|
+
* @param {string} key
|
|
46
|
+
* @return {string}
|
|
47
|
+
*/
|
|
48
|
+
_(key) {
|
|
49
|
+
return this.editor.translate(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import Key from './Key.js';
|
|
2
|
+
import Listener from './Listener.js';
|
|
3
|
+
|
|
4
|
+
export default class NavigableListener extends Listener {
|
|
5
|
+
/**
|
|
6
|
+
* @param {Editor} editor
|
|
7
|
+
*/
|
|
8
|
+
constructor(editor) {
|
|
9
|
+
super(editor);
|
|
10
|
+
this.editor.root.addEventListener('insert', this);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {CustomEvent} event
|
|
15
|
+
* @param {HTMLElement} event.detail.element
|
|
16
|
+
* @return {void}
|
|
17
|
+
*/
|
|
18
|
+
insert({ detail: { element } }) {
|
|
19
|
+
if (element.hasAttribute('data-navigable')) {
|
|
20
|
+
element.tabIndex = 0;
|
|
21
|
+
element.addEventListener('keydown', this);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {KeyboardEvent} event
|
|
27
|
+
* @param {HTMLElement} event.target
|
|
28
|
+
* @return {void}
|
|
29
|
+
*/
|
|
30
|
+
keydown(event) {
|
|
31
|
+
if (
|
|
32
|
+
event.target === event.currentTarget &&
|
|
33
|
+
Key.isEventFor(event, [Key.ARROWUP, Key.ARROWDOWN, Key.HOME, Key.END]) &&
|
|
34
|
+
this.#enabled(event.target)
|
|
35
|
+
) {
|
|
36
|
+
const prev = event.target.previousElementSibling;
|
|
37
|
+
const next = event.target.nextElementSibling;
|
|
38
|
+
const first = event.target.parentElement.firstElementChild;
|
|
39
|
+
const last = event.target.parentElement.lastElementChild;
|
|
40
|
+
const isFirst = event.target === first;
|
|
41
|
+
const isLast = event.target === last;
|
|
42
|
+
|
|
43
|
+
if (event.key === Key.ARROWUP && !isFirst && prev.hasAttribute('data-navigable')) {
|
|
44
|
+
prev.focus();
|
|
45
|
+
} else if (event.key === Key.ARROWDOWN && !isLast && next.hasAttribute('data-navigable')) {
|
|
46
|
+
next.focus();
|
|
47
|
+
} else if (
|
|
48
|
+
(event.key === Key.HOME || (event.key === Key.ARROWDOWN && isLast)) &&
|
|
49
|
+
first.hasAttribute('data-navigable')
|
|
50
|
+
) {
|
|
51
|
+
first.focus();
|
|
52
|
+
} else if (
|
|
53
|
+
(event.key === Key.END || (event.key === Key.ARROWUP && isFirst)) &&
|
|
54
|
+
last.hasAttribute('data-navigable')
|
|
55
|
+
) {
|
|
56
|
+
last.focus();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Indicates if navigation is enabled for element
|
|
66
|
+
*
|
|
67
|
+
* @param {HTMLElement} element
|
|
68
|
+
* @return {boolean}
|
|
69
|
+
*/
|
|
70
|
+
#enabled(element) {
|
|
71
|
+
if (element.contentEditable !== 'true') {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sel = this.editor.dom.getSelection();
|
|
76
|
+
const editable = this.editor.dom.getSelectedEditable();
|
|
77
|
+
const selected = this.editor.dom.getSelectedElement();
|
|
78
|
+
|
|
79
|
+
return sel.isCollapsed && sel.anchorOffset === 0 && [editable, element.firstChild].includes(selected);
|
|
80
|
+
}
|
|
81
|
+
}
|