@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.
- package/README.md +63 -0
- package/build/asset-manifest.json +12 -0
- package/build/favicon.ico +0 -0
- package/build/index.html +1 -0
- package/build/logo192.png +0 -0
- package/build/logo512.png +0 -0
- package/build/manifest.json +25 -0
- package/build/robots.txt +3 -0
- package/build/static/css/main.588cb535.css +9 -0
- package/build/static/js/206.a4343501.chunk.js +1 -0
- package/build/static/js/main.053d366a.js +2 -0
- package/build/static/js/main.053d366a.js.LICENSE.txt +56 -0
- package/package.json +64 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +50 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/src/App.js +15 -0
- package/src/components/EmailEditor/assets/App.css +339 -0
- package/src/components/EmailEditor/assets/Columns.css +309 -0
- package/src/components/EmailEditor/assets/Header.css +174 -0
- package/src/components/EmailEditor/assets/ImageBlock.css +12 -0
- package/src/components/EmailEditor/assets/Preview.css +30 -0
- package/src/components/EmailEditor/assets/RichText.css +199 -0
- package/src/components/EmailEditor/assets/RightSettings.css +520 -0
- package/src/components/EmailEditor/assets/Sidebar.css +195 -0
- package/src/components/EmailEditor/components/BlockItems/ButtonBlock.js +25 -0
- package/src/components/EmailEditor/components/BlockItems/DividerBlock.js +19 -0
- package/src/components/EmailEditor/components/BlockItems/HeadingBlock.js +16 -0
- package/src/components/EmailEditor/components/BlockItems/ImageBlock.js +28 -0
- package/src/components/EmailEditor/components/BlockItems/MenuBlock.js +52 -0
- package/src/components/EmailEditor/components/BlockItems/SocialLinkBlocks.js +26 -0
- package/src/components/EmailEditor/components/BlockItems/SpacerBlock.js +23 -0
- package/src/components/EmailEditor/components/BlockItems/TextBlock.js +16 -0
- package/src/components/EmailEditor/components/BlockItems/index.js +25 -0
- package/src/components/EmailEditor/components/ColorPicker/index.js +26 -0
- package/src/components/EmailEditor/components/Column/index.js +253 -0
- package/src/components/EmailEditor/components/Header/index.js +243 -0
- package/src/components/EmailEditor/components/LeftSideBar/index.js +253 -0
- package/src/components/EmailEditor/components/Main/index.js +281 -0
- package/src/components/EmailEditor/components/Preview/index.js +97 -0
- package/src/components/EmailEditor/components/RichText/Bold.js +37 -0
- package/src/components/EmailEditor/components/RichText/FontColor.js +39 -0
- package/src/components/EmailEditor/components/RichText/InsertOrderedList.js +36 -0
- package/src/components/EmailEditor/components/RichText/InsertUnorderedList.js +36 -0
- package/src/components/EmailEditor/components/RichText/Italic.js +36 -0
- package/src/components/EmailEditor/components/RichText/Link.js +99 -0
- package/src/components/EmailEditor/components/RichText/RichTextLayout.js +53 -0
- package/src/components/EmailEditor/components/RichText/Strikethrough.js +36 -0
- package/src/components/EmailEditor/components/RichText/TextAlign.js +58 -0
- package/src/components/EmailEditor/components/RichText/Underline.js +36 -0
- package/src/components/EmailEditor/components/RichText/index.js +210 -0
- package/src/components/EmailEditor/components/RightSetting/index.js +126 -0
- package/src/components/EmailEditor/components/StyleSettings/ButtonStyleSettings.js +141 -0
- package/src/components/EmailEditor/components/StyleSettings/ColumnStyleSettings.js +241 -0
- package/src/components/EmailEditor/components/StyleSettings/DividerStyleSettings.js +111 -0
- package/src/components/EmailEditor/components/StyleSettings/HeadingStyleSettings.js +162 -0
- package/src/components/EmailEditor/components/StyleSettings/ImageStyleSettings.js +217 -0
- package/src/components/EmailEditor/components/StyleSettings/MenuStyleSettings.js +177 -0
- package/src/components/EmailEditor/components/StyleSettings/PaddingSettings.js +30 -0
- package/src/components/EmailEditor/components/StyleSettings/SocialLinkSettings.js +250 -0
- package/src/components/EmailEditor/components/StyleSettings/SpacerStyleSettings.js +38 -0
- package/src/components/EmailEditor/components/StyleSettings/TextStyleSettings.js +108 -0
- package/src/components/EmailEditor/components/StyleSettings/index.js +32 -0
- package/src/components/EmailEditor/configs/getBlockConfigsList.js +263 -0
- package/src/components/EmailEditor/configs/getColumnConfigFunc.js +59 -0
- package/src/components/EmailEditor/configs/getColumnsSettings.js +246 -0
- package/src/components/EmailEditor/configs/useDataSource.js +19 -0
- package/src/components/EmailEditor/index.js +93 -0
- package/src/components/EmailEditor/reducers/index.js +173 -0
- package/src/components/EmailEditor/translation/en.js +166 -0
- package/src/components/EmailEditor/translation/index.js +39 -0
- package/src/components/EmailEditor/translation/zh.js +166 -0
- package/src/components/EmailEditor/utils/classNames.js +5 -0
- package/src/components/EmailEditor/utils/dataToHTML.js +323 -0
- package/src/components/EmailEditor/utils/exportValidation.js +296 -0
- package/src/components/EmailEditor/utils/helpers.js +48 -0
- package/src/components/EmailEditor/utils/pexels.js +20 -0
- package/src/components/EmailEditor/utils/useSection.js +24 -0
- package/src/components/EmailEditor/utils/useStyleLayout.js +82 -0
- package/src/index.css +99 -0
- package/src/index.js +15 -0
- package/src/logo.svg +1 -0
- package/src/pages/AppPage/index.js +10 -0
- package/src/pages/Dashboard/Header.js +192 -0
- package/src/pages/Dashboard/defaultBlockList.json +1758 -0
- package/src/pages/Dashboard/index.js +48 -0
- package/src/reportWebVitals.js +13 -0
- 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;
|