@seafile/sea-email-editor 0.0.9 → 0.0.10-beta1
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/README.md +51 -0
- package/dist/editor/main/index.css +1 -0
- package/dist/editor/main/index.js +13 -7
- package/dist/extension/plugins/text-style/render-elem.js +15 -0
- package/dist/slate-convert/html-to-slate/index.js +14 -10
- package/dist/slate-convert/html-to-slate/rules/blockquote.js +4 -1
- package/dist/slate-convert/html-to-slate/rules/code-block.js +7 -3
- package/dist/slate-convert/html-to-slate/rules/image.js +5 -1
- package/dist/slate-convert/html-to-slate/rules/link.js +2 -1
- package/dist/slate-convert/html-to-slate/rules/list.js +1 -1
- package/dist/slate-convert/html-to-slate/rules/p.js +8 -1
- package/dist/slate-convert/html-to-slate/rules/paragraph.js +7 -2
- package/dist/slate-convert/html-to-slate/rules/text.js +45 -39
- package/dist/slate-convert/md-to-slate/transform.js +20 -10
- package/dist/slate-convert/slate-to-html/index.js +16 -2
- package/dist/utils/common.js +3 -1
- package/dist/utils/dom.js +27 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1 +1,52 @@
|
|
|
1
1
|
# Sea email editor
|
|
2
|
+
|
|
3
|
+
Sea email editor is a React-based rich text editor for composing email content. It is built on top of Slate and includes HTML and Markdown conversion, along with support for common email editing blocks.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- HTML to editor content conversion
|
|
8
|
+
- Markdown to editor content conversion
|
|
9
|
+
- Rich text formatting: bold, italic, underline, inline code
|
|
10
|
+
- Block elements: headings, blockquote, lists, code blocks, divider, tables
|
|
11
|
+
- Link and image rendering
|
|
12
|
+
- Localization support
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @seafile/sea-email-editor
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```jsx
|
|
23
|
+
import React from "react";
|
|
24
|
+
import SeaEmailEditor from "@seafile/sea-email-editor";
|
|
25
|
+
|
|
26
|
+
export default function App() {
|
|
27
|
+
return (
|
|
28
|
+
<SeaEmailEditor
|
|
29
|
+
value={"<div>Hello, world!</div>"}
|
|
30
|
+
isHtmlValue={true}
|
|
31
|
+
onChange={(html) => {
|
|
32
|
+
console.log(html);
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Props
|
|
40
|
+
|
|
41
|
+
- `value`: initial editor content, HTML or Markdown depending on `isHtmlValue`
|
|
42
|
+
- `isHtmlValue`: set to `true` when `value` is HTML
|
|
43
|
+
- `options.lang`: locale, currently `en` and `zh-cn`
|
|
44
|
+
- `onChange`: called with serialized HTML when the document changes
|
|
45
|
+
- `assetURLPrefix`: prefix for editor assets
|
|
46
|
+
- `editorApi`: editor integration hooks
|
|
47
|
+
- `onLinkClick`: custom link click handler
|
|
48
|
+
|
|
49
|
+
## Notes
|
|
50
|
+
|
|
51
|
+
- The editor now re-initializes when the incoming `value` or `isHtmlValue` changes.
|
|
52
|
+
- The example app under `example/` demonstrates how to preview the generated HTML output.
|
|
@@ -75,13 +75,19 @@ const Main = /*#__PURE__*/(0, _react.forwardRef)((_ref, ref) => {
|
|
|
75
75
|
const [firstNode] = editor.children;
|
|
76
76
|
if (!firstNode) return;
|
|
77
77
|
if (focusRange && focusRange !== null && focusRange !== void 0 && focusRange.anchor) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
try {
|
|
79
|
+
const startOfFirstNode = _slate.Editor.start(editor, focusRange.anchor.path);
|
|
80
|
+
const range = {
|
|
81
|
+
anchor: startOfFirstNode,
|
|
82
|
+
focus: startOfFirstNode
|
|
83
|
+
};
|
|
84
|
+
(0, _core.focusEditor)(editor, range);
|
|
85
|
+
setTimeout(() => (0, _core.focusEditor)(editor, focusRange), 0);
|
|
86
|
+
} catch {
|
|
87
|
+
focusRangeRef.current = null;
|
|
88
|
+
focusNode(editor);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
85
91
|
focusRangeRef.current = null;
|
|
86
92
|
return;
|
|
87
93
|
}
|
|
@@ -49,6 +49,21 @@ const renderText = (props, editor) => {
|
|
|
49
49
|
children: markedChildren
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
+
if (leaf.superscript) {
|
|
53
|
+
markedChildren = /*#__PURE__*/(0, _jsxRuntime.jsx)("sup", {
|
|
54
|
+
children: markedChildren
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (leaf.subscript) {
|
|
58
|
+
markedChildren = /*#__PURE__*/(0, _jsxRuntime.jsx)("sub", {
|
|
59
|
+
children: markedChildren
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (leaf.highlight) {
|
|
63
|
+
markedChildren = /*#__PURE__*/(0, _jsxRuntime.jsx)("mark", {
|
|
64
|
+
children: markedChildren
|
|
65
|
+
});
|
|
66
|
+
}
|
|
52
67
|
if (leaf.decoration) {
|
|
53
68
|
markedChildren = /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
|
|
54
69
|
children: markedChildren
|
|
@@ -10,6 +10,17 @@ var _typeOf = _interopRequireDefault(require("type-of"));
|
|
|
10
10
|
var _constants = require("./constants");
|
|
11
11
|
var _rules = _interopRequireDefault(require("./rules"));
|
|
12
12
|
var _helper = require("./helper");
|
|
13
|
+
var _dom = require("../../utils/dom");
|
|
14
|
+
const generateDefaultValue = () => {
|
|
15
|
+
return [{
|
|
16
|
+
id: _slugid.default.nice(),
|
|
17
|
+
type: _constants.PARAGRAPH,
|
|
18
|
+
children: [{
|
|
19
|
+
text: '',
|
|
20
|
+
id: _slugid.default.nice()
|
|
21
|
+
}]
|
|
22
|
+
}];
|
|
23
|
+
};
|
|
13
24
|
const cruftNewline = element => {
|
|
14
25
|
return !(element.nodeName === '#text' && element.nodeValue === '\n');
|
|
15
26
|
};
|
|
@@ -98,14 +109,7 @@ const deserializeElements = function () {
|
|
|
98
109
|
|
|
99
110
|
const formatElementNodes = nodes => {
|
|
100
111
|
if (nodes.length === 0) {
|
|
101
|
-
return
|
|
102
|
-
id: _slugid.default.nice(),
|
|
103
|
-
type: _constants.PARAGRAPH,
|
|
104
|
-
children: [{
|
|
105
|
-
text: '',
|
|
106
|
-
id: _slugid.default.nice()
|
|
107
|
-
}]
|
|
108
|
-
}];
|
|
112
|
+
return generateDefaultValue();
|
|
109
113
|
}
|
|
110
114
|
nodes = nodes.reduce((memo, node) => {
|
|
111
115
|
if (_constants.TOP_LEVEL_TYPES.includes(node.type)) {
|
|
@@ -145,8 +149,8 @@ const formatElementNodes = nodes => {
|
|
|
145
149
|
return nodes;
|
|
146
150
|
};
|
|
147
151
|
const deserializeHtml = html => {
|
|
148
|
-
const
|
|
149
|
-
|
|
152
|
+
const fragment = (0, _dom.sanitizeHTMLContent)(html);
|
|
153
|
+
if (!fragment) return generateDefaultValue();
|
|
150
154
|
const children = Array.from(fragment.childNodes);
|
|
151
155
|
let nodes = [];
|
|
152
156
|
nodes = deserializeElements(children, true);
|
|
@@ -17,7 +17,10 @@ const blockquoteRule = (element, parseChild) => {
|
|
|
17
17
|
const node = {
|
|
18
18
|
id: _slugid.default.nice(),
|
|
19
19
|
type: _constants.BLOCKQUOTE,
|
|
20
|
-
children: parseChild(childNodes)
|
|
20
|
+
children: parseChild(childNodes) || [{
|
|
21
|
+
id: _slugid.default.nice(),
|
|
22
|
+
text: ''
|
|
23
|
+
}]
|
|
21
24
|
};
|
|
22
25
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
23
26
|
}
|
|
@@ -17,7 +17,10 @@ const codeBlockRule = (element, parseChild) => {
|
|
|
17
17
|
const children = Array.from(childNodes).filter(item => item.nodeName === 'CODE');
|
|
18
18
|
let codeChild = children[0];
|
|
19
19
|
if (codeChild) {
|
|
20
|
-
let lang = codeChild.getAttribute('lang');
|
|
20
|
+
let lang = codeChild.getAttribute('lang') || codeChild.getAttribute('class') || '';
|
|
21
|
+
if (lang.startsWith('language-')) {
|
|
22
|
+
lang = lang.replace('language-', '');
|
|
23
|
+
}
|
|
21
24
|
lang = (0, _helper.genCodeLangs)().find(item => item.value === lang) || 'plaintext';
|
|
22
25
|
const node = {
|
|
23
26
|
id: _slugid.default.nice(),
|
|
@@ -27,9 +30,10 @@ const codeBlockRule = (element, parseChild) => {
|
|
|
27
30
|
};
|
|
28
31
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
29
32
|
} else {
|
|
33
|
+
var _childNodes$;
|
|
30
34
|
const lang = 'plaintext';
|
|
31
|
-
const content = childNodes[0]
|
|
32
|
-
const textArr = content.split('\n')
|
|
35
|
+
const content = ((_childNodes$ = childNodes[0]) === null || _childNodes$ === void 0 ? void 0 : _childNodes$.textContent) || '';
|
|
36
|
+
const textArr = content.split('\n');
|
|
33
37
|
const children = textArr.map(text => {
|
|
34
38
|
return {
|
|
35
39
|
id: _slugid.default.nice(),
|
|
@@ -17,7 +17,11 @@ const imageRule = (element, parseChild) => {
|
|
|
17
17
|
id: _slugid.default.nice(),
|
|
18
18
|
type: _constants.IMAGE,
|
|
19
19
|
data: {
|
|
20
|
-
src: element.getAttribute('src')
|
|
20
|
+
src: element.getAttribute('src'),
|
|
21
|
+
alt: element.getAttribute('alt') || undefined,
|
|
22
|
+
title: element.getAttribute('title') || undefined,
|
|
23
|
+
width: element.getAttribute('width') || undefined,
|
|
24
|
+
height: element.getAttribute('height') || undefined
|
|
21
25
|
},
|
|
22
26
|
children: [{
|
|
23
27
|
text: '',
|
|
@@ -14,12 +14,13 @@ const linkRule = (element, parseChild) => {
|
|
|
14
14
|
} = element;
|
|
15
15
|
const content = element.textContent || element.getAttribute('title') || element.getAttribute('href');
|
|
16
16
|
if (nodeName === 'A') {
|
|
17
|
+
const children = parseChild(element.childNodes);
|
|
17
18
|
const node = {
|
|
18
19
|
id: _slugid.default.nice(),
|
|
19
20
|
type: _constants.LINK,
|
|
20
21
|
url: element.getAttribute('href') || content,
|
|
21
22
|
title: element.getAttribute('title'),
|
|
22
|
-
children: [{
|
|
23
|
+
children: Array.isArray(children) && children.length > 0 ? children : [{
|
|
23
24
|
id: _slugid.default.nice(),
|
|
24
25
|
text: content || ''
|
|
25
26
|
}]
|
|
@@ -53,7 +53,7 @@ const listRule = (element, parseChild) => {
|
|
|
53
53
|
};
|
|
54
54
|
normalizedChildren.forEach(child => {
|
|
55
55
|
if (!child) return;
|
|
56
|
-
const isInlineNode = !child.type || _constants.INLINE_LEVEL_TYPES.includes(child.type);
|
|
56
|
+
const isInlineNode = !child.type || _constants.INLINE_LEVEL_TYPES.includes(child.type) || child.type === 'text';
|
|
57
57
|
if (isInlineNode) {
|
|
58
58
|
inlineChildren.push(child);
|
|
59
59
|
return;
|
|
@@ -25,10 +25,17 @@ const pRule = (element, parseChild) => {
|
|
|
25
25
|
};
|
|
26
26
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
27
27
|
}
|
|
28
|
+
let children = parseChild(childNodes);
|
|
29
|
+
if (children.length === 0) {
|
|
30
|
+
children = [{
|
|
31
|
+
id: _slugid.default.nice(),
|
|
32
|
+
text: ''
|
|
33
|
+
}];
|
|
34
|
+
}
|
|
28
35
|
const node = {
|
|
29
36
|
id: _slugid.default.nice(),
|
|
30
37
|
type: _constants.P,
|
|
31
|
-
children
|
|
38
|
+
children
|
|
32
39
|
};
|
|
33
40
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
34
41
|
}
|
|
@@ -13,7 +13,8 @@ const paragraphRule = (element, parseChild) => {
|
|
|
13
13
|
nodeName,
|
|
14
14
|
childNodes
|
|
15
15
|
} = element;
|
|
16
|
-
|
|
16
|
+
// article
|
|
17
|
+
if ((nodeName === 'DIV' || nodeName === 'ARTICLE') && element.parentElement.nodeName !== 'LI') {
|
|
17
18
|
if (childNodes.length === 0) {
|
|
18
19
|
const node = {
|
|
19
20
|
id: _slugid.default.nice(),
|
|
@@ -25,10 +26,14 @@ const paragraphRule = (element, parseChild) => {
|
|
|
25
26
|
};
|
|
26
27
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
27
28
|
}
|
|
29
|
+
const children = parseChild(childNodes);
|
|
28
30
|
const node = {
|
|
29
31
|
id: _slugid.default.nice(),
|
|
30
32
|
type: _constants.PARAGRAPH,
|
|
31
|
-
children:
|
|
33
|
+
children: children.length === 0 ? [{
|
|
34
|
+
id: _slugid.default.nice(),
|
|
35
|
+
text: ''
|
|
36
|
+
}] : children
|
|
32
37
|
};
|
|
33
38
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
34
39
|
}
|
|
@@ -12,59 +12,65 @@ const textRule = (element, parseChild) => {
|
|
|
12
12
|
nodeName,
|
|
13
13
|
nodeType
|
|
14
14
|
} = element;
|
|
15
|
-
|
|
15
|
+
const createTextNode = function () {
|
|
16
|
+
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
16
17
|
const node = {
|
|
17
18
|
id: _slugid.default.nice(),
|
|
18
|
-
text: element.textContent
|
|
19
|
+
text: element.textContent,
|
|
20
|
+
...props
|
|
19
21
|
};
|
|
20
22
|
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
23
|
+
};
|
|
24
|
+
if (nodeName === 'SPAN') {
|
|
25
|
+
return createTextNode();
|
|
21
26
|
}
|
|
22
27
|
if (nodeName === 'STRONG' || nodeName === 'B') {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
text: element.textContent
|
|
27
|
-
};
|
|
28
|
-
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
28
|
+
return createTextNode({
|
|
29
|
+
bold: true
|
|
30
|
+
});
|
|
29
31
|
}
|
|
30
32
|
if (nodeName === 'CODE' && element.parentElement.nodeName !== 'PRE') {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
text: element.textContent
|
|
35
|
-
};
|
|
36
|
-
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
33
|
+
return createTextNode({
|
|
34
|
+
code: true
|
|
35
|
+
});
|
|
37
36
|
}
|
|
38
|
-
if (nodeName === 'DEL') {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
text: element.textContent
|
|
43
|
-
};
|
|
44
|
-
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
37
|
+
if (nodeName === 'DEL' || nodeName === 'S' || nodeName === 'STRIKE') {
|
|
38
|
+
return createTextNode({
|
|
39
|
+
delete: true
|
|
40
|
+
});
|
|
45
41
|
}
|
|
46
|
-
if (nodeName === 'I') {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
text: element.textContent
|
|
51
|
-
};
|
|
52
|
-
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
42
|
+
if (nodeName === 'I' || nodeName === 'EM') {
|
|
43
|
+
return createTextNode({
|
|
44
|
+
italic: true
|
|
45
|
+
});
|
|
53
46
|
}
|
|
54
47
|
if (nodeName === 'INS') {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return (
|
|
48
|
+
return createTextNode({
|
|
49
|
+
add: true
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (nodeName === 'U') {
|
|
53
|
+
return createTextNode({
|
|
54
|
+
underline: true
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (nodeName === 'SUP') {
|
|
58
|
+
return createTextNode({
|
|
59
|
+
superscript: true
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (nodeName === 'SUB') {
|
|
63
|
+
return createTextNode({
|
|
64
|
+
subscript: true
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (nodeName === 'MARK') {
|
|
68
|
+
return createTextNode({
|
|
69
|
+
highlight: true
|
|
70
|
+
});
|
|
61
71
|
}
|
|
62
72
|
if (nodeType === 3) {
|
|
63
|
-
|
|
64
|
-
id: _slugid.default.nice(),
|
|
65
|
-
text: element.textContent
|
|
66
|
-
};
|
|
67
|
-
return (0, _helper.mergeElementOther2SlateNode)(element, node);
|
|
73
|
+
return createTextNode();
|
|
68
74
|
}
|
|
69
75
|
return;
|
|
70
76
|
};
|
|
@@ -11,7 +11,8 @@ var _elementTypes = require("../../extension/constants/element-types");
|
|
|
11
11
|
var _htmlToSlate = _interopRequireDefault(require("../html-to-slate"));
|
|
12
12
|
const INLINE_KEY_MAP = {
|
|
13
13
|
strong: 'bold',
|
|
14
|
-
emphasis: 'italic'
|
|
14
|
+
emphasis: 'italic',
|
|
15
|
+
delete: 'delete'
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
// <strong><em>aa<em>bb<em></strong>
|
|
@@ -88,6 +89,15 @@ const applyMarkForInlineItem = function (result, item) {
|
|
|
88
89
|
textNode = {};
|
|
89
90
|
return;
|
|
90
91
|
}
|
|
92
|
+
if (type === 'break') {
|
|
93
|
+
result.push({
|
|
94
|
+
id: _slugid.default.nice(),
|
|
95
|
+
text: '\n',
|
|
96
|
+
...textNode
|
|
97
|
+
});
|
|
98
|
+
textNode = {};
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
91
101
|
if (type === 'inlineCode') {
|
|
92
102
|
textNode['code'] = true;
|
|
93
103
|
textNode['text'] = value || '';
|
|
@@ -430,16 +440,16 @@ const transformMath = node => {
|
|
|
430
440
|
};
|
|
431
441
|
exports.transformMath = transformMath;
|
|
432
442
|
const elementHandlers = {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
443
|
+
paragraph: transformParagraph,
|
|
444
|
+
heading: transformHeader,
|
|
445
|
+
blockquote: transformBlockquote,
|
|
446
|
+
table: transformTable,
|
|
447
|
+
list: transformList,
|
|
438
448
|
// ordered_list | unordered_list | check_list_item
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
449
|
+
code: transformCodeBlock,
|
|
450
|
+
thematicBreak: transformHr,
|
|
451
|
+
math: transformMath,
|
|
452
|
+
html: transformBlockHtml
|
|
443
453
|
};
|
|
444
454
|
const formatMdToSlate = children => {
|
|
445
455
|
const validChildren = children.filter(child => elementHandlers[child.type]);
|
|
@@ -12,8 +12,7 @@ const isContentValid = value => {
|
|
|
12
12
|
};
|
|
13
13
|
const isEmptyParagraph = node => {
|
|
14
14
|
const voidNodeTypes = ['image', 'formula'];
|
|
15
|
-
if (
|
|
16
|
-
if (node.type !== _constants.ElementTypes.P) return false;
|
|
15
|
+
if (![_constants.ElementTypes.PARAGRAPH, _constants.ElementTypes.P].includes(node.type)) return false;
|
|
17
16
|
const hasBlock = node.children.some(item => voidNodeTypes.includes(item.type));
|
|
18
17
|
const hasHtml = node.children.some(item => item.type === 'html');
|
|
19
18
|
if (hasBlock) return false;
|
|
@@ -128,6 +127,21 @@ const element2Html = (value, element, path) => {
|
|
|
128
127
|
if (underline) {
|
|
129
128
|
textDom = `<span style="text-decoration: underline;">${textDom}</span>`;
|
|
130
129
|
}
|
|
130
|
+
if (element.delete) {
|
|
131
|
+
textDom = `<del>${textDom}</del>`;
|
|
132
|
+
}
|
|
133
|
+
if (element.add) {
|
|
134
|
+
textDom = `<ins>${textDom}</ins>`;
|
|
135
|
+
}
|
|
136
|
+
if (element.superscript) {
|
|
137
|
+
textDom = `<sup>${textDom}</sup>`;
|
|
138
|
+
}
|
|
139
|
+
if (element.subscript) {
|
|
140
|
+
textDom = `<sub>${textDom}</sub>`;
|
|
141
|
+
}
|
|
142
|
+
if (element.highlight) {
|
|
143
|
+
textDom = `<mark>${textDom}</mark>`;
|
|
144
|
+
}
|
|
131
145
|
if (code) {
|
|
132
146
|
const {
|
|
133
147
|
style
|
package/dist/utils/common.js
CHANGED
|
@@ -33,8 +33,10 @@ const isUrl = url => {
|
|
|
33
33
|
// Check document is empty or only contains void nodes
|
|
34
34
|
exports.isUrl = isUrl;
|
|
35
35
|
const isDocumentEmpty = editor => {
|
|
36
|
-
const document = editor.children;
|
|
36
|
+
const document = editor === null || editor === void 0 ? void 0 : editor.children;
|
|
37
|
+
if (!Array.isArray(document) || document.length === 0) return true;
|
|
37
38
|
const [firstChildNode] = document;
|
|
39
|
+
if (!firstChildNode || !Array.isArray(firstChildNode.children)) return true;
|
|
38
40
|
// Check if document has only one block node
|
|
39
41
|
const isWrapperEmpty = document.length === 1 && _slate.Node.string(firstChildNode).length === 0;
|
|
40
42
|
if (!isWrapperEmpty) return false;
|
package/dist/utils/dom.js
CHANGED
|
@@ -8,7 +8,8 @@ exports.getTarget = exports.getEventClassName = exports.getDataAttr = exports.ca
|
|
|
8
8
|
exports.hasClass = hasClass;
|
|
9
9
|
exports.isNearBottom = exports.isInputOrEditorActive = void 0;
|
|
10
10
|
exports.removeClass = removeClass;
|
|
11
|
-
exports.removeClassName = void 0;
|
|
11
|
+
exports.sanitizeHTMLContent = exports.removeClassName = void 0;
|
|
12
|
+
var _typeDetection = require("./type-detection");
|
|
12
13
|
const canUseDOM = exports.canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
13
14
|
const getEventClassName = e => {
|
|
14
15
|
// svg mouseEvent event.target.className is an object
|
|
@@ -130,4 +131,28 @@ const isNearBottom = function (element) {
|
|
|
130
131
|
const distanceToBottom = scrollHeight - (scrollTop + clientHeight);
|
|
131
132
|
return distanceToBottom <= threshold;
|
|
132
133
|
};
|
|
133
|
-
exports.isNearBottom = isNearBottom;
|
|
134
|
+
exports.isNearBottom = isNearBottom;
|
|
135
|
+
const removeCommentNodes = node => {
|
|
136
|
+
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
137
|
+
const child = node.childNodes[i];
|
|
138
|
+
if (child.nodeType === Node.COMMENT_NODE) {
|
|
139
|
+
node.removeChild(child);
|
|
140
|
+
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
141
|
+
removeCommentNodes(child);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const sanitizeHTMLContent = function () {
|
|
146
|
+
let html = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
|
|
147
|
+
if ((0, _typeDetection.isNumber)(html)) {
|
|
148
|
+
html = String(html);
|
|
149
|
+
}
|
|
150
|
+
if (!(0, _typeDetection.isString)(html)) return null;
|
|
151
|
+
const sanitizedHTML = html.replace(/<!--([\s\S]*?)-->/g, '').replace(/<style\b[^>]*>[\s\S]*?<\/style>/gi, '').replace(/<title\b[^>]*>[\s\S]*?<\/title>/gi, '').replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, '').replace(/\s*[\n\t]\s*/g, '');
|
|
152
|
+
const parsed = new DOMParser().parseFromString(sanitizedHTML, 'text/html');
|
|
153
|
+
const body = parsed.body;
|
|
154
|
+
body.querySelectorAll('style, title, script').forEach(el => el.remove());
|
|
155
|
+
removeCommentNodes(body);
|
|
156
|
+
return body;
|
|
157
|
+
};
|
|
158
|
+
exports.sanitizeHTMLContent = sanitizeHTMLContent;
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seafile/sea-email-editor",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
3
|
+
"version": "0.0.10-beta1",
|
|
4
|
+
"description": "A Slate-based rich email editor with HTML, markdown, tables, images, links, and block plugins.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
|
|
7
|
+
"@seafile/react-image-lightbox": "^5.0.4",
|
|
8
8
|
"classnames": "2.3.2",
|
|
9
9
|
"copy-to-clipboard": "3.3.1",
|
|
10
10
|
"deep-copy": "1.4.2",
|