@ebl-vue/editor-full 2.31.35 → 2.31.36

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 (97) hide show
  1. package/.postcssrc.yml +33 -0
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.mjs +183 -182
  4. package/dist/index.mjs.map +1 -0
  5. package/package.json +1 -1
  6. package/postcss.config.js +15 -0
  7. package/src/components/Editor/Editor.vue +293 -0
  8. package/src/components/index.ts +27 -0
  9. package/src/constants/index.ts +1 -0
  10. package/src/i18n/zh-cn.ts +160 -0
  11. package/src/icons/index.ts +93 -0
  12. package/src/index.ts +21 -0
  13. package/src/installer.ts +21 -0
  14. package/src/plugins/alert/index.ts +455 -0
  15. package/src/plugins/block-alignment/index.ts +117 -0
  16. package/src/plugins/block-alignment/readme.md +1 -0
  17. package/src/plugins/code/LICENSE +21 -0
  18. package/src/plugins/code/index.ts +619 -0
  19. package/src/plugins/code/utils/string.ts +34 -0
  20. package/src/plugins/color-picker/index.ts +132 -0
  21. package/src/plugins/delimiter/index.ts +121 -0
  22. package/src/plugins/drag-drop/index.css +19 -0
  23. package/src/plugins/drag-drop/index.ts +151 -0
  24. package/src/plugins/drag-drop/readme.md +1 -0
  25. package/src/plugins/header/H1.ts +404 -0
  26. package/src/plugins/header/H2.ts +403 -0
  27. package/src/plugins/header/H3.ts +404 -0
  28. package/src/plugins/header/H4.ts +404 -0
  29. package/src/plugins/header/H5.ts +403 -0
  30. package/src/plugins/header/H6.ts +404 -0
  31. package/src/plugins/header/index.ts +15 -0
  32. package/src/plugins/header/types.d.ts +46 -0
  33. package/src/plugins/imageResizeCrop/ImageTune.ts +635 -0
  34. package/src/plugins/imageResizeCrop/index.css +230 -0
  35. package/src/plugins/imageResizeCrop/index.ts +5 -0
  36. package/src/plugins/imageResizeCrop/types.d.ts +23 -0
  37. package/src/plugins/imageTool/index.ts +510 -0
  38. package/src/plugins/imageTool/types/codexteam__ajax.d.ts +89 -0
  39. package/src/plugins/imageTool/types/types.ts +236 -0
  40. package/src/plugins/imageTool/ui.ts +313 -0
  41. package/src/plugins/imageTool/uploader.ts +287 -0
  42. package/src/plugins/imageTool/utils/dom.ts +24 -0
  43. package/src/plugins/imageTool/utils/index.ts +73 -0
  44. package/src/plugins/imageTool/utils/isPromise.ts +10 -0
  45. package/src/plugins/indent/index.ts +695 -0
  46. package/src/plugins/inline-code/index.ts +203 -0
  47. package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +208 -0
  48. package/src/plugins/list/ListRenderer/ListRenderer.ts +73 -0
  49. package/src/plugins/list/ListRenderer/OrderedListRenderer.ts +123 -0
  50. package/src/plugins/list/ListRenderer/UnorderedListRenderer.ts +123 -0
  51. package/src/plugins/list/ListRenderer/index.ts +6 -0
  52. package/src/plugins/list/ListTabulator/index.ts +1179 -0
  53. package/src/plugins/list/index.ts +488 -0
  54. package/src/plugins/list/styles/CssPrefix.ts +4 -0
  55. package/src/plugins/list/types/Elements.ts +14 -0
  56. package/src/plugins/list/types/ItemMeta.ts +40 -0
  57. package/src/plugins/list/types/ListParams.ts +102 -0
  58. package/src/plugins/list/types/ListRenderer.ts +6 -0
  59. package/src/plugins/list/types/OlCounterType.ts +63 -0
  60. package/src/plugins/list/types/index.ts +14 -0
  61. package/src/plugins/list/utils/focusItem.ts +18 -0
  62. package/src/plugins/list/utils/getChildItems.ts +40 -0
  63. package/src/plugins/list/utils/getItemChildWrapper.ts +10 -0
  64. package/src/plugins/list/utils/getItemContentElement.ts +10 -0
  65. package/src/plugins/list/utils/getSiblings.ts +52 -0
  66. package/src/plugins/list/utils/isLastItem.ts +9 -0
  67. package/src/plugins/list/utils/itemHasSublist.ts +10 -0
  68. package/src/plugins/list/utils/normalizeData.ts +83 -0
  69. package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
  70. package/src/plugins/list/utils/renderToolboxInput.ts +113 -0
  71. package/src/plugins/list/utils/stripNumbers.ts +7 -0
  72. package/src/plugins/list/utils/type-guards.ts +8 -0
  73. package/src/plugins/marker/index.ts +199 -0
  74. package/src/plugins/outline/index.ts +62 -0
  75. package/src/plugins/outline/outline.css +52 -0
  76. package/src/plugins/paragraph/index.ts +384 -0
  77. package/src/plugins/paragraph/types/icons.d.ts +4 -0
  78. package/src/plugins/paragraph/utils/makeFragment.ts +17 -0
  79. package/src/plugins/quote/index.ts +203 -0
  80. package/src/plugins/table/index.ts +4 -0
  81. package/src/plugins/table/plugin.ts +255 -0
  82. package/src/plugins/table/table.ts +1202 -0
  83. package/src/plugins/table/toolbox.ts +166 -0
  84. package/src/plugins/table/utils/dom.ts +130 -0
  85. package/src/plugins/table/utils/popover.ts +185 -0
  86. package/src/plugins/table/utils/throttled.ts +22 -0
  87. package/src/plugins/underline/index.ts +214 -0
  88. package/src/plugins/undo/index.ts +526 -0
  89. package/src/plugins/undo/observer.ts +101 -0
  90. package/src/plugins/undo/vanilla-caret-js.ts +102 -0
  91. package/src/style.css +139 -0
  92. package/src/types.ts +3 -0
  93. package/src/utils/AxiosService.ts +87 -0
  94. package/src/utils/index.ts +15 -0
  95. package/src/utils/install.ts +19 -0
  96. package/tsconfig.json +37 -0
  97. package/vite.config.ts +81 -0
