@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,39 @@
|
|
|
1
|
+
import zh from "./zh";
|
|
2
|
+
import en from "./en";
|
|
3
|
+
import { useContext } from "react";
|
|
4
|
+
import { GlobalContext } from "../reducers";
|
|
5
|
+
|
|
6
|
+
const useTranslation = () => {
|
|
7
|
+
const { language, languageLibraries } = useContext(GlobalContext);
|
|
8
|
+
let langLibraries = { zh, en };
|
|
9
|
+
|
|
10
|
+
if (languageLibraries) {
|
|
11
|
+
langLibraries = {
|
|
12
|
+
...langLibraries,
|
|
13
|
+
...languageLibraries,
|
|
14
|
+
zh: {
|
|
15
|
+
...zh,
|
|
16
|
+
...(langLibraries["zh"] || {}),
|
|
17
|
+
},
|
|
18
|
+
en: {
|
|
19
|
+
...en,
|
|
20
|
+
...(langLibraries["en"] || {}),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const t = (key, metaTag) => {
|
|
26
|
+
const langLibrary = langLibraries[language] || {};
|
|
27
|
+
let translation = langLibrary[key];
|
|
28
|
+
if (metaTag && metaTag instanceof Object) {
|
|
29
|
+
Object.keys(metaTag).forEach((key) => {
|
|
30
|
+
translation = translation.replace(`{{${key}}}`, metaTag[key]);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return translation || key;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return { t };
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export default useTranslation;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
const zh = {
|
|
2
|
+
drag_block_here: "请将模块拖放到此处",
|
|
3
|
+
blocks: "模块",
|
|
4
|
+
photos: "图片",
|
|
5
|
+
powered_by_pexels: "图片由Pexels提供",
|
|
6
|
+
loading: "加载中...",
|
|
7
|
+
content: "内容",
|
|
8
|
+
body_settings: "邮件主题设置",
|
|
9
|
+
pre_header: "预标题",
|
|
10
|
+
pre_header_description: "预标题是在收件箱中查看电子邮件时跟随主题行的简短摘要文本。",
|
|
11
|
+
confirm: "确认",
|
|
12
|
+
cancel: "取消",
|
|
13
|
+
add_blocks: "请添加块",
|
|
14
|
+
|
|
15
|
+
// column block
|
|
16
|
+
column: "列",
|
|
17
|
+
columns: "列排版",
|
|
18
|
+
column_settings: "列设置",
|
|
19
|
+
column_styles: "列样式",
|
|
20
|
+
column_delete: "删除列",
|
|
21
|
+
column_delete_desc: "您确定删除多余的{{count}}列吗?",
|
|
22
|
+
|
|
23
|
+
//text block
|
|
24
|
+
text: "文本",
|
|
25
|
+
text_content: "这是一个文本,点击修改文本",
|
|
26
|
+
text_settings: "文本设置",
|
|
27
|
+
text_styles: "文本样式",
|
|
28
|
+
text_align: "文本对齐方式",
|
|
29
|
+
|
|
30
|
+
// heading block
|
|
31
|
+
heading: "标题",
|
|
32
|
+
heading_content: "这是一个标题,点击修改标题",
|
|
33
|
+
heading_settings: "标题设置",
|
|
34
|
+
heading_type: "标题类型",
|
|
35
|
+
|
|
36
|
+
// button block
|
|
37
|
+
button: "按钮",
|
|
38
|
+
button_settings: "按钮设置",
|
|
39
|
+
button_action: "点击按钮触发",
|
|
40
|
+
button_styles: "按钮样式",
|
|
41
|
+
button_padding: "按钮内边距",
|
|
42
|
+
|
|
43
|
+
// spacer block
|
|
44
|
+
spacer: "间距",
|
|
45
|
+
spacer_block: "间距块",
|
|
46
|
+
spacer_settings: "间距设置",
|
|
47
|
+
// divider block
|
|
48
|
+
divider: "分割线",
|
|
49
|
+
divider_settings: "分割线设置",
|
|
50
|
+
divider_type: "分割线类型",
|
|
51
|
+
divider_styles: "分割线样式",
|
|
52
|
+
|
|
53
|
+
// image block
|
|
54
|
+
image: "图片",
|
|
55
|
+
image_settings: "图片设置",
|
|
56
|
+
image_action: "点击图片跳转",
|
|
57
|
+
image_url: "图片URL",
|
|
58
|
+
image_alt: "图片alt",
|
|
59
|
+
image_styles: "图片样式",
|
|
60
|
+
image_source: "图片来源",
|
|
61
|
+
image_source_url: "输入URL",
|
|
62
|
+
image_source_upload: "上传图片",
|
|
63
|
+
image_upload: "上传",
|
|
64
|
+
image_upload_click: "点击上传",
|
|
65
|
+
|
|
66
|
+
// menu block
|
|
67
|
+
menu: "菜单",
|
|
68
|
+
menu_settings: "菜单设置",
|
|
69
|
+
menu_items: "菜单项",
|
|
70
|
+
menu_item: "菜单项",
|
|
71
|
+
menu_styles: "菜单样式",
|
|
72
|
+
add_new_item: "添加新项",
|
|
73
|
+
label: "标签",
|
|
74
|
+
page: "页面",
|
|
75
|
+
url: "链接地址",
|
|
76
|
+
target: "打开方式",
|
|
77
|
+
same_tab: "当前窗口",
|
|
78
|
+
new_tab: "新窗口",
|
|
79
|
+
separator: "分隔符",
|
|
80
|
+
letter_spacing: "字间距",
|
|
81
|
+
link_color: "链接颜色",
|
|
82
|
+
|
|
83
|
+
// social link block
|
|
84
|
+
social_link: "社交链接",
|
|
85
|
+
add_social_link: "添加社交链接",
|
|
86
|
+
social_link_size: "社交链接大小",
|
|
87
|
+
social_links: "社交链接",
|
|
88
|
+
social_link_settings: "社交链接设置",
|
|
89
|
+
|
|
90
|
+
// colors
|
|
91
|
+
content_background_color: "内容背景颜色",
|
|
92
|
+
background_color: "背景颜色",
|
|
93
|
+
text_color: "文本颜色",
|
|
94
|
+
email_theme_background_color: "邮件主题色",
|
|
95
|
+
font_color: "字体颜色",
|
|
96
|
+
button_color: "按钮颜色",
|
|
97
|
+
divider_color: "分割线颜色",
|
|
98
|
+
|
|
99
|
+
// styles
|
|
100
|
+
action_type: "按钮类型",
|
|
101
|
+
top: "上",
|
|
102
|
+
right: "右",
|
|
103
|
+
left: "左",
|
|
104
|
+
bottom: "下",
|
|
105
|
+
line_height: "行高",
|
|
106
|
+
link: "超链接",
|
|
107
|
+
link_url: "超链接URL",
|
|
108
|
+
padding_settings: "内边距设置",
|
|
109
|
+
width_auto: "宽度自适应",
|
|
110
|
+
width: "宽度",
|
|
111
|
+
height: "高度",
|
|
112
|
+
font_size: "字体大小",
|
|
113
|
+
font_family: "字体",
|
|
114
|
+
align: "对齐方式",
|
|
115
|
+
solid: "实线",
|
|
116
|
+
dotted: "虚线(点)",
|
|
117
|
+
dashed: "虚线(破折号)",
|
|
118
|
+
spacing: "间距",
|
|
119
|
+
|
|
120
|
+
// Dashboard / export
|
|
121
|
+
export_html: "导出html",
|
|
122
|
+
export_json: "导出JSON",
|
|
123
|
+
language_zh: "中文",
|
|
124
|
+
|
|
125
|
+
// Export validation modal
|
|
126
|
+
export_validation_title: "导出前校验",
|
|
127
|
+
export_validation_issues_found: "导出前发现以下问题",
|
|
128
|
+
export_validation_errors: "错误",
|
|
129
|
+
export_validation_warnings: "警告",
|
|
130
|
+
export_validation_info: "提示",
|
|
131
|
+
export_anyway: "仍要导出",
|
|
132
|
+
validation_body_content_width_invalid: "邮件内容宽度必须为正数。",
|
|
133
|
+
validation_body_background_color_missing: "主题背景色未设置。",
|
|
134
|
+
validation_body_text_color_missing: "主题文字颜色未设置。",
|
|
135
|
+
validation_body_font_family_missing: "主题字体未设置。",
|
|
136
|
+
validation_image_empty_src: "图片地址为空,图片将无法显示。",
|
|
137
|
+
validation_image_invalid_src: "图片地址格式无效。",
|
|
138
|
+
validation_image_empty_alt: "图片未设置 alt 文本,建议添加以提升可访问性。",
|
|
139
|
+
validation_button_empty_link: "按钮链接为空,将导出为 href=\"https://\"。",
|
|
140
|
+
validation_button_invalid_link: "按钮链接格式无效。",
|
|
141
|
+
validation_menu_empty_label: "菜单项标签为空,将显示为「链接」。",
|
|
142
|
+
validation_menu_empty_url: "菜单项链接为空,将导出为 href=\"#\"。",
|
|
143
|
+
validation_menu_invalid_url: "菜单项链接格式无效。",
|
|
144
|
+
validation_menu_no_items: "菜单块没有任何项。",
|
|
145
|
+
validation_social_empty_link: "社交链接地址为空,将导出为 href=\"https://\"。",
|
|
146
|
+
validation_social_invalid_link: "社交链接地址格式无效。",
|
|
147
|
+
validation_social_empty_icon: "社交链接图标地址为空,图标将无法显示。",
|
|
148
|
+
validation_social_invalid_icon: "社交链接图标地址格式无效。",
|
|
149
|
+
validation_social_no_links: "社交链接块没有任何链接。",
|
|
150
|
+
|
|
151
|
+
// Rich text tooltips
|
|
152
|
+
tooltip_bold: "加粗",
|
|
153
|
+
tooltip_italic: "斜体",
|
|
154
|
+
tooltip_underline: "下划线",
|
|
155
|
+
tooltip_strikethrough: "删除线",
|
|
156
|
+
tooltip_align_left: "居左",
|
|
157
|
+
tooltip_align_center: "居中",
|
|
158
|
+
tooltip_align_right: "居右",
|
|
159
|
+
tooltip_ordered_list: "有序列表",
|
|
160
|
+
tooltip_unordered_list: "无序列表",
|
|
161
|
+
tooltip_link: "超链接",
|
|
162
|
+
tooltip_remove_link: "删除超链接",
|
|
163
|
+
add_link_modal_title: "添加超链接",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
export default zh;
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
const createStyleString = (className, styles) => {
|
|
2
|
+
const regex = new RegExp(/[A-Z]/g);
|
|
3
|
+
const kebabCase = (str) => str.replace(regex, (v) => `-${v.toLowerCase()}`);
|
|
4
|
+
|
|
5
|
+
let stylesConfig = {
|
|
6
|
+
className: className,
|
|
7
|
+
desktop: "",
|
|
8
|
+
mobile: "",
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
for (let item of Object.entries(styles.desktop)) {
|
|
12
|
+
if (item[1] && item[0] !== "contentBackground") {
|
|
13
|
+
stylesConfig.desktop += `${kebabCase(item[0])}:${typeof item[1] === "number" ? item[1] + "px" : item[1]};`;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (Object.keys(styles.mobile).length) {
|
|
18
|
+
let mobile = "";
|
|
19
|
+
for (let item of Object.entries(styles.mobile)) {
|
|
20
|
+
if (item[1] && item[0] !== "contentBackground") {
|
|
21
|
+
mobile += `${kebabCase(item[0])}:${typeof item[1] === "number" ? item[1] + "px" : item[1]} !important;`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
stylesConfig.mobile += `@media(max-width:620px){
|
|
26
|
+
.${className} {
|
|
27
|
+
${mobile}
|
|
28
|
+
}
|
|
29
|
+
}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return stylesConfig;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const createStyleTag = (list, styles = "", parentIndex) => {
|
|
36
|
+
const newBlockList = list.map((item, index) => {
|
|
37
|
+
let newItem = item;
|
|
38
|
+
newItem.styleConfig = createStyleString(parentIndex ? `${parentIndex}-${item.key}-${index}` : `${item.key}-${index}`, item.styles);
|
|
39
|
+
|
|
40
|
+
if (newItem.contentStyles) {
|
|
41
|
+
newItem.contentStyleConfig = createStyleString(`${item.key}-content-${index}`, item.contentStyles);
|
|
42
|
+
|
|
43
|
+
if (newItem.contentStyleConfig.mobile) {
|
|
44
|
+
styles += newItem.contentStyleConfig.mobile;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (newItem.styleConfig.mobile) {
|
|
48
|
+
styles += newItem.styleConfig.mobile;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (item.styles.desktop.contentBackground) {
|
|
52
|
+
newItem.contentStyleConfig = {
|
|
53
|
+
className: `${item.key}-content-${index}`,
|
|
54
|
+
desktop: `background-color:${item.styles.desktop.contentBackground};`,
|
|
55
|
+
mobile: "",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// contentBackground => column content background
|
|
60
|
+
if (item.styles.mobile.contentBackground) {
|
|
61
|
+
newItem.contentStyleConfig.mobile = ` @media(max-width:620px){
|
|
62
|
+
.${newItem.contentStyleConfig.className} {background-color:${item.styles.mobile.contentBackground};}
|
|
63
|
+
}`;
|
|
64
|
+
styles += newItem.contentStyleConfig.mobile;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (item.children?.length) {
|
|
68
|
+
const { newBlockList: childrenList, styles: childrenStyles } = createStyleTag(item.children, styles, index);
|
|
69
|
+
styles += childrenStyles;
|
|
70
|
+
newItem.children = childrenList;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
...newItem,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
newBlockList: newBlockList,
|
|
80
|
+
styles: styles,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const createImageString = (imageConfig) => {
|
|
85
|
+
return `<div ${imageConfig.contentStyleConfig.mobile ? `class="${imageConfig.contentStyleConfig.className}"` : ""}
|
|
86
|
+
style="${imageConfig.contentStyleConfig.desktop}">
|
|
87
|
+
<img src="${imageConfig.src}" alt="${imageConfig.alt}" style="max-width:100%;${imageConfig.styleConfig.desktop}"
|
|
88
|
+
${imageConfig.styleConfig.mobile ? `class="${imageConfig.styleConfig.className}"` : ""}/>
|
|
89
|
+
</div>`;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const createTextString = (textBlock) => {
|
|
93
|
+
return `<div ${textBlock.styleConfig.mobile ? `class="${textBlock.styleConfig.className}"` : ""}
|
|
94
|
+
style="${textBlock.styleConfig.desktop}">${textBlock.text}</div>`;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const createHeaderString = (headerBlock) => {
|
|
98
|
+
return `<${headerBlock.type} ${headerBlock.styleConfig.mobile ? `class="${headerBlock.styleConfig.className}"` : ""}
|
|
99
|
+
style="${headerBlock.styleConfig.desktop}">
|
|
100
|
+
${headerBlock.text}
|
|
101
|
+
</${headerBlock.type}>`;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const createButtonString = (buttonBlock) => {
|
|
105
|
+
return `<div ${buttonBlock.contentStyleConfig.mobile ? `class="${buttonBlock.contentStyleConfig.className}"` : ""}
|
|
106
|
+
style="${buttonBlock.contentStyleConfig.desktop}">
|
|
107
|
+
<a ${buttonBlock.styleConfig.mobile ? `class="${buttonBlock.styleConfig.className}"` : ""}
|
|
108
|
+
style="${buttonBlock.styleConfig.desktop}" target="_black" href="https://${buttonBlock.linkURL}">${buttonBlock.text}</a>
|
|
109
|
+
</div>`;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const createDividerString = (dividerBLock) => {
|
|
113
|
+
return `<div ${dividerBLock.contentStyleConfig.mobile ? `class="${dividerBLock.contentStyleConfig.className}"` : ""}
|
|
114
|
+
style="${dividerBLock.contentStyleConfig.desktop}">
|
|
115
|
+
<div ${dividerBLock.styleConfig.mobile ? `class="${dividerBLock.styleConfig.className}"` : ""}
|
|
116
|
+
style="${dividerBLock.styleConfig.desktop}"></div>
|
|
117
|
+
</div>`;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const createSpacerString = (spacerBlock) => {
|
|
121
|
+
return `<div ${spacerBlock.contentStyleConfig?.mobile ? `class="${spacerBlock.contentStyleConfig.className}"` : ""}
|
|
122
|
+
style="${spacerBlock.contentStyleConfig?.desktop || ""}">
|
|
123
|
+
<div ${spacerBlock.styleConfig?.mobile ? `class="${spacerBlock.styleConfig.className}"` : ""}
|
|
124
|
+
style="${spacerBlock.styleConfig?.desktop || ""}"></div>
|
|
125
|
+
</div>`;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const createMenuString = (menuBlock) => {
|
|
129
|
+
const items = menuBlock.list || [];
|
|
130
|
+
const sep = menuBlock.separator != null ? String(menuBlock.separator) : " | ";
|
|
131
|
+
const linkStyle = menuBlock.styleConfig?.desktop || "";
|
|
132
|
+
const wrapStyle = menuBlock.contentStyleConfig?.desktop || "";
|
|
133
|
+
const content = items
|
|
134
|
+
.map((item) => {
|
|
135
|
+
const href = item.url ? (item.url.startsWith("http") ? item.url : "https://" + item.url) : "#";
|
|
136
|
+
const target = item.target === "_blank" ? "_blank" : "_self";
|
|
137
|
+
const label = item.label || "Link";
|
|
138
|
+
return `<a target="${target}" href="${href}" style="${linkStyle};text-decoration:none;display:inline-block;">${label}</a>`;
|
|
139
|
+
})
|
|
140
|
+
.join(sep);
|
|
141
|
+
return `<div ${menuBlock.contentStyleConfig?.mobile ? `class="${menuBlock.contentStyleConfig.className}"` : ""} style="${wrapStyle}">${content}</div>`;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const createSocialLinkString = (socialLinkBlock) => {
|
|
145
|
+
return `<div ${socialLinkBlock.contentStyleConfig.mobile ? `class="${socialLinkBlock.contentStyleConfig.className}"` : ""}
|
|
146
|
+
style="${socialLinkBlock.contentStyleConfig.desktop}">
|
|
147
|
+
${socialLinkBlock.list
|
|
148
|
+
.map((socialLinkItem) => {
|
|
149
|
+
const { image, title, linkURL } = socialLinkItem;
|
|
150
|
+
return `<a target="_black" href="https://${linkURL}" style="${socialLinkBlock.styleConfig.desktop};display:inline-block;">
|
|
151
|
+
<img src="${image}" alt="${title}" style="width:${socialLinkBlock.imageWidth}px;"
|
|
152
|
+
${socialLinkBlock.styleConfig.mobile ? `class="${socialLinkBlock.styleConfig.className}"` : ""}/>
|
|
153
|
+
</a>`;
|
|
154
|
+
})
|
|
155
|
+
.join("")}
|
|
156
|
+
</div>`;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const blockListToHtml = (blockList, bodySettings) => {
|
|
160
|
+
let content = "";
|
|
161
|
+
blockList.forEach((item) => {
|
|
162
|
+
if (item.key === "column") {
|
|
163
|
+
content += `<div ${item.styleConfig.mobile ? `class="${item.styleConfig.className}"` : ""}
|
|
164
|
+
style="${item.styleConfig.desktop};width:100%;display:block;">
|
|
165
|
+
<table ${item.contentStyleConfig.mobile ? `class="${item.contentStyleConfig.className}"` : ""}
|
|
166
|
+
style="width:100%;max-width:${bodySettings.contentWidth}px;margin:0 auto;${item.contentStyleConfig.desktop}">
|
|
167
|
+
<tbody><tr>${blockListToHtml(item.children)}</tr></tbody>
|
|
168
|
+
</table></div>`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (item.key === "content") {
|
|
172
|
+
content += `<td ${item.styleConfig.mobile ? `class="${item.styleConfig.className}"` : ""}
|
|
173
|
+
style="width:${item.width}; ${item.styleConfig.desktop}">${blockListToHtml(item.children)}</td>`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (item.key === "text") {
|
|
177
|
+
content += createTextString(item);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (item.key === "heading") {
|
|
181
|
+
content += createHeaderString(item);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (item.key === "image") {
|
|
185
|
+
content += createImageString(item);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (item.key === "button") {
|
|
189
|
+
content += createButtonString(item);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (item.key === "divider") {
|
|
193
|
+
content += createDividerString(item);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (item.key === "spacer") {
|
|
197
|
+
content += createSpacerString(item);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (item.key === "menu") {
|
|
201
|
+
content += createMenuString(item);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (item.key === "social_link") {
|
|
205
|
+
content += createSocialLinkString(item);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return content;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const DEFAULT_BODY_SETTINGS = {
|
|
213
|
+
preHeader: "",
|
|
214
|
+
contentWidth: 600,
|
|
215
|
+
styles: {
|
|
216
|
+
backgroundColor: "#f5f5f5",
|
|
217
|
+
color: "#152b2a",
|
|
218
|
+
fontFamily: "Arial",
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const dataToHtml = ({ bodySettings, blockList }) => {
|
|
223
|
+
let content = "";
|
|
224
|
+
const { newBlockList, styles } = createStyleTag(blockList);
|
|
225
|
+
content = blockListToHtml(newBlockList, bodySettings);
|
|
226
|
+
return `<html>
|
|
227
|
+
<head>
|
|
228
|
+
<meta charset="UTF-8">
|
|
229
|
+
<title>email</title>
|
|
230
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
231
|
+
<style type="text/css">
|
|
232
|
+
*{
|
|
233
|
+
margin: 0;
|
|
234
|
+
padding: 0;
|
|
235
|
+
border: none;
|
|
236
|
+
box-sizing: border-box;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
html,body {
|
|
240
|
+
height:100%;
|
|
241
|
+
overflow-y:auto;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
table {
|
|
245
|
+
width: 100%;
|
|
246
|
+
color:unset;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
table, tr, td {
|
|
250
|
+
vertical-align: top;
|
|
251
|
+
border-collapse: collapse;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
h1,h2,h3,h4 {
|
|
255
|
+
display: block;
|
|
256
|
+
margin-block-start: 0px;
|
|
257
|
+
margin-block-end: 0px;
|
|
258
|
+
margin-inline-start: 0px;
|
|
259
|
+
margin-inline-end: 0px;
|
|
260
|
+
font-weight: bold;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@media(max-width:620px){
|
|
264
|
+
td {
|
|
265
|
+
display:inline-block;
|
|
266
|
+
width:100% !important;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
${styles}
|
|
270
|
+
</style>
|
|
271
|
+
</head>
|
|
272
|
+
<body>
|
|
273
|
+
<div style="opacity:0;">${bodySettings.preHeader}</div>
|
|
274
|
+
<div style="background-color:${bodySettings.styles.backgroundColor};color:${bodySettings.styles.color}; font-family:${bodySettings.styles.fontFamily};"> ${content}</div>
|
|
275
|
+
</body>
|
|
276
|
+
</html>`;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Export normalizer: accepts exported template JSON and returns a complete HTML file string.
|
|
281
|
+
* @param {string|object} input - Template JSON as string or parsed object. Supports:
|
|
282
|
+
* - Legacy: array of column blocks (blockList only)
|
|
283
|
+
* - Full: { blockList, bodySettings }
|
|
284
|
+
* @returns {string} Complete HTML document (with doctype, head, styles, body)
|
|
285
|
+
*/
|
|
286
|
+
export function exportNormalizerToHtml(input) {
|
|
287
|
+
let data = input;
|
|
288
|
+
if (typeof input === "string") {
|
|
289
|
+
try {
|
|
290
|
+
data = JSON.parse(input);
|
|
291
|
+
} catch (e) {
|
|
292
|
+
throw new Error("exportNormalizerToHtml: invalid JSON");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (data == null) {
|
|
296
|
+
throw new Error("exportNormalizerToHtml: input is null or undefined");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let blockList = [];
|
|
300
|
+
let bodySettings = { ...DEFAULT_BODY_SETTINGS, styles: { ...DEFAULT_BODY_SETTINGS.styles } };
|
|
301
|
+
|
|
302
|
+
if (Array.isArray(data)) {
|
|
303
|
+
blockList = data;
|
|
304
|
+
} else if (typeof data === "object") {
|
|
305
|
+
blockList = Array.isArray(data.blockList) ? data.blockList : [];
|
|
306
|
+
if (data.bodySettings && typeof data.bodySettings === "object") {
|
|
307
|
+
bodySettings = {
|
|
308
|
+
preHeader: data.bodySettings.preHeader != null ? data.bodySettings.preHeader : DEFAULT_BODY_SETTINGS.preHeader,
|
|
309
|
+
contentWidth: Number(data.bodySettings.contentWidth) || DEFAULT_BODY_SETTINGS.contentWidth,
|
|
310
|
+
styles: {
|
|
311
|
+
backgroundColor: data.bodySettings.styles?.backgroundColor ?? DEFAULT_BODY_SETTINGS.styles.backgroundColor,
|
|
312
|
+
color: data.bodySettings.styles?.color ?? DEFAULT_BODY_SETTINGS.styles.color,
|
|
313
|
+
fontFamily: data.bodySettings.styles?.fontFamily ?? DEFAULT_BODY_SETTINGS.styles.fontFamily,
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const html = dataToHtml({ bodySettings, blockList });
|
|
320
|
+
return `<!DOCTYPE html>\n${html}`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export default dataToHtml;
|