@seafile/sea-email-editor 0.0.9 → 0.0.10

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 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.
@@ -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
@@ -145,8 +145,9 @@ const formatElementNodes = nodes => {
145
145
  return nodes;
146
146
  };
147
147
  const deserializeHtml = html => {
148
- const parsed = new DOMParser().parseFromString(html.replace('\n\n', '').replace('\n ', ''), 'text/html');
148
+ const parsed = new DOMParser().parseFromString(html.replace('\n', '').replace(' ', ''), 'text/html');
149
149
  const fragment = parsed.body;
150
+ fragment.querySelectorAll('style, title').forEach(el => el.remove());
150
151
  const children = Array.from(fragment.childNodes);
151
152
  let nodes = [];
152
153
  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].textContent;
32
- const textArr = content.split('\n').filter(Boolean);
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: parseChild(childNodes)
38
+ children
32
39
  };
33
40
  return (0, _helper.mergeElementOther2SlateNode)(element, node);
34
41
  }
@@ -12,59 +12,65 @@ const textRule = (element, parseChild) => {
12
12
  nodeName,
13
13
  nodeType
14
14
  } = element;
15
- if (nodeName === 'SPAN') {
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
- const node = {
24
- id: _slugid.default.nice(),
25
- bold: true,
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
- const node = {
32
- id: _slugid.default.nice(),
33
- code: true,
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
- const node = {
40
- id: _slugid.default.nice(),
41
- delete: true,
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
- const node = {
48
- id: _slugid.default.nice(),
49
- italic: true,
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
- const node = {
56
- id: _slugid.default.nice(),
57
- add: true,
58
- text: element.textContent
59
- };
60
- return (0, _helper.mergeElementOther2SlateNode)(element, node);
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
- const node = {
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
- 'paragraph': transformParagraph,
434
- 'heading': transformHeader,
435
- 'blockquote': transformBlockquote,
436
- 'table': transformTable,
437
- 'list': transformList,
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
- 'code': transformCodeBlock,
440
- 'thematicBreak': transformHr,
441
- 'math': transformMath,
442
- 'html': transformBlockHtml
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 (node.type !== _constants.ElementTypes.PARAGRAPH) return false;
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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@seafile/sea-email-editor",
3
- "version": "0.0.9",
4
- "description": "",
3
+ "version": "0.0.10",
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
- "@seafile/react-image-lightbox": "^5.0.4",
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",