├── .gitignore ├── .nvmrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── demo ├── public │ └── _redirects └── src │ ├── dashboard │ ├── DesignEdit.js │ ├── DesignList.js │ └── index.js │ ├── example │ ├── index.js │ └── sample.json │ ├── index.css │ └── index.js ├── nwb.config.js ├── package-lock.json ├── package.json ├── prettier.config.js ├── src ├── index.js └── loadScript.js ├── tests ├── .eslintrc └── index-test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /demo/dist 3 | /es 4 | /lib 5 | /node_modules 6 | /umd 7 | npm-debug.log* 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.13.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | node_js: 5 | - 6 6 | 7 | before_install: 8 | - npm install codecov.io coveralls 9 | 10 | after_success: 11 | - cat ./coverage/lcov.info | ./node_modules/codecov.io/bin/codecov.io.js 12 | - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js 13 | 14 | branches: 15 | only: 16 | - master 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | [Node.js](http://nodejs.org/) >= v4 must be installed. 4 | 5 | ## Installation 6 | 7 | - Running `npm install` in the components's root directory will install everything you need for development. 8 | 9 | ## Demo Development Server 10 | 11 | - `npm start` will run a development server with the component's demo app at [http://localhost:3000](http://localhost:3000) with hot module reloading. 12 | 13 | ## Running Tests 14 | 15 | - `npm test` will run the tests once. 16 | 17 | - `npm run test:coverage` will run the tests and produce a coverage report in `coverage/`. 18 | 19 | - `npm run test:watch` will run the tests on every change. 20 | 21 | ## Building 22 | 23 | - `npm run build` will build the component for publishing to npm and also bundle the demo app. 24 | 25 | - `npm run clean` will delete built resources. 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Unlayer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Email Editor 2 | 3 | The excellent drag-n-drop email editor by [Unlayer](https://unlayer.com/embed) as a [React.js](http://facebook.github.io/react) _wrapper component_. This is the most powerful and developer friendly visual email er for your app. 4 | 5 | | Video Overview | 6 | | :-------------------------------------------------------------------------------------------------------------------------------: | 7 | | [![React Email Editor](https://unroll-assets.s3.amazonaws.com/unlayervideotour.png)](https://www.youtube.com/watch?v=MIWhX-NF3j8) | 8 | | _Watch video overview: https://youtu.be/MIWhX-NF3j8_ | 9 | 10 | ## Live Demo 11 | 12 | Check out the live demo here: http://react-email-editor-demo.netlify.com/ ([Source Code](https://github.com/unlayer/react-email-editor/blob/master/demo/src/index.js)) 13 | 14 | ## Blog Post 15 | 16 | Here's a blog post with a quickstart guide: https://medium.com/unlayer-blog/creating-a-drag-n-drop-email-editor-with-react-db1e9eb42386 17 | 18 | ## Installation 19 | 20 | The easiest way to use React Email Editor is to install it from NPM and include it in your own React build process. 21 | 22 | ``` 23 | npm install react-email-editor --save 24 | ``` 25 | 26 | ## Usage 27 | 28 | Require the EmailEditor component and render it with JSX: 29 | 30 | ```javascript 31 | import React, { useRef } from 'react'; 32 | import { render } from 'react-dom'; 33 | 34 | import EmailEditor from 'react-email-editor'; 35 | 36 | const App = (props) => { 37 | const emailEditorRef = useRef(null); 38 | 39 | const exportHtml = () => { 40 | emailEditorRef.current.editor.exportHtml((data) => { 41 | const { design, html } = data; 42 | console.log('exportHtml', html); 43 | }); 44 | }; 45 | 46 | const onLoad = () => { 47 | // editor instance is created 48 | // you can load your template here; 49 | // const templateJson = {}; 50 | // emailEditorRef.current.editor.loadDesign(templateJson); 51 | } 52 | 53 | const onReady = () => { 54 | // editor is ready 55 | console.log('onReady'); 56 | }; 57 | 58 | return ( 59 |
60 |
61 | 62 |
63 | 64 | 65 |
66 | ); 67 | }; 68 | 69 | render(, document.getElementById('app')); 70 | ``` 71 | 72 | ### Methods 73 | 74 | | method | params | description | 75 | | -------------- | ------------------- | ------------------------------------------------------- | 76 | | **loadDesign** | `Object data` | Takes the design JSON and loads it in the editor | 77 | | **saveDesign** | `Function callback` | Returns the design JSON in a callback function | 78 | | **exportHtml** | `Function callback` | Returns the design HTML and JSON in a callback function | 79 | 80 | See the [example source](https://github.com/unlayer/react-email-editor/blob/master/demo/src/index.js) for a reference implementation. 81 | 82 | ### Properties 83 | 84 | - `editorId` `String` HTML div id of the container where the editor will be embedded (optional) 85 | - `style` `Object` style object for the editor container (default {}) 86 | - `minHeight` `String` minimum height to initialize the editor with (default 500px) 87 | - `onLoad` `Function` called when the editor instance is created 88 | - `onReady` `Function` called when the editor has finished loading 89 | - `options` `Object` options passed to the Unlayer editor instance (default {}) 90 | - `tools` `Object` configuration for the built-in and custom tools (default {}) 91 | - `appearance` `Object` configuration for appearance and theme (default {}) 92 | - `projectId` `Integer` Unlayer project ID (optional) 93 | 94 | See the [Unlayer Docs](https://docs.unlayer.com/) for all available options. 95 | 96 | ## Custom Tools 97 | 98 | Custom tools can help you add your own content blocks to the editor. Every application is different and needs different tools to reach it's full potential. [Learn More](https://docs.unlayer.com/docs/custom-tools) 99 | 100 | [![Custom Tools](https://unroll-assets.s3.amazonaws.com/custom_tools.png)](https://docs.unlayer.com/docs/custom-tools) 101 | 102 | ## Localization 103 | 104 | You can submit new language translations by creating a PR on this GitHub repo: https://github.com/unlayer/translations. Translations managed by [PhraseApp](https://phraseapp.com) 105 | 106 | ### License 107 | 108 | Copyright (c) 2022 Unlayer. [MIT](LICENSE) Licensed. 109 | -------------------------------------------------------------------------------- /demo/public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /demo/src/dashboard/DesignEdit.js: -------------------------------------------------------------------------------- 1 | import React, { Component, useRef } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { Switch, Route, Link, useRouteMatch } from 'react-router-dom'; 5 | 6 | import EmailEditor from '../../../src'; 7 | 8 | const Container = styled.div` 9 | display: flex; 10 | flex-direction: column; 11 | position: relative; 12 | height: 100%; 13 | `; 14 | 15 | const Bar = styled.div` 16 | flex: 1; 17 | background-color: #61dafb; 18 | color: #000; 19 | padding: 10px; 20 | display: flex; 21 | max-height: 40px; 22 | 23 | h1 { 24 | flex: 1; 25 | font-size: 16px; 26 | text-align: left; 27 | } 28 | 29 | button { 30 | flex: 1; 31 | padding: 10px; 32 | margin-left: 10px; 33 | font-size: 14px; 34 | font-weight: bold; 35 | background-color: #000; 36 | color: #fff; 37 | border: 0px; 38 | max-width: 150px; 39 | cursor: pointer; 40 | } 41 | 42 | a { 43 | flex: 1; 44 | padding: 10px; 45 | margin-left: 10px; 46 | font-size: 14px; 47 | font-weight: bold; 48 | color: #fff; 49 | border: 0px; 50 | cursor: pointer; 51 | text-align: right; 52 | text-decoration: none; 53 | line-height: 160%; 54 | } 55 | `; 56 | 57 | const DesignEdit = (props) => { 58 | const ref = useRef(null); 59 | 60 | const saveDesign = () => { 61 | ref.current.saveDesign((design) => { 62 | console.log('saveDesign', design); 63 | alert('Design JSON has been logged in your developer console.'); 64 | }); 65 | }; 66 | 67 | const exportHtml = () => { 68 | ref.current.exportHtml((data) => { 69 | const { design, html } = data; 70 | console.log('exportHtml', html); 71 | alert('Output HTML has been logged in your developer console.'); 72 | }); 73 | }; 74 | 75 | return ( 76 | 77 | 78 |

