@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.
- package/.postcssrc.yml +33 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +2565 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +55 -0
- package/postcss.config.js +15 -0
- package/src/components/Editor/Editor.vue +209 -0
- package/src/components/index.ts +27 -0
- package/src/constants/index.ts +1 -0
- package/src/i18n/zh-cn.ts +151 -0
- package/src/icons/index.ts +78 -0
- package/src/index.ts +11 -0
- package/src/installer.ts +22 -0
- package/src/plugins/alert/index.css +150 -0
- package/src/plugins/alert/index.ts +463 -0
- package/src/plugins/block-alignment/index.css +9 -0
- package/src/plugins/block-alignment/index.ts +116 -0
- package/src/plugins/block-alignment/readme.md +1 -0
- package/src/plugins/code/LICENSE +21 -0
- package/src/plugins/code/index.css +120 -0
- package/src/plugins/code/index.ts +530 -0
- package/src/plugins/code/utils/string.ts +34 -0
- package/src/plugins/color-picker/index.ts +138 -0
- package/src/plugins/color-picker/styles.css +27 -0
- package/src/plugins/delimiter/index.css +14 -0
- package/src/plugins/delimiter/index.ts +122 -0
- package/src/plugins/drag-drop/index.css +19 -0
- package/src/plugins/drag-drop/index.ts +151 -0
- package/src/plugins/drag-drop/readme.md +1 -0
- package/src/plugins/header/H1.ts +405 -0
- package/src/plugins/header/H2.ts +403 -0
- package/src/plugins/header/H3.ts +404 -0
- package/src/plugins/header/H4.ts +405 -0
- package/src/plugins/header/H5.ts +405 -0
- package/src/plugins/header/H6.ts +406 -0
- package/src/plugins/header/index.css +20 -0
- package/src/plugins/header/index.ts +15 -0
- package/src/plugins/header/types.d.ts +46 -0
- package/src/plugins/indent/index.css +86 -0
- package/src/plugins/indent/index.ts +697 -0
- package/src/plugins/inline-code/index.css +11 -0
- package/src/plugins/inline-code/index.ts +205 -0
- package/src/plugins/list/ListRenderer/ChecklistRenderer.ts +211 -0
- package/src/plugins/list/ListRenderer/ListRenderer.ts +73 -0
- package/src/plugins/list/ListRenderer/OrderedListRenderer.ts +123 -0
- package/src/plugins/list/ListRenderer/UnorderedListRenderer.ts +123 -0
- package/src/plugins/list/ListRenderer/index.ts +6 -0
- package/src/plugins/list/ListTabulator/index.ts +1179 -0
- package/src/plugins/list/index.ts +502 -0
- package/src/plugins/list/styles/CssPrefix.ts +4 -0
- package/src/plugins/list/styles/icons/index.ts +10 -0
- package/src/plugins/list/styles/input.css +36 -0
- package/src/plugins/list/styles/list.css +165 -0
- package/src/plugins/list/types/Elements.ts +14 -0
- package/src/plugins/list/types/ItemMeta.ts +40 -0
- package/src/plugins/list/types/ListParams.ts +102 -0
- package/src/plugins/list/types/ListRenderer.ts +6 -0
- package/src/plugins/list/types/OlCounterType.ts +63 -0
- package/src/plugins/list/types/index.ts +14 -0
- package/src/plugins/list/utils/focusItem.ts +18 -0
- package/src/plugins/list/utils/getChildItems.ts +40 -0
- package/src/plugins/list/utils/getItemChildWrapper.ts +10 -0
- package/src/plugins/list/utils/getItemContentElement.ts +10 -0
- package/src/plugins/list/utils/getSiblings.ts +52 -0
- package/src/plugins/list/utils/isLastItem.ts +9 -0
- package/src/plugins/list/utils/itemHasSublist.ts +10 -0
- package/src/plugins/list/utils/normalizeData.ts +84 -0
- package/src/plugins/list/utils/removeChildWrapperIfEmpty.ts +31 -0
- package/src/plugins/list/utils/renderToolboxInput.ts +105 -0
- package/src/plugins/list/utils/stripNumbers.ts +7 -0
- package/src/plugins/list/utils/type-guards.ts +8 -0
- package/src/plugins/list.md +15 -0
- package/src/plugins/marker/index.css +4 -0
- package/src/plugins/marker/index.ts +187 -0
- package/src/plugins/paragraph/index.css +23 -0
- package/src/plugins/paragraph/index.ts +380 -0
- package/src/plugins/paragraph/types/icons.d.ts +4 -0
- package/src/plugins/paragraph/utils/makeFragment.ts +17 -0
- package/src/plugins/quote/index.css +26 -0
- package/src/plugins/quote/index.ts +206 -0
- package/src/plugins/table/index.ts +4 -0
- package/src/plugins/table/plugin.ts +254 -0
- package/src/plugins/table/style.css +388 -0
- package/src/plugins/table/table.ts +1192 -0
- package/src/plugins/table/toolbox.ts +165 -0
- package/src/plugins/table/utils/dom.ts +128 -0
- package/src/plugins/table/utils/popover.ts +172 -0
- package/src/plugins/table/utils/throttled.ts +22 -0
- package/src/plugins/underline/index.css +3 -0
- package/src/plugins/underline/index.ts +216 -0
- package/src/plugins/undo/index.ts +509 -0
- package/src/plugins/undo/observer.ts +101 -0
- package/src/style.css +89 -0
- package/src/utils/index.ts +15 -0
- package/src/utils/install.ts +19 -0
- package/tsconfig.json +37 -0
- package/types/index.d.ts +13 -0
- package/types/plugins/index.d.ts +0 -0
- 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,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
|
+
}
|