@lesjoursfr/edith 2.1.0 → 2.1.2
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/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/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 → edith.scss} +0 -0
package/src/core/dom.js
DELETED
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Check if the node has the given tag name, or if its tag name is in the given list.
|
|
3
|
-
* @param {Node} node the element to check
|
|
4
|
-
* @param {(string|Array)} tags a tag name or a list of tag name
|
|
5
|
-
* @returns {boolean} true if the node has the given tag name
|
|
6
|
-
*/
|
|
7
|
-
export function hasTagName(node, tags) {
|
|
8
|
-
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
9
|
-
return false;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (typeof tags === "string") {
|
|
13
|
-
return node.tagName === tags.toUpperCase();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return tags.some((tag) => node.tagName === tag.toUpperCase());
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Check if the node has the given class name.
|
|
21
|
-
* @param {Node} node the element to check
|
|
22
|
-
* @param {(string|Array)} className a class name
|
|
23
|
-
* @returns {boolean} true if the node has the given class name
|
|
24
|
-
*/
|
|
25
|
-
export function hasClass(node, className) {
|
|
26
|
-
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return node.classList.contains(className);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Create a new node.
|
|
35
|
-
* @param {string} tag the tag name of the node
|
|
36
|
-
* @param {object} options optional parameters
|
|
37
|
-
* @param {string} options.innerHTML the HTML code of the node
|
|
38
|
-
* @param {string} options.textContent the text content of the node
|
|
39
|
-
* @param {object} options.attributes attributes of the node
|
|
40
|
-
* @returns {Node} the created node
|
|
41
|
-
*/
|
|
42
|
-
export function createNodeWith(tag, { innerHTML, textContent, attributes } = {}) {
|
|
43
|
-
const node = document.createElement(tag);
|
|
44
|
-
|
|
45
|
-
if (attributes) {
|
|
46
|
-
for (const key in attributes) {
|
|
47
|
-
if (Object.hasOwnProperty.call(attributes, key)) {
|
|
48
|
-
node.setAttribute(key, attributes[key]);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (typeof innerHTML === "string") {
|
|
54
|
-
node.innerHTML = innerHTML;
|
|
55
|
-
} else if (typeof textContent === "string") {
|
|
56
|
-
node.textContent = textContent;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return node;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Replace a node.
|
|
64
|
-
* @param {Node} node the node to replace
|
|
65
|
-
* @param {Node} replacement the new node
|
|
66
|
-
* @returns {Node} the new node
|
|
67
|
-
*/
|
|
68
|
-
export function replaceNodeWith(node, replacement) {
|
|
69
|
-
node.replaceWith(replacement);
|
|
70
|
-
return replacement;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Replace the node by its child nodes.
|
|
75
|
-
* @param {Node} node the node to replace
|
|
76
|
-
* @returns {Array} its child nodes
|
|
77
|
-
*/
|
|
78
|
-
export function unwrapNode(node) {
|
|
79
|
-
const newNodes = [...node.childNodes];
|
|
80
|
-
node.replaceWith(...newNodes);
|
|
81
|
-
return newNodes;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Replace the node by its text content.
|
|
86
|
-
* @param {Node} node the node to replace
|
|
87
|
-
* @returns {Text} the created Text node
|
|
88
|
-
*/
|
|
89
|
-
export function textifyNode(node) {
|
|
90
|
-
const newNode = document.createTextNode(node.textContent);
|
|
91
|
-
node.replaceWith(newNode);
|
|
92
|
-
return newNode;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Know if a tag si a self-closing tag
|
|
97
|
-
* @param {String} tagName
|
|
98
|
-
* @returns {Boolean}
|
|
99
|
-
*/
|
|
100
|
-
export function isSelfClosing(tagName) {
|
|
101
|
-
return [
|
|
102
|
-
"AREA",
|
|
103
|
-
"BASE",
|
|
104
|
-
"BR",
|
|
105
|
-
"COL",
|
|
106
|
-
"EMBED",
|
|
107
|
-
"HR",
|
|
108
|
-
"IMG",
|
|
109
|
-
"INPUT",
|
|
110
|
-
"KEYGEN",
|
|
111
|
-
"LINK",
|
|
112
|
-
"META",
|
|
113
|
-
"PARAM",
|
|
114
|
-
"SOURCE",
|
|
115
|
-
"TRACK",
|
|
116
|
-
"WBR",
|
|
117
|
-
].includes(tagName);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Remove all node's child nodes that pass the test implemented by the provided function.
|
|
122
|
-
* @param {Node} node the node to process
|
|
123
|
-
* @param {Function} callbackFn the predicate
|
|
124
|
-
*/
|
|
125
|
-
export function removeNodes(node, callbackFn) {
|
|
126
|
-
for (const el of [...node.childNodes]) {
|
|
127
|
-
if (callbackFn(el)) {
|
|
128
|
-
el.remove();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Remove recursively all node's child nodes that pass the test implemented by the provided function.
|
|
135
|
-
* @param {Node} node the node to process
|
|
136
|
-
* @param {Function} callbackFn the predicate
|
|
137
|
-
*/
|
|
138
|
-
export function removeNodesRecursively(node, callbackFn) {
|
|
139
|
-
// Remove the node if it meets the condition
|
|
140
|
-
if (callbackFn(node)) {
|
|
141
|
-
node.remove();
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Loop through the node’s children
|
|
146
|
-
for (const el of [...node.childNodes]) {
|
|
147
|
-
// Execute the same function if it’s an element node
|
|
148
|
-
removeNodesRecursively(el, callbackFn);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Remove all node's child nodes that are empty text nodes.
|
|
154
|
-
* @param {Node} node the node to process
|
|
155
|
-
*/
|
|
156
|
-
export function removeEmptyTextNodes(node) {
|
|
157
|
-
removeNodes(node, (el) => el.nodeType === Node.TEXT_NODE && el.textContent.trim().length === 0);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Remove all node's child nodes that are comment nodes.
|
|
162
|
-
* @param {Node} node the node to process
|
|
163
|
-
*/
|
|
164
|
-
export function removeCommentNodes(node) {
|
|
165
|
-
removeNodes(node, (el) => el.nodeType === Node.COMMENT_NODE);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Reset all node's attributes to the given list.
|
|
170
|
-
* @param {Node} node the node
|
|
171
|
-
* @param {object} targetAttributes the requested node's attributes
|
|
172
|
-
*/
|
|
173
|
-
export function resetAttributesTo(node, targetAttributes) {
|
|
174
|
-
for (const name of node.getAttributeNames()) {
|
|
175
|
-
if (targetAttributes[name] === undefined) {
|
|
176
|
-
node.removeAttribute(name);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
for (const name of Object.keys(targetAttributes)) {
|
|
180
|
-
node.setAttribute(name, targetAttributes[name]);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Replace the node's style attribute by some regular nodes (<b>, <i>, <u> or <s>).
|
|
186
|
-
* @param {Node} node the node to process
|
|
187
|
-
* @returns {Node} the new node
|
|
188
|
-
*/
|
|
189
|
-
export function replaceNodeStyleByTag(node) {
|
|
190
|
-
// Get the style
|
|
191
|
-
const styleAttr = node.getAttribute("style") || "";
|
|
192
|
-
|
|
193
|
-
// Check if a tag is override by the style attribute
|
|
194
|
-
if (
|
|
195
|
-
(hasTagName(node, "b") && styleAttr.match(/font-weight\s*:\s*(normal|400);/)) ||
|
|
196
|
-
(hasTagName(node, "i") && styleAttr.match(/font-style\s*:\s*normal;/)) ||
|
|
197
|
-
(hasTagName(node, ["u", "s"]) && styleAttr.match(/text-decoration\s*:\s*none;/))
|
|
198
|
-
) {
|
|
199
|
-
node = replaceNodeWith(
|
|
200
|
-
node,
|
|
201
|
-
createNodeWith("span", { attributes: { style: styleAttr }, innerHTML: node.innerHTML })
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Infer the tag from the style
|
|
206
|
-
if (styleAttr.match(/font-weight\s*:\s*(bold|700|800|900);/)) {
|
|
207
|
-
node = replaceNodeWith(
|
|
208
|
-
node,
|
|
209
|
-
createNodeWith("b", {
|
|
210
|
-
innerHTML: `<span style="${styleAttr.replace(/font-weight\s*:\s*(bold|700|800|900);/, "")}">${
|
|
211
|
-
node.innerHTML
|
|
212
|
-
}</span>`,
|
|
213
|
-
})
|
|
214
|
-
);
|
|
215
|
-
} else if (styleAttr.match(/font-style\s*:\s*italic;/)) {
|
|
216
|
-
node = replaceNodeWith(
|
|
217
|
-
node,
|
|
218
|
-
createNodeWith("i", {
|
|
219
|
-
innerHTML: `<span style="${styleAttr.replace(/font-style\s*:\s*italic;/, "")}">${node.innerHTML}</span>`,
|
|
220
|
-
})
|
|
221
|
-
);
|
|
222
|
-
} else if (styleAttr.match(/text-decoration\s*:\s*underline;/)) {
|
|
223
|
-
node = replaceNodeWith(
|
|
224
|
-
node,
|
|
225
|
-
createNodeWith("u", {
|
|
226
|
-
innerHTML: `<span style="${styleAttr.replace(/text-decoration\s*:\s*underline;/, "")}">${
|
|
227
|
-
node.innerHTML
|
|
228
|
-
}</span>`,
|
|
229
|
-
})
|
|
230
|
-
);
|
|
231
|
-
} else if (styleAttr.match(/text-decoration\s*:\s*line-through;/)) {
|
|
232
|
-
node = replaceNodeWith(
|
|
233
|
-
node,
|
|
234
|
-
createNodeWith("s", {
|
|
235
|
-
innerHTML: `<span style="${styleAttr.replace(/text-decoration\s*:\s*line-through;/, "")}">${
|
|
236
|
-
node.innerHTML
|
|
237
|
-
}</span>`,
|
|
238
|
-
})
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Return the node
|
|
243
|
-
return node;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Remove all leading & trailing node's child nodes that match the given tag.
|
|
248
|
-
* @param {Node} node the node to process
|
|
249
|
-
* @param {string} tag the tag
|
|
250
|
-
*/
|
|
251
|
-
export function trimTag(node, tag) {
|
|
252
|
-
// Children
|
|
253
|
-
const children = node.childNodes;
|
|
254
|
-
|
|
255
|
-
// Remove Leading
|
|
256
|
-
while (children.length > 0 && hasTagName(children[0], tag)) {
|
|
257
|
-
children[0].remove();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Remove Trailing
|
|
261
|
-
while (children.length > 0 && hasTagName(children[children.length - 1], tag)) {
|
|
262
|
-
children[children.length - 1].remove();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Clean the DOM content of the node
|
|
268
|
-
* @param {Node} root the node to process
|
|
269
|
-
* @param {object} style active styles for the root
|
|
270
|
-
*/
|
|
271
|
-
export function cleanDomContent(root, style) {
|
|
272
|
-
// Iterate through children
|
|
273
|
-
for (let el of [...root.children]) {
|
|
274
|
-
// Check if the span is an edith-nbsp
|
|
275
|
-
if (hasTagName(el, "span") && hasClass(el, "edith-nbsp")) {
|
|
276
|
-
// Ensure that we have a clean element
|
|
277
|
-
resetAttributesTo(el, { class: "edith-nbsp", contenteditable: "false" });
|
|
278
|
-
el.innerHTML = "¶";
|
|
279
|
-
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
// Check if there is a style attribute on the current node
|
|
284
|
-
if (el.hasAttribute("style")) {
|
|
285
|
-
// Replace the style attribute by tags
|
|
286
|
-
el = replaceNodeStyleByTag(el);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Check if the Tag Match a Parent Tag
|
|
290
|
-
if (style[el.tagName]) {
|
|
291
|
-
el = replaceNodeWith(
|
|
292
|
-
el,
|
|
293
|
-
createNodeWith("span", { attributes: { style: el.getAttribute("style") || "" }, innerHTML: el.innerHTML })
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Save the Current Style Tag
|
|
298
|
-
const newTags = { ...style };
|
|
299
|
-
if (hasTagName(el, ["b", "i", "q", "u", "s"])) {
|
|
300
|
-
newTags[el.tagName] = true;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Clean Children
|
|
304
|
-
cleanDomContent(el, newTags);
|
|
305
|
-
|
|
306
|
-
// Keep only href & target attributes for <a> tags
|
|
307
|
-
if (hasTagName(el, "a")) {
|
|
308
|
-
const linkAttributes = {};
|
|
309
|
-
if (el.hasAttribute("href")) {
|
|
310
|
-
linkAttributes.href = el.getAttribute("href");
|
|
311
|
-
}
|
|
312
|
-
if (el.hasAttribute("target")) {
|
|
313
|
-
linkAttributes.target = el.getAttribute("target");
|
|
314
|
-
}
|
|
315
|
-
resetAttributesTo(el, linkAttributes);
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Remove all tag attributes for tags in the allowed list
|
|
320
|
-
if (hasTagName(el, ["b", "i", "q", "u", "s", "br", "sup"])) {
|
|
321
|
-
resetAttributesTo(el, {});
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// Remove useless tags
|
|
326
|
-
if (hasTagName(el, ["style", "meta", "link"])) {
|
|
327
|
-
el.remove();
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Check if it's a <p> tag
|
|
332
|
-
if (hasTagName(el, "p")) {
|
|
333
|
-
// Check if the element contains text
|
|
334
|
-
if (el.textContent.trim().length === 0) {
|
|
335
|
-
// Remove the node
|
|
336
|
-
el.remove();
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Remove all tag attributes
|
|
341
|
-
resetAttributesTo(el, {});
|
|
342
|
-
|
|
343
|
-
// Remove leading & trailing <br>
|
|
344
|
-
trimTag(el, "br");
|
|
345
|
-
|
|
346
|
-
// Return
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Unwrap the node
|
|
351
|
-
unwrapNode(el);
|
|
352
|
-
}
|
|
353
|
-
}
|
package/src/core/event.js
DELETED
package/src/core/history.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
function History() {
|
|
2
|
-
this.buffer = [];
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Add a new snapshot to the history.
|
|
7
|
-
* @param {string} doc the element to save
|
|
8
|
-
*/
|
|
9
|
-
History.prototype.push = function (doc) {
|
|
10
|
-
this.buffer.push(doc);
|
|
11
|
-
if (this.buffer.length > 20) {
|
|
12
|
-
this.buffer.shift();
|
|
13
|
-
}
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Get the last saved element
|
|
18
|
-
* @returns {(string|null)} the last saved element or null
|
|
19
|
-
*/
|
|
20
|
-
History.prototype.pop = function () {
|
|
21
|
-
if (this.buffer.length === 0) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
return this.buffer.pop();
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export { History };
|
package/src/core/mode.js
DELETED
package/src/index.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { EdithEditor } from "./ui/editor.js";
|
|
2
|
-
import { EdithButton, EdithButtons } from "./ui/button.js";
|
|
3
|
-
import { Events } from "./core/event.js";
|
|
4
|
-
|
|
5
|
-
/*
|
|
6
|
-
* Represents an editor
|
|
7
|
-
* @constructor
|
|
8
|
-
* @param {HTMLElement} element - The <input> element to add the Wysiwyg to.
|
|
9
|
-
* @param {Object} options - Options for the editor.
|
|
10
|
-
*/
|
|
11
|
-
function Edith(element, options) {
|
|
12
|
-
// Render the editor in the element
|
|
13
|
-
this.element = element;
|
|
14
|
-
this.element.classList.add("edith");
|
|
15
|
-
|
|
16
|
-
// Create the toolbar
|
|
17
|
-
this.toolbar = document.createElement("div");
|
|
18
|
-
this.toolbar.setAttribute("class", "edith-toolbar");
|
|
19
|
-
this.element.append(this.toolbar);
|
|
20
|
-
|
|
21
|
-
// Create buttons
|
|
22
|
-
options.buttons = options.buttons || {};
|
|
23
|
-
options.toolbar = options.toolbar || [["style", ["bold", "italic", "underline", "strikethrough"]]];
|
|
24
|
-
for (const { 0: group, 1: buttons } of options.toolbar) {
|
|
25
|
-
// Create the button group
|
|
26
|
-
const btnGroup = document.createElement("div");
|
|
27
|
-
btnGroup.setAttribute("id", group);
|
|
28
|
-
btnGroup.setAttribute("class", "edith-btn-group");
|
|
29
|
-
this.toolbar.append(btnGroup);
|
|
30
|
-
|
|
31
|
-
// Add buttons
|
|
32
|
-
for (const buttonId of buttons) {
|
|
33
|
-
// Render the button
|
|
34
|
-
const button = options.buttons[buttonId] || EdithButtons[buttonId];
|
|
35
|
-
btnGroup.append(button(this).render());
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Create the editor
|
|
40
|
-
this.editor = new EdithEditor(this, options);
|
|
41
|
-
this.element.append(this.editor.render());
|
|
42
|
-
|
|
43
|
-
// Create the modals
|
|
44
|
-
this.modals = document.createElement("div");
|
|
45
|
-
this.modals.setAttribute("class", "edith-modals");
|
|
46
|
-
this.element.append(this.modals);
|
|
47
|
-
|
|
48
|
-
// Trigger the initialized event once its initialized
|
|
49
|
-
this.trigger(Events.initialized);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
Edith.prototype.on = function (type, listener, options) {
|
|
53
|
-
this.element.addEventListener(type, listener, options);
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
Edith.prototype.off = function (type, listener, options) {
|
|
57
|
-
this.element.removeEventListener(type, listener, options);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
Edith.prototype.trigger = function (type, payload = null) {
|
|
61
|
-
this.element.dispatchEvent(new CustomEvent(type, { detail: payload }));
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
Edith.prototype.setContent = function (content) {
|
|
65
|
-
this.editor.setContent(content);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
Edith.prototype.getContent = function () {
|
|
69
|
-
return this.editor.getContent();
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
Edith.prototype.destroy = function () {
|
|
73
|
-
// Delete the modals
|
|
74
|
-
this.modals.remove();
|
|
75
|
-
this.modals = undefined;
|
|
76
|
-
|
|
77
|
-
// Delete the editor
|
|
78
|
-
this.editor.destroy();
|
|
79
|
-
this.editor = undefined;
|
|
80
|
-
|
|
81
|
-
// Delete the toolbar
|
|
82
|
-
this.toolbar.remove();
|
|
83
|
-
this.toolbar = undefined;
|
|
84
|
-
|
|
85
|
-
// Clean the main element
|
|
86
|
-
this.element.classList.remove("edith");
|
|
87
|
-
this.element = undefined;
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
export { Edith, EdithButton };
|
package/src/ui/button.js
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { createPopper } from "@popperjs/core";
|
|
2
|
-
import { Events } from "../core/event.js";
|
|
3
|
-
import { EditorModes } from "../core/mode.js";
|
|
4
|
-
|
|
5
|
-
function onButtonClick(context, kind) {
|
|
6
|
-
switch (kind) {
|
|
7
|
-
case "bold":
|
|
8
|
-
context.editor.wrapInsideTag("b");
|
|
9
|
-
break;
|
|
10
|
-
case "italic":
|
|
11
|
-
context.editor.wrapInsideTag("i");
|
|
12
|
-
break;
|
|
13
|
-
case "underline":
|
|
14
|
-
context.editor.wrapInsideTag("u");
|
|
15
|
-
break;
|
|
16
|
-
case "strikethrough":
|
|
17
|
-
context.editor.wrapInsideTag("s");
|
|
18
|
-
break;
|
|
19
|
-
case "subscript":
|
|
20
|
-
context.editor.wrapInsideTag("sub");
|
|
21
|
-
break;
|
|
22
|
-
case "superscript":
|
|
23
|
-
context.editor.wrapInsideTag("sup");
|
|
24
|
-
break;
|
|
25
|
-
case "nbsp":
|
|
26
|
-
context.editor.replaceByHtml('<span class="edith-nbsp" contenteditable="false">¶</span>');
|
|
27
|
-
break;
|
|
28
|
-
case "clear":
|
|
29
|
-
context.editor.clearStyle();
|
|
30
|
-
break;
|
|
31
|
-
case "link":
|
|
32
|
-
context.editor.insertLink();
|
|
33
|
-
break;
|
|
34
|
-
case "codeview":
|
|
35
|
-
context.editor.toggleCodeView();
|
|
36
|
-
break;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function EdithButton(ctx, options) {
|
|
41
|
-
this.ctx = ctx;
|
|
42
|
-
this.icon = options.icon;
|
|
43
|
-
this.title = options.title;
|
|
44
|
-
this.onclick = options.onclick;
|
|
45
|
-
this.showOnCodeView = options.showOnCodeView === true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
EdithButton.prototype.click = function (event) {
|
|
49
|
-
// Prevent default
|
|
50
|
-
event.preventDefault();
|
|
51
|
-
|
|
52
|
-
// Check if the onclick attribute is a internal function ID or a custom function
|
|
53
|
-
if (typeof this.onclick === "string") {
|
|
54
|
-
onButtonClick(this.ctx, this.onclick);
|
|
55
|
-
} else {
|
|
56
|
-
this.onclick(this.ctx, event);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
EdithButton.prototype.showTooltip = function () {
|
|
61
|
-
if (this.popper !== undefined) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Add the tooltip content to the DOM
|
|
66
|
-
this.popperEl = document.createElement("div");
|
|
67
|
-
this.popperEl.setAttribute("class", "edith-tooltip");
|
|
68
|
-
this.popperEl.textContent = this.title;
|
|
69
|
-
const arrowEl = document.createElement("div");
|
|
70
|
-
arrowEl.setAttribute("class", "arrow");
|
|
71
|
-
arrowEl.setAttribute("data-popper-arrow", "");
|
|
72
|
-
this.popperEl.append(arrowEl);
|
|
73
|
-
this.ctx.toolbar.append(this.popperEl);
|
|
74
|
-
|
|
75
|
-
// Create the tooltip
|
|
76
|
-
this.popper = createPopper(this.el, this.popperEl, {
|
|
77
|
-
placement: "bottom",
|
|
78
|
-
modifiers: [
|
|
79
|
-
{
|
|
80
|
-
name: "arrow",
|
|
81
|
-
options: {
|
|
82
|
-
padding: 5, // 5px from the edges of the popper
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "offset",
|
|
87
|
-
options: {
|
|
88
|
-
offset: [0, 8],
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
});
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
EdithButton.prototype.hideTooltip = function () {
|
|
96
|
-
if (this.popper === undefined) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Destroy the tooltip
|
|
101
|
-
this.popper.destroy();
|
|
102
|
-
this.popper = undefined;
|
|
103
|
-
|
|
104
|
-
// Remove the tooltip content from the DOM
|
|
105
|
-
this.popperEl.remove();
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
EdithButton.prototype.onEditorModeChange = function (event) {
|
|
109
|
-
if (event.detail.mode === EditorModes.Code) {
|
|
110
|
-
this.el.setAttribute("disabled", "disabled");
|
|
111
|
-
} else {
|
|
112
|
-
this.el.removeAttribute("disabled");
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
EdithButton.prototype.render = function () {
|
|
117
|
-
// Create the button
|
|
118
|
-
this.el = document.createElement("button");
|
|
119
|
-
this.el.setAttribute("class", `edith-btn ${this.icon}`);
|
|
120
|
-
this.el.setAttribute("type", "button");
|
|
121
|
-
|
|
122
|
-
// Bind events
|
|
123
|
-
this.el.onclick = this.click.bind(this);
|
|
124
|
-
this.el.onmouseenter = this.showTooltip.bind(this);
|
|
125
|
-
this.el.onmouseleave = this.hideTooltip.bind(this);
|
|
126
|
-
|
|
127
|
-
// Check if we have to disable the button on the code view
|
|
128
|
-
if (this.showOnCodeView !== true) {
|
|
129
|
-
this.ctx.on(Events.modeChanged, this.onEditorModeChange.bind(this));
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Return the button
|
|
133
|
-
return this.el;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
const EdithButtons = {
|
|
137
|
-
bold: (context) =>
|
|
138
|
-
new EdithButton(context, {
|
|
139
|
-
icon: "fa-solid fa-bold",
|
|
140
|
-
title: "Gras",
|
|
141
|
-
onclick: "bold",
|
|
142
|
-
}),
|
|
143
|
-
italic: (context) =>
|
|
144
|
-
new EdithButton(context, {
|
|
145
|
-
icon: "fa-solid fa-italic",
|
|
146
|
-
title: "Italique",
|
|
147
|
-
onclick: "italic",
|
|
148
|
-
}),
|
|
149
|
-
underline: (context) =>
|
|
150
|
-
new EdithButton(context, {
|
|
151
|
-
icon: "fa-solid fa-underline",
|
|
152
|
-
title: "Souligner",
|
|
153
|
-
onclick: "underline",
|
|
154
|
-
}),
|
|
155
|
-
strikethrough: (context) =>
|
|
156
|
-
new EdithButton(context, {
|
|
157
|
-
icon: "fa-solid fa-strikethrough",
|
|
158
|
-
title: "Barrer",
|
|
159
|
-
onclick: "strikethrough",
|
|
160
|
-
}),
|
|
161
|
-
subscript: (context) =>
|
|
162
|
-
new EdithButton(context, {
|
|
163
|
-
icon: "fa-solid fa-subscript",
|
|
164
|
-
title: "Indice",
|
|
165
|
-
onclick: "subscript",
|
|
166
|
-
}),
|
|
167
|
-
superscript: (context) =>
|
|
168
|
-
new EdithButton(context, {
|
|
169
|
-
icon: "fa-solid fa-superscript",
|
|
170
|
-
title: "Exposant",
|
|
171
|
-
onclick: "superscript",
|
|
172
|
-
}),
|
|
173
|
-
nbsp: (context) =>
|
|
174
|
-
new EdithButton(context, {
|
|
175
|
-
icon: "edith-btn-nbsp",
|
|
176
|
-
title: "Ajouter une espace insécable",
|
|
177
|
-
onclick: "nbsp",
|
|
178
|
-
}),
|
|
179
|
-
clear: (context) =>
|
|
180
|
-
new EdithButton(context, {
|
|
181
|
-
icon: "fa-solid fa-eraser",
|
|
182
|
-
title: "Effacer la mise en forme",
|
|
183
|
-
onclick: "clear",
|
|
184
|
-
}),
|
|
185
|
-
link: (context) =>
|
|
186
|
-
new EdithButton(context, {
|
|
187
|
-
icon: "fa-solid fa-link",
|
|
188
|
-
title: "Lien",
|
|
189
|
-
onclick: "link",
|
|
190
|
-
}),
|
|
191
|
-
codeview: (context) =>
|
|
192
|
-
new EdithButton(context, {
|
|
193
|
-
icon: "fa-solid fa-code",
|
|
194
|
-
title: "Afficher le code HTML",
|
|
195
|
-
onclick: "codeview",
|
|
196
|
-
showOnCodeView: true,
|
|
197
|
-
}),
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
export { EdithButton, EdithButtons };
|