@lesjoursfr/edith 2.0.1 → 2.1.0
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 +18 -18
- package/src/core/dom.js +1 -1
- package/src/core/edit.js +57 -12
- package/src/core/range.js +21 -0
- package/src/ui/editor.js +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lesjoursfr/edith",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Simple WYSIWYG editor.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "lesjoursfr/edith",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"lint-js": "eslint . --fix --ext .js",
|
|
33
33
|
"lint-sass": "stylelint **/*.scss --fix",
|
|
34
34
|
"format": "prettier --write .",
|
|
35
|
-
"test": "npx
|
|
35
|
+
"test": "npx mocha",
|
|
36
36
|
"builddeps": "webpack --mode production --config server/deps/codemirror.webpack.js --progress",
|
|
37
37
|
"server": "webpack serve --mode development --config server/editor.webpack.js --hot --open"
|
|
38
38
|
},
|
|
@@ -44,35 +44,35 @@
|
|
|
44
44
|
"editor"
|
|
45
45
|
],
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@babel/core": "^7.22.
|
|
48
|
-
"@babel/preset-env": "^7.22.
|
|
49
|
-
"@codemirror/lang-html": "^6.4.
|
|
50
|
-
"@fortawesome/fontawesome-free": "^6.4.
|
|
47
|
+
"@babel/core": "^7.22.11",
|
|
48
|
+
"@babel/preset-env": "^7.22.14",
|
|
49
|
+
"@codemirror/lang-html": "^6.4.6",
|
|
50
|
+
"@fortawesome/fontawesome-free": "^6.4.2",
|
|
51
51
|
"@popperjs/core": "^2.11.8",
|
|
52
|
-
"
|
|
53
|
-
"babel-loader": "^9.1.2",
|
|
52
|
+
"babel-loader": "^9.1.3",
|
|
54
53
|
"codemirror": "^6.0.1",
|
|
55
54
|
"css-loader": "^6.8.1",
|
|
56
|
-
"eslint": "^8.
|
|
57
|
-
"eslint-config-prettier": "^
|
|
55
|
+
"eslint": "^8.48.0",
|
|
56
|
+
"eslint-config-prettier": "^9.0.0",
|
|
58
57
|
"jsdom": "^22.1.0",
|
|
59
58
|
"mini-css-extract-plugin": "^2.7.6",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
59
|
+
"mocha": "^10.2.0",
|
|
60
|
+
"postcss": "^8.4.29",
|
|
61
|
+
"prettier": "^3.0.3",
|
|
62
|
+
"sass": "^1.66.1",
|
|
63
63
|
"sass-loader": "^13.3.2",
|
|
64
64
|
"style-loader": "^3.3.3",
|
|
65
|
-
"stylelint": "^15.
|
|
65
|
+
"stylelint": "^15.10.3",
|
|
66
66
|
"stylelint-config-sass-guidelines": "^10.0.0",
|
|
67
|
-
"webpack": "^5.88.
|
|
67
|
+
"webpack": "^5.88.2",
|
|
68
68
|
"webpack-cli": "^5.1.4",
|
|
69
69
|
"webpack-dev-server": "^4.15.1"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@codemirror/lang-html": "^6.4.
|
|
73
|
-
"@fortawesome/fontawesome-free": "^6.4.
|
|
72
|
+
"@codemirror/lang-html": "^6.4.6",
|
|
73
|
+
"@fortawesome/fontawesome-free": "^6.4.2",
|
|
74
74
|
"@popperjs/core": "^2.11.8",
|
|
75
75
|
"codemirror": "^6.0.1"
|
|
76
76
|
},
|
|
77
|
-
"packageManager": "yarn@3.6.
|
|
77
|
+
"packageManager": "yarn@3.6.3"
|
|
78
78
|
}
|
package/src/core/dom.js
CHANGED
|
@@ -76,7 +76,7 @@ export function replaceNodeWith(node, replacement) {
|
|
|
76
76
|
* @returns {Array} its child nodes
|
|
77
77
|
*/
|
|
78
78
|
export function unwrapNode(node) {
|
|
79
|
-
const newNodes = node.childNodes;
|
|
79
|
+
const newNodes = [...node.childNodes];
|
|
80
80
|
node.replaceWith(...newNodes);
|
|
81
81
|
return newNodes;
|
|
82
82
|
}
|
package/src/core/edit.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getSelection, moveCursorInsideNode, moveCursorAfterNode, selectNodeContents } from "./range.js";
|
|
1
|
+
import { getSelection, moveCursorInsideNode, moveCursorAfterNode, selectNodeContents, selectNodes } from "./range.js";
|
|
2
2
|
import {
|
|
3
3
|
hasClass,
|
|
4
4
|
hasTagName,
|
|
@@ -43,6 +43,48 @@ function splitNodeAtCaret(range, node) {
|
|
|
43
43
|
return textNode;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Extract the selection from the node.
|
|
48
|
+
* @param {Range} range the selection to extract
|
|
49
|
+
* @param {Node} node the node to split
|
|
50
|
+
* @param {String} tag the tag to remove
|
|
51
|
+
* @returns {Node} the created node
|
|
52
|
+
*/
|
|
53
|
+
function extractSelectionFromNode(range, node) {
|
|
54
|
+
// Get the node's parent
|
|
55
|
+
const parent = node.parentNode;
|
|
56
|
+
|
|
57
|
+
// Clone the current range & move the starting point to the beginning of the parent's node
|
|
58
|
+
const beforeSelection = new Range();
|
|
59
|
+
beforeSelection.selectNodeContents(parent);
|
|
60
|
+
beforeSelection.setEnd(range.startContainer, range.startOffset);
|
|
61
|
+
const afterSelection = new Range();
|
|
62
|
+
afterSelection.selectNodeContents(parent);
|
|
63
|
+
afterSelection.setStart(range.endContainer, range.endOffset);
|
|
64
|
+
|
|
65
|
+
// Extract the content of the selection
|
|
66
|
+
const fragBefore = beforeSelection.extractContents();
|
|
67
|
+
const fragAfter = afterSelection.extractContents();
|
|
68
|
+
|
|
69
|
+
// Add back the content into the node's parent
|
|
70
|
+
parent.prepend(fragBefore);
|
|
71
|
+
parent.append(fragAfter);
|
|
72
|
+
|
|
73
|
+
// Remove the parent from the selection
|
|
74
|
+
let current = range.commonAncestorContainer;
|
|
75
|
+
while (current.tagName !== node.tagName) {
|
|
76
|
+
// Take the parent
|
|
77
|
+
current = current.parentNode;
|
|
78
|
+
}
|
|
79
|
+
let innerNodes = unwrapNode(current);
|
|
80
|
+
|
|
81
|
+
// Preserve the selection
|
|
82
|
+
selectNodes(innerNodes);
|
|
83
|
+
|
|
84
|
+
// Return the inserted TextNode
|
|
85
|
+
return range.commonAncestorContainer;
|
|
86
|
+
}
|
|
87
|
+
|
|
46
88
|
/**
|
|
47
89
|
* Create a node at the caret position.
|
|
48
90
|
* @param {Range} range the caret position
|
|
@@ -116,9 +158,11 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
116
158
|
const { sel, range } = getSelection();
|
|
117
159
|
|
|
118
160
|
// Check if the user has selected something
|
|
119
|
-
if (range === undefined)
|
|
161
|
+
if (range === undefined) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
120
164
|
|
|
121
|
-
// Check if
|
|
165
|
+
// Check if there is a Selection
|
|
122
166
|
if (range.collapsed) {
|
|
123
167
|
// Check if a parent element has the same tag name
|
|
124
168
|
let parent = sel.anchorNode.parentNode;
|
|
@@ -142,8 +186,9 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
142
186
|
let parent = range.commonAncestorContainer;
|
|
143
187
|
while (!hasClass(parent, "edith-visual")) {
|
|
144
188
|
if (hasTagName(parent, tag)) {
|
|
145
|
-
// One of the parent has the same tag name
|
|
146
|
-
|
|
189
|
+
// One of the parent has the same tag name
|
|
190
|
+
// Extract the selection from the parent
|
|
191
|
+
return extractSelectionFromNode(range, parent);
|
|
147
192
|
}
|
|
148
193
|
|
|
149
194
|
// Take the parent
|
|
@@ -151,18 +196,18 @@ export function wrapInsideTag(tag, options = {}) {
|
|
|
151
196
|
}
|
|
152
197
|
|
|
153
198
|
// Try to replace all elements with the same tag name in the selection
|
|
154
|
-
let replaced = false;
|
|
155
199
|
for (const el of [...parent.getElementsByTagName(tag)]) {
|
|
156
200
|
// Check if the the Element Intersect the Selection
|
|
157
201
|
if (sel.containsNode(el, true)) {
|
|
158
|
-
|
|
159
|
-
|
|
202
|
+
// Unwrap the node
|
|
203
|
+
let innerNodes = unwrapNode(el);
|
|
204
|
+
|
|
205
|
+
// Return the node
|
|
206
|
+
selectNodes(innerNodes);
|
|
207
|
+
parent.normalize();
|
|
208
|
+
return parent;
|
|
160
209
|
}
|
|
161
210
|
}
|
|
162
|
-
if (replaced) {
|
|
163
|
-
parent.normalize();
|
|
164
|
-
return parent;
|
|
165
|
-
}
|
|
166
211
|
|
|
167
212
|
// Nothing was replaced
|
|
168
213
|
// Wrap the selection inside the given tag
|
package/src/core/range.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isSelfClosing } from "./dom.js";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @typedef {Object} CurrentSelection
|
|
3
5
|
* @property {Selection} sel the current selection
|
|
@@ -63,6 +65,25 @@ export function selectNodeContents(target) {
|
|
|
63
65
|
sel.addRange(range);
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Select the given Nodes.
|
|
70
|
+
* @param {Array<Node>} nodes The list of Nodes to select.
|
|
71
|
+
*/
|
|
72
|
+
export function selectNodes(nodes) {
|
|
73
|
+
// Check if we just have a self-closing tag
|
|
74
|
+
if (nodes.length === 1 && isSelfClosing(nodes[0].tagName)) {
|
|
75
|
+
moveCursorAfterNode(nodes[0]); // Move the cursor after the Node
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Select Nodes
|
|
79
|
+
const range = document.createRange();
|
|
80
|
+
const sel = window.getSelection();
|
|
81
|
+
range.setStartBefore(nodes[0]);
|
|
82
|
+
range.setEndAfter(nodes[nodes.length - 1]);
|
|
83
|
+
sel.removeAllRanges();
|
|
84
|
+
sel.addRange(range);
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
/**
|
|
67
88
|
* Check if the current selection is inside the given node.
|
|
68
89
|
* @param {Node} node the targeted node
|
package/src/ui/editor.js
CHANGED
|
@@ -50,6 +50,10 @@ EdithEditor.prototype.render = function () {
|
|
|
50
50
|
this.editors.visual = document.createElement("div");
|
|
51
51
|
this.editors.visual.setAttribute("class", "edith-visual");
|
|
52
52
|
this.editors.visual.setAttribute("contenteditable", "true");
|
|
53
|
+
this.editors.visual.setAttribute(
|
|
54
|
+
"style",
|
|
55
|
+
this.resizable ? `min-height: ${this.height - 10}px` : `height: ${this.height - 10}px`
|
|
56
|
+
);
|
|
53
57
|
this.editors.visual.innerHTML = this.content;
|
|
54
58
|
this.editors.wrapper.append(this.editors.visual);
|
|
55
59
|
|
|
@@ -334,7 +338,7 @@ EdithEditor.prototype.onPasteEvent = function (e) {
|
|
|
334
338
|
|
|
335
339
|
// Add the content as text nodes with a <br> node between each line
|
|
336
340
|
for (let i = 0; i < lines.length; i++) {
|
|
337
|
-
if (
|
|
341
|
+
if (i !== 0) {
|
|
338
342
|
frag.append(document.createElement("br"));
|
|
339
343
|
}
|
|
340
344
|
frag.append(document.createTextNode(lines[i]));
|