@lesjoursfr/edith 2.1.2 → 2.1.3
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/build/edith.css +1 -0
- package/build/edith.js +1 -0
- package/dist/core/dom.d.ts +224 -0
- package/dist/core/dom.js +480 -0
- package/dist/core/edit.d.ts +36 -0
- package/dist/core/edit.js +255 -0
- package/dist/core/events.d.ts +47 -0
- package/dist/core/events.js +100 -0
- package/dist/core/history.d.ts +14 -0
- package/dist/core/history.js +24 -0
- package/dist/core/index.d.ts +7 -0
- package/dist/core/index.js +7 -0
- package/dist/core/mode.d.ts +4 -0
- package/dist/core/mode.js +5 -0
- package/dist/core/range.d.ts +45 -0
- package/dist/core/range.js +86 -0
- package/dist/core/throttle.d.ts +53 -0
- package/dist/core/throttle.js +139 -0
- package/dist/css/edith.scss +283 -0
- package/dist/edith-options.d.ts +17 -0
- package/dist/edith-options.js +56 -0
- package/dist/edith.d.ts +30 -0
- package/dist/edith.js +76 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ui/button.d.ts +25 -0
- package/dist/ui/button.js +165 -0
- package/dist/ui/editor.d.ts +37 -0
- package/dist/ui/editor.js +322 -0
- package/dist/ui/index.d.ts +3 -0
- package/dist/ui/index.js +3 -0
- package/dist/ui/modal.d.ts +32 -0
- package/dist/ui/modal.js +145 -0
- package/package.json +1 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { cleanDomContent, createNodeWith, hasClass, hasTagName, isHTMLElement, isSelfClosing, removeCommentNodes, removeEmptyTextNodes, removeNodes, textifyNode, unwrapNode, } from "./dom.js";
|
|
2
|
+
import { getSelection, moveCursorAfterNode, moveCursorInsideNode, selectNodeContents, selectNodes } from "./range.js";
|
|
3
|
+
/**
|
|
4
|
+
* Split the node at the caret position.
|
|
5
|
+
* @param {Range} range the caret position
|
|
6
|
+
* @param {HTMLElement} node the node to split
|
|
7
|
+
* @returns {Text} the created text node with the caret inside
|
|
8
|
+
*/
|
|
9
|
+
function splitNodeAtCaret(range, node) {
|
|
10
|
+
// Get the node's parent
|
|
11
|
+
const parent = node.parentNode;
|
|
12
|
+
// Clone the current range & move the starting point to the beginning of the parent's node
|
|
13
|
+
const beforeCaret = range.cloneRange();
|
|
14
|
+
beforeCaret.setStart(parent, 0);
|
|
15
|
+
// Extract the content before the caret
|
|
16
|
+
const frag = beforeCaret.extractContents();
|
|
17
|
+
// Add a TextNode
|
|
18
|
+
const textNode = document.createTextNode("\u200B");
|
|
19
|
+
frag.append(textNode);
|
|
20
|
+
// Add back the content into the node's parent
|
|
21
|
+
parent.prepend(frag);
|
|
22
|
+
// Move the cursor in the created TextNode
|
|
23
|
+
moveCursorInsideNode(textNode);
|
|
24
|
+
// Return the inserted TextNode
|
|
25
|
+
return textNode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Extract the selection from the node.
|
|
29
|
+
* @param {Range} range the selection to extract
|
|
30
|
+
* @param {HTMLElement} node the node to split
|
|
31
|
+
* @param {string} tag the tag to remove
|
|
32
|
+
* @returns {HTMLElement} the created node
|
|
33
|
+
*/
|
|
34
|
+
function extractSelectionFromNode(range, node) {
|
|
35
|
+
// Get the node's parent
|
|
36
|
+
const parent = node.parentNode;
|
|
37
|
+
// Clone the current range & move the starting point to the beginning of the parent's node
|
|
38
|
+
const beforeSelection = new Range();
|
|
39
|
+
beforeSelection.selectNodeContents(parent);
|
|
40
|
+
beforeSelection.setEnd(range.startContainer, range.startOffset);
|
|
41
|
+
const afterSelection = new Range();
|
|
42
|
+
afterSelection.selectNodeContents(parent);
|
|
43
|
+
afterSelection.setStart(range.endContainer, range.endOffset);
|
|
44
|
+
// Extract the content of the selection
|
|
45
|
+
const fragBefore = beforeSelection.extractContents();
|
|
46
|
+
const fragAfter = afterSelection.extractContents();
|
|
47
|
+
// Add back the content into the node's parent
|
|
48
|
+
parent.prepend(fragBefore);
|
|
49
|
+
parent.append(fragAfter);
|
|
50
|
+
// Remove the parent from the selection
|
|
51
|
+
let current = !isHTMLElement(range.commonAncestorContainer)
|
|
52
|
+
? range.commonAncestorContainer.parentNode
|
|
53
|
+
: range.commonAncestorContainer;
|
|
54
|
+
while (current.tagName !== node.tagName) {
|
|
55
|
+
// Take the parent
|
|
56
|
+
current = current.parentNode;
|
|
57
|
+
}
|
|
58
|
+
const innerNodes = unwrapNode(current);
|
|
59
|
+
// Preserve the selection
|
|
60
|
+
selectNodes(innerNodes);
|
|
61
|
+
// Return the inserted TextNode
|
|
62
|
+
return innerNodes[0].parentNode;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create a node at the caret position.
|
|
66
|
+
* @param {Range} range the caret position
|
|
67
|
+
* @param {string} tag the tag name of the node
|
|
68
|
+
* @param {object} options optional parameters
|
|
69
|
+
* @param {string} options.textContent the text content of the node
|
|
70
|
+
* @returns {HTMLElement} the created node with the caret inside
|
|
71
|
+
*/
|
|
72
|
+
function insertTagAtCaret(range, tag, options = {}) {
|
|
73
|
+
// Create the tag
|
|
74
|
+
const node = document.createElement(tag);
|
|
75
|
+
// Add a zero-width char or the word "lien" to create a valid cursor position inside the element
|
|
76
|
+
if (tag === "a") {
|
|
77
|
+
node.textContent = options.textContent || "lien";
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
node.innerHTML = "\u200B";
|
|
81
|
+
}
|
|
82
|
+
// Insert the tag at the cursor position
|
|
83
|
+
range.insertNode(node);
|
|
84
|
+
// Add an extra space after the tag if it's a link
|
|
85
|
+
if (tag === "a") {
|
|
86
|
+
node.insertAdjacentText("afterend", " ");
|
|
87
|
+
}
|
|
88
|
+
// Move the cursor inside the created tag
|
|
89
|
+
moveCursorInsideNode(node);
|
|
90
|
+
// Return the inserted tag
|
|
91
|
+
return node;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Replace the current selection by the given HTML code.
|
|
95
|
+
* @param {string} html the HTML code
|
|
96
|
+
*/
|
|
97
|
+
export function replaceSelectionByHtml(html) {
|
|
98
|
+
// Get the caret position
|
|
99
|
+
const { sel, range } = getSelection();
|
|
100
|
+
// Check if the user has selected something
|
|
101
|
+
if (range === undefined) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Create the fragment to insert
|
|
105
|
+
const frag = document.createDocumentFragment();
|
|
106
|
+
// Create the nodes to insert
|
|
107
|
+
const el = createNodeWith("div", { innerHTML: html });
|
|
108
|
+
frag.append(...el.childNodes);
|
|
109
|
+
const lastNode = frag.childNodes[frag.childNodes.length - 1];
|
|
110
|
+
// Replace the current selection by the pasted content
|
|
111
|
+
sel.deleteFromDocument();
|
|
112
|
+
range.insertNode(frag);
|
|
113
|
+
// Preserve the selection
|
|
114
|
+
moveCursorAfterNode(lastNode);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Wrap the current selection inside a new node.
|
|
118
|
+
* @param {string} tag the tag name of the node
|
|
119
|
+
* @param {object} options optional parameters
|
|
120
|
+
* @param {string} options.textContent the text content of the node
|
|
121
|
+
* @returns {HTMLElement|Text} the created node or the root node
|
|
122
|
+
*/
|
|
123
|
+
export function wrapInsideTag(tag, options = {}) {
|
|
124
|
+
// Get the caret position
|
|
125
|
+
const { sel, range } = getSelection();
|
|
126
|
+
// Check if the user has selected something
|
|
127
|
+
if (range === undefined) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Check if there is a Selection
|
|
131
|
+
if (range.collapsed) {
|
|
132
|
+
// Check if a parent element has the same tag name
|
|
133
|
+
let parent = sel.anchorNode.parentNode;
|
|
134
|
+
while (!hasClass(parent, "edith-visual")) {
|
|
135
|
+
if (hasTagName(parent, tag)) {
|
|
136
|
+
// One of the parent has the same tag name
|
|
137
|
+
// Split the parent at the caret & insert a TextNode
|
|
138
|
+
return splitNodeAtCaret(range, parent);
|
|
139
|
+
}
|
|
140
|
+
// Take the parent
|
|
141
|
+
parent = parent.parentNode;
|
|
142
|
+
}
|
|
143
|
+
// We just have to insert a new Node at the caret position
|
|
144
|
+
return insertTagAtCaret(range, tag, options);
|
|
145
|
+
}
|
|
146
|
+
// There is a selection
|
|
147
|
+
// Check if a parent element has the same tag name
|
|
148
|
+
let parent = range.commonAncestorContainer;
|
|
149
|
+
while (!isHTMLElement(parent) || !hasClass(parent, "edith-visual")) {
|
|
150
|
+
if (isHTMLElement(parent) && hasTagName(parent, tag)) {
|
|
151
|
+
// One of the parent has the same tag name
|
|
152
|
+
// Extract the selection from the parent
|
|
153
|
+
return extractSelectionFromNode(range, parent);
|
|
154
|
+
}
|
|
155
|
+
// Take the parent
|
|
156
|
+
parent = parent.parentNode;
|
|
157
|
+
}
|
|
158
|
+
// Try to replace all elements with the same tag name in the selection
|
|
159
|
+
for (const el of [...parent.getElementsByTagName(tag)]) {
|
|
160
|
+
// Check if the the Element Intersect the Selection
|
|
161
|
+
if (sel.containsNode(el, true)) {
|
|
162
|
+
// Unwrap the node
|
|
163
|
+
const innerNodes = unwrapNode(el);
|
|
164
|
+
// Return the node
|
|
165
|
+
selectNodes(innerNodes);
|
|
166
|
+
parent.normalize();
|
|
167
|
+
return parent;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// Nothing was replaced
|
|
171
|
+
// Wrap the selection inside the given tag
|
|
172
|
+
const node = document.createElement(tag);
|
|
173
|
+
node.appendChild(range.extractContents());
|
|
174
|
+
range.insertNode(node);
|
|
175
|
+
// Remove empty tags
|
|
176
|
+
removeNodes(parent, (el) => {
|
|
177
|
+
return isHTMLElement(el) && !isSelfClosing(el.tagName) && (el.textContent === null || el.textContent.length === 0);
|
|
178
|
+
});
|
|
179
|
+
// Return the node
|
|
180
|
+
selectNodeContents(node);
|
|
181
|
+
return node;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Wrap the current selection inside a link.
|
|
185
|
+
* @param {string} text the text of the link
|
|
186
|
+
* @param {string} href the href of the link
|
|
187
|
+
* @param {boolean} targetBlank add target="_blank" attribute or not
|
|
188
|
+
* @returns {HTMLElement|Text} the created node
|
|
189
|
+
*/
|
|
190
|
+
export function wrapInsideLink(text, href, targetBlank) {
|
|
191
|
+
// Wrap the selection inside a link
|
|
192
|
+
const tag = wrapInsideTag("a", { textContent: text });
|
|
193
|
+
// Check if we have a tag
|
|
194
|
+
if (tag === undefined) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
// Check if it's a Text node
|
|
198
|
+
if (!isHTMLElement(tag)) {
|
|
199
|
+
return tag;
|
|
200
|
+
}
|
|
201
|
+
// Add an href Attribute
|
|
202
|
+
tag.setAttribute("href", href);
|
|
203
|
+
// Create a target="_blank" attribute if required
|
|
204
|
+
if (targetBlank === true) {
|
|
205
|
+
tag.setAttribute("target", "_blank");
|
|
206
|
+
}
|
|
207
|
+
// Return the tag
|
|
208
|
+
return tag;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Clear the style in the current selection.
|
|
212
|
+
*/
|
|
213
|
+
export function clearSelectionStyle() {
|
|
214
|
+
// Get the caret position
|
|
215
|
+
const { sel, range } = getSelection();
|
|
216
|
+
// Check if there is something to do
|
|
217
|
+
if (range === undefined || !isHTMLElement(range.commonAncestorContainer)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Try to replace all non-text elements by their text
|
|
221
|
+
for (const el of [...range.commonAncestorContainer.children]) {
|
|
222
|
+
// Check if the the Element Intersect the Selection
|
|
223
|
+
if (sel.containsNode(el, true)) {
|
|
224
|
+
// Replace the node by its text
|
|
225
|
+
textifyNode(el);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Clean the given HTML code.
|
|
231
|
+
* @param {string} html the HTML code to clean
|
|
232
|
+
* @param {object} style active styles
|
|
233
|
+
* @returns {HTMLElement} the cleaned HTML code
|
|
234
|
+
*/
|
|
235
|
+
export function cleanPastedHtml(html, style) {
|
|
236
|
+
// Create a new div with the HTML content
|
|
237
|
+
const result = document.createElement("div");
|
|
238
|
+
result.innerHTML = html;
|
|
239
|
+
// Clean the HTML content
|
|
240
|
+
cleanDomContent(result, style);
|
|
241
|
+
result.normalize();
|
|
242
|
+
// Clean empty text nodes
|
|
243
|
+
removeEmptyTextNodes(result);
|
|
244
|
+
// Fix extra stuff in the HTML code :
|
|
245
|
+
// - Clean spaces
|
|
246
|
+
// - Merge siblings tags
|
|
247
|
+
result.innerHTML = result.innerHTML
|
|
248
|
+
.replace(/\s* \s*/g, " ")
|
|
249
|
+
.replace(/\s+/g, " ")
|
|
250
|
+
.replace(/(<\/b>[\n\r\s]*<b>|<\/i>[\n\r\s]*<i>|<\/u>[\n\r\s]*<u>|<\/s>[\n\r\s]*<s>)/g, " ");
|
|
251
|
+
// Clean comment nodes
|
|
252
|
+
removeCommentNodes(result);
|
|
253
|
+
// Return Cleaned HTML
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
type EdithEvent = {
|
|
2
|
+
type: string;
|
|
3
|
+
ns: Array<string> | null;
|
|
4
|
+
handler: EventListenerOrEventListenerObject;
|
|
5
|
+
};
|
|
6
|
+
type EdithEvents = {
|
|
7
|
+
[key: string]: EdithEvent;
|
|
8
|
+
};
|
|
9
|
+
declare global {
|
|
10
|
+
interface Window {
|
|
11
|
+
edithEvents: EdithEvents;
|
|
12
|
+
}
|
|
13
|
+
interface Document {
|
|
14
|
+
edithEvents: EdithEvents;
|
|
15
|
+
}
|
|
16
|
+
interface HTMLElement {
|
|
17
|
+
edithEvents: EdithEvents;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export declare enum Events {
|
|
21
|
+
modeChanged = "edith-mode-changed",
|
|
22
|
+
initialized = "edith-initialized"
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Set an event listener on every node.
|
|
26
|
+
* @param {Window|Document|HTMLElement|NodeList} nodes
|
|
27
|
+
* @param {string} events
|
|
28
|
+
* @param {Function} handler
|
|
29
|
+
*/
|
|
30
|
+
export declare function on(nodes: Window | Document | HTMLElement | NodeListOf<HTMLElement>, events: string, handler: EventListenerOrEventListenerObject): void;
|
|
31
|
+
/**
|
|
32
|
+
* Remove event listeners from the node.
|
|
33
|
+
* @param {Window|Document|HTMLElement|NodeList} node
|
|
34
|
+
* @param {string} events
|
|
35
|
+
* @param {Function|undefined} handler
|
|
36
|
+
*/
|
|
37
|
+
export declare function off(nodes: Window | Document | HTMLElement | NodeListOf<HTMLElement>, events: string, handler?: EventListenerOrEventListenerObject): void;
|
|
38
|
+
/**
|
|
39
|
+
* Trigger the EdithEvent on the node.
|
|
40
|
+
* @param {Window|Document|HTMLElement} node
|
|
41
|
+
* @param {string} event
|
|
42
|
+
* @param {Object|undefined} payload
|
|
43
|
+
*/
|
|
44
|
+
export declare function trigger(node: Window | Document | HTMLElement, event: string, payload?: {
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
}): void;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const eventsNamespace = "edithEvents";
|
|
2
|
+
let eventsGuid = 0;
|
|
3
|
+
export var Events;
|
|
4
|
+
(function (Events) {
|
|
5
|
+
Events["modeChanged"] = "edith-mode-changed";
|
|
6
|
+
Events["initialized"] = "edith-initialized";
|
|
7
|
+
})(Events || (Events = {}));
|
|
8
|
+
/**
|
|
9
|
+
* Parse an event type to separate the type & the namespace
|
|
10
|
+
* @param {string} string
|
|
11
|
+
*/
|
|
12
|
+
function parseEventType(string) {
|
|
13
|
+
const [type, ...nsArray] = string.split(".");
|
|
14
|
+
return {
|
|
15
|
+
type,
|
|
16
|
+
ns: nsArray ?? null,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Set an event listener on the node.
|
|
21
|
+
* @param {Window|Document|HTMLElement} node
|
|
22
|
+
* @param {string} events
|
|
23
|
+
* @param {Function} handler
|
|
24
|
+
*/
|
|
25
|
+
function addEventListener(node, events, handler) {
|
|
26
|
+
if (node[eventsNamespace] === undefined) {
|
|
27
|
+
node[eventsNamespace] = {};
|
|
28
|
+
}
|
|
29
|
+
for (const event of events.split(" ")) {
|
|
30
|
+
const { type, ns } = parseEventType(event);
|
|
31
|
+
const handlerGuid = (++eventsGuid).toString(10);
|
|
32
|
+
node.addEventListener(type, handler);
|
|
33
|
+
node[eventsNamespace][handlerGuid] = { type, ns, handler };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Remove event listeners from the node.
|
|
38
|
+
* @param {Window|Document|HTMLElement} node
|
|
39
|
+
* @param {string} events
|
|
40
|
+
* @param {Function|undefined} handler
|
|
41
|
+
*/
|
|
42
|
+
function removeEventListener(node, events, handler) {
|
|
43
|
+
if (node[eventsNamespace] === undefined) {
|
|
44
|
+
node[eventsNamespace] = {};
|
|
45
|
+
}
|
|
46
|
+
for (const event of events.split(" ")) {
|
|
47
|
+
const { type, ns } = parseEventType(event);
|
|
48
|
+
for (const [guid, handlerObj] of Object.entries(node[eventsNamespace])) {
|
|
49
|
+
if (handlerObj.type !== type && type !== "*") {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if ((ns === null || handlerObj.ns?.includes(ns[0])) &&
|
|
53
|
+
(handler === undefined || (typeof handler === "function" && handler === handlerObj.handler))) {
|
|
54
|
+
delete node[eventsNamespace][guid];
|
|
55
|
+
node.removeEventListener(handlerObj.type, handlerObj.handler);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Set an event listener on every node.
|
|
62
|
+
* @param {Window|Document|HTMLElement|NodeList} nodes
|
|
63
|
+
* @param {string} events
|
|
64
|
+
* @param {Function} handler
|
|
65
|
+
*/
|
|
66
|
+
export function on(nodes, events, handler) {
|
|
67
|
+
if (nodes instanceof NodeList) {
|
|
68
|
+
for (const node of nodes) {
|
|
69
|
+
addEventListener(node, events, handler);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
addEventListener(nodes, events, handler);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Remove event listeners from the node.
|
|
78
|
+
* @param {Window|Document|HTMLElement|NodeList} node
|
|
79
|
+
* @param {string} events
|
|
80
|
+
* @param {Function|undefined} handler
|
|
81
|
+
*/
|
|
82
|
+
export function off(nodes, events, handler) {
|
|
83
|
+
if (nodes instanceof NodeList) {
|
|
84
|
+
for (const node of nodes) {
|
|
85
|
+
removeEventListener(node, events, handler);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
removeEventListener(nodes, events, handler);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Trigger the EdithEvent on the node.
|
|
94
|
+
* @param {Window|Document|HTMLElement} node
|
|
95
|
+
* @param {string} event
|
|
96
|
+
* @param {Object|undefined} payload
|
|
97
|
+
*/
|
|
98
|
+
export function trigger(node, event, payload) {
|
|
99
|
+
node.dispatchEvent(new CustomEvent(event, { detail: payload }));
|
|
100
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class History {
|
|
2
|
+
private buffer;
|
|
3
|
+
constructor();
|
|
4
|
+
/**
|
|
5
|
+
* Add a new snapshot to the history.
|
|
6
|
+
* @param {string} doc the element to save
|
|
7
|
+
*/
|
|
8
|
+
push(doc: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Get the last saved element
|
|
11
|
+
* @returns {(string|null)} the last saved element or null
|
|
12
|
+
*/
|
|
13
|
+
pop(): string | null;
|
|
14
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class History {
|
|
2
|
+
buffer = [];
|
|
3
|
+
constructor() { }
|
|
4
|
+
/**
|
|
5
|
+
* Add a new snapshot to the history.
|
|
6
|
+
* @param {string} doc the element to save
|
|
7
|
+
*/
|
|
8
|
+
push(doc) {
|
|
9
|
+
this.buffer.push(doc);
|
|
10
|
+
if (this.buffer.length > 20) {
|
|
11
|
+
this.buffer.shift();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the last saved element
|
|
16
|
+
* @returns {(string|null)} the last saved element or null
|
|
17
|
+
*/
|
|
18
|
+
pop() {
|
|
19
|
+
if (this.buffer.length === 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return this.buffer.pop();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} CurrentSelection
|
|
3
|
+
* @property {Selection} sel the current selection
|
|
4
|
+
* @property {(Range|undefined)} range the current range
|
|
5
|
+
*/
|
|
6
|
+
export type CurrentSelection = {
|
|
7
|
+
sel: Selection;
|
|
8
|
+
range?: Range;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Get the current selection.
|
|
12
|
+
* @returns {CurrentSelection} the current selection
|
|
13
|
+
*/
|
|
14
|
+
export declare function getSelection(): CurrentSelection;
|
|
15
|
+
/**
|
|
16
|
+
* Restore the given selection.
|
|
17
|
+
* @param {CurrentSelection} selection the selection to restore
|
|
18
|
+
*/
|
|
19
|
+
export declare function restoreSelection(selection: CurrentSelection): void;
|
|
20
|
+
/**
|
|
21
|
+
* Move the cursor inside the node.
|
|
22
|
+
* @param {ChildNode} target the targeted node
|
|
23
|
+
*/
|
|
24
|
+
export declare function moveCursorInsideNode(target: ChildNode): void;
|
|
25
|
+
/**
|
|
26
|
+
* Move the cursor after the node.
|
|
27
|
+
* @param {ChildNode} target the targeted node
|
|
28
|
+
*/
|
|
29
|
+
export declare function moveCursorAfterNode(target: ChildNode): void;
|
|
30
|
+
/**
|
|
31
|
+
* Select the node's content.
|
|
32
|
+
* @param {ChildNode} target the targeted node
|
|
33
|
+
*/
|
|
34
|
+
export declare function selectNodeContents(target: ChildNode): void;
|
|
35
|
+
/**
|
|
36
|
+
* Select the given Nodes.
|
|
37
|
+
* @param {Array<ChildNode>} nodes The list of Nodes to select.
|
|
38
|
+
*/
|
|
39
|
+
export declare function selectNodes(nodes: ChildNode[]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Check if the current selection is inside the given node.
|
|
42
|
+
* @param {ChildNode} node the targeted node
|
|
43
|
+
* @returns {boolean} true if the selection is inside
|
|
44
|
+
*/
|
|
45
|
+
export declare function isSelectionInsideNode(node: ChildNode): boolean;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { isHTMLElement, isSelfClosing } from "./dom.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get the current selection.
|
|
4
|
+
* @returns {CurrentSelection} the current selection
|
|
5
|
+
*/
|
|
6
|
+
export function getSelection() {
|
|
7
|
+
const sel = window.getSelection();
|
|
8
|
+
return { sel, range: sel.rangeCount ? sel.getRangeAt(0) : undefined };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Restore the given selection.
|
|
12
|
+
* @param {CurrentSelection} selection the selection to restore
|
|
13
|
+
*/
|
|
14
|
+
export function restoreSelection(selection) {
|
|
15
|
+
const sel = window.getSelection();
|
|
16
|
+
sel.removeAllRanges();
|
|
17
|
+
if (selection.range !== undefined) {
|
|
18
|
+
sel.addRange(selection.range);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Move the cursor inside the node.
|
|
23
|
+
* @param {ChildNode} target the targeted node
|
|
24
|
+
*/
|
|
25
|
+
export function moveCursorInsideNode(target) {
|
|
26
|
+
const range = document.createRange();
|
|
27
|
+
const sel = window.getSelection();
|
|
28
|
+
range.setStart(target, 1);
|
|
29
|
+
range.collapse(true);
|
|
30
|
+
sel.removeAllRanges();
|
|
31
|
+
sel.addRange(range);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Move the cursor after the node.
|
|
35
|
+
* @param {ChildNode} target the targeted node
|
|
36
|
+
*/
|
|
37
|
+
export function moveCursorAfterNode(target) {
|
|
38
|
+
const range = document.createRange();
|
|
39
|
+
const sel = window.getSelection();
|
|
40
|
+
range.setStartAfter(target);
|
|
41
|
+
range.collapse(true);
|
|
42
|
+
sel.removeAllRanges();
|
|
43
|
+
sel.addRange(range);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Select the node's content.
|
|
47
|
+
* @param {ChildNode} target the targeted node
|
|
48
|
+
*/
|
|
49
|
+
export function selectNodeContents(target) {
|
|
50
|
+
const range = document.createRange();
|
|
51
|
+
const sel = window.getSelection();
|
|
52
|
+
range.selectNodeContents(target);
|
|
53
|
+
range.collapse(false);
|
|
54
|
+
sel.removeAllRanges();
|
|
55
|
+
sel.addRange(range);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Select the given Nodes.
|
|
59
|
+
* @param {Array<ChildNode>} nodes The list of Nodes to select.
|
|
60
|
+
*/
|
|
61
|
+
export function selectNodes(nodes) {
|
|
62
|
+
// Check if we just have a self-closing tag
|
|
63
|
+
if (nodes.length === 1 && isHTMLElement(nodes[0]) && isSelfClosing(nodes[0].tagName)) {
|
|
64
|
+
moveCursorAfterNode(nodes[0]); // Move the cursor after the Node
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
// Select Nodes
|
|
68
|
+
const range = document.createRange();
|
|
69
|
+
const sel = window.getSelection();
|
|
70
|
+
range.setStartBefore(nodes[0]);
|
|
71
|
+
range.setEndAfter(nodes[nodes.length - 1]);
|
|
72
|
+
sel.removeAllRanges();
|
|
73
|
+
sel.addRange(range);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if the current selection is inside the given node.
|
|
77
|
+
* @param {ChildNode} node the targeted node
|
|
78
|
+
* @returns {boolean} true if the selection is inside
|
|
79
|
+
*/
|
|
80
|
+
export function isSelectionInsideNode(node) {
|
|
81
|
+
const { range } = getSelection();
|
|
82
|
+
if (range === undefined) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return node.contains(range.startContainer) && node.contains(range.endContainer);
|
|
86
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Based on lodash version of throttle : https://github.com/lodash/lodash/blob/master/throttle.js
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Creates a debounced function that delays invoking `func` until after `wait`
|
|
6
|
+
* milliseconds have elapsed since the last time the debounced function was
|
|
7
|
+
* invoked, or until the next browser frame is drawn. Provide `options` to indicate
|
|
8
|
+
* whether `func` should be invoked on the leading and/or trailing edge of the
|
|
9
|
+
* `wait` timeout. The `func` is invoked with the last arguments provided to the
|
|
10
|
+
* debounced function. Subsequent calls to the debounced function return the
|
|
11
|
+
* result of the last `func` invocation.
|
|
12
|
+
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
|
13
|
+
* invoked on the trailing edge of the timeout only if the debounced function
|
|
14
|
+
* is invoked more than once during the `wait` timeout.
|
|
15
|
+
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
|
16
|
+
* until the next tick, similar to `setTimeout` with a timeout of `0`.
|
|
17
|
+
* @param {Function} func The function to debounce
|
|
18
|
+
* @param {number} wait The number of milliseconds to delay
|
|
19
|
+
* @param {Object} [options={}] The options object
|
|
20
|
+
* @param {boolean} [options.leading=false] Specify invoking on the leading edge of the timeout
|
|
21
|
+
* @param {boolean} [options.trailing=true] Specify invoking on the trailing edge of the timeout
|
|
22
|
+
* @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's invoked
|
|
23
|
+
* @returns {Function} Returns the new debounced function
|
|
24
|
+
*/
|
|
25
|
+
declare function debounce<F extends (...args: any) => any>(func: F, wait: number, options?: {
|
|
26
|
+
leading?: boolean;
|
|
27
|
+
trailing?: boolean;
|
|
28
|
+
maxWait?: number;
|
|
29
|
+
}): (...args: Parameters<F>) => ReturnType<F>;
|
|
30
|
+
/**
|
|
31
|
+
* Creates a throttled function that only invokes `func` at most once per
|
|
32
|
+
* every `wait` milliseconds (or once per browser frame). Provide `options` to indicate
|
|
33
|
+
* whether `func` should be invoked on the leading and/or trailing edge of the
|
|
34
|
+
* `wait` timeout. The `func` is invoked with the last arguments provided to the
|
|
35
|
+
* throttled function. Subsequent calls to the throttled function return the
|
|
36
|
+
* result of the last `func` invocation.
|
|
37
|
+
* **Note:** If `leading` and `trailing` options are `true`, `func` is
|
|
38
|
+
* invoked on the trailing edge of the timeout only if the throttled function
|
|
39
|
+
* is invoked more than once during the `wait` timeout.
|
|
40
|
+
* If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
|
|
41
|
+
* until the next tick, similar to `setTimeout` with a timeout of `0`.
|
|
42
|
+
* @param {Function} func The function to throttle
|
|
43
|
+
* @param {number} wait The number of milliseconds to throttle invocations to
|
|
44
|
+
* @param {Object} [options={}] The options object
|
|
45
|
+
* @param {boolean} [options.leading=true] Specify invoking on the leading edge of the timeout
|
|
46
|
+
* @param {boolean} [options.trailing=true] Specify invoking on the trailing edge of the timeout
|
|
47
|
+
* @returns {Function} Returns the new throttled function
|
|
48
|
+
*/
|
|
49
|
+
declare function throttle<F extends (...args: any) => any>(func: F, wait: number, options?: {
|
|
50
|
+
leading?: boolean;
|
|
51
|
+
trailing?: boolean;
|
|
52
|
+
}): (...args: Parameters<F>) => ReturnType<F>;
|
|
53
|
+
export { debounce, throttle };
|