@markopolo_ai_inc/markopolo-email-editor 1.0.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.
Files changed (91) hide show
  1. package/README.md +63 -0
  2. package/build/asset-manifest.json +12 -0
  3. package/build/favicon.ico +0 -0
  4. package/build/index.html +1 -0
  5. package/build/logo192.png +0 -0
  6. package/build/logo512.png +0 -0
  7. package/build/manifest.json +25 -0
  8. package/build/robots.txt +3 -0
  9. package/build/static/css/main.588cb535.css +9 -0
  10. package/build/static/js/206.a4343501.chunk.js +1 -0
  11. package/build/static/js/main.053d366a.js +2 -0
  12. package/build/static/js/main.053d366a.js.LICENSE.txt +56 -0
  13. package/package.json +64 -0
  14. package/public/favicon.ico +0 -0
  15. package/public/index.html +50 -0
  16. package/public/logo192.png +0 -0
  17. package/public/logo512.png +0 -0
  18. package/public/manifest.json +25 -0
  19. package/public/robots.txt +3 -0
  20. package/src/App.js +15 -0
  21. package/src/components/EmailEditor/assets/App.css +339 -0
  22. package/src/components/EmailEditor/assets/Columns.css +309 -0
  23. package/src/components/EmailEditor/assets/Header.css +174 -0
  24. package/src/components/EmailEditor/assets/ImageBlock.css +12 -0
  25. package/src/components/EmailEditor/assets/Preview.css +30 -0
  26. package/src/components/EmailEditor/assets/RichText.css +199 -0
  27. package/src/components/EmailEditor/assets/RightSettings.css +520 -0
  28. package/src/components/EmailEditor/assets/Sidebar.css +195 -0
  29. package/src/components/EmailEditor/components/BlockItems/ButtonBlock.js +25 -0
  30. package/src/components/EmailEditor/components/BlockItems/DividerBlock.js +19 -0
  31. package/src/components/EmailEditor/components/BlockItems/HeadingBlock.js +16 -0
  32. package/src/components/EmailEditor/components/BlockItems/ImageBlock.js +28 -0
  33. package/src/components/EmailEditor/components/BlockItems/MenuBlock.js +52 -0
  34. package/src/components/EmailEditor/components/BlockItems/SocialLinkBlocks.js +26 -0
  35. package/src/components/EmailEditor/components/BlockItems/SpacerBlock.js +23 -0
  36. package/src/components/EmailEditor/components/BlockItems/TextBlock.js +16 -0
  37. package/src/components/EmailEditor/components/BlockItems/index.js +25 -0
  38. package/src/components/EmailEditor/components/ColorPicker/index.js +26 -0
  39. package/src/components/EmailEditor/components/Column/index.js +253 -0
  40. package/src/components/EmailEditor/components/Header/index.js +243 -0
  41. package/src/components/EmailEditor/components/LeftSideBar/index.js +253 -0
  42. package/src/components/EmailEditor/components/Main/index.js +281 -0
  43. package/src/components/EmailEditor/components/Preview/index.js +97 -0
  44. package/src/components/EmailEditor/components/RichText/Bold.js +37 -0
  45. package/src/components/EmailEditor/components/RichText/FontColor.js +39 -0
  46. package/src/components/EmailEditor/components/RichText/InsertOrderedList.js +36 -0
  47. package/src/components/EmailEditor/components/RichText/InsertUnorderedList.js +36 -0
  48. package/src/components/EmailEditor/components/RichText/Italic.js +36 -0
  49. package/src/components/EmailEditor/components/RichText/Link.js +99 -0
  50. package/src/components/EmailEditor/components/RichText/RichTextLayout.js +53 -0
  51. package/src/components/EmailEditor/components/RichText/Strikethrough.js +36 -0
  52. package/src/components/EmailEditor/components/RichText/TextAlign.js +58 -0
  53. package/src/components/EmailEditor/components/RichText/Underline.js +36 -0
  54. package/src/components/EmailEditor/components/RichText/index.js +210 -0
  55. package/src/components/EmailEditor/components/RightSetting/index.js +126 -0
  56. package/src/components/EmailEditor/components/StyleSettings/ButtonStyleSettings.js +141 -0
  57. package/src/components/EmailEditor/components/StyleSettings/ColumnStyleSettings.js +241 -0
  58. package/src/components/EmailEditor/components/StyleSettings/DividerStyleSettings.js +111 -0
  59. package/src/components/EmailEditor/components/StyleSettings/HeadingStyleSettings.js +162 -0
  60. package/src/components/EmailEditor/components/StyleSettings/ImageStyleSettings.js +217 -0
  61. package/src/components/EmailEditor/components/StyleSettings/MenuStyleSettings.js +177 -0
  62. package/src/components/EmailEditor/components/StyleSettings/PaddingSettings.js +30 -0
  63. package/src/components/EmailEditor/components/StyleSettings/SocialLinkSettings.js +250 -0
  64. package/src/components/EmailEditor/components/StyleSettings/SpacerStyleSettings.js +38 -0
  65. package/src/components/EmailEditor/components/StyleSettings/TextStyleSettings.js +108 -0
  66. package/src/components/EmailEditor/components/StyleSettings/index.js +32 -0
  67. package/src/components/EmailEditor/configs/getBlockConfigsList.js +263 -0
  68. package/src/components/EmailEditor/configs/getColumnConfigFunc.js +59 -0
  69. package/src/components/EmailEditor/configs/getColumnsSettings.js +246 -0
  70. package/src/components/EmailEditor/configs/useDataSource.js +19 -0
  71. package/src/components/EmailEditor/index.js +93 -0
  72. package/src/components/EmailEditor/reducers/index.js +173 -0
  73. package/src/components/EmailEditor/translation/en.js +166 -0
  74. package/src/components/EmailEditor/translation/index.js +39 -0
  75. package/src/components/EmailEditor/translation/zh.js +166 -0
  76. package/src/components/EmailEditor/utils/classNames.js +5 -0
  77. package/src/components/EmailEditor/utils/dataToHTML.js +323 -0
  78. package/src/components/EmailEditor/utils/exportValidation.js +296 -0
  79. package/src/components/EmailEditor/utils/helpers.js +48 -0
  80. package/src/components/EmailEditor/utils/pexels.js +20 -0
  81. package/src/components/EmailEditor/utils/useSection.js +24 -0
  82. package/src/components/EmailEditor/utils/useStyleLayout.js +82 -0
  83. package/src/index.css +99 -0
  84. package/src/index.js +15 -0
  85. package/src/logo.svg +1 -0
  86. package/src/pages/AppPage/index.js +10 -0
  87. package/src/pages/Dashboard/Header.js +192 -0
  88. package/src/pages/Dashboard/defaultBlockList.json +1758 -0
  89. package/src/pages/Dashboard/index.js +48 -0
  90. package/src/reportWebVitals.js +13 -0
  91. package/src/setupTests.js +5 -0
