@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.
Files changed (126) hide show
  1. package/abbreviation/Abbreviation.js +32 -0
  2. package/abbreviation/AbbreviationDialog.js +21 -0
  3. package/audio/Audio.js +47 -0
  4. package/audio/AudioDialog.js +18 -0
  5. package/audio/AudioListener.js +50 -0
  6. package/base/AlignCommand.js +34 -0
  7. package/base/AlignableListener.js +45 -0
  8. package/base/Alignment.js +36 -0
  9. package/base/BarListener.js +95 -0
  10. package/base/Base.js +127 -0
  11. package/base/Command.js +139 -0
  12. package/base/CommandManager.js +60 -0
  13. package/base/ContentFilter.js +109 -0
  14. package/base/DeletableListener.js +36 -0
  15. package/base/DeleteCommand.js +18 -0
  16. package/base/Dialog.js +153 -0
  17. package/base/DialogManager.js +44 -0
  18. package/base/Dispatcher.js +88 -0
  19. package/base/Dom.js +790 -0
  20. package/base/EditableListener.js +82 -0
  21. package/base/Editor.js +448 -0
  22. package/base/Filter.js +35 -0
  23. package/base/FilterManager.js +44 -0
  24. package/base/FocusableListener.js +22 -0
  25. package/base/FocusbarListener.js +99 -0
  26. package/base/FormCreator.js +162 -0
  27. package/base/FormatbarListener.js +32 -0
  28. package/base/Key.js +258 -0
  29. package/base/Listener.js +51 -0
  30. package/base/NavigableListener.js +81 -0
  31. package/base/Plugin.js +176 -0
  32. package/base/PluginManager.js +51 -0
  33. package/base/SlotableListener.js +40 -0
  34. package/base/SortCommand.js +30 -0
  35. package/base/SortableListener.js +135 -0
  36. package/base/Sorting.js +36 -0
  37. package/base/Tag.js +113 -0
  38. package/base/TagGroup.js +183 -0
  39. package/base/TagListener.js +34 -0
  40. package/base/TagManager.js +61 -0
  41. package/base/TagName.js +470 -0
  42. package/base/ToolbarListener.js +11 -0
  43. package/base/util.js +59 -0
  44. package/block/Block.js +51 -0
  45. package/block/BlockDialog.js +11 -0
  46. package/block/BlockElement.js +21 -0
  47. package/block/BlockListener.js +60 -0
  48. package/blockquote/Blockquote.js +43 -0
  49. package/blockquote/BlockquoteFilter.js +22 -0
  50. package/blockquote/BlockquoteListener.js +34 -0
  51. package/bold/Bold.js +30 -0
  52. package/break/Break.js +33 -0
  53. package/break/BreakFilter.js +24 -0
  54. package/build/BuildEditor.js +97 -0
  55. package/build/editor.css +548 -0
  56. package/build/editor.woff2 +0 -0
  57. package/cite/Cite.js +30 -0
  58. package/code/Code.js +30 -0
  59. package/data/Data.js +32 -0
  60. package/data/DataDialog.js +13 -0
  61. package/definition/Definition.js +32 -0
  62. package/definition/DefinitionDialog.js +13 -0
  63. package/deletion/Deletion.js +30 -0
  64. package/details/Details.js +63 -0
  65. package/details/DetailsFilter.js +17 -0
  66. package/details/DetailsListener.js +102 -0
  67. package/division/Division.js +53 -0
  68. package/division/DivisionDialog.js +13 -0
  69. package/emphasis/Emphasis.js +30 -0
  70. package/figure/Figure.js +58 -0
  71. package/figure/FigureFilter.js +14 -0
  72. package/figure/FigureListener.js +23 -0
  73. package/heading/Heading.js +38 -0
  74. package/horizontalrule/HorizontalRule.js +37 -0
  75. package/i18n/I18n.js +26 -0
  76. package/i18n/de.js +167 -0
  77. package/iframe/Iframe.js +49 -0
  78. package/iframe/IframeDialog.js +20 -0
  79. package/iframe/IframeListener.js +48 -0
  80. package/image/Image.js +47 -0
  81. package/image/ImageDialog.js +23 -0
  82. package/image/ImageListener.js +47 -0
  83. package/insertion/Insertion.js +30 -0
  84. package/italic/Italic.js +30 -0
  85. package/keyboard/Keyboard.js +30 -0
  86. package/link/Link.js +34 -0
  87. package/link/LinkDialog.js +14 -0
  88. package/link/LinkListener.js +45 -0
  89. package/list/List.js +40 -0
  90. package/list/ListListener.js +91 -0
  91. package/mark/Mark.js +30 -0
  92. package/orderedlist/OrderedList.js +39 -0
  93. package/package.json +24 -0
  94. package/paragraph/Paragraph.js +42 -0
  95. package/paragraph/ParagraphListener.js +40 -0
  96. package/preformat/Preformat.js +43 -0
  97. package/preformat/PreformatFilter.js +22 -0
  98. package/preformat/PreformatListener.js +34 -0
  99. package/quote/Quote.js +30 -0
  100. package/sample/Sample.js +30 -0
  101. package/section/Section.js +55 -0
  102. package/section/SectionDialog.js +13 -0
  103. package/small/Small.js +30 -0
  104. package/strikethrough/Strikethrough.js +30 -0
  105. package/strong/Strong.js +30 -0
  106. package/subheading/Subheading.js +38 -0
  107. package/subscript/Subscript.js +30 -0
  108. package/superscript/Superscript.js +30 -0
  109. package/table/Table.js +113 -0
  110. package/table/TableCellListener.js +125 -0
  111. package/table/TableColumnAddCommand.js +19 -0
  112. package/table/TableColumnListener.js +34 -0
  113. package/table/TableCommand.js +23 -0
  114. package/table/TableDialog.js +14 -0
  115. package/table/TableFilter.js +35 -0
  116. package/table/TableListener.js +39 -0
  117. package/table/TableRowAddCommand.js +18 -0
  118. package/table/TableRowListener.js +34 -0
  119. package/time/Time.js +32 -0
  120. package/time/TimeDialog.js +13 -0
  121. package/underline/Underline.js +30 -0
  122. package/unorderedlist/UnorderedList.js +39 -0
  123. package/variable/Variable.js +30 -0
  124. package/video/Video.js +47 -0
  125. package/video/VideoDialog.js +20 -0
  126. package/video/VideoListener.js +48 -0