React Email Editor (Demo)

79 | 80 | Dashboard 81 | 82 | 83 |
84 | 85 | 87 |
88 | ); 89 | }; 90 | 91 | export default DesignEdit; 92 | -------------------------------------------------------------------------------- /demo/src/dashboard/DesignList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { Switch, Route, Link, useRouteMatch } from 'react-router-dom'; 5 | 6 | const DesignList = (props) => { 7 | const match = useRouteMatch(); 8 | 9 | return ( 10 |
11 |

My Designs

12 | 13 |

New Design

14 |
15 | ); 16 | }; 17 | 18 | export default DesignList; 19 | -------------------------------------------------------------------------------- /demo/src/dashboard/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import { Switch, Route, Link, useRouteMatch } from 'react-router-dom'; 5 | 6 | import DesignList from './DesignList'; 7 | import DesignEdit from './DesignEdit'; 8 | 9 | const Dashboard = (props) => { 10 | const match = useRouteMatch(); 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default Dashboard; 28 | -------------------------------------------------------------------------------- /demo/src/example/index.js: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import EmailEditor from '../../../src'; 5 | // import sample from './sample.json'; 6 | 7 | import "../../src/index.css"; 8 | 9 | const Container = styled.div` 10 | display: flex; 11 | flex-direction: column; 12 | position: relative; 13 | height: 100%; 14 | 15 | a { 16 | display:none!important; 17 | } 18 | `; 19 | 20 | const Bar = styled.div` 21 | flex: 1; 22 | background-color: #61dafb; 23 | color: #000; 24 | padding: 10px; 25 | display: flex; 26 | max-height: 70px; 27 | 28 | h1 { 29 | flex: 1; 30 | font-size: 16px; 31 | text-align: left; 32 | } 33 | 34 | button { 35 | flex: 1; 36 | padding: 10px; 37 | margin-left: 10px; 38 | font-size: 14px; 39 | font-weight: bold; 40 | background-color: #000; 41 | color: #fff; 42 | border: 0px; 43 | max-width: 150px; 44 | cursor: pointer; 45 | } 46 | `; 47 | 48 | const Example = (props) => { 49 | const emailEditorRef = useRef(null); 50 | 51 | const saveDesign = () => { 52 | emailEditorRef.current.editor.saveDesign((design) => { 53 | console.log('saveDesign', design); 54 | alert('Design JSON has been logged in your developer console.'); 55 | }); 56 | }; 57 | 58 | const exportHtml = () => { 59 | emailEditorRef.current.editor.exportHtml((data) => { 60 | const { design, html } = data; 61 | console.log('exportHtml', html); 62 | alert('Output HTML has been logged in your developer console.'); 63 | }); 64 | }; 65 | 66 | const onDesignLoad = (data) => { 67 | console.log('onDesignLoad', data); 68 | }; 69 | 70 | const onLoad = () => { 71 | console.log('onLoad'); 72 | 73 | emailEditorRef.current.editor.addEventListener( 74 | 'design:loaded', 75 | onDesignLoad 76 | ); 77 | 78 | emailEditorRef.current.editor.loadDesign(sample); 79 | } 80 | 81 | const onReady = () => { 82 | console.log('onReady'); 83 | }; 84 | 85 | 86 | 87 | return ( 88 | 89 | 90 |

React Design Editor

91 | 92 | 93 |
94 | 95 | 96 | 120 | 121 |
122 | ); 123 | }; 124 | 125 | export default Example; 126 | -------------------------------------------------------------------------------- /demo/src/example/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "counters": { 3 | "u_row": 13, 4 | "u_column": 16, 5 | "u_content_menu": 3, 6 | "u_content_text": 11, 7 | "u_content_image": 3, 8 | "u_content_button": 4, 9 | "u_content_social": 1, 10 | "u_content_divider": 6 11 | }, 12 | "body": { 13 | "rows": [{ 14 | "cells": [1], 15 | "columns": [{ 16 | "contents": [{ 17 | "type": "divider", 18 | "values": { 19 | "containerPadding": "5px", 20 | "_meta": { 21 | "htmlID": "u_content_divider_6", 22 | "htmlClassNames": "u_content_divider" 23 | }, 24 | "selectable": true, 25 | "draggable": true, 26 | "duplicatable": true, 27 | "deletable": true, 28 | "width": "100%", 29 | "border": { 30 | "borderTopWidth": "0px", 31 | "borderTopStyle": "solid", 32 | "borderTopColor": "#BBBBBB" 33 | }, 34 | "textAlign": "center", 35 | "hideDesktop": false, 36 | "hideMobile": false 37 | } 38 | }], 39 | "values": { 40 | "_meta": { 41 | "htmlID": "u_column_16", 42 | "htmlClassNames": "u_column" 43 | }, 44 | "border": {}, 45 | "padding": "0px", 46 | "backgroundColor": "" 47 | } 48 | }], 49 | "values": { 50 | "displayCondition": null, 51 | "columns": false, 52 | "backgroundColor": "", 53 | "columnsBackgroundColor": "", 54 | "backgroundImage": { 55 | "url": "", 56 | "fullWidth": true, 57 | "repeat": false, 58 | "center": true, 59 | "cover": false 60 | }, 61 | "padding": "0px", 62 | "hideDesktop": false, 63 | "hideMobile": false, 64 | "noStackMobile": false, 65 | "_meta": { 66 | "htmlID": "u_row_13", 67 | "htmlClassNames": "u_row" 68 | }, 69 | "selectable": true, 70 | "draggable": true, 71 | "duplicatable": true, 72 | "deletable": true 73 | } 74 | }, { 75 | "cells": [1, 1, 1], 76 | "columns": [{ 77 | "contents": [{ 78 | "type": "menu", 79 | "values": { 80 | "containerPadding": "25px 10px 10px", 81 | "_meta": { 82 | "htmlID": "u_content_menu_3", 83 | "htmlClassNames": "u_content_menu" 84 | }, 85 | "selectable": true, 86 | "draggable": true, 87 | "duplicatable": true, 88 | "deletable": true, 89 | "menu": { 90 | "items": [{ 91 | "key": "1606923979328", 92 | "link": { 93 | "name": "web", 94 | "values": { 95 | "href": "", 96 | "target": "_self" 97 | } 98 | }, 99 | "text": "NEWS" 100 | }, { 101 | "key": "1606924033905", 102 | "link": { 103 | "name": "web", 104 | "values": { 105 | "href": "", 106 | "target": "_self" 107 | } 108 | }, 109 | "text": "SERVICE" 110 | }] 111 | }, 112 | "fontFamily": { 113 | "label": "Montserrat", 114 | "value": "'Montserrat',sans-serif", 115 | "url": "https://fonts.googleapis.com/css?family=Montserrat:400,700", 116 | "defaultFont": true 117 | }, 118 | "fontSize": "14px", 119 | "textColor": "#444444", 120 | "linkColor": "#0068A5", 121 | "align": "center", 122 | "layout": "horizontal", 123 | "separator": "", 124 | "padding": "5px 15px", 125 | "hideDesktop": false, 126 | "hideMobile": false 127 | } 128 | }], 129 | "values": { 130 | "_meta": { 131 | "htmlID": "u_column_1", 132 | "htmlClassNames": "u_column" 133 | }, 134 | "border": {}, 135 | "padding": "0px", 136 | "backgroundColor": "" 137 | } 138 | }, { 139 | "contents": [{ 140 | "type": "image", 141 | "values": { 142 | "containerPadding": "20px 10px", 143 | "_meta": { 144 | "htmlID": "u_content_image_1", 145 | "htmlClassNames": "u_content_image" 146 | }, 147 | "selectable": true, 148 | "draggable": true, 149 | "duplicatable": true, 150 | "deletable": true, 151 | "src": { 152 | "url": "https://cdn.templates.unlayer.com/assets/1606906849237-logo.png", 153 | "width": 248, 154 | "height": 56, 155 | "maxWidth": "77%", 156 | "autoWidth": false 157 | }, 158 | "textAlign": "center", 159 | "altText": "Image", 160 | "action": { 161 | "name": "web", 162 | "values": { 163 | "href": "", 164 | "target": "_blank" 165 | } 166 | }, 167 | "hideDesktop": false, 168 | "hideMobile": false, 169 | "_override": { 170 | "mobile": { 171 | "src": { 172 | "maxWidth": "58%", 173 | "autoWidth": false 174 | } 175 | } 176 | } 177 | } 178 | }], 179 | "values": { 180 | "_meta": { 181 | "htmlID": "u_column_2", 182 | "htmlClassNames": "u_column" 183 | }, 184 | "border": {}, 185 | "padding": "0px", 186 | "backgroundColor": "" 187 | } 188 | }, { 189 | "contents": [{ 190 | "type": "menu", 191 | "values": { 192 | "containerPadding": "25px 10px 30px", 193 | "_meta": { 194 | "htmlID": "u_content_menu_2", 195 | "htmlClassNames": "u_content_menu" 196 | }, 197 | "selectable": true, 198 | "draggable": true, 199 | "duplicatable": true, 200 | "deletable": true, 201 | "menu": { 202 | "items": [{ 203 | "key": "1606923979328", 204 | "link": { 205 | "name": "web", 206 | "values": { 207 | "href": "", 208 | "target": "_self" 209 | } 210 | }, 211 | "text": "ABOUT" 212 | }, { 213 | "key": "1606924033905", 214 | "link": { 215 | "name": "web", 216 | "values": { 217 | "href": "", 218 | "target": "_self" 219 | } 220 | }, 221 | "text": "CONTACT" 222 | }] 223 | }, 224 | "fontFamily": { 225 | "label": "Montserrat", 226 | "value": "'Montserrat',sans-serif", 227 | "url": "https://fonts.googleapis.com/css?family=Montserrat:400,700", 228 | "defaultFont": true 229 | }, 230 | "fontSize": "14px", 231 | "textColor": "#444444", 232 | "linkColor": "#0068A5", 233 | "align": "center", 234 | "layout": "horizontal", 235 | "separator": "", 236 | "padding": "5px 15px", 237 | "hideDesktop": false, 238 | "hideMobile": false 239 | } 240 | }], 241 | "values": { 242 | "_meta": { 243 | "htmlID": "u_column_3", 244 | "htmlClassNames": "u_column" 245 | }, 246 | "border": {}, 247 | "padding": "0px", 248 | "backgroundColor": "" 249 | } 250 | }], 251 | "values": { 252 | "displayCondition": null, 253 | "columns": false, 254 | "backgroundColor": "", 255 | "columnsBackgroundColor": "#ffffff", 256 | "backgroundImage": { 257 | "url": "", 258 | "fullWidth": true, 259 | "repeat": false, 260 | "center": true, 261 | "cover": false 262 | }, 263 | "padding": "0px", 264 | "hideDesktop": false, 265 | "hideMobile": false, 266 | "noStackMobile": false, 267 | "_meta": { 268 | "htmlID": "u_row_1", 269 | "htmlClassNames": "u_row" 270 | }, 271 | "selectable": true, 272 | "draggable": true, 273 | "duplicatable": true, 274 | "deletable": true 275 | } 276 | }, { 277 | "cells": [1], 278 | "columns": [{ 279 | "contents": [{ 280 | "type": "divider", 281 | "values": { 282 | "containerPadding": "150px 10px 10px", 283 | "_meta": { 284 | "htmlID": "u_content_divider_2", 285 | "htmlClassNames": "u_content_divider" 286 | }, 287 | "selectable": true, 288 | "draggable": true, 289 | "duplicatable": true, 290 | "deletable": true, 291 | "width": "100%", 292 | "border": { 293 | "borderTopWidth": "0px", 294 | "borderTopStyle": "solid", 295 | "borderTopColor": "#BBBBBB" 296 | }, 297 | "textAlign": "center", 298 | "hideDesktop": false, 299 | "hideMobile": false 300 | } 301 | }, { 302 | "type": "text", 303 | "values": { 304 | "containerPadding": "10px", 305 | "_meta": { 306 | "htmlID": "u_content_text_1", 307 | "htmlClassNames": "u_content_text" 308 | }, 309 | "selectable": true, 310 | "draggable": true, 311 | "duplicatable": true, 312 | "deletable": true, 313 | "color": "#ffffff", 314 | "textAlign": "center", 315 | "lineHeight": "140%", 316 | "linkStyle": { 317 | "inherit": true, 318 | "linkColor": "#0000ee", 319 | "linkHoverColor": "#0000ee", 320 | "linkUnderline": true, 321 | "linkHoverUnderline": true 322 | }, 323 | "hideDesktop": false, 324 | "hideMobile": false, 325 | "text": "

NEW ARRIVAL

" 326 | } 327 | }, { 328 | "type": "button", 329 | "values": { 330 | "containerPadding": "10px 10px 50px", 331 | "_meta": { 332 | "htmlID": "u_content_button_1", 333 | "htmlClassNames": "u_content_button" 334 | }, 335 | "selectable": true, 336 | "draggable": true, 337 | "duplicatable": true, 338 | "deletable": true, 339 | "href": { 340 | "name": "web", 341 | "values": { 342 | "href": "", 343 | "target": "_blank" 344 | } 345 | }, 346 | "buttonColors": { 347 | "color": "#463a41", 348 | "backgroundColor": "#ffffff", 349 | "hoverColor": "#FFFFFF", 350 | "hoverBackgroundColor": "#3AAEE0" 351 | }, 352 | "size": { 353 | "autoWidth": true, 354 | "width": "100%" 355 | }, 356 | "lineHeight": "120%", 357 | "textAlign": "center", 358 | "border": {}, 359 | "borderRadius": "0px", 360 | "padding": "12px 22px", 361 | "hideDesktop": false, 362 | "hideMobile": false, 363 | "text": "VIEW MORE", 364 | "calculatedWidth": 134, 365 | "calculatedHeight": 40 366 | } 367 | }], 368 | "values": { 369 | "_meta": { 370 | "htmlID": "u_column_5", 371 | "htmlClassNames": "u_column" 372 | }, 373 | "border": {}, 374 | "padding": "0px", 375 | "backgroundColor": "" 376 | } 377 | }], 378 | "values": { 379 | "displayCondition": null, 380 | "columns": false, 381 | "backgroundColor": "", 382 | "columnsBackgroundColor": "", 383 | "backgroundImage": { 384 | "url": "https://cdn.templates.unlayer.com/assets/1606924485372-1.jpg", 385 | "fullWidth": false, 386 | "repeat": false, 387 | "center": true, 388 | "cover": false, 389 | "width": 626, 390 | "height": 500 391 | }, 392 | "padding": "0px", 393 | "hideDesktop": false, 394 | "hideMobile": false, 395 | "noStackMobile": false, 396 | "_meta": { 397 | "htmlID": "u_row_3", 398 | "htmlClassNames": "u_row" 399 | }, 400 | "selectable": true, 401 | "draggable": true, 402 | "duplicatable": true, 403 | "deletable": true 404 | } 405 | }, { 406 | "cells": [1], 407 | "columns": [{ 408 | "contents": [{ 409 | "type": "text", 410 | "values": { 411 | "containerPadding": "40px 10px 10px", 412 | "_meta": { 413 | "htmlID": "u_content_text_2", 414 | "htmlClassNames": "u_content_text" 415 | }, 416 | "selectable": true, 417 | "draggable": true, 418 | "duplicatable": true, 419 | "deletable": true, 420 | "color": "#000000", 421 | "textAlign": "center", 422 | "lineHeight": "140%", 423 | "linkStyle": { 424 | "inherit": true, 425 | "linkColor": "#0000ee", 426 | "linkHoverColor": "#0000ee", 427 | "linkUnderline": true, 428 | "linkHoverUnderline": true 429 | }, 430 | "hideDesktop": false, 431 | "hideMobile": false, 432 | "text": "

Purchasing Focal Just got easier

" 433 | } 434 | }, { 435 | "type": "text", 436 | "values": { 437 | "containerPadding": "0px 10px 40px", 438 | "_meta": { 439 | "htmlID": "u_content_text_11", 440 | "htmlClassNames": "u_content_text" 441 | }, 442 | "selectable": true, 443 | "draggable": true, 444 | "duplicatable": true, 445 | "deletable": true, 446 | "color": "#000000", 447 | "textAlign": "center", 448 | "lineHeight": "140%", 449 | "linkStyle": { 450 | "inherit": true, 451 | "linkColor": "#0000ee", 452 | "linkHoverColor": "#0000ee", 453 | "linkUnderline": true, 454 | "linkHoverUnderline": true 455 | }, 456 | "hideDesktop": false, 457 | "hideMobile": false, 458 | "text": "

Lorem ipsum dolor sit amet, 

" 459 | } 460 | }], 461 | "values": { 462 | "_meta": { 463 | "htmlID": "u_column_7", 464 | "htmlClassNames": "u_column" 465 | }, 466 | "border": {}, 467 | "padding": "0px", 468 | "backgroundColor": "" 469 | } 470 | }], 471 | "values": { 472 | "displayCondition": null, 473 | "columns": false, 474 | "backgroundColor": "", 475 | "columnsBackgroundColor": "#ffffff", 476 | "backgroundImage": { 477 | "url": "", 478 | "fullWidth": true, 479 | "repeat": false, 480 | "center": true, 481 | "cover": false 482 | }, 483 | "padding": "0px", 484 | "hideDesktop": false, 485 | "hideMobile": false, 486 | "noStackMobile": false, 487 | "_meta": { 488 | "htmlID": "u_row_5", 489 | "htmlClassNames": "u_row" 490 | }, 491 | "selectable": true, 492 | "draggable": true, 493 | "duplicatable": true, 494 | "deletable": true 495 | } 496 | }, { 497 | "cells": [1, 1], 498 | "columns": [{ 499 | "contents": [{ 500 | "type": "image", 501 | "values": { 502 | "containerPadding": "10px", 503 | "_meta": { 504 | "htmlID": "u_content_image_3", 505 | "htmlClassNames": "u_content_image" 506 | }, 507 | "selectable": true, 508 | "draggable": true, 509 | "duplicatable": true, 510 | "deletable": true, 511 | "src": { 512 | "url": "https://cdn.templates.unlayer.com/assets/1606934810497-02.png", 513 | "width": 626, 514 | "height": 418 515 | }, 516 | "textAlign": "center", 517 | "altText": "Image", 518 | "action": { 519 | "name": "web", 520 | "values": { 521 | "href": "", 522 | "target": "_blank" 523 | } 524 | }, 525 | "hideDesktop": false, 526 | "hideMobile": false 527 | } 528 | }, { 529 | "type": "text", 530 | "values": { 531 | "containerPadding": "10px 10px 0px", 532 | "_meta": { 533 | "htmlID": "u_content_text_3", 534 | "htmlClassNames": "u_content_text" 535 | }, 536 | "selectable": true, 537 | "draggable": true, 538 | "duplicatable": true, 539 | "deletable": true, 540 | "color": "#000000", 541 | "textAlign": "center", 542 | "lineHeight": "140%", 543 | "linkStyle": { 544 | "inherit": true, 545 | "linkColor": "#0000ee", 546 | "linkHoverColor": "#0000ee", 547 | "linkUnderline": true, 548 | "linkHoverUnderline": true 549 | }, 550 | "hideDesktop": false, 551 | "hideMobile": false, 552 | "text": "

Ray-Ban

" 553 | } 554 | }, { 555 | "type": "text", 556 | "values": { 557 | "containerPadding": "10px", 558 | "_meta": { 559 | "htmlID": "u_content_text_4", 560 | "htmlClassNames": "u_content_text" 561 | }, 562 | "selectable": true, 563 | "draggable": true, 564 | "duplicatable": true, 565 | "deletable": true, 566 | "color": "#000000", 567 | "textAlign": "center", 568 | "lineHeight": "140%", 569 | "linkStyle": { 570 | "inherit": true, 571 | "linkColor": "#0000ee", 572 | "linkHoverColor": "#0000ee", 573 | "linkUnderline": true, 574 | "linkHoverUnderline": true 575 | }, 576 | "hideDesktop": false, 577 | "hideMobile": false, 578 | "text": "

$20

" 579 | } 580 | }, { 581 | "type": "button", 582 | "values": { 583 | "containerPadding": "10px", 584 | "_meta": { 585 | "htmlID": "u_content_button_2", 586 | "htmlClassNames": "u_content_button" 587 | }, 588 | "selectable": true, 589 | "draggable": true, 590 | "duplicatable": true, 591 | "deletable": true, 592 | "href": { 593 | "name": "web", 594 | "values": { 595 | "href": "", 596 | "target": "_blank" 597 | } 598 | }, 599 | "buttonColors": { 600 | "color": "#FFFFFF", 601 | "backgroundColor": "#262425", 602 | "hoverColor": "#FFFFFF", 603 | "hoverBackgroundColor": "#3AAEE0" 604 | }, 605 | "size": { 606 | "autoWidth": true, 607 | "width": "100%" 608 | }, 609 | "lineHeight": "120%", 610 | "textAlign": "center", 611 | "border": {}, 612 | "borderRadius": "0px", 613 | "padding": "10px 20px", 614 | "hideDesktop": false, 615 | "hideMobile": false, 616 | "text": "Buy Now", 617 | "calculatedWidth": 104, 618 | "calculatedHeight": 36 619 | } 620 | }], 621 | "values": { 622 | "_meta": { 623 | "htmlID": "u_column_6", 624 | "htmlClassNames": "u_column" 625 | }, 626 | "border": {}, 627 | "padding": "0px", 628 | "backgroundColor": "" 629 | } 630 | }, { 631 | "contents": [{ 632 | "type": "image", 633 | "values": { 634 | "containerPadding": "10px", 635 | "_meta": { 636 | "htmlID": "u_content_image_2", 637 | "htmlClassNames": "u_content_image" 638 | }, 639 | "selectable": true, 640 | "draggable": true, 641 | "duplicatable": true, 642 | "deletable": true, 643 | "src": { 644 | "url": "https://cdn.templates.unlayer.com/assets/1606932761674-2.jpg", 645 | "width": 626, 646 | "height": 417 647 | }, 648 | "textAlign": "center", 649 | "altText": "Image", 650 | "action": { 651 | "name": "web", 652 | "values": { 653 | "href": "", 654 | "target": "_blank" 655 | } 656 | }, 657 | "hideDesktop": false, 658 | "hideMobile": false 659 | } 660 | }, { 661 | "type": "text", 662 | "values": { 663 | "containerPadding": "10px 10px 0px", 664 | "_meta": { 665 | "htmlID": "u_content_text_5", 666 | "htmlClassNames": "u_content_text" 667 | }, 668 | "selectable": true, 669 | "draggable": true, 670 | "duplicatable": true, 671 | "deletable": true, 672 | "color": "#000000", 673 | "textAlign": "center", 674 | "lineHeight": "140%", 675 | "linkStyle": { 676 | "inherit": true, 677 | "linkColor": "#0000ee", 678 | "linkHoverColor": "#0000ee", 679 | "linkUnderline": true, 680 | "linkHoverUnderline": true 681 | }, 682 | "hideDesktop": false, 683 | "hideMobile": false, 684 | "text": "

Ray-Ban

" 685 | } 686 | }, { 687 | "type": "text", 688 | "values": { 689 | "containerPadding": "10px", 690 | "_meta": { 691 | "htmlID": "u_content_text_6", 692 | "htmlClassNames": "u_content_text" 693 | }, 694 | "selectable": true, 695 | "draggable": true, 696 | "duplicatable": true, 697 | "deletable": true, 698 | "color": "#000000", 699 | "textAlign": "center", 700 | "lineHeight": "140%", 701 | "linkStyle": { 702 | "inherit": true, 703 | "linkColor": "#0000ee", 704 | "linkHoverColor": "#0000ee", 705 | "linkUnderline": true, 706 | "linkHoverUnderline": true 707 | }, 708 | "hideDesktop": false, 709 | "hideMobile": false, 710 | "text": "

$25

" 711 | } 712 | }, { 713 | "type": "button", 714 | "values": { 715 | "containerPadding": "10px", 716 | "_meta": { 717 | "htmlID": "u_content_button_3", 718 | "htmlClassNames": "u_content_button" 719 | }, 720 | "selectable": true, 721 | "draggable": true, 722 | "duplicatable": true, 723 | "deletable": true, 724 | "href": { 725 | "name": "web", 726 | "values": { 727 | "href": "", 728 | "target": "_blank" 729 | } 730 | }, 731 | "buttonColors": { 732 | "color": "#FFFFFF", 733 | "backgroundColor": "#262425", 734 | "hoverColor": "#FFFFFF", 735 | "hoverBackgroundColor": "#3AAEE0" 736 | }, 737 | "size": { 738 | "autoWidth": true, 739 | "width": "100%" 740 | }, 741 | "lineHeight": "120%", 742 | "textAlign": "center", 743 | "border": {}, 744 | "borderRadius": "0px", 745 | "padding": "10px 20px", 746 | "hideDesktop": false, 747 | "hideMobile": false, 748 | "text": "Buy Now", 749 | "calculatedWidth": 104, 750 | "calculatedHeight": 36 751 | } 752 | }], 753 | "values": { 754 | "_meta": { 755 | "htmlID": "u_column_10", 756 | "htmlClassNames": "u_column" 757 | }, 758 | "border": {}, 759 | "padding": "0px", 760 | "backgroundColor": "" 761 | } 762 | }], 763 | "values": { 764 | "displayCondition": null, 765 | "columns": false, 766 | "backgroundColor": "", 767 | "columnsBackgroundColor": "#ffffff", 768 | "backgroundImage": { 769 | "url": "", 770 | "fullWidth": true, 771 | "repeat": false, 772 | "center": true, 773 | "cover": false 774 | }, 775 | "padding": "0px", 776 | "hideDesktop": false, 777 | "hideMobile": false, 778 | "noStackMobile": false, 779 | "_meta": { 780 | "htmlID": "u_row_4", 781 | "htmlClassNames": "u_row" 782 | }, 783 | "selectable": true, 784 | "draggable": true, 785 | "duplicatable": true, 786 | "deletable": true 787 | } 788 | }, { 789 | "cells": [1], 790 | "columns": [{ 791 | "contents": [{ 792 | "type": "text", 793 | "values": { 794 | "containerPadding": "30px 30px 40px", 795 | "_meta": { 796 | "htmlID": "u_content_text_7", 797 | "htmlClassNames": "u_content_text" 798 | }, 799 | "selectable": true, 800 | "draggable": true, 801 | "duplicatable": true, 802 | "deletable": true, 803 | "color": "#000000", 804 | "textAlign": "center", 805 | "lineHeight": "160%", 806 | "linkStyle": { 807 | "inherit": true, 808 | "linkColor": "#0000ee", 809 | "linkHoverColor": "#0000ee", 810 | "linkUnderline": true, 811 | "linkHoverUnderline": true 812 | }, 813 | "hideDesktop": false, 814 | "hideMobile": false, 815 | "text": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 

" 816 | } 817 | }], 818 | "values": { 819 | "_meta": { 820 | "htmlID": "u_column_11", 821 | "htmlClassNames": "u_column" 822 | }, 823 | "border": {}, 824 | "padding": "0px", 825 | "backgroundColor": "" 826 | } 827 | }], 828 | "values": { 829 | "displayCondition": null, 830 | "columns": false, 831 | "backgroundColor": "", 832 | "columnsBackgroundColor": "#ffffff", 833 | "backgroundImage": { 834 | "url": "", 835 | "fullWidth": true, 836 | "repeat": false, 837 | "center": true, 838 | "cover": false 839 | }, 840 | "padding": "0px", 841 | "hideDesktop": false, 842 | "hideMobile": false, 843 | "noStackMobile": false, 844 | "_meta": { 845 | "htmlID": "u_row_8", 846 | "htmlClassNames": "u_row" 847 | }, 848 | "selectable": true, 849 | "draggable": true, 850 | "duplicatable": true, 851 | "deletable": true 852 | } 853 | }, { 854 | "cells": [1], 855 | "columns": [{ 856 | "contents": [{ 857 | "type": "text", 858 | "values": { 859 | "containerPadding": "60px 30px 0px", 860 | "_meta": { 861 | "htmlID": "u_content_text_8", 862 | "htmlClassNames": "u_content_text" 863 | }, 864 | "selectable": true, 865 | "draggable": true, 866 | "duplicatable": true, 867 | "deletable": true, 868 | "color": "#ffffff", 869 | "textAlign": "left", 870 | "lineHeight": "120%", 871 | "linkStyle": { 872 | "inherit": true, 873 | "linkColor": "#0000ee", 874 | "linkHoverColor": "#0000ee", 875 | "linkUnderline": true, 876 | "linkHoverUnderline": true 877 | }, 878 | "hideDesktop": false, 879 | "hideMobile": false, 880 | "text": "

ABOUT OUR

\n

PRODUCT

", 881 | "_override": { 882 | "mobile": { 883 | "textAlign": "center" 884 | } 885 | } 886 | } 887 | }, { 888 | "type": "text", 889 | "values": { 890 | "containerPadding": "22px 30px 10px", 891 | "_meta": { 892 | "htmlID": "u_content_text_9", 893 | "htmlClassNames": "u_content_text" 894 | }, 895 | "selectable": true, 896 | "draggable": true, 897 | "duplicatable": true, 898 | "deletable": true, 899 | "color": "#ffffff", 900 | "textAlign": "left", 901 | "lineHeight": "140%", 902 | "linkStyle": { 903 | "inherit": true, 904 | "linkColor": "#0000ee", 905 | "linkHoverColor": "#0000ee", 906 | "linkUnderline": true, 907 | "linkHoverUnderline": true 908 | }, 909 | "hideDesktop": false, 910 | "hideMobile": false, 911 | "text": "

Lorem ipsum dolor sit amet, consectetur

\n

adipiscing elit, sed do eiusmod tempor

\n

incididunt ut labore et dolore magna

\n

aliqua.enim ad minim veniam, quis nostrud

\n

exercitation ullamco 

", 912 | "_override": { 913 | "mobile": { 914 | "textAlign": "center" 915 | } 916 | } 917 | } 918 | }, { 919 | "type": "button", 920 | "values": { 921 | "containerPadding": "10px 30px 40px", 922 | "_meta": { 923 | "htmlID": "u_content_button_4", 924 | "htmlClassNames": "u_content_button" 925 | }, 926 | "selectable": true, 927 | "draggable": true, 928 | "duplicatable": true, 929 | "deletable": true, 930 | "href": { 931 | "name": "web", 932 | "values": { 933 | "href": "", 934 | "target": "_blank" 935 | } 936 | }, 937 | "buttonColors": { 938 | "color": "#252324", 939 | "backgroundColor": "#ffffff", 940 | "hoverColor": "#FFFFFF", 941 | "hoverBackgroundColor": "#3AAEE0" 942 | }, 943 | "size": { 944 | "autoWidth": true, 945 | "width": "100%" 946 | }, 947 | "lineHeight": "120%", 948 | "textAlign": "left", 949 | "border": {}, 950 | "borderRadius": "0px", 951 | "padding": "12px 25px", 952 | "hideDesktop": false, 953 | "hideMobile": false, 954 | "text": "VIEW MORE", 955 | "_override": { 956 | "mobile": { 957 | "textAlign": "center" 958 | } 959 | }, 960 | "calculatedWidth": 139, 961 | "calculatedHeight": 40 962 | } 963 | }], 964 | "values": { 965 | "_meta": { 966 | "htmlID": "u_column_9", 967 | "htmlClassNames": "u_column" 968 | }, 969 | "border": {}, 970 | "padding": "0px", 971 | "backgroundColor": "" 972 | } 973 | }], 974 | "values": { 975 | "displayCondition": null, 976 | "columns": false, 977 | "backgroundColor": "", 978 | "columnsBackgroundColor": "", 979 | "backgroundImage": { 980 | "url": "https://cdn.templates.unlayer.com/assets/1606937518713-ASASS.png", 981 | "fullWidth": false, 982 | "repeat": false, 983 | "center": true, 984 | "cover": false, 985 | "width": 600, 986 | "height": 500 987 | }, 988 | "padding": "0px", 989 | "hideDesktop": false, 990 | "hideMobile": false, 991 | "noStackMobile": false, 992 | "_meta": { 993 | "htmlID": "u_row_7", 994 | "htmlClassNames": "u_row" 995 | }, 996 | "selectable": true, 997 | "draggable": true, 998 | "duplicatable": true, 999 | "deletable": true 1000 | } 1001 | }, { 1002 | "cells": [1], 1003 | "columns": [{ 1004 | "contents": [{ 1005 | "type": "divider", 1006 | "values": { 1007 | "containerPadding": "15px", 1008 | "_meta": { 1009 | "htmlID": "u_content_divider_4", 1010 | "htmlClassNames": "u_content_divider" 1011 | }, 1012 | "selectable": true, 1013 | "draggable": true, 1014 | "duplicatable": true, 1015 | "deletable": true, 1016 | "width": "100%", 1017 | "border": { 1018 | "borderTopWidth": "0px", 1019 | "borderTopStyle": "solid", 1020 | "borderTopColor": "#BBBBBB" 1021 | }, 1022 | "textAlign": "center", 1023 | "hideDesktop": false, 1024 | "hideMobile": false 1025 | } 1026 | }], 1027 | "values": { 1028 | "_meta": { 1029 | "htmlID": "u_column_12", 1030 | "htmlClassNames": "u_column" 1031 | }, 1032 | "border": {}, 1033 | "padding": "0px", 1034 | "backgroundColor": "" 1035 | } 1036 | }], 1037 | "values": { 1038 | "displayCondition": null, 1039 | "columns": false, 1040 | "backgroundColor": "", 1041 | "columnsBackgroundColor": "#ffffff", 1042 | "backgroundImage": { 1043 | "url": "", 1044 | "fullWidth": true, 1045 | "repeat": false, 1046 | "center": true, 1047 | "cover": false 1048 | }, 1049 | "padding": "0px", 1050 | "hideDesktop": false, 1051 | "hideMobile": false, 1052 | "noStackMobile": false, 1053 | "_meta": { 1054 | "htmlID": "u_row_9", 1055 | "htmlClassNames": "u_row" 1056 | }, 1057 | "selectable": true, 1058 | "draggable": true, 1059 | "duplicatable": true, 1060 | "deletable": true 1061 | } 1062 | }, { 1063 | "cells": [1], 1064 | "columns": [{ 1065 | "contents": [{ 1066 | "type": "text", 1067 | "values": { 1068 | "containerPadding": "20px 10px 10px", 1069 | "_meta": { 1070 | "htmlID": "u_content_text_10", 1071 | "htmlClassNames": "u_content_text" 1072 | }, 1073 | "selectable": true, 1074 | "draggable": true, 1075 | "duplicatable": true, 1076 | "deletable": true, 1077 | "color": "#ffffff", 1078 | "textAlign": "center", 1079 | "lineHeight": "140%", 1080 | "linkStyle": { 1081 | "inherit": true, 1082 | "linkColor": "#0000ee", 1083 | "linkHoverColor": "#0000ee", 1084 | "linkUnderline": true, 1085 | "linkHoverUnderline": true 1086 | }, 1087 | "hideDesktop": false, 1088 | "hideMobile": false, 1089 | "text": "

FOLLOW  US  ON

" 1090 | } 1091 | }, { 1092 | "type": "social", 1093 | "values": { 1094 | "containerPadding": "0px 10px 20px", 1095 | "_meta": { 1096 | "htmlID": "u_content_social_1", 1097 | "htmlClassNames": "u_content_social" 1098 | }, 1099 | "selectable": true, 1100 | "draggable": true, 1101 | "duplicatable": true, 1102 | "deletable": true, 1103 | "icons": { 1104 | "iconType": "circle-white", 1105 | "icons": [{ 1106 | "url": "https://facebook.com/", 1107 | "name": "Facebook" 1108 | }, { 1109 | "url": "https://instagram.com/", 1110 | "name": "Instagram" 1111 | }, { 1112 | "url": "https://twitter.com/", 1113 | "name": "Twitter" 1114 | }] 1115 | }, 1116 | "align": "center", 1117 | "spacing": 10, 1118 | "hideDesktop": false, 1119 | "hideMobile": false 1120 | } 1121 | }], 1122 | "values": { 1123 | "_meta": { 1124 | "htmlID": "u_column_14", 1125 | "htmlClassNames": "u_column" 1126 | }, 1127 | "border": {}, 1128 | "padding": "0px", 1129 | "backgroundColor": "" 1130 | } 1131 | }], 1132 | "values": { 1133 | "displayCondition": null, 1134 | "columns": false, 1135 | "backgroundColor": "", 1136 | "columnsBackgroundColor": "#d4ae7f", 1137 | "backgroundImage": { 1138 | "url": "", 1139 | "fullWidth": true, 1140 | "repeat": false, 1141 | "center": true, 1142 | "cover": false 1143 | }, 1144 | "padding": "0px", 1145 | "hideDesktop": false, 1146 | "hideMobile": false, 1147 | "noStackMobile": false, 1148 | "_meta": { 1149 | "htmlID": "u_row_11", 1150 | "htmlClassNames": "u_row" 1151 | }, 1152 | "selectable": true, 1153 | "draggable": true, 1154 | "duplicatable": true, 1155 | "deletable": true 1156 | } 1157 | }, { 1158 | "cells": [1], 1159 | "columns": [{ 1160 | "contents": [{ 1161 | "type": "divider", 1162 | "values": { 1163 | "containerPadding": "10px", 1164 | "_meta": { 1165 | "htmlID": "u_content_divider_5", 1166 | "htmlClassNames": "u_content_divider" 1167 | }, 1168 | "selectable": true, 1169 | "draggable": true, 1170 | "duplicatable": true, 1171 | "deletable": true, 1172 | "width": "100%", 1173 | "border": { 1174 | "borderTopWidth": "0px", 1175 | "borderTopStyle": "solid", 1176 | "borderTopColor": "#BBBBBB" 1177 | }, 1178 | "textAlign": "center", 1179 | "hideDesktop": false, 1180 | "hideMobile": false 1181 | } 1182 | }], 1183 | "values": { 1184 | "_meta": { 1185 | "htmlID": "u_column_15", 1186 | "htmlClassNames": "u_column" 1187 | }, 1188 | "border": {}, 1189 | "padding": "0px", 1190 | "backgroundColor": "" 1191 | } 1192 | }], 1193 | "values": { 1194 | "displayCondition": null, 1195 | "columns": false, 1196 | "backgroundColor": "", 1197 | "columnsBackgroundColor": "", 1198 | "backgroundImage": { 1199 | "url": "", 1200 | "fullWidth": true, 1201 | "repeat": false, 1202 | "center": true, 1203 | "cover": false 1204 | }, 1205 | "padding": "0px", 1206 | "hideDesktop": false, 1207 | "hideMobile": false, 1208 | "noStackMobile": false, 1209 | "_meta": { 1210 | "htmlID": "u_row_12", 1211 | "htmlClassNames": "u_row" 1212 | }, 1213 | "selectable": true, 1214 | "draggable": true, 1215 | "duplicatable": true, 1216 | "deletable": true 1217 | } 1218 | }], 1219 | "values": { 1220 | "backgroundColor": "#e8d4bb", 1221 | "backgroundImage": { 1222 | "url": "", 1223 | "fullWidth": true, 1224 | "repeat": false, 1225 | "center": true, 1226 | "cover": false 1227 | }, 1228 | "contentWidth": "600px", 1229 | "contentAlign": "center", 1230 | "fontFamily": { 1231 | "label": "Montserrat", 1232 | "value": "'Montserrat',sans-serif", 1233 | "url": "https://fonts.googleapis.com/css?family=Montserrat:400,700", 1234 | "defaultFont": true 1235 | }, 1236 | "preheaderText": "", 1237 | "linkStyle": { 1238 | "body": true, 1239 | "linkColor": "#0000ee", 1240 | "linkHoverColor": "#0000ee", 1241 | "linkUnderline": true, 1242 | "linkHoverUnderline": true 1243 | }, 1244 | "_meta": { 1245 | "htmlID": "u_body", 1246 | "htmlClassNames": "u_body" 1247 | } 1248 | } 1249 | }, 1250 | "schemaVersion": 5 1251 | } 1252 | -------------------------------------------------------------------------------- /demo/src/index.css: -------------------------------------------------------------------------------- 1 | @import '~antd/dist/antd.css'; -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import styled, { createGlobalStyle } from 'styled-components'; 4 | 5 | import { BrowserRouter as Router, Switch, Route, Link } from 'react-router-dom'; 6 | 7 | import Example from './example'; 8 | import Dashboard from './dashboard'; 9 | 10 | const GlobalStyle = createGlobalStyle` 11 | html, body { 12 | margin: 0; 13 | padding: 0; 14 | height: 100%; 15 | font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; 16 | } 17 | 18 | #demo { 19 | height: 100%; 20 | } 21 | `; 22 | 23 | class Demo extends Component { 24 | render() { 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | } 41 | } 42 | 43 | ReactDOM.render(, document.querySelector('#demo')); 44 | -------------------------------------------------------------------------------- /nwb.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | type: 'react-component', 3 | npm: { 4 | esModules: true, 5 | umd: { 6 | global: 'EmailEditor', 7 | externals: { 8 | react: 'React' 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-email-editor", 3 | "version": "1.6.0", 4 | "description": "Unlayer's Email Editor Component for React.js", 5 | "main": "lib/index.js", 6 | "module": "es/index.js", 7 | "files": [ 8 | "css", 9 | "es", 10 | "lib", 11 | "umd" 12 | ], 13 | "scripts": { 14 | "build": "nwb build-react-component", 15 | "clean": "nwb clean-module && nwb clean-demo", 16 | "start": "nwb serve-react-demo", 17 | "test": "nwb test-react", 18 | "test:coverage": "nwb test-react --coverage", 19 | "test:watch": "nwb test-react --server", 20 | "release": "npm run build && npm publish" 21 | }, 22 | "peerDependencies": { 23 | "react": "15.x || 16.x || 17.x" 24 | }, 25 | "devDependencies": { 26 | "nwb": "^0.22.0", 27 | "react": "^17.0.2", 28 | "react-dom": "^17.0.2", 29 | "react-router-dom": "^5.2.0", 30 | "styled-components": "^4.2.0" 31 | }, 32 | "author": "", 33 | "homepage": "https://github.com/unlayer/react-email-editor#readme", 34 | "license": "MIT", 35 | "repository": "https://github.com/unlayer/react-email-editor.git", 36 | "keywords": [ 37 | "react-component" 38 | ], 39 | "dependencies": { 40 | "antd": "^4.21.5" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | arrowParens: 'always', 4 | trailingComma: 'es5', 5 | }; 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { loadScript } from './loadScript'; 3 | import pkg from '../package.json'; 4 | 5 | let lastEditorId = 0; 6 | 7 | export default class extends Component { 8 | constructor(props) { 9 | super(props); 10 | 11 | this.editorId = props.editorId || `editor-${++lastEditorId}`; 12 | } 13 | 14 | componentDidMount() { 15 | loadScript(this.loadEditor, this.props.scriptUrl); 16 | } 17 | 18 | render() { 19 | let { 20 | props: { minHeight = 500, style = {} }, 21 | } = this; 22 | 23 | return ( 24 |
31 |
32 |
33 | ); 34 | } 35 | 36 | loadEditor = () => { 37 | const options = this.props.options || {}; 38 | 39 | if (this.props.projectId) { 40 | options.projectId = this.props.projectId; 41 | } 42 | 43 | if (this.props.tools) { 44 | options.tools = this.props.tools; 45 | } 46 | 47 | if (this.props.appearance) { 48 | options.appearance = this.props.appearance; 49 | } 50 | 51 | if (this.props.locale) { 52 | options.locale = this.props.locale; 53 | } 54 | 55 | this.editor = unlayer.createEditor({ 56 | ...options, 57 | id: this.editorId, 58 | displayMode: 'email', 59 | source: { 60 | name: pkg.name, 61 | version: pkg.version, 62 | }, 63 | }); 64 | 65 | // All properties starting with on[Name] are registered as event listeners. 66 | for (const [key, value] of Object.entries(this.props)) { 67 | if (/^on/.test(key) && key !== 'onLoad' && key !== 'onReady') { 68 | this.addEventListener(key, value); 69 | } 70 | } 71 | 72 | const { onLoad, onReady } = this.props; 73 | 74 | // @deprecated 75 | onLoad && onLoad(); 76 | 77 | if (onReady) this.editor.addEventListener('editor:ready', onReady); 78 | }; 79 | 80 | registerCallback = (type, callback) => { 81 | this.editor.registerCallback(type, callback); 82 | }; 83 | 84 | addEventListener = (type, callback) => { 85 | this.editor.addEventListener(type, callback); 86 | }; 87 | 88 | loadDesign = (design) => { 89 | this.editor.loadDesign(design); 90 | }; 91 | 92 | saveDesign = (callback) => { 93 | this.editor.saveDesign(callback); 94 | }; 95 | 96 | exportHtml = (callback) => { 97 | this.editor.exportHtml(callback); 98 | }; 99 | 100 | setMergeTags = (mergeTags) => { 101 | this.editor.setMergeTags(mergeTags); 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /src/loadScript.js: -------------------------------------------------------------------------------- 1 | const defaultScriptUrl = 'https://editor.unlayer.com/embed.js?2'; 2 | const callbacks = []; 3 | let loaded = false; 4 | 5 | const isScriptInjected = (scriptUrl) => { 6 | const scripts = document.querySelectorAll('script'); 7 | let injected = false; 8 | 9 | scripts.forEach((script) => { 10 | if (script.src.includes(scriptUrl)) { 11 | injected = true; 12 | } 13 | }); 14 | 15 | return injected; 16 | }; 17 | 18 | const addCallback = (callback) => { 19 | callbacks.push(callback); 20 | }; 21 | 22 | const runCallbacks = () => { 23 | if (loaded) { 24 | let callback; 25 | 26 | while ((callback = callbacks.shift())) { 27 | callback(); 28 | } 29 | } 30 | }; 31 | 32 | export const loadScript = (callback, scriptUrl = defaultScriptUrl) => { 33 | addCallback(callback); 34 | 35 | if (!isScriptInjected(scriptUrl)) { 36 | const embedScript = document.createElement('script'); 37 | embedScript.setAttribute('src', scriptUrl); 38 | embedScript.onload = () => { 39 | loaded = true; 40 | runCallbacks(); 41 | }; 42 | document.head.appendChild(embedScript); 43 | } else { 44 | runCallbacks(); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /tests/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tests/index-test.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect' 2 | import React from 'react' 3 | import {render, unmountComponentAtNode} from 'react-dom' 4 | 5 | import Component from 'src/' 6 | 7 | describe('Component', () => { 8 | let node 9 | 10 | beforeEach(() => { 11 | node = document.createElement('div') 12 | }) 13 | 14 | afterEach(() => { 15 | unmountComponentAtNode(node) 16 | }) 17 | 18 | it('displays a welcome message', () => { 19 | render(, node, () => { 20 | expect(node.innerHTML).toContain('Welcome to React components') 21 | }) 22 | }) 23 | }) 24 | --------------------------------------------------------------------------------