@ebl-vue/editor-full 1.0.8

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