@lesjoursfr/edith 2.1.0 → 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/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 +49 -32
- package/src/core/dom.ts +584 -0
- package/src/core/{edit.js → edit.ts} +59 -40
- package/src/core/events.ts +148 -0
- package/src/core/history.ts +28 -0
- package/src/core/index.ts +7 -0
- package/src/core/mode.ts +4 -0
- package/src/core/{range.js → range.ts} +32 -22
- package/src/core/{throttle.js → throttle.ts} +37 -23
- package/src/css/edith.scss +283 -0
- package/src/edith-options.ts +75 -0
- package/src/edith.ts +98 -0
- package/src/index.ts +1 -0
- package/src/ui/button.ts +197 -0
- package/src/ui/editor.ts +403 -0
- package/src/ui/index.ts +3 -0
- package/src/ui/modal.ts +180 -0
- package/src/core/dom.js +0 -353
- package/src/core/event.js +0 -4
- package/src/core/history.js +0 -27
- package/src/core/mode.js +0 -4
- package/src/index.js +0 -90
- package/src/ui/button.js +0 -200
- package/src/ui/editor.js +0 -392
- package/src/ui/modal.js +0 -151
- /package/{src/css/main.scss → dist/css/edith.scss} +0 -0
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
import { getSelection, moveCursorInsideNode, moveCursorAfterNode, selectNodeContents, selectNodes } from "./range.js";
|
|
2
1
|
import {
|
|
3
|
-
hasClass,
|
|
4
|
-
hasTagName,
|
|
5
2
|
cleanDomContent,
|
|
6
3
|
createNodeWith,
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
hasClass,
|
|
5
|
+
hasTagName,
|
|
6
|
+
isHTMLElement,
|
|
9
7
|
isSelfClosing,
|
|
10
|
-
removeNodes,
|
|
11
|
-
removeEmptyTextNodes,
|
|
12
8
|
removeCommentNodes,
|
|
9
|
+
removeEmptyTextNodes,
|
|
10
|
+
removeNodes,
|
|
11
|
+
textifyNode,
|
|
12
|
+
unwrapNode,
|
|
13
13
|
} from "./dom.js";
|
|
14
|
+
import { getSelection, moveCursorAfterNode, moveCursorInsideNode, selectNodeContents, selectNodes } from "./range.js";
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* Split the node at the caret position.
|
|
17
18
|
* @param {Range} range the caret position
|
|
18
|
-
* @param {
|
|
19
|
+
* @param {HTMLElement} node the node to split
|
|
19
20
|
* @returns {Text} the created text node with the caret inside
|
|
20
21
|
*/
|
|
21
|
-
function splitNodeAtCaret(range, node) {
|
|
22
|
+
function splitNodeAtCaret(range: Range, node: HTMLElement): Text {
|
|
22
23
|
// Get the node's parent
|
|
23
|
-
const parent = node.parentNode;
|
|
24
|
+
const parent = node.parentNode as HTMLElement;
|
|
24
25
|
|
|
25
26
|
// Clone the current range & move the starting point to the beginning of the parent's node
|
|
26
27
|
const beforeCaret = range.cloneRange();
|
|
@@ -46,13 +47,13 @@ function splitNodeAtCaret(range, node) {
|
|
|
46
47
|
/**
|
|
47
48
|
* Extract the selection from the node.
|
|
48
49
|
* @param {Range} range the selection to extract
|
|
49
|
-
* @param {
|
|
50
|
-
* @param {
|
|
51
|
-
* @returns {
|
|
50
|
+
* @param {HTMLElement} node the node to split
|
|
51
|
+
* @param {string} tag the tag to remove
|
|
52
|
+
* @returns {HTMLElement} the created node
|
|
52
53
|
*/
|
|
53
|
-
function extractSelectionFromNode(range, node) {
|
|
54
|
+
function extractSelectionFromNode(range: Range, node: HTMLElement): HTMLElement {
|
|
54
55
|
// Get the node's parent
|
|
55
|
-
const parent = node.parentNode;
|
|
56
|
+
const parent = node.parentNode as HTMLElement;
|
|
56
57
|
|
|
57
58
|
// Clone the current range & move the starting point to the beginning of the parent's node
|
|
58
59
|
const beforeSelection = new Range();
|
|
@@ -71,18 +72,20 @@ function extractSelectionFromNode(range, node) {
|
|
|
71
72
|
parent.append(fragAfter);
|
|
72
73
|
|
|
73
74
|
// Remove the parent from the selection
|
|
74
|
-
let current = range.commonAncestorContainer
|
|
75
|
+
let current = !isHTMLElement(range.commonAncestorContainer)
|
|
76
|
+
? (range.commonAncestorContainer.parentNode as HTMLElement)
|
|
77
|
+
: range.commonAncestorContainer;
|
|
75
78
|
while (current.tagName !== node.tagName) {
|
|
76
79
|
// Take the parent
|
|
77
|
-
current = current.parentNode;
|
|
80
|
+
current = current.parentNode as HTMLElement;
|
|
78
81
|
}
|
|
79
|
-
|
|
82
|
+
const innerNodes = unwrapNode(current);
|
|
80
83
|
|
|
81
84
|
// Preserve the selection
|
|
82
85
|
selectNodes(innerNodes);
|
|
83
86
|
|
|
84
87
|
// Return the inserted TextNode
|
|
85
|
-
return
|
|
88
|
+
return innerNodes[0].parentNode as HTMLElement;
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
/**
|
|
@@ -91,9 +94,13 @@ function extractSelectionFromNode(range, node) {
|
|
|
91
94
|
* @param {string} tag the tag name of the node
|
|
92
95
|
* @param {object} options optional parameters
|
|
93
96
|
* @param {string} options.textContent the text content of the node
|
|
94
|
-
* @returns {
|
|
97
|
+
* @returns {HTMLElement} the created node with the caret inside
|
|
95
98
|
*/
|
|
96
|
-
function insertTagAtCaret
|
|
99
|
+
function insertTagAtCaret<K extends keyof HTMLElementTagNameMap>(
|
|
100
|
+
range: Range,
|
|
101
|
+
tag: K,
|
|
102
|
+
options: { textContent?: string } = {}
|
|
103
|
+
): HTMLElementTagNameMap[K] {
|
|
97
104
|
// Create the tag
|
|
98
105
|
const node = document.createElement(tag);
|
|
99
106
|
|
|
@@ -123,12 +130,14 @@ function insertTagAtCaret(range, tag, options) {
|
|
|
123
130
|
* Replace the current selection by the given HTML code.
|
|
124
131
|
* @param {string} html the HTML code
|
|
125
132
|
*/
|
|
126
|
-
export function replaceSelectionByHtml(html) {
|
|
133
|
+
export function replaceSelectionByHtml(html: string): void {
|
|
127
134
|
// Get the caret position
|
|
128
135
|
const { sel, range } = getSelection();
|
|
129
136
|
|
|
130
137
|
// Check if the user has selected something
|
|
131
|
-
if (range === undefined)
|
|
138
|
+
if (range === undefined) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
132
141
|
|
|
133
142
|
// Create the fragment to insert
|
|
134
143
|
const frag = document.createDocumentFragment();
|
|
@@ -151,9 +160,12 @@ export function replaceSelectionByHtml(html) {
|
|
|
151
160
|
* @param {string} tag the tag name of the node
|
|
152
161
|
* @param {object} options optional parameters
|
|
153
162
|
* @param {string} options.textContent the text content of the node
|
|
154
|
-
* @returns {
|
|
163
|
+
* @returns {HTMLElement|Text} the created node or the root node
|
|
155
164
|
*/
|
|
156
|
-
export function wrapInsideTag
|
|
165
|
+
export function wrapInsideTag<K extends keyof HTMLElementTagNameMap>(
|
|
166
|
+
tag: K,
|
|
167
|
+
options: { textContent?: string } = {}
|
|
168
|
+
): HTMLElement | Text | undefined {
|
|
157
169
|
// Get the caret position
|
|
158
170
|
const { sel, range } = getSelection();
|
|
159
171
|
|
|
@@ -165,7 +177,7 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
165
177
|
// Check if there is a Selection
|
|
166
178
|
if (range.collapsed) {
|
|
167
179
|
// Check if a parent element has the same tag name
|
|
168
|
-
let parent = sel.anchorNode
|
|
180
|
+
let parent = sel.anchorNode!.parentNode as HTMLElement;
|
|
169
181
|
while (!hasClass(parent, "edith-visual")) {
|
|
170
182
|
if (hasTagName(parent, tag)) {
|
|
171
183
|
// One of the parent has the same tag name
|
|
@@ -174,7 +186,7 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
174
186
|
}
|
|
175
187
|
|
|
176
188
|
// Take the parent
|
|
177
|
-
parent = parent.parentNode;
|
|
189
|
+
parent = parent.parentNode as HTMLElement;
|
|
178
190
|
}
|
|
179
191
|
|
|
180
192
|
// We just have to insert a new Node at the caret position
|
|
@@ -184,23 +196,23 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
184
196
|
// There is a selection
|
|
185
197
|
// Check if a parent element has the same tag name
|
|
186
198
|
let parent = range.commonAncestorContainer;
|
|
187
|
-
while (!hasClass(parent, "edith-visual")) {
|
|
188
|
-
if (hasTagName(parent, tag)) {
|
|
199
|
+
while (!isHTMLElement(parent) || !hasClass(parent, "edith-visual")) {
|
|
200
|
+
if (isHTMLElement(parent) && hasTagName(parent, tag)) {
|
|
189
201
|
// One of the parent has the same tag name
|
|
190
202
|
// Extract the selection from the parent
|
|
191
203
|
return extractSelectionFromNode(range, parent);
|
|
192
204
|
}
|
|
193
205
|
|
|
194
206
|
// Take the parent
|
|
195
|
-
parent = parent.parentNode;
|
|
207
|
+
parent = parent.parentNode as HTMLElement;
|
|
196
208
|
}
|
|
197
209
|
|
|
198
210
|
// Try to replace all elements with the same tag name in the selection
|
|
199
|
-
for (const el of [...parent.getElementsByTagName(tag)]) {
|
|
211
|
+
for (const el of [...parent.getElementsByTagName(tag)] as HTMLElement[]) {
|
|
200
212
|
// Check if the the Element Intersect the Selection
|
|
201
213
|
if (sel.containsNode(el, true)) {
|
|
202
214
|
// Unwrap the node
|
|
203
|
-
|
|
215
|
+
const innerNodes = unwrapNode(el);
|
|
204
216
|
|
|
205
217
|
// Return the node
|
|
206
218
|
selectNodes(innerNodes);
|
|
@@ -216,7 +228,9 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
216
228
|
range.insertNode(node);
|
|
217
229
|
|
|
218
230
|
// Remove empty tags
|
|
219
|
-
removeNodes(parent, (el) =>
|
|
231
|
+
removeNodes(parent, (el) => {
|
|
232
|
+
return isHTMLElement(el) && !isSelfClosing(el.tagName) && (el.textContent === null || el.textContent.length === 0);
|
|
233
|
+
});
|
|
220
234
|
|
|
221
235
|
// Return the node
|
|
222
236
|
selectNodeContents(node);
|
|
@@ -228,9 +242,9 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
228
242
|
* @param {string} text the text of the link
|
|
229
243
|
* @param {string} href the href of the link
|
|
230
244
|
* @param {boolean} targetBlank add target="_blank" attribute or not
|
|
231
|
-
* @returns the created node
|
|
245
|
+
* @returns {HTMLElement|Text} the created node
|
|
232
246
|
*/
|
|
233
|
-
export function wrapInsideLink(text, href, targetBlank) {
|
|
247
|
+
export function wrapInsideLink(text: string, href: string, targetBlank: boolean): HTMLElement | Text | undefined {
|
|
234
248
|
// Wrap the selection inside a link
|
|
235
249
|
const tag = wrapInsideTag("a", { textContent: text });
|
|
236
250
|
|
|
@@ -239,6 +253,11 @@ export function wrapInsideLink(text, href, targetBlank) {
|
|
|
239
253
|
return;
|
|
240
254
|
}
|
|
241
255
|
|
|
256
|
+
// Check if it's a Text node
|
|
257
|
+
if (!isHTMLElement(tag)) {
|
|
258
|
+
return tag;
|
|
259
|
+
}
|
|
260
|
+
|
|
242
261
|
// Add an href Attribute
|
|
243
262
|
tag.setAttribute("href", href);
|
|
244
263
|
|
|
@@ -254,17 +273,17 @@ export function wrapInsideLink(text, href, targetBlank) {
|
|
|
254
273
|
/**
|
|
255
274
|
* Clear the style in the current selection.
|
|
256
275
|
*/
|
|
257
|
-
export function clearSelectionStyle() {
|
|
276
|
+
export function clearSelectionStyle(): void {
|
|
258
277
|
// Get the caret position
|
|
259
278
|
const { sel, range } = getSelection();
|
|
260
279
|
|
|
261
280
|
// Check if there is something to do
|
|
262
|
-
if (range
|
|
281
|
+
if (range === undefined || !isHTMLElement(range.commonAncestorContainer)) {
|
|
263
282
|
return;
|
|
264
283
|
}
|
|
265
284
|
|
|
266
285
|
// Try to replace all non-text elements by their text
|
|
267
|
-
for (const el of [...range.commonAncestorContainer.children]) {
|
|
286
|
+
for (const el of [...range.commonAncestorContainer.children] as HTMLElement[]) {
|
|
268
287
|
// Check if the the Element Intersect the Selection
|
|
269
288
|
if (sel.containsNode(el, true)) {
|
|
270
289
|
// Replace the node by its text
|
|
@@ -277,9 +296,9 @@ export function clearSelectionStyle() {
|
|
|
277
296
|
* Clean the given HTML code.
|
|
278
297
|
* @param {string} html the HTML code to clean
|
|
279
298
|
* @param {object} style active styles
|
|
280
|
-
* @returns the cleaned HTML code
|
|
299
|
+
* @returns {HTMLElement} the cleaned HTML code
|
|
281
300
|
*/
|
|
282
|
-
export function cleanPastedHtml(html, style) {
|
|
301
|
+
export function cleanPastedHtml(html: string, style: { [keyof: string]: boolean }): HTMLElement {
|
|
283
302
|
// Create a new div with the HTML content
|
|
284
303
|
const result = document.createElement("div");
|
|
285
304
|
result.innerHTML = html;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const eventsNamespace = "edithEvents";
|
|
2
|
+
let eventsGuid = 0;
|
|
3
|
+
|
|
4
|
+
type EdithEvent = { type: string; ns: Array<string> | null; handler: EventListenerOrEventListenerObject };
|
|
5
|
+
|
|
6
|
+
type EdithEvents = {
|
|
7
|
+
[key: string]: EdithEvent;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
interface Window {
|
|
12
|
+
edithEvents: EdithEvents;
|
|
13
|
+
}
|
|
14
|
+
interface Document {
|
|
15
|
+
edithEvents: EdithEvents;
|
|
16
|
+
}
|
|
17
|
+
interface HTMLElement {
|
|
18
|
+
edithEvents: EdithEvents;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export enum Events {
|
|
23
|
+
modeChanged = "edith-mode-changed",
|
|
24
|
+
initialized = "edith-initialized",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse an event type to separate the type & the namespace
|
|
29
|
+
* @param {string} string
|
|
30
|
+
*/
|
|
31
|
+
function parseEventType(string: string): Omit<EdithEvent, "handler"> {
|
|
32
|
+
const [type, ...nsArray] = string.split(".");
|
|
33
|
+
return {
|
|
34
|
+
type,
|
|
35
|
+
ns: nsArray ?? null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Set an event listener on the node.
|
|
41
|
+
* @param {Window|Document|HTMLElement} node
|
|
42
|
+
* @param {string} events
|
|
43
|
+
* @param {Function} handler
|
|
44
|
+
*/
|
|
45
|
+
function addEventListener(
|
|
46
|
+
node: Window | Document | HTMLElement,
|
|
47
|
+
events: string,
|
|
48
|
+
handler: EventListenerOrEventListenerObject
|
|
49
|
+
): void {
|
|
50
|
+
if (node[eventsNamespace] === undefined) {
|
|
51
|
+
node[eventsNamespace] = {};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const event of events.split(" ")) {
|
|
55
|
+
const { type, ns } = parseEventType(event);
|
|
56
|
+
const handlerGuid = (++eventsGuid).toString(10);
|
|
57
|
+
node.addEventListener(type, handler);
|
|
58
|
+
node[eventsNamespace][handlerGuid] = { type, ns, handler };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Remove event listeners from the node.
|
|
64
|
+
* @param {Window|Document|HTMLElement} node
|
|
65
|
+
* @param {string} events
|
|
66
|
+
* @param {Function|undefined} handler
|
|
67
|
+
*/
|
|
68
|
+
function removeEventListener(
|
|
69
|
+
node: Window | Document | HTMLElement,
|
|
70
|
+
events: string,
|
|
71
|
+
handler?: EventListenerOrEventListenerObject
|
|
72
|
+
): void {
|
|
73
|
+
if (node[eventsNamespace] === undefined) {
|
|
74
|
+
node[eventsNamespace] = {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const event of events.split(" ")) {
|
|
78
|
+
const { type, ns } = parseEventType(event);
|
|
79
|
+
|
|
80
|
+
for (const [guid, handlerObj] of Object.entries(node[eventsNamespace])) {
|
|
81
|
+
if (handlerObj.type !== type && type !== "*") {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
(ns === null || handlerObj.ns?.includes(ns[0])) &&
|
|
87
|
+
(handler === undefined || (typeof handler === "function" && handler === handlerObj.handler))
|
|
88
|
+
) {
|
|
89
|
+
delete node[eventsNamespace][guid];
|
|
90
|
+
node.removeEventListener(handlerObj.type, handlerObj.handler);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Set an event listener on every node.
|
|
98
|
+
* @param {Window|Document|HTMLElement|NodeList} nodes
|
|
99
|
+
* @param {string} events
|
|
100
|
+
* @param {Function} handler
|
|
101
|
+
*/
|
|
102
|
+
export function on(
|
|
103
|
+
nodes: Window | Document | HTMLElement | NodeListOf<HTMLElement>,
|
|
104
|
+
events: string,
|
|
105
|
+
handler: EventListenerOrEventListenerObject
|
|
106
|
+
): void {
|
|
107
|
+
if (nodes instanceof NodeList) {
|
|
108
|
+
for (const node of nodes) {
|
|
109
|
+
addEventListener(node, events, handler);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
addEventListener(nodes, events, handler);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Remove event listeners from the node.
|
|
118
|
+
* @param {Window|Document|HTMLElement|NodeList} node
|
|
119
|
+
* @param {string} events
|
|
120
|
+
* @param {Function|undefined} handler
|
|
121
|
+
*/
|
|
122
|
+
export function off(
|
|
123
|
+
nodes: Window | Document | HTMLElement | NodeListOf<HTMLElement>,
|
|
124
|
+
events: string,
|
|
125
|
+
handler?: EventListenerOrEventListenerObject
|
|
126
|
+
): void {
|
|
127
|
+
if (nodes instanceof NodeList) {
|
|
128
|
+
for (const node of nodes) {
|
|
129
|
+
removeEventListener(node, events, handler);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
removeEventListener(nodes, events, handler);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Trigger the EdithEvent on the node.
|
|
138
|
+
* @param {Window|Document|HTMLElement} node
|
|
139
|
+
* @param {string} event
|
|
140
|
+
* @param {Object|undefined} payload
|
|
141
|
+
*/
|
|
142
|
+
export function trigger(
|
|
143
|
+
node: Window | Document | HTMLElement,
|
|
144
|
+
event: string,
|
|
145
|
+
payload?: { [key: string]: unknown }
|
|
146
|
+
): void {
|
|
147
|
+
node.dispatchEvent(new CustomEvent(event, { detail: payload }));
|
|
148
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export class History {
|
|
2
|
+
private buffer: string[] = [];
|
|
3
|
+
|
|
4
|
+
constructor() {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Add a new snapshot to the history.
|
|
8
|
+
* @param {string} doc the element to save
|
|
9
|
+
*/
|
|
10
|
+
public push(doc: string): void {
|
|
11
|
+
this.buffer.push(doc);
|
|
12
|
+
if (this.buffer.length > 20) {
|
|
13
|
+
this.buffer.shift();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get the last saved element
|
|
19
|
+
* @returns {(string|null)} the last saved element or null
|
|
20
|
+
*/
|
|
21
|
+
public pop(): string | null {
|
|
22
|
+
if (this.buffer.length === 0) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return this.buffer.pop()!;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/core/mode.ts
ADDED
|
@@ -1,38 +1,44 @@
|
|
|
1
|
-
import { isSelfClosing } from "./dom.js";
|
|
1
|
+
import { isHTMLElement, isSelfClosing } from "./dom.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {Object} CurrentSelection
|
|
5
5
|
* @property {Selection} sel the current selection
|
|
6
6
|
* @property {(Range|undefined)} range the current range
|
|
7
7
|
*/
|
|
8
|
+
export type CurrentSelection = {
|
|
9
|
+
sel: Selection;
|
|
10
|
+
range?: Range;
|
|
11
|
+
};
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Get the current selection.
|
|
11
15
|
* @returns {CurrentSelection} the current selection
|
|
12
16
|
*/
|
|
13
|
-
export function getSelection() {
|
|
14
|
-
const sel = window.getSelection()
|
|
17
|
+
export function getSelection(): CurrentSelection {
|
|
18
|
+
const sel = window.getSelection()!;
|
|
15
19
|
|
|
16
20
|
return { sel, range: sel.rangeCount ? sel.getRangeAt(0) : undefined };
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
/**
|
|
20
24
|
* Restore the given selection.
|
|
21
|
-
* @param {
|
|
25
|
+
* @param {CurrentSelection} selection the selection to restore
|
|
22
26
|
*/
|
|
23
|
-
export function restoreSelection(selection) {
|
|
24
|
-
const sel = window.getSelection()
|
|
27
|
+
export function restoreSelection(selection: CurrentSelection): void {
|
|
28
|
+
const sel = window.getSelection()!;
|
|
25
29
|
sel.removeAllRanges();
|
|
26
|
-
|
|
30
|
+
if (selection.range !== undefined) {
|
|
31
|
+
sel.addRange(selection.range);
|
|
32
|
+
}
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Move the cursor inside the node.
|
|
31
|
-
* @param {
|
|
37
|
+
* @param {ChildNode} target the targeted node
|
|
32
38
|
*/
|
|
33
|
-
export function moveCursorInsideNode(target) {
|
|
39
|
+
export function moveCursorInsideNode(target: ChildNode): void {
|
|
34
40
|
const range = document.createRange();
|
|
35
|
-
const sel = window.getSelection()
|
|
41
|
+
const sel = window.getSelection()!;
|
|
36
42
|
range.setStart(target, 1);
|
|
37
43
|
range.collapse(true);
|
|
38
44
|
sel.removeAllRanges();
|
|
@@ -41,11 +47,11 @@ export function moveCursorInsideNode(target) {
|
|
|
41
47
|
|
|
42
48
|
/**
|
|
43
49
|
* Move the cursor after the node.
|
|
44
|
-
* @param {
|
|
50
|
+
* @param {ChildNode} target the targeted node
|
|
45
51
|
*/
|
|
46
|
-
export function moveCursorAfterNode(target) {
|
|
52
|
+
export function moveCursorAfterNode(target: ChildNode): void {
|
|
47
53
|
const range = document.createRange();
|
|
48
|
-
const sel = window.getSelection()
|
|
54
|
+
const sel = window.getSelection()!;
|
|
49
55
|
range.setStartAfter(target);
|
|
50
56
|
range.collapse(true);
|
|
51
57
|
sel.removeAllRanges();
|
|
@@ -54,11 +60,11 @@ export function moveCursorAfterNode(target) {
|
|
|
54
60
|
|
|
55
61
|
/**
|
|
56
62
|
* Select the node's content.
|
|
57
|
-
* @param {
|
|
63
|
+
* @param {ChildNode} target the targeted node
|
|
58
64
|
*/
|
|
59
|
-
export function selectNodeContents(target) {
|
|
65
|
+
export function selectNodeContents(target: ChildNode): void {
|
|
60
66
|
const range = document.createRange();
|
|
61
|
-
const sel = window.getSelection()
|
|
67
|
+
const sel = window.getSelection()!;
|
|
62
68
|
range.selectNodeContents(target);
|
|
63
69
|
range.collapse(false);
|
|
64
70
|
sel.removeAllRanges();
|
|
@@ -67,17 +73,17 @@ export function selectNodeContents(target) {
|
|
|
67
73
|
|
|
68
74
|
/**
|
|
69
75
|
* Select the given Nodes.
|
|
70
|
-
* @param {Array<
|
|
76
|
+
* @param {Array<ChildNode>} nodes The list of Nodes to select.
|
|
71
77
|
*/
|
|
72
|
-
export function selectNodes(nodes) {
|
|
78
|
+
export function selectNodes(nodes: ChildNode[]): void {
|
|
73
79
|
// Check if we just have a self-closing tag
|
|
74
|
-
if (nodes.length === 1 && isSelfClosing(nodes[0].tagName)) {
|
|
80
|
+
if (nodes.length === 1 && isHTMLElement(nodes[0]) && isSelfClosing(nodes[0].tagName)) {
|
|
75
81
|
moveCursorAfterNode(nodes[0]); // Move the cursor after the Node
|
|
76
82
|
return;
|
|
77
83
|
}
|
|
78
84
|
// Select Nodes
|
|
79
85
|
const range = document.createRange();
|
|
80
|
-
const sel = window.getSelection()
|
|
86
|
+
const sel = window.getSelection()!;
|
|
81
87
|
range.setStartBefore(nodes[0]);
|
|
82
88
|
range.setEndAfter(nodes[nodes.length - 1]);
|
|
83
89
|
sel.removeAllRanges();
|
|
@@ -86,10 +92,14 @@ export function selectNodes(nodes) {
|
|
|
86
92
|
|
|
87
93
|
/**
|
|
88
94
|
* Check if the current selection is inside the given node.
|
|
89
|
-
* @param {
|
|
95
|
+
* @param {ChildNode} node the targeted node
|
|
90
96
|
* @returns {boolean} true if the selection is inside
|
|
91
97
|
*/
|
|
92
|
-
export function isSelectionInsideNode(node) {
|
|
98
|
+
export function isSelectionInsideNode(node: ChildNode): boolean {
|
|
93
99
|
const { range } = getSelection();
|
|
100
|
+
if (range === undefined) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
94
104
|
return node.contains(range.startContainer) && node.contains(range.endContainer);
|
|
95
105
|
}
|