@@ -0,0 +1,32 @@
1
+ import AbbreviationDialog from './AbbreviationDialog.js';
2
+ import Base from '../base/Base.js';
3
+ import Key from '../base/Key.js';
4
+ import Plugin from '../base/Plugin.js';
5
+ import TagGroup from '../base/TagGroup.js';
6
+ import TagName from '../base/TagName.js';
7
+
8
+ export default class Abbreviation extends Plugin {
9
+ /**
10
+ * @type {string}
11
+ */
12
+ static get name() {
13
+ return 'abbreviation';
14
+ }
15
+
16
+ /**
17
+ * @type {Plugin[]}
18
+ */
19
+ static get dependencies() {
20
+ return [Base];
21
+ }
22
+
23
+ /**
24
+ * @return {void}
25
+ */
26
+ init() {
27
+ this._tag({ name: TagName.ABBR, group: TagGroup.FORMAT, attributes: ['title'] });
28
+ this.editor.dialogs.set(new AbbreviationDialog(this.editor));
29
+ this._command(TagName.ABBR);
30
+ this._formatbar('Abbreviation', Key.A);
31
+ }
32
+ }
@@ -0,0 +1,21 @@
1
+ import Abbreviation from './Abbreviation.js';
2
+ import Dialog from '../base/Dialog.js';
3
+
4
+ export default class AbbreviationDialog extends Dialog {
5
+ /**
6
+ * @param {Editor} editor
7
+ */
8
+ constructor(editor) {
9
+ super(editor, Abbreviation.name);
10
+ }
11
+
12
+ /**
13
+ * @protected
14
+ * @return {void}
15
+ */
16
+ _prepareForm() {
17
+ this.formCreator.addLegend(this._('Abbreviation')).addTextInput('title', this._('Full term'), {
18
+ placeholder: this._('Insert full term or leave empty to remove it'),
19
+ });
20
+ }
21
+ }
package/audio/Audio.js ADDED
@@ -0,0 +1,47 @@
1
+ import AudioDialog from './AudioDialog.js';
2
+ import AudioListener from './AudioListener.js';
3
+ import Base from '../base/Base.js';
4
+ import Figure from '../figure/Figure.js';
5
+ import Plugin from '../base/Plugin.js';
6
+ import TagGroup from '../base/TagGroup.js';
7
+ import TagName from '../base/TagName.js';
8
+
9
+ export default class Audio extends Plugin {
10
+ /**
11
+ * @type {string}
12
+ */
13
+ static get name() {
14
+ return 'audio';
15
+ }
16
+
17
+ /**
18
+ * @type {Plugin[]}
19
+ */
20
+ static get dependencies() {
21
+ return [Base, Figure];
22
+ }
23
+
24
+ /**
25
+ * @type {Object.<string, any>}
26
+ */
27
+ static get config() {
28
+ return { browser: undefined };
29
+ }
30
+
31
+ /**
32
+ * @return {void}
33
+ */
34
+ init() {
35
+ this._tag({
36
+ name: TagName.AUDIO,
37
+ group: TagGroup.AUDIO,
38
+ attributes: ['controls', 'id', 'src'],
39
+ empty: true,
40
+ navigable: true,
41
+ });
42
+ new AudioListener(this.editor);
43
+ this.editor.dialogs.set(new AudioDialog(this.editor, this.constructor.name, this.editor.config.audio.browser));
44
+ this._command(TagName.AUDIO);
45
+ this._toolbar('Audio');
46
+ }
47
+ }
@@ -0,0 +1,18 @@
1
+ import Dialog from '../base/Dialog.js';
2
+
3
+ export default class AudioDialog extends Dialog {
4
+ /**
5
+ * @protected
6
+ * @return {void}
7
+ */
8
+ _prepareForm() {
9
+ this.formCreator
10
+ .addLegend(this._('Audio'))
11
+ .addTextInput('src', this._('URL'), {
12
+ pattern: '(https?|/).+',
13
+ placeholder: this._('Insert URL to audio'),
14
+ required: 'required',
15
+ })
16
+ .addTextInput('id', this._('ID'));
17
+ }
18
+ }
@@ -0,0 +1,50 @@
1
+ import Audio from './Audio.js';
2
+ import Listener from '../base/Listener.js';
3
+ import TagName from '../base/TagName.js';
4
+
5
+ export default class AudioListener extends Listener {
6
+ /**
7
+ * @param {Editor} editor
8
+ */
9
+ constructor(editor) {
10
+ super(editor);
11
+ this.editor.root.addEventListener('sethtml', this);
12
+ this.editor.root.addEventListener('insertaudio', this);
13
+ }
14
+
15
+ /**
16
+ * @param {CustomEvent} event
17
+ * @param {HTMLElement} event.detail.element
18
+ * @return {void}
19
+ */
20
+ sethtml({ detail: { element } }) {
21
+ Array.from(element.getElementsByTagName(TagName.AUDIO)).forEach((item) => this.#init(item));
22
+ }
23
+
24
+ /**
25
+ * @param {CustomEvent} event
26
+ * @param {HTMLAudioElement} event.detail.element
27
+ * @return {void}
28
+ */
29
+ insertaudio({ detail: { element } }) {
30
+ this.#init(element);
31
+ }
32
+
33
+ /**
34
+ * Adds controls and wraps in figure if necessary
35
+ *
36
+ * @param {HTMLAudioElement} element
37
+ * @return {void}
38
+ */
39
+ #init(element) {
40
+ const src = element.getAttribute('src');
41
+
42
+ if (!src) {
43
+ element.parentElement.removeChild(element);
44
+ } else {
45
+ element.setAttribute('src', this.editor.url(src));
46
+ element.controls = true;
47
+ this.editor.dom.wrap(element, TagName.FIGURE, { attributes: { class: Audio.name } });
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,34 @@
1
+ import Alignment from './Alignment.js';
2
+ import Command from './Command.js';
3
+
4
+ export default class AlignCommand extends Command {
5
+ /**
6
+ * @type {string}
7
+ */
8
+ #alignment;
9
+
10
+ /**
11
+ * @param {Editor} editor
12
+ * @param {string} alignment
13
+ */
14
+ constructor(editor, alignment) {
15
+ if (!Alignment.values().includes(alignment)) {
16
+ throw new TypeError('Invalid argument');
17
+ }
18
+
19
+ super(editor, 'align-' + alignment);
20
+ this.#alignment = alignment;
21
+ }
22
+
23
+ /**
24
+ * @return {void}
25
+ */
26
+ execute() {
27
+ const element = this.editor.dom.getActiveElement();
28
+
29
+ if (element?.hasAttribute('data-alignable')) {
30
+ this.editor.dom.removeClass(element, ...Alignment.values());
31
+ this.#alignment !== Alignment.NONE && element.classList.add(this.#alignment);
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,45 @@
1
+ import Alignment from './Alignment.js';
2
+ import Key from './Key.js';
3
+ import Listener from './Listener.js';
4
+
5
+ export default class AlignableListener 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.hasAttribute('data-alignable')) {
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
+ const map = {
32
+ [Key.ARROWUP]: Alignment.NONE,
33
+ [Key.ARROWLEFT]: Alignment.LEFT,
34
+ [Key.ARROWDOWN]: Alignment.CENTER,
35
+ [Key.ARROWRIGHT]: Alignment.RIGHT,
36
+ };
37
+
38
+ if (event.target === event.currentTarget && Key.isEventFor(event, Object.keys(map), { shift: true })) {
39
+ event.preventDefault();
40
+ event.stopPropagation();
41
+ this.editor.dom.removeClass(event.target, ...Alignment.values());
42
+ map[event.key] !== Alignment.NONE && event.target.classList.add(map[event.key]);
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,36 @@
1
+ export default class Alignment {
2
+ /**
3
+ * @return {string}
4
+ */
5
+ static get NONE() {
6
+ return 'none';
7
+ }
8
+
9
+ /**
10
+ * @return {string}
11
+ */
12
+ static get LEFT() {
13
+ return 'left';
14
+ }
15
+
16
+ /**
17
+ * @return {string}
18
+ */
19
+ static get CENTER() {
20
+ return 'center';
21
+ }
22
+
23
+ /**
24
+ * @return {string}
25
+ */
26
+ static get RIGHT() {
27
+ return 'right';
28
+ }
29
+
30
+ /**
31
+ * @return {string[]}
32
+ */
33
+ static values() {
34
+ return [this.NONE, this.LEFT, this.CENTER, this.RIGHT];
35
+ }
36
+ }
@@ -0,0 +1,95 @@
1
+ import Key from './Key.js';
2
+ import Listener from './Listener.js';
3
+
4
+ /**
5
+ * @abstract
6
+ */
7
+ export default class BarListener extends Listener {
8
+ /**
9
+ * @param {CustomEvent} event
10
+ * @param {HTMLButtonElement} event.detail.element
11
+ * @return {void}
12
+ */
13
+ insertbutton({ target, detail: { element } }) {
14
+ if (element.getAttribute('data-command')) {
15
+ element.addEventListener('click', this);
16
+ }
17
+
18
+ element.tabIndex = element === target.firstElementChild ? 0 : -1;
19
+ element.addEventListener('keydown', this);
20
+ }
21
+
22
+ /**
23
+ * @param {MouseEvent} event
24
+ * @param {HTMLElement} event.target
25
+ * @return {void}
26
+ */
27
+ click({ target }) {
28
+ this.editor.commands.execute(target.getAttribute('data-command'));
29
+ }
30
+
31
+ /**
32
+ * @param {KeyboardEvent} event
33
+ * @param {HTMLElement} event.target
34
+ * @return {void}
35
+ */
36
+ keydown(event) {
37
+ if (Key.isEventFor(event, [Key.ARROWLEFT, Key.ARROWRIGHT, Key.HOME, Key.END])) {
38
+ const prev = event.target.previousElementSibling;
39
+ const next = event.target.nextElementSibling;
40
+ const first = event.target.parentElement.firstElementChild;
41
+ const last = event.target.parentElement.lastElementChild;
42
+ const isFirst = event.target === first;
43
+ const isLast = event.target === last;
44
+
45
+ if (event.key === Key.ARROWLEFT && !isFirst) {
46
+ prev.focus();
47
+ } else if (event.key === Key.ARROWRIGHT && !isLast) {
48
+ next.focus();
49
+ } else if (event.key === Key.HOME || (event.key === Key.ARROWRIGHT && isLast)) {
50
+ first.focus();
51
+ } else if (event.key === Key.END || (event.key === Key.ARROWLEFT && isFirst)) {
52
+ last.focus();
53
+ }
54
+
55
+ event.preventDefault();
56
+ event.stopPropagation();
57
+ }
58
+ }
59
+
60
+ /**
61
+ * @protected
62
+ * @param {HTMLElement} toolbar
63
+ * @param {HTMLElement} element
64
+ * @return {void}
65
+ */
66
+ _show(toolbar, element) {
67
+ if (!(toolbar instanceof HTMLElement) || !(element instanceof HTMLElement)) {
68
+ throw new TypeError('Invalid argument');
69
+ }
70
+
71
+ toolbar.hidden = false;
72
+ const { x, y } = element.getBoundingClientRect();
73
+ const { x: rx, y: ry } = this.editor.root.getBoundingClientRect();
74
+ const diff = x - rx + toolbar.clientWidth - this.editor.root.clientWidth;
75
+ const top = y - ry - element.scrollTop + this.editor.root.offsetTop - toolbar.clientHeight;
76
+ let left = toolbar.clientWidth < this.editor.root.clientWidth ? x - rx : 0;
77
+
78
+ if (left > 0 && diff > 0) {
79
+ left = left > diff ? (left - diff) / 2 : 0;
80
+ }
81
+
82
+ toolbar.style.left = `${left}px`;
83
+ toolbar.style.top = `${top}px`;
84
+ }
85
+
86
+ /**
87
+ * @protected
88
+ * @param {HTMLElement} toolbar
89
+ * @return {void}
90
+ */
91
+ _hide(toolbar) {
92
+ toolbar.hidden = true;
93
+ toolbar.removeAttribute('style');
94
+ }
95
+ }
package/base/Base.js ADDED
@@ -0,0 +1,127 @@
1
+ import AlignCommand from './AlignCommand.js';
2
+ import AlignableListener from './AlignableListener.js';
3
+ import Alignment from './Alignment.js';
4
+ import ContentFilter from './ContentFilter.js';
5
+ import DeleteCommand from './DeleteCommand.js';
6
+ import DeletableListener from './DeletableListener.js';
7
+ import EditableListener from './EditableListener.js';
8
+ import FocusableListener from './FocusableListener.js';
9
+ import FocusbarListener from './FocusbarListener.js';
10
+ import FormatbarListener from './FormatbarListener.js';
11
+ import NavigableListener from './NavigableListener.js';
12
+ import Plugin from './Plugin.js';
13
+ import SlotableListener from './SlotableListener.js';
14
+ import SortCommand from './SortCommand.js';
15
+ import SortableListener from './SortableListener.js';
16
+ import Sorting from './Sorting.js';
17
+ import TagGroup from './TagGroup.js';
18
+ import TagListener from './TagListener.js';
19
+ import ToolbarListener from './ToolbarListener.js';
20
+ import TagName from './TagName.js';
21
+
22
+ export default class Base extends Plugin {
23
+ /**
24
+ * @type {string}
25
+ */
26
+ static get name() {
27
+ return 'base';
28
+ }
29
+
30
+ /**
31
+ * @type {Object.<string, any>}
32
+ */
33
+ static get config() {
34
+ return { browser: {}, filter: {}, lang: undefined, plugins: [], pluginsDisabled: false };
35
+ }
36
+
37
+ /**
38
+ * @return {void}
39
+ */
40
+ init() {
41
+ this._tag({
42
+ name: TagName.ROOT,
43
+ group: TagGroup.ROOT,
44
+ children: [
45
+ TagGroup.AUDIO,
46
+ TagGroup.BLOCK,
47
+ TagGroup.CONTAINER,
48
+ TagGroup.FIGURE,
49
+ TagGroup.HEADING,
50
+ TagGroup.IFRAME,
51
+ TagGroup.IMAGE,
52
+ TagGroup.LIST,
53
+ TagGroup.PARAGRAPH,
54
+ TagGroup.PREFORMAT,
55
+ TagGroup.QUOTE,
56
+ TagGroup.RULE,
57
+ TagGroup.TABLE,
58
+ TagGroup.VIDEO,
59
+ ],
60
+ });
61
+ this._tag({
62
+ name: TagName.SLOT,
63
+ group: TagGroup.SLOT,
64
+ editable: true,
65
+ focusable: true,
66
+ navigable: true,
67
+ sortable: true,
68
+ });
69
+ new TagListener(this.editor);
70
+ new ToolbarListener(this.editor);
71
+ new FormatbarListener(this.editor);
72
+ new FocusbarListener(this.editor);
73
+ new EditableListener(this.editor);
74
+ new DeletableListener(this.editor);
75
+ new NavigableListener(this.editor);
76
+ new SortableListener(this.editor);
77
+ new AlignableListener(this.editor);
78
+ new FocusableListener(this.editor);
79
+ new SlotableListener(this.editor);
80
+ this.editor.filters.add(new ContentFilter(this.editor));
81
+ this.#initAlign();
82
+ this.#initSort();
83
+ this.#initDelete();
84
+ }
85
+
86
+ /**
87
+ * @return {void}
88
+ */
89
+ #initAlign() {
90
+ const alignments = {
91
+ [Alignment.NONE]: 'No alignment',
92
+ [Alignment.LEFT]: 'Align left',
93
+ [Alignment.CENTER]: 'Align center',
94
+ [Alignment.RIGHT]: 'Align right',
95
+ };
96
+ Object.entries(alignments).forEach(([alignment, label]) => {
97
+ const command = new AlignCommand(this.editor, alignment);
98
+ this.editor.commands.set(command);
99
+ this._focusbar(label, command.name);
100
+ });
101
+ }
102
+
103
+ /**
104
+ * @return {void}
105
+ */
106
+ #initSort() {
107
+ const sortings = {
108
+ [Sorting.FIRST]: 'Sort to the beginning',
109
+ [Sorting.PREV]: 'Sort before previous element',
110
+ [Sorting.NEXT]: 'Sort after next element',
111
+ [Sorting.LAST]: 'Sort to the end',
112
+ };
113
+ Object.entries(sortings).forEach(([sorting, label]) => {
114
+ const command = new SortCommand(this.editor, sorting);
115
+ this.editor.commands.set(command);
116
+ this._focusbar(label, command.name);
117
+ });
118
+ }
119
+
120
+ /**
121
+ * @return {void}
122
+ */
123
+ #initDelete() {
124
+ this.editor.commands.set(new DeleteCommand(this.editor));
125
+ this._focusbar('Delete', 'delete');
126
+ }
127
+ }
@@ -0,0 +1,139 @@
1
+ import Editor from './Editor.js';
2
+ import TagGroup from './TagGroup.js';
3
+ import { isOptString, isString } from './util.js';
4
+
5
+ export default class Command {
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 {string}
20
+ */
21
+ #name;
22
+
23
+ /**
24
+ * @return {string}
25
+ */
26
+ get name() {
27
+ return this.#name;
28
+ }
29
+
30
+ /**
31
+ * @type {Tag|undefined}
32
+ */
33
+ #tag;
34
+
35
+ /**
36
+ * @return {Tag|undefined}
37
+ */
38
+ get tag() {
39
+ return this.#tag;
40
+ }
41
+
42
+ /**
43
+ * @type {Dialog|undefined}
44
+ */
45
+ #dialog;
46
+
47
+ /**
48
+ * @return {Dialog|undefined}
49
+ */
50
+ get dialog() {
51
+ return this.#dialog;
52
+ }
53
+
54
+ /**
55
+ * @param {Editor} editor
56
+ * @param {string} name
57
+ * @param {string|undefined} [tagName = undefined]
58
+ */
59
+ constructor(editor, name, tagName = undefined) {
60
+ if (!(editor instanceof Editor) || !isString(name) || !isOptString(tagName)) {
61
+ throw new TypeError('Invalid argument');
62
+ }
63
+
64
+ this.#editor = editor;
65
+ this.#name = name;
66
+ this.#tag = tagName && this.editor.tags.get(tagName);
67
+ this.#dialog = this.editor.dialogs.get(name);
68
+ }
69
+
70
+ /**
71
+ * @return {void}
72
+ */
73
+ execute() {
74
+ this.dialog ? this._openDialog() : this._insert(this.#selectedAttributes());
75
+ }
76
+
77
+ /**
78
+ * @protected
79
+ * @param {Object.<string, string>} [attributes = {}]
80
+ * @return {void}
81
+ */
82
+ _insert(attributes = {}) {
83
+ if (!this.tag) {
84
+ return;
85
+ }
86
+
87
+ this.#filterAttributes(attributes);
88
+ const element = this.editor.dom.getSelectedElementByName(this.tag.name);
89
+
90
+ if (this.tag.group !== TagGroup.FORMAT) {
91
+ this.editor.dom.insert(this.editor.dom.createElement(this.tag.name, { attributes }));
92
+ } else if (element && Object.keys(attributes).length > 0) {
93
+ element.parentElement.replaceChild(
94
+ this.editor.dom.createElement(this.tag.name, { attributes, html: element.textContent }),
95
+ element
96
+ );
97
+ } else if (element) {
98
+ element.parentElement.replaceChild(this.editor.dom.createText(element.textContent), element);
99
+ } else {
100
+ this.editor.dom.format(this.editor.dom.createElement(this.tag.name, { attributes }));
101
+ }
102
+ }
103
+
104
+ /**
105
+ * @protected
106
+ * @return {void}
107
+ */
108
+ _openDialog() {
109
+ this.dialog?.open((attributes) => this._insert(attributes), this.#selectedAttributes());
110
+ }
111
+
112
+ /**
113
+ * Returns attributes from selected element if its tag name matches configured tag name
114
+ *
115
+ * @return {Object.<string, string>}
116
+ */
117
+ #selectedAttributes() {
118
+ if (!this.tag) {
119
+ return {};
120
+ }
121
+
122
+ const attributes = this.editor.dom.getSelectedAttributesByName(this.tag.name);
123
+ this.#filterAttributes(attributes);
124
+
125
+ return attributes;
126
+ }
127
+
128
+ /**
129
+ * Filters allowed attributes
130
+ *
131
+ * @param {Object.<string, string>} attributes
132
+ * @return {void}
133
+ */
134
+ #filterAttributes(attributes) {
135
+ Object.keys(attributes).forEach(
136
+ (item) => (this.tag.attributes.includes(item) && attributes[item]) || delete attributes[item]
137
+ );
138
+ }
139
+ }