@@ -0,0 +1,166 @@
1
+ import Popover from "./utils/popover";
2
+ import * as $ from "./utils/dom";
3
+ import { IconMenuSmall } from "../../icons";
4
+
5
+ /**
6
+ * @typedef {object} PopoverItem
7
+ * @property {string} label - button text
8
+ * @property {string} icon - button icon
9
+ * @property {boolean} confirmationRequired - if true, a confirmation state will be applied on the first click
10
+ * @property {function} hideIf - if provided, item will be hid, if this method returns true
11
+ * @property {function} onClick - click callback
12
+ */
13
+
14
+ /**
15
+ * Toolbox is a menu for manipulation of rows/cols
16
+ *
17
+ * It contains toggler and Popover:
18
+ * <toolbox>
19
+ * <toolbox-toggler />
20
+ * <popover />
21
+ * <toolbox>
22
+ */
23
+ export default class Toolbox {
24
+
25
+ private items: any;
26
+ private onOpen: any;
27
+ private onClose: any;
28
+ private cssModifier: string;
29
+ private popover: Popover | null;
30
+ private wrapper: HTMLElement;
31
+ /**
32
+ * Creates toolbox buttons and toolbox menus
33
+ *
34
+ * @param {Object} config
35
+ * @param {any} config.api - Editor.js api
36
+ * @param {PopoverItem[]} config.items - Editor.js api
37
+ * @param {function} config.onOpen - callback fired when the Popover is opening
38
+ * @param {function} config.onClose - callback fired when the Popover is closing
39
+ * @param {string} config.cssModifier - the modifier for the Toolbox. Allows to add some specific styles.
40
+ */
41
+ constructor({ items, onOpen, onClose, cssModifier = "" }: any) {
42
+
43
+
44
+ this.items = items;
45
+ this.onOpen = onOpen;
46
+ this.onClose = onClose;
47
+ this.cssModifier = cssModifier;
48
+
49
+ this.popover = null;
50
+ this.wrapper = this.createToolbox();
51
+ }
52
+
53
+ /**
54
+ * Style classes
55
+ */
56
+ static get CSS() {
57
+ return {
58
+ toolbox: "tc-toolbox",
59
+ toolboxShowed: "tc-toolbox--showed",
60
+ toggler: "tc-toolbox__toggler",
61
+ };
62
+ }
63
+
64
+ /**
65
+ * Returns rendered Toolbox element
66
+ */
67
+ get element() {
68
+ return this.wrapper;
69
+ }
70
+
71
+ /**
72
+ * Creating a toolbox to open menu for a manipulating columns
73
+ *
74
+ * @returns {Element}
75
+ */
76
+ createToolbox() {
77
+ const wrapper = $.make("div", [
78
+ Toolbox.CSS.toolbox,
79
+ this.cssModifier ? `${Toolbox.CSS.toolbox}--${this.cssModifier}` : "",
80
+ ]);
81
+
82
+ wrapper.dataset.mutationFree = "true";
83
+ const popover = this.createPopover();
84
+ const toggler = this.createToggler();
85
+
86
+ wrapper.appendChild(toggler);
87
+ wrapper.appendChild(popover);
88
+
89
+ return wrapper;
90
+ }
91
+
92
+ /**
93
+ * Creates the Toggler
94
+ *
95
+ * @returns {Element}
96
+ */
97
+ createToggler() {
98
+ const toggler = $.make("div", Toolbox.CSS.toggler, {
99
+ innerHTML: IconMenuSmall,
100
+ });
101
+
102
+ toggler.addEventListener("click", () => {
103
+ this.togglerClicked();
104
+ });
105
+
106
+ return toggler;
107
+ }
108
+
109
+ /**
110
+ * Creates the Popover instance and render it
111
+ *
112
+ * @returns {Element}
113
+ */
114
+ createPopover() {
115
+ this.popover = new Popover({
116
+ items: this.items,
117
+ });
118
+
119
+ return this.popover.render();
120
+ }
121
+
122
+ /**
123
+ * Toggler click handler. Opens/Closes the popover
124
+ *
125
+ * @returns {void}
126
+ */
127
+ togglerClicked() {
128
+ if (this.popover?.opened) {
129
+ this.popover.close();
130
+ this.onClose();
131
+ } else {
132
+ this.popover?.open();
133
+ this.onOpen();
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Shows the Toolbox
139
+ *
140
+ * @param {function} computePositionMethod - method that returns the position coordinate
141
+ * @returns {void}
142
+ */
143
+ show(computePositionMethod:Function) {
144
+ const position = computePositionMethod();
145
+
146
+ /**
147
+ * Set 'top' or 'left' style
148
+ */
149
+ Object.entries(position).forEach(([prop, value]) => {
150
+ //this.wrapper.style[prop] = value;
151
+ this.wrapper.style.setProperty(prop, value as string);
152
+ });
153
+
154
+ this.wrapper.classList.add(Toolbox.CSS.toolboxShowed);
155
+ }
156
+
157
+ /**
158
+ * Hides the Toolbox
159
+ *
160
+ * @returns {void}
161
+ */
162
+ hide() {
163
+ this.popover?.close();
164
+ this.wrapper.classList.remove(Toolbox.CSS.toolboxShowed);
165
+ }
166
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Helper for making Elements with attributes
3
+ *
4
+ * @param {string} tagName - new Element tag name
5
+ * @param {string|string[]} classNames - list or name of CSS classname(s)
6
+ * @param {object} attributes - any attributes
7
+ * @returns {Element}
8
+ */
9
+ export function make(
10
+ tagName: string,
11
+ classNames: string | string[],
12
+ attributes:Record<string, any> = {}
13
+ ) {
14
+ const el = document.createElement(tagName);
15
+
16
+ if (Array.isArray(classNames)) {
17
+ el.classList.add(...classNames);
18
+ } else if (classNames) {
19
+ el.classList.add(classNames);
20
+ }
21
+
22
+ for (const attrName in attributes) {
23
+ if (!Object.prototype.hasOwnProperty.call(attributes, attrName)) {
24
+ continue;
25
+ }
26
+
27
+ (el as any)[attrName] = attributes[attrName];
28
+ //不能用
29
+ //el.setAttribute(attrName, attributes[attrName]);
30
+ }
31
+
32
+ return el;
33
+ }
34
+
35
+ /**
36
+ * Get item position relative to document
37
+ *
38
+ * @param {HTMLElement} elem - item
39
+ * @returns {{x1: number, y1: number, x2: number, y2: number}} coordinates of the upper left (x1,y1) and lower right(x2,y2) corners
40
+ */
41
+ export function getCoords(elem: HTMLElement) {
42
+ const rect = elem.getBoundingClientRect();
43
+
44
+ return {
45
+ y1: Math.floor(rect.top + window.pageYOffset),
46
+ x1: Math.floor(rect.left + window.pageXOffset),
47
+ x2: Math.floor(rect.right + window.pageXOffset),
48
+ y2: Math.floor(rect.bottom + window.pageYOffset)
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Calculate paddings of the first element relative to the second
54
+ *
55
+ * @param {HTMLElement} firstElem - outer element, if the second element is inside it, then all padding will be positive
56
+ * @param {HTMLElement} secondElem - inner element, if its borders go beyond the first, then the paddings will be considered negative
57
+ * @returns {{fromTopBorder: number, fromLeftBorder: number, fromRightBorder: number, fromBottomBorder: number}}
58
+ */
59
+ export function getRelativeCoordsOfTwoElems(firstElem: HTMLElement, secondElem: HTMLElement) {
60
+ const firstCoords = getCoords(firstElem);
61
+ const secondCoords = getCoords(secondElem);
62
+
63
+ return {
64
+ fromTopBorder: secondCoords.y1 - firstCoords.y1,
65
+ fromLeftBorder: secondCoords.x1 - firstCoords.x1,
66
+ fromRightBorder: firstCoords.x2 - secondCoords.x2,
67
+ fromBottomBorder: firstCoords.y2 - secondCoords.y2
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Get the width and height of an element and the position of the cursor relative to it
73
+ *
74
+ * @param {HTMLElement} elem - element relative to which the coordinates will be calculated
75
+ * @param {Event} event - mouse event
76
+ */
77
+ export function getCursorPositionRelativeToElement(elem: HTMLElement, event: MouseEvent) {
78
+ const rect = elem.getBoundingClientRect();
79
+ const { width, height, x, y } = rect;
80
+ const { clientX, clientY } = event;
81
+
82
+ return {
83
+ width,
84
+ height,
85
+ x: clientX - x,
86
+ y: clientY - y
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Insert element after the referenced
92
+ *
93
+ * @param {HTMLElement} newNode
94
+ * @param {HTMLElement} referenceNode
95
+ * @returns {HTMLElement}
96
+ */
97
+ export function insertAfter(newNode: HTMLElement, referenceNode: HTMLElement) {
98
+ return referenceNode?.parentNode?.insertBefore(newNode, referenceNode.nextSibling);
99
+ }
100
+
101
+ /**
102
+ * Insert element after the referenced
103
+ *
104
+ * @param {HTMLElement} newNode
105
+ * @param {HTMLElement} referenceNode
106
+ * @returns {HTMLElement}
107
+ */
108
+ export function insertBefore(newNode: HTMLElement, referenceNode: HTMLElement) {
109
+ return referenceNode?.parentNode?.insertBefore(newNode, referenceNode);
110
+ }
111
+
112
+
113
+ /**
114
+ * Set focus to contenteditable or native input element
115
+ *
116
+ * @param {Element} element - element where to set focus
117
+ * @param {boolean} atStart - where to set focus: at the start or at the end
118
+ *
119
+ * @returns {void}
120
+ */
121
+ export function focus(element: HTMLElement, atStart = true) {
122
+ const range = document.createRange();
123
+ const selection = window.getSelection();
124
+
125
+ range.selectNodeContents(element);
126
+ range.collapse(atStart);
127
+
128
+ selection?.removeAllRanges();
129
+ selection?.addRange(range);
130
+ }
@@ -0,0 +1,185 @@
1
+ import * as $ from './dom';
2
+
3
+ /**
4
+ * @typedef {object} PopoverItem
5
+ * @property {string} label - button text
6
+ * @property {string} icon - button icon
7
+ * @property {boolean} confirmationRequired - if true, a confirmation state will be applied on the first click
8
+ * @property {function} hideIf - if provided, item will be hid, if this method returns true
9
+ * @property {function} onClick - click callback
10
+ */
11
+ interface IPopoverItem {
12
+ label: string;
13
+ icon?: string;
14
+ confirmationRequired?: boolean;
15
+ hideIf?: () => boolean;
16
+ onClick: () => void;
17
+ }
18
+ /**
19
+ * This cass provides a popover rendering
20
+ */
21
+ export default class Popover {
22
+ private wrapper: HTMLElement|null|undefined;
23
+ private items: IPopoverItem[];
24
+ private itemEls: HTMLElement[];
25
+ /**
26
+ * @param {object} options - constructor options
27
+ * @param {PopoverItem[]} options.items - constructor options
28
+ */
29
+ constructor({items}: {items: any}) {
30
+ this.items = items;
31
+ this.wrapper = undefined;
32
+ this.itemEls = [];
33
+ }
34
+
35
+ /**
36
+ * Set of CSS classnames used in popover
37
+ *
38
+ * @returns {object}
39
+ */
40
+ static get CSS() {
41
+ return {
42
+ popover: 'tc-popover',
43
+ popoverOpened: 'tc-popover--opened',
44
+ item: 'tc-popover__item',
45
+ itemHidden: 'tc-popover__item--hidden',
46
+ itemConfirmState: 'tc-popover__item--confirm',
47
+ itemIcon: 'tc-popover__item-icon',
48
+ itemLabel: 'tc-popover__item-label'
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Returns the popover element
54
+ *
55
+ * @returns {Element}
56
+ */
57
+ render() {
58
+ this.wrapper = $.make('div', Popover.CSS.popover);
59
+
60
+ this.items.forEach((item:IPopoverItem, index:number) => {
61
+ const itemEl = $.make('div', Popover.CSS.item);
62
+ const icon = $.make('div', Popover.CSS.itemIcon, {
63
+ innerHTML: item.icon
64
+ });
65
+ const label = $.make('div', Popover.CSS.itemLabel, {
66
+ textContent: item.label
67
+ });
68
+
69
+ itemEl.dataset.index = index + '';
70
+
71
+
72
+ itemEl.appendChild(icon);
73
+ itemEl.appendChild(label);
74
+
75
+ this.wrapper?.appendChild(itemEl);
76
+ this.itemEls.push(itemEl);
77
+ });
78
+
79
+ /**
80
+ * Delegate click
81
+ */
82
+ this.wrapper.addEventListener('click', (event) => {
83
+ this.popoverClicked(event);
84
+ });
85
+
86
+ return this.wrapper;
87
+ }
88
+
89
+ /**
90
+ * Popover wrapper click listener
91
+ * Used to delegate clicks in items
92
+ *
93
+ * @returns {void}
94
+ */
95
+ popoverClicked(event: MouseEvent) {
96
+ const target = event.target as HTMLElement;
97
+ const clickedItem = target.closest(`.${Popover.CSS.item}`) as HTMLElement;
98
+
99
+ /**
100
+ * Clicks outside or between item
101
+ */
102
+ if (!clickedItem) {
103
+ return;
104
+ }
105
+
106
+ const clickedItemIndex:string|undefined = clickedItem.dataset.index;
107
+ if(!clickedItemIndex) return;
108
+
109
+ const item = this.items[parseInt(clickedItemIndex)];
110
+
111
+ if (item.confirmationRequired && !this.hasConfirmationState(clickedItem)) {
112
+ this.setConfirmationState(clickedItem);
113
+
114
+ return;
115
+ }
116
+
117
+ item.onClick();
118
+ }
119
+
120
+ /**
121
+ * Enable the confirmation state on passed item
122
+ *
123
+ * @returns {void}
124
+ */
125
+ setConfirmationState(itemEl: HTMLElement) {
126
+ itemEl.classList.add(Popover.CSS.itemConfirmState);
127
+ }
128
+
129
+ /**
130
+ * Disable the confirmation state on passed item
131
+ *
132
+ * @returns {void}
133
+ */
134
+ clearConfirmationState(itemEl: HTMLElement) {
135
+ itemEl.classList.remove(Popover.CSS.itemConfirmState);
136
+ }
137
+
138
+ /**
139
+ * Check if passed item has the confirmation state
140
+ *
141
+ * @returns {boolean}
142
+ */
143
+ hasConfirmationState(itemEl: HTMLElement) {
144
+ return itemEl.classList.contains(Popover.CSS.itemConfirmState);
145
+ }
146
+
147
+ /**
148
+ * Return an opening state
149
+ *
150
+ * @returns {boolean}
151
+ */
152
+ get opened() {
153
+ return this.wrapper?.classList.contains(Popover.CSS.popoverOpened) || false;
154
+ }
155
+
156
+ /**
157
+ * Opens the popover
158
+ *
159
+ * @returns {void}
160
+ */
161
+ open() {
162
+ /**
163
+ * If item provides 'hideIf()' method that returns true, hide item
164
+ */
165
+ this.items.forEach((item, index) => {
166
+ if (typeof item.hideIf === 'function') {
167
+ this.itemEls[index].classList.toggle(Popover.CSS.itemHidden, item.hideIf());
168
+ }
169
+ });
170
+
171
+ this.wrapper?.classList.add(Popover.CSS.popoverOpened);
172
+ }
173
+
174
+ /**
175
+ * Closes the popover
176
+ *
177
+ * @returns {void}
178
+ */
179
+ close() {
180
+ this.wrapper?.classList.remove(Popover.CSS.popoverOpened);
181
+ this.itemEls.forEach(el => {
182
+ this.clearConfirmationState(el);
183
+ });
184
+ }
185
+ }
@@ -0,0 +1,22 @@
1
+
2
+ /**
3
+ * Limits the frequency of calling a function
4
+ *
5
+ * @param {number} delay - delay between calls in milliseconds
6
+ * @param {function} fn - function to be throttled
7
+ */
8
+ export default function throttled(delay:number, fn:Function) {
9
+ let lastCall = 0;
10
+
11
+ return function (...args:any[]) {
12
+ const now = new Date().getTime();
13
+
14
+ if (now - lastCall < delay) {
15
+ return;
16
+ }
17
+
18
+ lastCall = now;
19
+
20
+ return fn(...args);
21
+ };
22
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Build styles
3
+ */
4
+ import '@ebl-vue/editor-render/styles/underline.css';
5
+ import { IconUnderline } from '../../icons';
6
+
7
+ import {type API, type InlineTool, type SanitizerConfig} from "@ebl-vue/editorjs";
8
+ import {type InlineToolConstructorOptions} from "@ebl-vue/editorjs/types/tools/inline-tool";
9
+
10
+ /**
11
+ * Underline Tool for the Editor.js
12
+ *
13
+ * Allows to wrap inline fragment and style it somehow.
14
+ */
15
+ export default class Underline implements InlineTool {
16
+ /**
17
+ * Class name for term-tag
18
+ *
19
+ * @type {string}
20
+ */
21
+ static get CSS(): string {
22
+ return 'cdx-underline';
23
+ };
24
+
25
+ /**
26
+ * Toolbar Button
27
+ *
28
+ * @type {HTMLButtonElement}
29
+ */
30
+ private button: HTMLButtonElement | undefined
31
+
32
+ /**
33
+ * Tag represented the term
34
+ *
35
+ * @type {string}
36
+ */
37
+ private tag: string = 'U';
38
+
39
+ /**
40
+ * API InlineToolConstructorOptions
41
+ *
42
+ * @type {API}
43
+ */
44
+ private api: API
45
+
46
+ /**
47
+ * CSS classes
48
+ *
49
+ * @type {object}
50
+ */
51
+ private iconClasses: {base: string, active: string}
52
+
53
+ /**
54
+ * @param options InlineToolConstructorOptions
55
+ */
56
+ public constructor(options: InlineToolConstructorOptions) {
57
+ this.api = options.api;
58
+
59
+ /**
60
+ * CSS classes
61
+ */
62
+ this.iconClasses = {
63
+ base: this.api.styles.inlineToolButton,
64
+ active: this.api.styles.inlineToolButtonActive,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Specifies Tool as Inline Toolbar Tool
70
+ *
71
+ * @returns {boolean}
72
+ */
73
+ public static isInline = true;
74
+
75
+ /**
76
+ * Create button element for Toolbar
77
+ *
78
+ * @returns {HTMLElement}
79
+ */
80
+ public render(): HTMLElement {
81
+ this.button = document.createElement('button');
82
+ this.button.type = 'button';
83
+ this.button.classList.add(this.iconClasses.base);
84
+ this.button.innerHTML = this.toolboxIcon;
85
+
86
+ return this.button;
87
+ }
88
+
89
+ /**
90
+ * Wrap/Unwrap selected fragment
91
+ *
92
+ * @param {Range} range - selected fragment
93
+ */
94
+ public surround(range: Range): void {
95
+ if (!range) {
96
+ return;
97
+ }
98
+
99
+ const termWrapper = this.api.selection.findParentTag(this.tag, Underline.CSS);
100
+
101
+ /**
102
+ * If start or end of selection is in the highlighted block
103
+ */
104
+ if (termWrapper) {
105
+ this.unwrap(termWrapper);
106
+ } else {
107
+ this.wrap(range);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Wrap selection with term-tag
113
+ *
114
+ * @param {Range} range - selected fragment
115
+ */
116
+ public wrap(range: Range) {
117
+ /**
118
+ * Create a wrapper for highlighting
119
+ */
120
+ const u = document.createElement(this.tag);
121
+
122
+ u.classList.add(Underline.CSS);
123
+
124
+ /**
125
+ * SurroundContent throws an error if the Range splits a non-Text node with only one of its boundary points
126
+ *
127
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Range/surroundContents}
128
+ *
129
+ * // range.surroundContents(span);
130
+ */
131
+ u.appendChild(range.extractContents());
132
+ range.insertNode(u);
133
+
134
+ /**
135
+ * Expand (add) selection to highlighted block
136
+ */
137
+ this.api.selection.expandToTag(u);
138
+ }
139
+
140
+ /**
141
+ * Unwrap term-tag
142
+ *
143
+ * @param {HTMLElement} termWrapper - term wrapper tag
144
+ */
145
+ public unwrap(termWrapper: HTMLElement): void {
146
+ /**
147
+ * Expand selection to all term-tag
148
+ */
149
+ this.api.selection.expandToTag(termWrapper);
150
+
151
+ const sel = window.getSelection();
152
+ if (!sel) {
153
+ return;
154
+ }
155
+ const range = sel.getRangeAt(0);
156
+ if (!range) {
157
+ return
158
+ }
159
+
160
+ const unwrappedContent = range.extractContents();
161
+ if (!unwrappedContent) {
162
+ return
163
+ }
164
+
165
+ /**
166
+ * Remove empty term-tag
167
+ */
168
+ termWrapper.parentNode?.removeChild(termWrapper);
169
+
170
+ /**
171
+ * Insert extracted content
172
+ */
173
+ range.insertNode(unwrappedContent);
174
+
175
+ /**
176
+ * Restore selection
177
+ */
178
+ sel.removeAllRanges();
179
+ sel.addRange(range);
180
+ }
181
+
182
+ /**
183
+ * Check and change Term's state for current selection
184
+ */
185
+ public checkState(): boolean {
186
+ const termTag = this.api.selection.findParentTag(this.tag, Underline.CSS);
187
+
188
+ this.button?.classList.toggle(this.iconClasses.active, !!termTag);
189
+
190
+ return !!termTag
191
+ }
192
+
193
+ /**
194
+ * Get Tool icon's SVG
195
+ *
196
+ * @returns {string}
197
+ */
198
+ public get toolboxIcon(): string {
199
+ return IconUnderline;
200
+ }
201
+
202
+ /**
203
+ * Sanitizer rule
204
+ *
205
+ * @returns {{u: {class: string}}}
206
+ */
207
+ public static get sanitize(): SanitizerConfig {
208
+ return {
209
+ u: {
210
+ class: Underline.CSS,
211
+ },
212
+ };
213
+ }
214
+ }