@@ -0,0 +1,53 @@
1
+ import { useContext, useRef, useEffect } from "react";
2
+ import { GlobalContext } from "../../reducers";
3
+ import classNames from "../../utils/classNames";
4
+ import { deepClone } from "../../utils/helpers";
5
+ import RichText from "../RichText";
6
+
7
+ const RichTextLayout = ({ index, blockItem }) => {
8
+ const { currentItem, previewMode, blockList, setBlockList, setCurrentItem, isDragStart } = useContext(GlobalContext);
9
+
10
+ const richTextRef = useRef(null);
11
+
12
+ const isEdit = currentItem && currentItem.index === index;
13
+ const styles = previewMode === "desktop" ? blockItem.styles.desktop : { ...blockItem.styles.desktop, ...blockItem.styles.mobile };
14
+
15
+ useEffect(() => {
16
+ if (isEdit) {
17
+ richTextRef.current.focus();
18
+ }
19
+ }, [isEdit]);
20
+
21
+ const setTextContent = (event) => {
22
+ const indexArray = index.split("-");
23
+ let newBlockList = deepClone(blockList);
24
+ let newCurrentItem = deepClone(currentItem);
25
+ newCurrentItem.data.text = event.target.innerHTML;
26
+ newBlockList[indexArray[0]].children[indexArray[1]].children[indexArray[2]].text = event.target.innerHTML;
27
+ setBlockList(newBlockList);
28
+ setCurrentItem({ ...newCurrentItem });
29
+ };
30
+
31
+ const preventDefault = (event) => {
32
+ event.preventDefault();
33
+ event.stopPropagation();
34
+ };
35
+
36
+ return (
37
+ <div className="relative">
38
+ {isEdit && blockItem && !isDragStart && <RichText textBlock={richTextRef} index={index} styles={styles} />}
39
+ <div
40
+ className={classNames(isEdit && "text-block", "text-content_editable")}
41
+ onClick={preventDefault}
42
+ onInput={setTextContent}
43
+ style={styles}
44
+ contentEditable={isEdit}
45
+ suppressContentEditableWarning
46
+ ref={richTextRef}
47
+ dangerouslySetInnerHTML={{ __html: blockItem.text }}
48
+ ></div>
49
+ </div>
50
+ );
51
+ };
52
+
53
+ export default RichTextLayout;
@@ -0,0 +1,36 @@
1
+ import { useMemo, useContext } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { faStrikethrough } from "@fortawesome/free-solid-svg-icons";
4
+ import useSection from "../../utils/useSection";
5
+ import classNames from "../../utils/classNames";
6
+ import { GlobalContext } from "../../reducers";
7
+ import useTranslation from "../../translation";
8
+
9
+ const Strikethrough = ({ modifyText, setTextContent }) => {
10
+ const { selectionRange } = useContext(GlobalContext);
11
+ const { getSelectionNode } = useSection();
12
+ const { t } = useTranslation();
13
+
14
+ const node = useMemo(() => {
15
+ if (selectionRange) {
16
+ return getSelectionNode(selectionRange.commonAncestorContainer, "strike");
17
+ } else {
18
+ return null;
19
+ }
20
+ }, [selectionRange, getSelectionNode]);
21
+
22
+ return (
23
+ <button
24
+ className={classNames("rich-text-tools-button ", node && "rich-text-tools-button-active")}
25
+ title={t("tooltip_strikethrough")}
26
+ onClick={() => {
27
+ modifyText("strikethrough", false, null);
28
+ setTextContent();
29
+ }}
30
+ >
31
+ <FontAwesomeIcon icon={faStrikethrough} className="rich-text-tools-button-icon" />
32
+ </button>
33
+ );
34
+ };
35
+
36
+ export default Strikethrough;
@@ -0,0 +1,58 @@
1
+ import { useMemo, useContext } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { faAlignLeft, faAlignCenter, faAlignRight } from "@fortawesome/free-solid-svg-icons";
4
+ import useSection from "../../utils/useSection";
5
+ import classNames from "../../utils/classNames";
6
+ import { GlobalContext } from "../../reducers";
7
+ import useTranslation from "../../translation";
8
+
9
+ const TextAlign = ({ modifyText, setTextContent }) => {
10
+ const { selectionRange } = useContext(GlobalContext);
11
+ const { getSectionStyle } = useSection();
12
+ const { t } = useTranslation();
13
+
14
+ const textAlign = useMemo(() => {
15
+ if (selectionRange) {
16
+ return getSectionStyle(selectionRange.commonAncestorContainer, "textAlign");
17
+ } else {
18
+ return null;
19
+ }
20
+ }, [selectionRange, getSectionStyle]);
21
+
22
+ return (
23
+ <>
24
+ <button
25
+ className={classNames("rich-text-tools-button ", textAlign === "left" && "rich-text-tools-button-active")}
26
+ title={t("tooltip_align_left")}
27
+ onClick={() => {
28
+ modifyText("justifyLeft", false, null);
29
+ setTextContent();
30
+ }}
31
+ >
32
+ <FontAwesomeIcon icon={faAlignLeft} className="rich-text-tools-button-icon" />
33
+ </button>
34
+ <button
35
+ className={classNames("rich-text-tools-button ", textAlign === "center" && "rich-text-tools-button-active")}
36
+ title={t("tooltip_align_center")}
37
+ onClick={() => {
38
+ modifyText("justifyCenter", false, null);
39
+ setTextContent();
40
+ }}
41
+ >
42
+ <FontAwesomeIcon icon={faAlignCenter} className="rich-text-tools-button-icon" />
43
+ </button>
44
+ <button
45
+ className={classNames("rich-text-tools-button ", textAlign === "right" && "rich-text-tools-button-active")}
46
+ title={t("tooltip_align_right")}
47
+ onClick={() => {
48
+ modifyText("justifyRight", false, null);
49
+ setTextContent();
50
+ }}
51
+ >
52
+ <FontAwesomeIcon icon={faAlignRight} className="rich-text-tools-button-icon" />
53
+ </button>
54
+ </>
55
+ );
56
+ };
57
+
58
+ export default TextAlign;
@@ -0,0 +1,36 @@
1
+ import { useMemo, useContext } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { faUnderline } from "@fortawesome/free-solid-svg-icons";
4
+ import useSection from "../../utils/useSection";
5
+ import classNames from "../../utils/classNames";
6
+ import { GlobalContext } from "../../reducers";
7
+ import useTranslation from "../../translation";
8
+
9
+ const Underline = ({ modifyText, setTextContent }) => {
10
+ const { selectionRange } = useContext(GlobalContext);
11
+ const { getSelectionNode } = useSection();
12
+ const { t } = useTranslation();
13
+
14
+ const node = useMemo(() => {
15
+ if (selectionRange) {
16
+ return getSelectionNode(selectionRange.commonAncestorContainer, "u");
17
+ } else {
18
+ return null;
19
+ }
20
+ }, [selectionRange, getSelectionNode]);
21
+
22
+ return (
23
+ <button
24
+ className={classNames("rich-text-tools-button ", node && "rich-text-tools-button-active")}
25
+ title={t("tooltip_underline")}
26
+ onClick={() => {
27
+ modifyText("underline", false, null);
28
+ setTextContent();
29
+ }}
30
+ >
31
+ <FontAwesomeIcon icon={faUnderline} className="rich-text-tools-button-icon" />
32
+ </button>
33
+ );
34
+ };
35
+
36
+ export default Underline;
@@ -0,0 +1,210 @@
1
+ import { useEffect, useRef, useContext } from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { faAngleDown, faExpandAlt, faCompressAlt } from "@fortawesome/free-solid-svg-icons";
4
+ import { useState } from "react";
5
+ import { GlobalContext } from "../../reducers";
6
+ import { deepClone } from "../../utils/helpers";
7
+ import { motion } from "framer-motion";
8
+
9
+ import Bold from "./Bold";
10
+ import Italic from "./Italic";
11
+ import Underline from "./Underline";
12
+ import Strikethrough from "./Strikethrough";
13
+ import InsertOrderedList from "./InsertOrderedList";
14
+ import InsertUnorderedList from "./InsertUnorderedList";
15
+ import Link from "./Link";
16
+ import TextAlign from "./TextAlign";
17
+ import FontColor from "./FontColor";
18
+
19
+ const RichText = ({ index, textBlock, styles }) => {
20
+ const { blockList, setBlockList, currentItem, setCurrentItem } = useContext(GlobalContext);
21
+ const richTextRef = useRef(null);
22
+ const [isHidden, setIsHidden] = useState(false);
23
+
24
+ useEffect(() => {
25
+ if (richTextRef.current) {
26
+ var viewportOffset = richTextRef.current.parentNode?.getBoundingClientRect();
27
+ const preview = document.querySelector("#preview");
28
+ const previewOffset = preview.getBoundingClientRect();
29
+ if (viewportOffset.top - 190 < 0) {
30
+ richTextRef.current.style.bottom = "auto";
31
+ richTextRef.current.style.top = "110%";
32
+ } else {
33
+ richTextRef.current.style.bottom = "110%";
34
+ richTextRef.current.style.top = "auto";
35
+ }
36
+ if (viewportOffset.left + 375 > previewOffset.left + previewOffset.width) {
37
+ richTextRef.current.style.left = "auto";
38
+ richTextRef.current.style.right = "0";
39
+ } else {
40
+ richTextRef.current.style.left = "0";
41
+ richTextRef.current.style.right = "auto";
42
+ }
43
+ }
44
+ }, []);
45
+
46
+ const modifyText = (command, defaultUi, value) => {
47
+ document.execCommand(command, defaultUi, value);
48
+ };
49
+
50
+ const fontSizeList = [
51
+ "8px",
52
+ "10px",
53
+ "12px",
54
+ "14px",
55
+ "16px",
56
+ "18px",
57
+ "20px",
58
+ "22px",
59
+ "24px",
60
+ "26px",
61
+ "28px",
62
+ "30px",
63
+ "32px",
64
+ "34px",
65
+ "36px",
66
+ "38px",
67
+ "40px",
68
+ "44px",
69
+ "48px",
70
+ "72px",
71
+ ];
72
+
73
+ const fontname_configs = ["sans-serif", "Arial", "Verdana", "Times New Roman", "Garamond", "Georgia", "Courier New", "cursive"];
74
+
75
+ const setTextContent = () => {
76
+ const indexArray = index.split("-");
77
+ let newBlockList = deepClone(blockList);
78
+ let newCurrentItem = deepClone(currentItem);
79
+ newCurrentItem.data.text = textBlock.current.innerHTML;
80
+ newBlockList[indexArray[0]].children[indexArray[1]].children[indexArray[2]].text = textBlock.current.innerHTML;
81
+ setCurrentItem({ ...newCurrentItem });
82
+ setBlockList(newBlockList);
83
+ };
84
+
85
+ const selectElement = (selectList, defaultValue, type, onChange) => {
86
+ const hideOptions = () => {
87
+ const options = document.querySelector(`#richText-options-${type}-${index}`);
88
+ const mask = document.querySelector(`#richText-mask-${type}-${index}`);
89
+ options.style.animation = "leave 0.2s linear";
90
+ setTimeout(() => {
91
+ options.style.display = "none";
92
+ mask.style.display = "none";
93
+ }, 100);
94
+ };
95
+
96
+ const showOptions = () => {
97
+ const options = document.querySelector(`#richText-options-${type}-${index}`);
98
+ const mask = document.querySelector(`#richText-mask-${type}-${index}`);
99
+ options.style.display = "block";
100
+ mask.style.display = "block";
101
+ options.style.animation = "move 0.2s linear";
102
+ };
103
+ return (
104
+ <div className="richText-select">
105
+ <div
106
+ className="richText-select-select"
107
+ onClick={(event) => {
108
+ event.stopPropagation();
109
+ const options = document.querySelector(`#richText-options-${type}-${index}`);
110
+ if (options.style.display === "block") {
111
+ hideOptions();
112
+ } else {
113
+ showOptions();
114
+ }
115
+ }}
116
+ >
117
+ <span className="richText-select-value" id={`richText-select-value-${type}-${index}`}>
118
+ {defaultValue}
119
+ </span>
120
+ <FontAwesomeIcon icon={faAngleDown} className="richText-select-icon" />
121
+ </div>
122
+ <div className="richText-mask" id={`richText-mask-${type}-${index}`} onClick={hideOptions}></div>
123
+ <div className="richText-select-option" id={`richText-options-${type}-${index}`}>
124
+ {selectList.map((item) => {
125
+ return (
126
+ <div
127
+ className="richText-select-option_item"
128
+ key={item}
129
+ onClick={() => {
130
+ const currentValueDom = document.querySelector(`#richText-select-value-${type}-${index}`);
131
+ currentValueDom.innerHTML = item;
132
+ hideOptions();
133
+ onChange && onChange(item);
134
+ setTextContent();
135
+ }}
136
+ >
137
+ {item}
138
+ </div>
139
+ );
140
+ })}
141
+ </div>
142
+ </div>
143
+ );
144
+ };
145
+
146
+ const editFontSize = (item) => {
147
+ document.execCommand("fontSize", 0, "7");
148
+ var fontElements = document.getElementsByTagName("font");
149
+ for (var idx = 0, len = fontElements.length; idx < len; ++idx) {
150
+ if (fontElements[idx].size === "7") {
151
+ fontElements[idx].removeAttribute("size");
152
+ fontElements[idx].style.fontSize = item;
153
+ }
154
+ }
155
+ };
156
+
157
+ const editFontName = (item) => {
158
+ modifyText("fontName", false, item);
159
+ };
160
+
161
+ return (
162
+ <div
163
+ className="rich-text"
164
+ ref={richTextRef}
165
+ onClick={(event) => {
166
+ event.preventDefault();
167
+ event.stopPropagation();
168
+ }}
169
+ >
170
+ <motion.div
171
+ className="rich-text-tools"
172
+ initial={{ scale: 0, x: 0 }}
173
+ animate={{ scale: 1, x: 0 }}
174
+ style={{ width: isHidden ? "auto" : "375px" }}
175
+ >
176
+ <div className="rich-text-tools-body items-center">
177
+ {!isHidden && (
178
+ <>
179
+ {selectElement(fontSizeList, styles.fontSize + "px", "fontSize", editFontSize)}
180
+ {selectElement(fontname_configs, styles.fontFamily, "fontName", editFontName)}
181
+ {/* Font color */}
182
+ <FontColor modifyText={modifyText} setTextContent={setTextContent} />
183
+ {/* Bold */}
184
+ <Bold modifyText={modifyText} setTextContent={setTextContent} />
185
+ {/* Italic */}
186
+ <Italic modifyText={modifyText} setTextContent={setTextContent} />
187
+ {/* Underline */}
188
+ <Underline modifyText={modifyText} setTextContent={setTextContent} />
189
+ {/* Strikethrough */}
190
+ <Strikethrough modifyText={modifyText} setTextContent={setTextContent} />
191
+ {/* Ordered list */}
192
+ <InsertOrderedList modifyText={modifyText} setTextContent={setTextContent} />
193
+ {/* Unordered list */}
194
+ <InsertUnorderedList modifyText={modifyText} setTextContent={setTextContent} />
195
+ {/* Link */}
196
+ <Link modifyText={modifyText} setTextContent={setTextContent} />
197
+ {/* Text align */}
198
+ <TextAlign modifyText={modifyText} setTextContent={setTextContent} />
199
+ </>
200
+ )}
201
+ <button className="rich-text-tools-button" onClick={() => setIsHidden(!isHidden)}>
202
+ {<FontAwesomeIcon icon={isHidden ? faExpandAlt : faCompressAlt} className="rich-text-tools-button-icon" />}
203
+ </button>
204
+ </div>
205
+ </motion.div>
206
+ </div>
207
+ );
208
+ };
209
+
210
+ export default RichText;
@@ -0,0 +1,126 @@
1
+ import { useContext } from "react";
2
+ import { AnimatePresence, motion } from "framer-motion";
3
+ import ColorPicker from "../ColorPicker";
4
+ import { GlobalContext } from "../../reducers";
5
+ import { InputNumber, Input } from "antd";
6
+ import StyleSettings from "../StyleSettings";
7
+ import useLayout from "../../utils/useStyleLayout";
8
+ import useTranslation from "../../translation";
9
+
10
+ const RightSetting = () => {
11
+ const { currentItem, isDragStart, bodySettings, setBodySettings } = useContext(GlobalContext);
12
+ const { t } = useTranslation();
13
+ const { cardItemElement } = useLayout();
14
+ const blockTitle = () => {
15
+ let title = "Block";
16
+ const type = currentItem?.data.key;
17
+ switch (type) {
18
+ case "text":
19
+ title = t("text_settings");
20
+ break;
21
+ case "column":
22
+ title = t("column_settings");
23
+ break;
24
+ case "heading":
25
+ title = t("heading_settings");
26
+ break;
27
+ case "button":
28
+ title = t("button_settings");
29
+ break;
30
+ case "divider":
31
+ title = t("divider_settings");
32
+ break;
33
+ case "spacer":
34
+ title = t("spacer_settings");
35
+ break;
36
+ case "menu":
37
+ title = t("menu_settings");
38
+ break;
39
+ case "image":
40
+ title = t("image_settings");
41
+ break;
42
+ case "social_link":
43
+ title = t("social_link_settings");
44
+ break;
45
+ default:
46
+ break;
47
+ }
48
+ return title;
49
+ };
50
+
51
+ const colorChange = (key) => (color) => {
52
+ setBodySettings({ ...bodySettings, styles: { ...bodySettings.styles, [key]: color.hex } }, "set_body_settings");
53
+ };
54
+
55
+ const themeElement = () => {
56
+ return (
57
+ <>
58
+ <div className="subject-settings">{t("body_settings")}</div>
59
+ <div className="margin-top-32">
60
+ {cardItemElement(t("text_color"), <ColorPicker color={bodySettings.styles.color} setColor={colorChange("color")} />)}
61
+ {cardItemElement(
62
+ t("email_theme_background_color"),
63
+ <ColorPicker color={bodySettings.styles.backgroundColor} setColor={colorChange("backgroundColor")} />
64
+ )}
65
+ {cardItemElement(
66
+ t("line_height"),
67
+ <InputNumber
68
+ className="input-width"
69
+ addonAfter="px"
70
+ min={0}
71
+ max={900}
72
+ value={Number(bodySettings.contentWidth)}
73
+ onChange={(value) => setBodySettings({ ...bodySettings, contentWidth: value }, "set_body_settings")}
74
+ />
75
+ )}
76
+ <div>
77
+ <div className="pre_header">{t("pre_header")}</div>
78
+ <Input
79
+ className="margin-top-12"
80
+ value={bodySettings.preHeader}
81
+ onChange={(event) => setBodySettings({ ...bodySettings, preHeader: event.target.value }, "set_body_settings")}
82
+ />
83
+ <div className="pre_header-desc">{t("pre_header_description")}</div>
84
+ </div>
85
+ </div>
86
+ </>
87
+ );
88
+ };
89
+
90
+ const stopPropagation = (event) => {
91
+ event.stopPropagation();
92
+ };
93
+
94
+ return (
95
+ <div className="right-settings default-scrollbar" onClick={stopPropagation}>
96
+ <AnimatePresence mode="wait">
97
+ {!isDragStart && currentItem && currentItem.type === "edit" ? (
98
+ <motion.div
99
+ initial={{ opacity: 0, y: -10 }}
100
+ animate={{ opacity: 1, y: 0 }}
101
+ exit={{ opacity: 0, y: 10 }}
102
+ transition={{ duration: 0.3 }}
103
+ key={0}
104
+ >
105
+ <h2 className="right-setting-block-title">{blockTitle()}</h2>
106
+ <div className="margin-top-18">
107
+ <StyleSettings />
108
+ </div>
109
+ </motion.div>
110
+ ) : (
111
+ <motion.div
112
+ initial={{ opacity: 0, y: -10 }}
113
+ animate={{ opacity: 1, y: 0 }}
114
+ exit={{ opacity: 0, y: 10 }}
115
+ transition={{ duration: 0.3 }}
116
+ key={1}
117
+ >
118
+ {themeElement()}
119
+ </motion.div>
120
+ )}
121
+ </AnimatePresence>
122
+ </div>
123
+ );
124
+ };
125
+
126
+ export default RightSetting;
@@ -0,0 +1,141 @@
1
+ import { useContext } from "react";
2
+ import { GlobalContext } from "../../reducers";
3
+ import classNames from "../../utils/classNames";
4
+ import { InputNumber, Select, Switch, Slider, Input } from "antd";
5
+
6
+ import ColorPicker from "../ColorPicker";
7
+ import PaddingSettings from "./PaddingSettings";
8
+ import useLayout from "../../utils/useStyleLayout";
9
+ import { deepClone } from "../../utils/helpers";
10
+ import useTranslation from "../../translation";
11
+
12
+ const ButtonStyleSettings = () => {
13
+ const { currentItem, previewMode } = useContext(GlobalContext);
14
+ const { t } = useTranslation();
15
+ const { findStyleItem, cardItemElement, colorChange, paddingChange, inputChange, updateItemStyles } = useLayout();
16
+
17
+ const buttonActionElement = () => {
18
+ const { linkURL } = currentItem.data;
19
+ const linkChange = (event) => {
20
+ const newValue = event.target.value;
21
+ const newCurrentItem = deepClone(currentItem);
22
+ newCurrentItem.data.linkURL = newValue;
23
+ updateItemStyles(newCurrentItem.data);
24
+ };
25
+
26
+ return (
27
+ <div>
28
+ <div className="right-setting-block-item-title">{t("button_action")}</div>
29
+ {cardItemElement(t("action_type"), <div className="link-tag">{t("link")}</div>)}
30
+ <div className="card-item-title">{t("link_url")}</div>
31
+ <div className="margin-top-6">
32
+ <Input addonBefore="https://" value={linkURL} onChange={linkChange} />
33
+ </div>
34
+ </div>
35
+ );
36
+ };
37
+
38
+ const PaddingStylesElement = () => {
39
+ const updateContentStylesPadding = (padding) => {
40
+ const newData = deepClone(currentItem.data);
41
+ newData.contentStyles[previewMode] = {
42
+ ...newData.contentStyles[previewMode],
43
+ ...padding,
44
+ };
45
+ updateItemStyles(newData);
46
+ };
47
+
48
+ return (
49
+ <>
50
+ <div className="right-setting-block-item-title"> {t("padding_settings")}</div>
51
+ <PaddingSettings
52
+ padding={{
53
+ paddingTop: findStyleItem(currentItem.data.contentStyles, "paddingTop"),
54
+ paddingRight: findStyleItem(currentItem.data.contentStyles, "paddingRight"),
55
+ paddingLeft: findStyleItem(currentItem.data.contentStyles, "paddingLeft"),
56
+ paddingBottom: findStyleItem(currentItem.data.contentStyles, "paddingBottom"),
57
+ }}
58
+ setPadding={updateContentStylesPadding}
59
+ />
60
+ </>
61
+ );
62
+ };
63
+
64
+ const textStylesElement = () => {
65
+ const width = findStyleItem(currentItem.data.styles, "width");
66
+ const color = findStyleItem(currentItem.data.styles, "color");
67
+ const fontFamily = findStyleItem(currentItem.data.styles, "fontFamily");
68
+ const fontSize = findStyleItem(currentItem.data.styles, "fontSize");
69
+ const lineHeight = findStyleItem(currentItem.data.styles, "lineHeight");
70
+ const backgroundColor = findStyleItem(currentItem.data.styles, "backgroundColor");
71
+ const fontFamilyList = ["sans-serif", "Arial", "Verdana", "Times New Roman", "Garamond", "Georgia", "Courier New", "cursive"];
72
+
73
+ return (
74
+ <>
75
+ <div className="right-setting-block-item-title">{t("button_styles")}</div>
76
+ {cardItemElement(
77
+ t("width_auto"),
78
+ <Switch
79
+ checked={width === "auto"}
80
+ className={classNames(width === "auto" ? "switch-active" : "switch-disabled")}
81
+ onChange={() => {
82
+ const value = width === "auto" ? "100%" : "auto";
83
+ inputChange("width")(value);
84
+ }}
85
+ />
86
+ )}
87
+ {width !== "auto" && <Slider value={Number(width.replace("%", ""))} onChange={(value) => inputChange("width")(value + "%")} />}
88
+ {cardItemElement(t("font_color"), <ColorPicker color={color} setColor={colorChange("color")} />)}
89
+ {cardItemElement(t("button_color"), <ColorPicker color={backgroundColor} setColor={colorChange("backgroundColor")} />)}
90
+ {cardItemElement(
91
+ t("font_family"),
92
+ <Select
93
+ className="input-width"
94
+ value={fontFamily}
95
+ onChange={inputChange("fontFamily")}
96
+ getPopupContainer={() => document.querySelector(".right-settings") || document.body}
97
+ >
98
+ {fontFamilyList.map((item) => (
99
+ <Select.Option key={item} value={item}>
100
+ {item}
101
+ </Select.Option>
102
+ ))}
103
+ </Select>
104
+ )}
105
+ {cardItemElement(
106
+ t("font_size"),
107
+ <InputNumber min={0} className="input-width" addonAfter="px" value={fontSize} onChange={inputChange("fontSize")} />
108
+ )}
109
+ {cardItemElement(
110
+ t("line_height"),
111
+ <InputNumber
112
+ className="input-width"
113
+ addonAfter="%"
114
+ min={0}
115
+ value={Number(lineHeight.replace("%", ""))}
116
+ onChange={(value) => inputChange("lineHeight")(value + "%")}
117
+ />
118
+ )}
119
+ <div className="card-item-title">{t("button_padding")}</div>
120
+ <PaddingSettings
121
+ padding={{
122
+ paddingTop: findStyleItem(currentItem.data.styles, "paddingTop"),
123
+ paddingRight: findStyleItem(currentItem.data.styles, "paddingRight"),
124
+ paddingLeft: findStyleItem(currentItem.data.styles, "paddingLeft"),
125
+ paddingBottom: findStyleItem(currentItem.data.styles, "paddingBottom"),
126
+ }}
127
+ setPadding={paddingChange}
128
+ />
129
+ </>
130
+ );
131
+ };
132
+ return (
133
+ <div className="margin-y-30">
134
+ {buttonActionElement()}
135
+ {textStylesElement()}
136
+ {PaddingStylesElement()}
137
+ </div>
138
+ );
139
+ };
140
+
141
+ export default ButtonStyleSettings;