├── .gitignore ├── LICENSE.txt ├── README.md ├── _ajax.php ├── _app.php ├── _config.php ├── _footer.php ├── _header.php ├── _init.php ├── _security.php ├── apple-touch-icon.png ├── builder.php ├── dist ├── css │ ├── editor.css │ └── editor.min.css ├── fonts │ ├── emailbuilder-icon-font.eot │ ├── emailbuilder-icon-font.svg │ ├── emailbuilder-icon-font.ttf │ └── emailbuilder-icon-font.woff ├── images │ ├── logo.png │ └── logo_login.png └── js │ ├── custom.js │ ├── custom.min.js │ ├── editor.js │ └── editor.min.js ├── favicon-32x32.png ├── favicon.ico ├── images └── vendor │ ├── @claviska │ └── jquery-minicolors │ │ └── jquery.minicolors.png │ └── jquery-ui │ └── themes │ └── base │ ├── ui-icons_444444_256x240.png │ ├── ui-icons_555555_256x240.png │ ├── ui-icons_777620_256x240.png │ ├── ui-icons_777777_256x240.png │ ├── ui-icons_cc0000_256x240.png │ └── ui-icons_ffffff_256x240.png ├── index.php ├── login.php ├── mix-manifest.json ├── package.json ├── server ├── _config.php ├── _export.php ├── async.php ├── fetch.php ├── slim.php └── vendor │ └── class-sendy-php-api.php ├── src ├── fonts │ ├── emailbuilder-icon-font.eot │ ├── emailbuilder-icon-font.svg │ ├── emailbuilder-icon-font.ttf │ └── emailbuilder-icon-font.woff ├── images │ └── logo.png ├── js │ ├── _content_editor.js │ ├── _emitter.js │ ├── _module.js │ ├── _nav.js │ ├── _theme.js │ ├── _utils.js │ ├── custom.js │ └── editor.js ├── libs │ ├── jquery.htmlClean.js │ └── uploader │ │ ├── slim.css │ │ └── slim.jquery.js └── sass │ ├── _base.scss │ ├── _colors.scss │ ├── _external.scss │ ├── _fonts.scss │ ├── _main_sidebar.scss │ ├── _modal.scss │ ├── _module.scss │ ├── _second_sidebar.scss │ ├── _transitions.scss │ ├── _typography.scss │ └── editor.scss ├── templates └── default │ ├── img │ ├── bg-main-b.jpg │ ├── bg-sep-a.jpg │ ├── icon32-1.png │ ├── icon32-2.png │ ├── icon32-3.png │ ├── icon32-4.png │ ├── icon64-1.png │ ├── icon64-2.png │ ├── icon64-3.png │ ├── icon64-7.png │ ├── img183-7.jpg │ ├── img183-8.jpg │ ├── img183-9.jpg │ ├── img250-1.jpg │ ├── img250-2.jpg │ ├── img287-1.jpg │ ├── img287-2.jpg │ ├── img287-5.jpg │ ├── img287-6.jpg │ └── img600.jpg │ ├── template.json │ └── thumbnails │ ├── article-2-alt.png │ ├── article-2-img.png │ ├── article-2-no-img.png │ ├── article-2.png │ ├── article-3.png │ ├── article-full.png │ ├── article-left.png │ ├── article-right.png │ ├── contact.png │ ├── features-3.png │ ├── header.png │ ├── headline-content.png │ ├── line-separator.png │ ├── pricing-table-2-alt.png │ ├── separator-alt.png │ ├── social-4.png │ └── space.png ├── theme.php ├── uploads └── .gitignore ├── webpack.config.js └── webpack.mix.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Clean Up 2 | Thumbs.db 3 | Desktop.ini 4 | $RECYCLE.BIN/ 5 | .DS_Store 6 | .idea 7 | 8 | # NPM 9 | node_modules/ 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright 2018 DigitalWheat OÜ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drag'n'Drop Email Builder for Sendy 2 | 3 | Drag'n'drop email builder for [Sendy](https://sendy.co) 4 | 5 | ![email-builder-ui](https://user-images.githubusercontent.com/10295466/37458623-a03b9c4c-2856-11e8-9061-c8e126937729.png) 6 | 7 | ## Getting Started 8 | Set the following variables in ```_config.php```: 9 | ``` 10 | APP_PATH 11 | SENDY_URL 12 | SENDY_API_KEY 13 | DATABASE HOST, USER PASSWORD AND DATABASE NAME 14 | ``` 15 | Do not forget to set writing permissions to ```/uploads/``` folder. 16 | 17 | View [online documentation](https://getemailbuilder.com/sendy/documentation) for more information. 18 | 19 | ## Development 20 | The package is based on [Laravel Mix](https://github.com/JeffreyWay/laravel-mix). 21 | ### Installation 22 | ``` 23 | git clone 24 | npm install 25 | npm run watch 26 | ``` 27 | ### Available NPM commands 28 | ``` 29 | npm run watch 30 | npm run dev 31 | npm run production 32 | ``` 33 | 34 | ## Changelog 35 | ``` 36 | v1.0.0 - March 15, 2018 37 | ** Initial release ** 38 | ``` 39 | 40 | ## Credits 41 | - [Max Kostinevich](https://maxkostinevich.com) 42 | 43 | ## [MIT License](https://opensource.org/licenses/MIT) 44 | (c) 2018 [DigitalWheat](https://digitalwheat.com) - All rights reserved. 45 | 46 | ## About 47 | At [DigitalWheat](https://digitalwheat.com) we create modern web-applications for small and medium-sized business. 48 | 49 | **Have a project in mind? [Let's talk!](https://digitalwheat.com/get-quote)** -------------------------------------------------------------------------------- /_ajax.php: -------------------------------------------------------------------------------- 1 | = 0) //openssl_decrypt requires at least 5.3.0 32 | { 33 | $decrypted = str_replace('892', '/', $in); 34 | $decrypted = str_replace('763', '+', $decrypted); 35 | 36 | if(function_exists('openssl_encrypt')) 37 | { 38 | $decrypted = version_compare(PHP_VERSION, '5.3.3') >= 0 ? openssl_decrypt($decrypted, $encryptionMethod, $api_key, 0, '3j9hwG7uj8uvpRAT') : openssl_decrypt($decrypted, $encryptionMethod, $api_key, 0); 39 | if(!$decrypted) return $is_email ? $in : intval($in, 36); 40 | } 41 | else return $is_email ? $in : intval($in, 36); 42 | 43 | return $decrypted=='' ? intval($in, 36) : $decrypted; 44 | } 45 | else return $is_email ? $in : intval($in, 36); 46 | } 47 | else 48 | { 49 | if(version_compare(PHP_VERSION, '5.3.0') >= 0) //openssl_encrypt requires at least 5.3.0 50 | { 51 | if(function_exists('openssl_encrypt')) 52 | { 53 | $encrypted = version_compare(PHP_VERSION, '5.3.3') >= 0 ? openssl_encrypt($in, $encryptionMethod, $api_key, 0, '3j9hwG7uj8uvpRAT') : openssl_encrypt($in, $encryptionMethod, $api_key, 0); 54 | if(!$encrypted) return $is_email ? $in : base_convert($in, 10, 36); 55 | } 56 | else return $is_email ? $in : base_convert($in, 10, 36); 57 | 58 | $encrypted = str_replace('/', '892', $encrypted); 59 | $encrypted = str_replace('+', '763', $encrypted); 60 | $encrypted = str_replace('=', '', $encrypted); 61 | 62 | return $encrypted; 63 | } 64 | else return $is_email ? $in : base_convert($in, 10, 36); 65 | } 66 | } 67 | 68 | if(isset($_POST['get_lists'])){ 69 | // Get brand id 70 | $brand_id = isset($_POST['brand_id']) ? $_POST['brand_id'] : ''; 71 | // Get list of the subscriber lists 72 | $lists = App::DBQuery('SELECT `id`, `name` FROM `lists` where `app`="' . $brand_id . '" ')->fetchAll(); 73 | if(count($lists)<1){ 74 | $response=array( 75 | 'message' => 'There is no subscriber lists for this Brand, your campaign will be saved as "Draft".', 76 | 'type' => 'success' 77 | ); 78 | }else { 79 | 80 | // Hash the List ID 81 | foreach ($lists as &$list) { 82 | $list['id'] = short($list['id']); 83 | } 84 | 85 | $response = array( 86 | 'message' => $lists, 87 | 'type' => 'success' 88 | ); 89 | } 90 | print json_encode($response); 91 | exit; 92 | } 93 | 94 | if(isset($_POST['get_brand_info'])){ 95 | // Get brand id 96 | $brand_id = isset($_POST['brand_id']) ? $_POST['brand_id'] : ''; 97 | // Get list of the subscriber lists 98 | $brand_info = App::DBQuery('SELECT `from_name`, `from_email`, `reply_to` FROM `apps` where `id`="' . $brand_id . '"')->fetch(); 99 | if(!is_array($brand_info)){ 100 | $response=array( 101 | 'message' => array( 102 | 'from_name' => '', 103 | 'from_email' => '', 104 | 'reply_to' => '', 105 | ), 106 | 'type' => 'success' 107 | ); 108 | }else { 109 | 110 | $response = array( 111 | 'message' => $brand_info, 112 | 'type' => 'success' 113 | ); 114 | } 115 | print json_encode($response); 116 | exit; 117 | } 118 | 119 | 120 | if(isset($_POST['process_campaign'])){ 121 | // Get brand id 122 | $brand_id = isset($_POST['brand_id']) ? $_POST['brand_id'] : 0; 123 | $subject = isset($_POST['subject']) ? $_POST['subject'] : ''; 124 | $from_name = isset($_POST['from_name']) ? $_POST['from_name'] : ''; 125 | $from_email = isset($_POST['from_email']) ? $_POST['from_email'] : ''; 126 | $reply_to = isset($_POST['reply_to']) ? $_POST['reply_to'] : ''; 127 | $plain_text = isset($_POST['plain_text']) ? $_POST['plain_text'] : ''; 128 | $html_text = isset($_POST['html_text']) ? base64_decode($_POST['html_text']) : ''; 129 | $query_string = isset($_POST['query_string']) ? $_POST['query_string'] : ''; 130 | $list_ids = isset($_POST['list_ids']) ? implode(',',$_POST['list_ids']) : ''; 131 | $send_campaign = isset($_POST['send_campaign']) ? $_POST['send_campaign'] : 0; 132 | 133 | $config = array( 134 | 'installation_url' => SENDY_URL, // Your Sendy installation URL (without trailing slash). 135 | 'api_key' => SENDY_API_KEY, // Your API key. Aavailable in Sendy Settings. 136 | 'list_id' => 'dummy' 137 | ); 138 | 139 | $sendy = new \SENDY\Sendy_PHP_API( $config ); 140 | 141 | $campaign_settings = array( 142 | 'from_name' => $from_name, 143 | 'from_email' => $from_email, 144 | 'reply_to' => $reply_to, 145 | 'subject' => $subject, 146 | 'plain_text' => $plain_text, // (optional). 147 | 'html_text' => $html_text, 148 | 'brand_id' => (int)$brand_id, // Required only if you are creating a 'Draft' campaign. 149 | 'send_campaign' => $send_campaign, // Set to 1 if you want to send the campaign as well and not just create a draft. Default is 0. 150 | 'list_ids' => $list_ids, // Required only if you set send_campaign to 1. 151 | 'query_string' => $query_string, // Eg. Google Analytics tags. 152 | ); 153 | 154 | $result_array = $sendy->campaign( $campaign_settings ); 155 | 156 | $response=array( 157 | 'message' => $result_array, 158 | 'type' => 'success' 159 | ); 160 | print json_encode($response); 161 | exit; 162 | } 163 | 164 | 165 | 166 | 167 | // Display an error in case of direct file call 168 | // Return empty JSON to avoid parse error in javascript 169 | print json_encode('{Ajax error}'); 170 | exit; -------------------------------------------------------------------------------- /_app.php: -------------------------------------------------------------------------------- 1 | 16 | private static $pageTitles = [ 17 | 'theme' => 'Choose campaign theme', 18 | 'builder' => 'Create new campaign', 19 | 'login' => 'Authorization' 20 | ]; 21 | 22 | /** 23 | * @var Singleton The reference to *Singleton* instance of this class 24 | */ 25 | private static $instance; 26 | 27 | /** 28 | * Returns the *Singleton* instance of this class. 29 | * 30 | * @return Singleton The *Singleton* instance. 31 | */ 32 | public static function getInstance() 33 | { 34 | if (null === static::$instance) { 35 | static::$instance = new static(); 36 | } 37 | 38 | return static::$instance; 39 | } 40 | /** 41 | * Protected constructor to prevent creating a new instance of the 42 | * *Singleton* via the `new` operator from outside of this class. 43 | */ 44 | protected function __construct() 45 | { 46 | } 47 | /** 48 | * Private clone method to prevent cloning of the instance of the 49 | * *Singleton* instance. 50 | * 51 | * @return void 52 | */ 53 | private function __clone() 54 | { 55 | } 56 | /** 57 | * Private unserialize method to prevent unserializing of the *Singleton* 58 | * instance. 59 | * 60 | * @return void 61 | */ 62 | private function __wakeup() 63 | { 64 | } 65 | 66 | /** VIEW FUNCTIONS **/ 67 | 68 | // Set current page 69 | public static function setPage( $page ){ 70 | self::$page = $page; 71 | } 72 | 73 | // Get current page 74 | public static function getPage(){ 75 | return self::$page; 76 | } 77 | 78 | // Display page title according to current page 79 | public static function displayPageTitle(){ 80 | echo isset( self::$pageTitles[self::$page] ) ? self::$pageTitles[self::$page] : self::$pageTitles['builder']; 81 | } 82 | 83 | 84 | 85 | /** USER FUNCTIONS **/ 86 | 87 | // Check if the user is logged in 88 | public static function isUserLoggedIn(){ 89 | if ( isset( $_SESSION['user_id'] ) ) { 90 | return true; 91 | } 92 | return false; 93 | } 94 | 95 | 96 | // Get current user information 97 | // if $attr is passed - return specific attribute 98 | // otherwise - return whole array 99 | public static function getUserInfo($attr = null){ 100 | if ( !self::isUserLoggedIn() ) { 101 | return false; 102 | } 103 | 104 | $result = self::DBQuery('SELECT * FROM `login` where `id`= "' . $_SESSION['user_id'] . '" LIMIT 1')->fetch(); 105 | if(is_array($result)){ 106 | if(!$attr) { 107 | return $result; 108 | } 109 | if(array_key_exists($attr, $result)){ 110 | return $result[$attr]; 111 | } 112 | } 113 | 114 | return false; 115 | } 116 | 117 | 118 | // Get user ID 119 | public static function getUserID(){ 120 | return self::getUserInfo('id'); 121 | } 122 | 123 | // Get user App ID 124 | public static function getUserApp(){ 125 | return self::getUserInfo('app'); 126 | } 127 | 128 | // Get user name 129 | public static function getUserName(){ 130 | return self::getUserInfo('name'); 131 | } 132 | 133 | // Get user company 134 | public static function getUserCompany(){ 135 | return self::getUserInfo('company'); 136 | } 137 | 138 | // Get user email 139 | public static function getUserEmail(){ 140 | return self::getUserInfo('username'); 141 | } 142 | 143 | 144 | // Do user logout 145 | public static function doUserLogout(){ 146 | $_SESSION['user_id'] = ''; 147 | unset( $_SESSION['user_id'] ); 148 | } 149 | 150 | /** THEME/TEMPLATE FUNCTIONS **/ 151 | // Get list of available email templates 152 | // return array with template slug and name 153 | public static function getTemplates(){ 154 | $result = array(); 155 | $templates = array_filter(glob('templates/*'), 'is_dir'); 156 | foreach ($templates as $template){ 157 | $template_slug = basename($template); 158 | 159 | $template_name = str_replace('_', ' ', $template_slug); 160 | $template_name = str_replace('-', ' ', $template_name); 161 | $template_name = ucwords($template_name); 162 | $tmp = array( 163 | 'slug'=> $template_slug, 164 | 'name'=> $template_name 165 | ); 166 | 167 | array_push($result, $tmp); 168 | } 169 | return $result; 170 | } 171 | 172 | /** DATABASE FUNCTIONS **/ 173 | // Connect to database 174 | public static function DBConnect($db_name, $db_user, $db_password, $db_host = '127.0.0.1'){ 175 | $db_host = $db_host ? $db_host : '127.0.0.1'; 176 | /* Connect to a MySQL database using driver invocation */ 177 | $dsn = 'mysql:dbname=' . $db_name . ';host=' . $db_host . ';charset=utf8'; 178 | 179 | try { 180 | self::$db = new PDO($dsn, $db_user, $db_password); 181 | } catch (PDOException $e) { 182 | echo 'Connection failed: ' . $e->getMessage(); 183 | die(); 184 | } 185 | } 186 | 187 | // SQL Query 188 | public static function DBQuery( $q ){ 189 | $q = self::$db->query($q ); 190 | return $q; 191 | } 192 | 193 | // Insert Records, Inserted ID will be returned 194 | public static function DBExecute( $q ){ 195 | self::$db->exec($q ); 196 | $inserted_id = self::$db->lastInsertId(); 197 | return $inserted_id; 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /_config.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /_header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <?php echo APP_NAME; ?> - <?php App::displayPageTitle();?> 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /_init.php: -------------------------------------------------------------------------------- 1 | 9 |
10 | 11 | 53 | 54 |
55 | 56 | 73 | 74 |
75 | 76 | 81 | 82 |
83 | 84 |
85 | 86 |
87 |
88 |
89 |
90 |
91 | 92 | 93 | 94 |
95 |
96 | 97 | 98 | 108 | 109 | 129 | 130 | 140 | 141 | 194 | 195 | 254 | 255 | 256 | 257 | 258 | 259 | 268 | 269 | 270 | 271 | 272 | 276 | 277 | -------------------------------------------------------------------------------- /dist/fonts/emailbuilder-icon-font.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/dist/fonts/emailbuilder-icon-font.eot -------------------------------------------------------------------------------- /dist/fonts/emailbuilder-icon-font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/dist/fonts/emailbuilder-icon-font.ttf -------------------------------------------------------------------------------- /dist/fonts/emailbuilder-icon-font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/dist/fonts/emailbuilder-icon-font.woff -------------------------------------------------------------------------------- /dist/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/dist/images/logo.png -------------------------------------------------------------------------------- /dist/images/logo_login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/dist/images/logo_login.png -------------------------------------------------------------------------------- /dist/js/custom.js: -------------------------------------------------------------------------------- 1 | !function(n){function e(a){if(t[a])return t[a].exports;var o=t[a]={i:a,l:!1,exports:{}};return n[a].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var t={};e.m=n,e.c=t,e.i=function(n){return n},e.d=function(n,t,a){e.o(n,t)||Object.defineProperty(n,t,{configurable:!1,enumerable:!0,get:a})},e.n=function(n){var t=n&&n.__esModule?function(){return n.default}:function(){return n};return e.d(t,"a",t),t},e.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},e.p="",e(e.s=40)}({12:function(n,e,t){"use strict";function a(){swal({title:"Export to HTML",text:"Export template to single HTML file",type:"info",showCancelButton:!0,showLoaderOnConfirm:!0},function(){setTimeout(function(){swal("Template has been exported successfully"),$('#export-form [name="type"]').val("html"),$("#export-form").submit()},1e3)})}function o(){swal({title:"Export to ZIP",text:"Export template to zip-archive",type:"info",showCancelButton:!0,showLoaderOnConfirm:!0},function(){setTimeout(function(){swal("Template has been exported successfully"),$('#export-form [name="type"]').val("zip"),$("#export-form").submit()},1e3)})}function i(){var n=$("#campaignmodal");return $.fancybox.open({src:"#campaignmodal",type:"inline",opts:{onComplete:function(){$(".modal-btn-cancel",n).off("click"),$(".modal-btn-ok",n).off("click"),$(".modal-btn-cancel",n).on("click",function(n){$.fancybox.close()}),$(".modal-btn-ok",n).on("click",function(n){var e="Save"+($("input[name=send_campaign]").is(":checked")?" and send":"")+" campaign?";swal({title:e,type:"info",showCancelButton:!0,closeOnConfirm:!1,showLoaderOnConfirm:!0},function(){var n=[];$(".list_ids:checked").each(function(){n.push(this.value)}),$.ajax({url:"_ajax.php",type:"POST",dataType:"json",data:{process_campaign:1,from_name:$("input[name=from_name]").val(),from_email:$("input[name=from_email]").val(),reply_to:$("input[name=reply_to]").val(),subject:$("input[name=subject]").val(),plain_text:$("input[name=plain_text]").val(),html_text:$("#templateHTML").val(),brand_id:$("#brand_id").val(),send_campaign:$("input[name=send_campaign]").is(":checked")?1:0,list_ids:n,query_string:$("input[name=query_string]").val()},success:function(n){if("success"==n.type){var e=n.message;!0===e.status?(swal("Success",e.message,"success"),$("input[name=from_name]").val(""),$("input[name=from_email]").val(""),$("input[name=reply_to]").val(""),$("input[name=subject]").val(""),$("input[name=plain_text]").val(""),$("input[name=send_campaign]").prop("checked",!1),$("input[name=query_string]").val(""),$.fancybox.close()):swal("Error",e.message,"error")}},error:function(n,e){}})})})}}}),!1}function c(){return $.fancybox.open({src:"#sendyhelpers",type:"inline"}),!1}window.top.jsEmailBuilderEmitter.on("init",function(){$('[data-action="export-html"]').on("click",function(n){a()}),$('[data-action="export-zip"]').on("click",function(n){o()}),$('[data-action="campaign-settings"]').on("click",function(n){i()}),$('[data-action="sendy-helpers"]').on("click",function(n){c()}),$('[data-action="expand-account"]').on("click",function(n){var e=this;$(e).closest(".nav-item").find(".subnav").slideToggle()}),$("#brand_id").on("change",function(n){var e=this.value;$.ajax({url:"_ajax.php",type:"POST",dataType:"json",data:{get_lists:1,brand_id:e},success:function(n){if("success"!=n.type)$("#subscribers-list-container").html(n.message);else{var e=n.message,t="";e instanceof Array?$.each(e,function(n,e){t+='"}):t=e,$("#subscribers-list-container").html(t)}},error:function(n,e){}}),$.ajax({url:"_ajax.php",type:"POST",dataType:"json",data:{get_brand_info:1,brand_id:e},success:function(n){if("success"==n.type){var e=n.message;$("input[name=from_name]").val(e.from_name),$("input[name=from_email]").val(e.from_email),$("input[name=reply_to]").val(e.reply_to)}},error:function(n,e){}})})})},40:function(n,e,t){n.exports=t(12)}}); -------------------------------------------------------------------------------- /dist/js/custom.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return t[o].call(r.exports,r,r.exports,e),r.l=!0,r.exports}var n={};e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,o){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:o})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=40)}({12:function(t,e,n){"use strict";function o(){swal({title:"Send test email",text:"Enter email address to send:",type:"input",showCancelButton:!0,closeOnConfirm:!1,animation:"slide-from-top",inputPlaceholder:"john@example.com",showLoaderOnConfirm:!0},function(t){if(!1===t)return!1;if(""===t)return swal.showInputError("Please enter email address to send."),!1;var e=$("#templateHTML").val(),n={};return n.action="send-test-emails",n.emails=t,n.templateHTML=e,$.ajax({url:config.send_script,type:"POST",dataType:"json",data:n,success:function(e){"success"!=e.type?swal("Nice!","Email has been sent to: "+t,"success"):swal("Oops!","An error is occurred","error")},error:function(t,e){}}),!1})}function r(){swal({title:"Export to HTML",text:"Export template to single HTML file",type:"info",showCancelButton:!0,showLoaderOnConfirm:!0},function(){setTimeout(function(){swal("Template has been exported successfully"),$('#export-form [name="type"]').val("html"),$("#export-form").submit()},1e3)})}function i(){swal({title:"Export to ZIP",text:"Export template to zip-archive",type:"info",showCancelButton:!0,showLoaderOnConfirm:!0},function(){setTimeout(function(){swal("Template has been exported successfully"),$('#export-form [name="type"]').val("zip"),$("#export-form").submit()},1e3)})}window.top.jsEmailBuilderEmitter.on("init",function(){$('[data-action="send-test-email"]').on("click",function(t){o()}),$('[data-action="export-html"]').on("click",function(t){r()}),$('[data-action="export-zip"]').on("click",function(t){i()})})},40:function(t,e,n){t.exports=n(12)}}); -------------------------------------------------------------------------------- /favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/favicon-32x32.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/favicon.ico -------------------------------------------------------------------------------- /images/vendor/@claviska/jquery-minicolors/jquery.minicolors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/@claviska/jquery-minicolors/jquery.minicolors.png -------------------------------------------------------------------------------- /images/vendor/jquery-ui/themes/base/ui-icons_444444_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/jquery-ui/themes/base/ui-icons_444444_256x240.png -------------------------------------------------------------------------------- /images/vendor/jquery-ui/themes/base/ui-icons_555555_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/jquery-ui/themes/base/ui-icons_555555_256x240.png -------------------------------------------------------------------------------- /images/vendor/jquery-ui/themes/base/ui-icons_777620_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/jquery-ui/themes/base/ui-icons_777620_256x240.png -------------------------------------------------------------------------------- /images/vendor/jquery-ui/themes/base/ui-icons_777777_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/jquery-ui/themes/base/ui-icons_777777_256x240.png -------------------------------------------------------------------------------- /images/vendor/jquery-ui/themes/base/ui-icons_cc0000_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/jquery-ui/themes/base/ui-icons_cc0000_256x240.png -------------------------------------------------------------------------------- /images/vendor/jquery-ui/themes/base/ui-icons_ffffff_256x240.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/images/vendor/jquery-ui/themes/base/ui-icons_ffffff_256x240.png -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | fetch(); 37 | if(!is_array($result) || (hash('sha512', $user_password.'PectGtma') !== $result['password']) ){ 38 | $statusMsg = "Incorrect email or password, please, try again."; 39 | $hasError = true; 40 | } 41 | } 42 | 43 | 44 | // Write User ID to Session in case of success 45 | if ( !$hasError ) { 46 | $_SESSION['user_id'] = $result['id']; 47 | header('Location: index.php'); 48 | die(); 49 | } 50 | } 51 | 52 | 53 | App::setPage('login'); 54 | 55 | require_once '_security.php'; 56 | 57 | require_once '_header.php'; 58 | ?> 59 | 60 |
61 | 62 |
63 | 64 |

Authorization

65 |

Please, login using your Sendy email and password

66 | 67 | 68 |
69 | 70 |
71 | 72 |
73 | 74 |
75 | 76 |
77 | 78 |
79 | 80 |
81 | 82 | 83 |
84 | 85 |
86 |
87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/dist/js/editor.js": "/dist/js/editor.js", 3 | "/dist/css/editor.css": "/dist/css/editor.css", 4 | "/dist/js/custom.js": "/dist/js/custom.js" 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emailbuilder-sendy", 3 | "version": "1.0.0", 4 | "description": "Drag'n'Drop Email Builder for Sendy", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "cross-env NODE_ENV=development webpack --progress --hide-modules", 9 | "watch": "cross-env NODE_ENV=development webpack --watch --progress --hide-modules", 10 | "hot": "cross-env NODE_ENV=development webpack-dev-server --inline --hot", 11 | "production": "cross-env NODE_ENV=production webpack --progress --hide-modules" 12 | }, 13 | "keywords": [], 14 | "author": "DigitalWheat", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@claviska/jquery-minicolors": "^2.2.4", 18 | "@fancyapps/fancybox": "^3.0.48", 19 | "codemirror": "^5.25.0", 20 | "jquery": "^3.2.1", 21 | "jquery-ui": "^1.12.1", 22 | "laravel-mix": "^0.10.0", 23 | "medium-editor": "^5.23.0", 24 | "sweetalert": "^1.1.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/_config.php: -------------------------------------------------------------------------------- 1 | open($file, ZipArchive::OVERWRITE); 42 | 43 | // Stuff with content 44 | $templateHTML = base64_decode($_POST['templateHTML']); 45 | $usedImages = array(); 46 | // Get used images 47 | preg_match_all('#([^/]+)\.jpg|jpeg|png|gif#i', $templateHTML, $usedImages ); 48 | // Remove repeated images 49 | $usedImages = array_unique($usedImages[0]); 50 | // Transform list of used images into string 51 | $usedImages = implode(',', $usedImages); 52 | $newImgBase = 'img'; 53 | // Change absolute image paths to relative across all img tags 54 | $templateHTML = preg_replace('#()#i', "$1{$newImgBase}$2", $templateHTML); 55 | // Change absolute image paths to relative across all background images 56 | $templateHTML = preg_replace('#(<.*background=")[^"]+(\/(.*)">)#i', "$1{$newImgBase}$2", $templateHTML); 57 | $templateHTML = preg_replace('#(<.*style=".*url\("|\'|\")[^"]+(\/(.*)\).*"|\'|\">)#i', "$1{$newImgBase}$2", $templateHTML); 58 | // echo $templateHTML;die(); 59 | $zip->addFromString('index.html', $templateHTML); 60 | // $zip->addFile('file_on_server.ext', 'second_file_name_within_archive.ext'); 61 | $options = array('add_path' => 'img/', 'remove_all_path' => TRUE); 62 | // $zip->addGlob('uploads/*.{jpg,jpeg,gif,png}', GLOB_BRACE, $options); 63 | // Add used images only 64 | $zip->addGlob( '../uploads/{' . $usedImages . '}', GLOB_BRACE, $options); 65 | 66 | $options = array('add_path' => 'img/', 'remove_all_path' => TRUE); 67 | $zip->addGlob('../templates/' . $template . '/img/{'. $usedImages .'}', GLOB_BRACE, $options); 68 | 69 | // Close and send to users 70 | $zip->close(); 71 | header('Content-Type: application/zip'); 72 | header('Content-Length: ' . filesize($file)); 73 | header('Content-Disposition: attachment; filename="template.zip"'); 74 | readfile($file); 75 | unlink($file); 76 | exit(); 77 | break; 78 | } 79 | 80 | } -------------------------------------------------------------------------------- /server/async.php: -------------------------------------------------------------------------------- 1 | SlimStatus::FAILURE, 29 | 'message' => 'Unknown' 30 | )); 31 | 32 | return; 33 | } 34 | 35 | // No image found under the supplied input name 36 | if ($images === false) { 37 | 38 | // Possible solutions 39 | // ---------- 40 | // Make sure the name of the file input is "slim[]" or you have passed your custom 41 | // name to the getImages method above like this -> Slim::getImages("myFieldName") 42 | 43 | Slim::outputJSON(array( 44 | 'status' => SlimStatus::FAILURE, 45 | 'message' => 'No data posted' 46 | )); 47 | 48 | return; 49 | } 50 | 51 | // Should always be one image (when posting async), so we'll use the first on in the array (if available) 52 | $image = array_shift($images); 53 | 54 | // Something was posted but no images were found 55 | if (!isset($image)) { 56 | 57 | // Possible solutions 58 | // ---------- 59 | // Make sure you're running PHP version 5.6 or higher 60 | 61 | Slim::outputJSON(array( 62 | 'status' => SlimStatus::FAILURE, 63 | 'message' => 'No images found' 64 | )); 65 | 66 | return; 67 | } 68 | 69 | // If image found but no output or input data present 70 | if (!isset($image['output']['data']) && !isset($image['input']['data'])) { 71 | 72 | // Possible solutions 73 | // ---------- 74 | // If you've set the data-post attribute make sure it contains the "output" value -> data-post="actions,output" 75 | // If you want to use the input data and have set the data-post attribute to include "input", replace the 'output' String above with 'input' 76 | 77 | Slim::outputJSON(array( 78 | 'status' => SlimStatus::FAILURE, 79 | 'message' => 'No image data' 80 | )); 81 | 82 | return; 83 | } 84 | 85 | 86 | 87 | // if we've received output data save as file 88 | if (isset($image['output']['data'])) { 89 | 90 | // get the name of the file 91 | $name = $image['output']['name']; 92 | 93 | // get the crop data for the output image 94 | $data = $image['output']['data']; 95 | 96 | // If you want to store the file in another directory pass the directory name as the third parameter. 97 | // $file = Slim::saveFile($data, $name, 'my-directory/'); 98 | 99 | // If you want to prevent Slim from adding a unique id to the file name add false as the fourth parameter. 100 | // $file = Slim::saveFile($data, $name, 'tmp/', false); 101 | $output = Slim::saveFile($data, $name, '../uploads/'); 102 | } 103 | 104 | // if we've received input data (do the same as above but for input data) 105 | if (isset($image['input']['data'])) { 106 | 107 | // get the name of the file 108 | $name = $image['input']['name']; 109 | 110 | // get the crop data for the output image 111 | $data = $image['input']['data']; 112 | 113 | // If you want to store the file in another directory pass the directory name as the third parameter. 114 | // $file = Slim::saveFile($data, $name, 'my-directory/'); 115 | 116 | // If you want to prevent Slim from adding a unique id to the file name add false as the fourth parameter. 117 | // $file = Slim::saveFile($data, $name, 'tmp/', false); 118 | $input = Slim::saveFile($data, $name, '../uploads/'); 119 | 120 | } 121 | 122 | 123 | 124 | // 125 | // Build response to client 126 | // 127 | $response = array( 128 | 'status' => SlimStatus::SUCCESS 129 | ); 130 | 131 | if (isset($output) && isset($input)) { 132 | 133 | $response['output'] = array( 134 | 'file' => $output['name'], 135 | 'path' => $output['path'] 136 | ); 137 | 138 | $response['input'] = array( 139 | 'file' => $input['name'], 140 | 'path' => $input['path'] 141 | ); 142 | 143 | } 144 | else { 145 | $response['file'] = isset($output) ? $output['name'] : $input['name']; 146 | $response['path'] = isset($output) ? $output['path'] : $input['path']; 147 | } 148 | 149 | // Return results as JSON String 150 | Slim::outputJSON($response); -------------------------------------------------------------------------------- /server/fetch.php: -------------------------------------------------------------------------------- 1 | SlimStatus::FAILURE, 30 | 'message' => 'URL load failed, "allow_url_fopen" is not enabled. Add "allow_url_fopen = On" to your php.ini file.' 31 | )); 32 | return; 33 | } 34 | 35 | 36 | // Something else went wrong (for instance, remote server is down) 37 | if ($data === false) { 38 | Slim::outputJSON(array( 39 | 'status' => SlimStatus::FAILURE, 40 | 'message' => 'URL load failed for unknown reasons.' 41 | )); 42 | return; 43 | } 44 | 45 | 46 | // get the file name from the url 47 | $name = basename($_SERVER['REQUEST_URI'], '?' . $_SERVER['QUERY_STRING']); 48 | 49 | 50 | // If you want to store the file in another directory pass the directory name as the third parameter. 51 | // $file = Slim::saveFile($data, $name, 'my-directory/'); 52 | $file = Slim::saveFile($data, $name); 53 | $filename = $file['path']; 54 | 55 | 56 | // Test if file is safe for use 57 | $type = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $filename); 58 | 59 | if ( 60 | // is it not an image 61 | !(substr($type, 0, 6) === 'image/') 62 | ) { 63 | 64 | // remove file 65 | if (file_exists($filename)) { 66 | unlink($filename); 67 | } 68 | 69 | // echo error 70 | Slim::outputJSON(array( 71 | 'status' => SlimStatus::FAILURE, 72 | 'message' => 'URL load failed for unknown reasons.' 73 | )); 74 | 75 | return; 76 | } 77 | 78 | // return name of file on server 79 | Slim::outputJSON(array( 80 | 'status' => SlimStatus::SUCCESS, 81 | 'body' => $filename 82 | )); -------------------------------------------------------------------------------- /server/slim.php: -------------------------------------------------------------------------------- 1 | input)) { 63 | 64 | $inputData = null; 65 | if (isset($data->input->image)) { 66 | $inputData = Slim::getBase64Data($data->input->image); 67 | } 68 | else if (isset($data->input->field)) { 69 | $filename = $_FILES[$data->input->field]['tmp_name']; 70 | if ($filename) { 71 | $inputData = file_get_contents($filename); 72 | } 73 | } 74 | 75 | $input = array( 76 | 'data' => $inputData, 77 | 'name' => $data->input->name, 78 | 'type' => $data->input->type, 79 | 'size' => $data->input->size, 80 | 'width' => $data->input->width, 81 | 'height' => $data->input->height, 82 | ); 83 | 84 | } 85 | 86 | if (isset($data->output)) { 87 | 88 | $outputDate = null; 89 | if (isset($data->output->image)) { 90 | $outputData = Slim::getBase64Data($data->output->image); 91 | } 92 | else if (isset ($data->output->field)) { 93 | $filename = $_FILES[$data->output->field]['tmp_name']; 94 | if ($filename) { 95 | $outputData = file_get_contents($filename); 96 | } 97 | } 98 | 99 | $output = array( 100 | 'data' => $outputData, 101 | 'name' => $data->output->name, 102 | 'type' => $data->output->type, 103 | 'width' => $data->output->width, 104 | 'height' => $data->output->height 105 | ); 106 | } 107 | 108 | if (isset($data->actions)) { 109 | $actions = array( 110 | 'crop' => $data->actions->crop ? array( 111 | 'x' => $data->actions->crop->x, 112 | 'y' => $data->actions->crop->y, 113 | 'width' => $data->actions->crop->width, 114 | 'height' => $data->actions->crop->height, 115 | 'type' => $data->actions->crop->type 116 | ) : null, 117 | 'size' => $data->actions->size ? array( 118 | 'width' => $data->actions->size->width, 119 | 'height' => $data->actions->size->height 120 | ) : null, 121 | 'rotation' => $data->actions->rotation, 122 | 'filters' => $data->actions->filters ? array( 123 | 'sharpen' => $data->actions->filters->sharpen 124 | ) : null 125 | ); 126 | } 127 | 128 | if (isset($data->meta)) { 129 | $meta = $data->meta; 130 | } 131 | 132 | // We've sanitized the base64data and will now return the clean file object 133 | return array( 134 | 'input' => $input, 135 | 'output' => $output, 136 | 'actions' => $actions, 137 | 'meta' => $meta 138 | ); 139 | } 140 | 141 | // $path should have trailing slash 142 | public static function saveFile($data, $name, $path = 'tmp/', $uid = true) { 143 | 144 | // Add trailing slash if omitted 145 | if (substr($path, -1) !== '/') { 146 | $path .= '/'; 147 | } 148 | 149 | // Test if directory already exists 150 | if(!is_dir($path)){ 151 | mkdir($path, 0755, true); 152 | } 153 | 154 | // Sanitize characters in file name 155 | $name = Slim::sanitizeFileName($name); 156 | 157 | // Let's put a unique id in front of the filename so we don't accidentally overwrite other files 158 | if ($uid) { 159 | $name = uniqid() . '_' . $name; 160 | } 161 | 162 | // Add name to path, we need the full path including the name to save the file 163 | $path = $path . $name; 164 | 165 | // store the file 166 | Slim::save($data, $path); 167 | 168 | // return the files new name and location 169 | return array( 170 | 'name' => $name, 171 | 'path' => $path 172 | ); 173 | } 174 | 175 | /** 176 | * Get data from remote URL 177 | * @param $url 178 | * @return string 179 | */ 180 | public static function fetchURL($url, $maxFileSize) { 181 | if (!ini_get('allow_url_fopen')) { 182 | return null; 183 | } 184 | $content = null; 185 | try { 186 | $content = @file_get_contents($url, false, null, 0, $maxFileSize); 187 | } catch(Exception $e) { 188 | return false; 189 | } 190 | return $content; 191 | } 192 | 193 | public static function outputJSON($data) { 194 | header('Content-Type: application/json'); 195 | echo json_encode($data); 196 | } 197 | 198 | /** 199 | * http://stackoverflow.com/a/2021729 200 | * Remove anything which isn't a word, whitespace, number 201 | * or any of the following characters -_~,;[](). 202 | * If you don't need to handle multi-byte characters 203 | * you can use preg_replace rather than mb_ereg_replace 204 | * @param $str 205 | * @return string 206 | */ 207 | public static function sanitizeFileName($str) { 208 | // Basic clean up 209 | $str = mb_ereg_replace("([^\w\s\d\-_~,;\[\]\(\).])", '', $str); 210 | // Remove any runs of periods 211 | $str = mb_ereg_replace("([\.]{2,})", '', $str); 212 | return $str; 213 | } 214 | 215 | /** 216 | * Gets the posted data from the POST or FILES object. If was using Slim to upload it will be in POST (as posted with hidden field) if not enhanced with Slim it'll be in FILES. 217 | * @param $inputName 218 | * @return array|bool 219 | */ 220 | private static function getPostData($inputName) { 221 | 222 | $values = array(); 223 | 224 | if (isset($_POST[$inputName])) { 225 | $values = $_POST[$inputName]; 226 | } 227 | else if (isset($_FILES[$inputName])) { 228 | // Slim was not used to upload this file 229 | return false; 230 | } 231 | 232 | return $values; 233 | } 234 | 235 | /** 236 | * Saves the data to a given location 237 | * @param $data 238 | * @param $path 239 | * @return bool 240 | */ 241 | private static function save($data, $path) { 242 | if (!file_put_contents($path, $data)) { 243 | return false; 244 | } 245 | return true; 246 | } 247 | 248 | /** 249 | * Strips the "data:image..." part of the base64 data string so PHP can save the string as a file 250 | * @param $data 251 | * @return string 252 | */ 253 | private static function getBase64Data($data) { 254 | return base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $data)); 255 | } 256 | 257 | } -------------------------------------------------------------------------------- /server/vendor/class-sendy-php-api.php: -------------------------------------------------------------------------------- 1 | Nothing to see here!'; 18 | exit; 19 | } 20 | 21 | // Helps with the CORS issues. 22 | header( 'Access-Control-Allow-Origin: *' ); 23 | header( 'Access-Control-Allow-Methods: POST, GET' ); 24 | header( 'Access-Control-Allow-Credentials: true' ); 25 | 26 | // Make sure class is unique. 27 | if ( ! class_exists( 'Sendy_PHP_API' ) ) { 28 | /** 29 | * Sendy_PHP_API. 30 | * 31 | * Sendy PHP API Class. 32 | * 33 | * @since 1.0.0 34 | */ 35 | class Sendy_PHP_API { 36 | // Installation URL. 37 | protected $installation_url; 38 | 39 | // API key. 40 | protected $api_key; 41 | 42 | // List ID> 43 | protected $list_id; 44 | 45 | /** 46 | * Constructor. 47 | * 48 | * @param array $config 49 | * @since 1.0.0 50 | */ 51 | public function __construct( array $config ) { 52 | // Error checking. 53 | $installation_url = @$config['installation_url']; 54 | $list_id = @$config['list_id']; 55 | $api_key = @$config['api_key']; 56 | 57 | // Bail if empty. 58 | if ( empty( $list_id ) ) { 59 | throw new \Exception( 'Required config parameter [list_id] is not set or empty', 1 ); 60 | } 61 | 62 | // Bail if empty. 63 | if ( empty( $installation_url ) ) { 64 | throw new \Exception( 'Required config parameter [installation_url] is not set or empty', 1 ); 65 | } 66 | 67 | // Bail if empty. 68 | if ( empty( $api_key ) ) { 69 | throw new \Exception( 'Required config parameter [api_key] is not set or empty', 1 ); 70 | } 71 | 72 | // Define the class vars. 73 | $this->installation_url = $installation_url; 74 | $this->list_id = $list_id; 75 | $this->api_key = $api_key; 76 | } 77 | 78 | /** 79 | * Set List ID. 80 | * 81 | * @param string $list_id List ID. 82 | * @since 1.0.0 83 | */ 84 | public function set_list_id( $list_id ) { 85 | // Bail if empty. 86 | if ( empty( $list_id ) ) { 87 | throw new \Exception( "Required config parameter [list_id] is not set", 1 ); 88 | } 89 | 90 | // Set the ID. 91 | $this->list_id = $list_id; 92 | } 93 | 94 | /** 95 | * Get List ID. 96 | * 97 | * @return string ID. 98 | * @since 1.0.0 99 | */ 100 | public function get_list_id() { 101 | return $this->list_id; 102 | } 103 | 104 | /** 105 | * Subscribe. 106 | * 107 | * @param array $values 108 | * @return array 109 | * @since 1.0.0 110 | */ 111 | public function subscribe( array $values ) { 112 | // Route. 113 | $route = 'subscribe'; 114 | 115 | // Send the subscribe command. 116 | $result = strval( $this->query( $route, $values ) ); 117 | 118 | // Handle results. 119 | switch ( $result ) { 120 | case '1': 121 | return array( 122 | 'status' => true, 123 | 'message' => 'Subscribed!', 124 | ); 125 | break; 126 | 127 | case 'Already subscribed.': 128 | return array( 129 | 'status' => true, 130 | 'message' => 'Already subscribed.', 131 | ); 132 | break; 133 | 134 | default: 135 | return array( 136 | 'status' => false, 137 | 'message' => $result, 138 | ); 139 | break; 140 | } 141 | } 142 | 143 | /** 144 | * Unsubscribe 145 | * 146 | * @param string $email Email ID. 147 | * @return array 148 | * @since 1.0.0 149 | */ 150 | public function unsubscribe( $email ) { 151 | // Route. 152 | $route = 'unsubscribe'; 153 | 154 | // Send the unsubscribe. 155 | $result = strval( $this->query( $route, array( 'email' => $email ) ) ); 156 | 157 | // Handle results. 158 | switch ( $result ) { 159 | case '1': 160 | return array( 161 | 'status' => true, 162 | 'message' => 'Unsubscribed', 163 | ); 164 | break; 165 | 166 | default: 167 | return array( 168 | 'status' => false, 169 | 'message' => $result, 170 | ); 171 | break; 172 | } 173 | } 174 | 175 | /** 176 | * Delete Subsriber 177 | * 178 | * @param string $email 179 | * @return array 180 | * @since 1.0.0 181 | */ 182 | public function delete( $email ) { 183 | // Route. 184 | $route = 'api/subscribers/delete.php'; 185 | 186 | // Send the delete subscriber. 187 | $result = strval( $this->query( $route, array( 188 | 'email' => $email, 189 | 'api_key' => $this->api_key, 190 | 'list_id' => $this->list_id, 191 | ) ) ); 192 | 193 | // Handle the results. 194 | switch ( $result ) { 195 | case 'true': 196 | case '1': 197 | return array( 198 | 'status' => true, 199 | 'message' => $result, 200 | ); 201 | break; 202 | 203 | case 'No data passed': 204 | case 'API key not passed': 205 | case 'Invalid API key': 206 | case 'List ID not passed': 207 | case 'List does not exist': 208 | case 'Email address not passed': 209 | case 'Subscriber does not exist': 210 | default: 211 | return array( 212 | 'status' => false, 213 | 'message' => $result, 214 | ); 215 | break; 216 | } // End switch. 217 | } // End delete(). 218 | 219 | 220 | /** 221 | * Subscriber Status 222 | * 223 | * @param string $email Email ID. 224 | * @return array 225 | * @since 1.0.0 226 | */ 227 | public function substatus( $email ) { 228 | // Route. 229 | $route = 'api/subscribers/subscription-status.php'; 230 | 231 | // Send the request for status. 232 | $result = $this->query( $route, array( 233 | 'email' => $email, 234 | 'api_key' => $this->api_key, 235 | 'list_id' => $this->list_id, 236 | ) ); 237 | 238 | // Handle the results. 239 | switch ( $result ) { 240 | case 'Subscribed!': 241 | case 'Unsubscribed': 242 | case 'Unconfirmed': 243 | case 'Bounced': 244 | case 'Soft bounced': 245 | case 'Complained': 246 | return array( 247 | 'status' => true, 248 | 'message' => $result, 249 | ); 250 | break; 251 | 252 | default: 253 | return array( 254 | 'status' => false, 255 | 'message' => $result, 256 | ); 257 | break; 258 | } // End switch. 259 | } // End substatus(). 260 | 261 | /** 262 | * Subscriber Count. 263 | * 264 | * @param string $list List ID. 265 | * @return array 266 | * @since 1.0.0 267 | */ 268 | public function subcount( $list = '' ) { 269 | // Route. 270 | $route = 'api/subscribers/active-subscriber-count.php'; 271 | 272 | // If a list is passed in use it, otherwise use $this->list_id. 273 | if ( empty( $list ) ) { 274 | $list = $this->list_id; 275 | } 276 | 277 | // Handle exceptions. 278 | if ( empty( $list ) ) { 279 | throw new \Exception( "method [subcount] requires parameter [list] or [$this->list_id] to be set.", 1 ); 280 | } 281 | 282 | // Send request for subcount. 283 | $result = $this->query( $route, array( 284 | 'api_key' => $this->api_key, 285 | 'list_id' => $list, 286 | ) ); 287 | 288 | // Handle the results. 289 | if ( is_numeric( $result ) ) { 290 | return array( 291 | 'status' => true, 292 | 'message' => $result, 293 | ); 294 | } 295 | 296 | // Error. 297 | return array( 298 | 'status' => false, 299 | 'message' => $result, 300 | ); 301 | } // End subcount(). 302 | 303 | /** 304 | * Create Campaign 305 | * 306 | * @param array $values 307 | * @return array 308 | * @since 1.0.0 309 | */ 310 | public function campaign( array $values ) { 311 | // Route. 312 | $route = 'api/campaigns/create.php'; 313 | 314 | // Global options. 315 | $global_options = array( 316 | 'api_key' => $this->api_key 317 | ); 318 | 319 | // Merge the passed in values with the global options. 320 | $values = array_merge( $global_options, $values ); 321 | 322 | // Send request for campaign. 323 | $result = $this->query( $route, $values ); 324 | 325 | // Handle the results. 326 | switch ( $result ) { 327 | case 'Campaign created': 328 | case 'Campaign created and now sending': 329 | return array( 330 | 'status' => true, 331 | 'message' => $result, 332 | ); 333 | break; 334 | 335 | default: 336 | return array( 337 | 'status' => false, 338 | 'message' => $result, 339 | ); 340 | break; 341 | } 342 | } 343 | 344 | /** 345 | * Query 346 | * 347 | * Build and Send the query via CURL. 348 | * 349 | * @param string $route API Route. 350 | * @param array $values Parameters. 351 | * @return string 352 | * @since 1.0.0 353 | */ 354 | private function query( $route, array $values ) { 355 | // Baild if empty. 356 | if ( empty( $route ) ) { 357 | throw new \Exception( "Required config parameter [type] is not set or empty", 1 ); 358 | } 359 | 360 | // Baild if empty. 361 | if ( empty( $values ) ) { 362 | throw new \Exception( "Required config parameter [values] is not set or empty", 1 ); 363 | } 364 | 365 | // Global options for return. 366 | $return_options = array( 367 | 'list' => $this->list_id, 368 | 'boolean' => 'true', 369 | ); 370 | 371 | // Merge the passed in values with the options for return. 372 | $content = array_merge( $values, $return_options ); 373 | 374 | // Build a query using the $content. 375 | $postdata = http_build_query( $content ); 376 | 377 | // Let's CURL ;). 378 | $ch = curl_init( $this->installation_url . '/' . $route ); 379 | 380 | // Settings to disable SSL verification for testing ( leave commented for production use ) 381 | // curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, 0 ); 382 | // curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 ); 383 | 384 | curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Content-Type: application/x-www-form-urlencoded' ) ); 385 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); 386 | curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, 1 ); 387 | curl_setopt( $ch, CURLOPT_POST, 1 ); 388 | curl_setopt( $ch, CURLOPT_POSTFIELDS, $postdata ); 389 | $result = curl_exec( $ch ); 390 | curl_close( $ch ); 391 | 392 | // API Result. 393 | return $result; 394 | } 395 | } // End class. 396 | } // End if(). 397 | -------------------------------------------------------------------------------- /src/fonts/emailbuilder-icon-font.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/src/fonts/emailbuilder-icon-font.eot -------------------------------------------------------------------------------- /src/fonts/emailbuilder-icon-font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/src/fonts/emailbuilder-icon-font.ttf -------------------------------------------------------------------------------- /src/fonts/emailbuilder-icon-font.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/src/fonts/emailbuilder-icon-font.woff -------------------------------------------------------------------------------- /src/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/src/images/logo.png -------------------------------------------------------------------------------- /src/js/_content_editor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Global Event Emitter 4 | var emitter = window.top.jsEmailBuilderEmitter; 5 | 6 | // Content Editor 7 | var ContentEditor = { 8 | 9 | editorText: null, 10 | editorLink: null, 11 | editorImage: null, 12 | imageUploader: null, 13 | 14 | init: function () { 15 | 16 | $('[data-editable="link"]').off('click', this.linkEditor); 17 | $('[data-editable="image"]').off('click', this.imageEditor); 18 | 19 | this.textEditor(); 20 | $('[data-editable="link"]').on('click', this.linkEditor); 21 | $('[data-editable="image"]').on('click', this.imageEditor); 22 | }, 23 | 24 | // Text Editor 25 | textEditor: function () { 26 | this.editorText = new MediumEditor('[data-editable="text"]',{ 27 | toolbar: { 28 | buttons: ['bold', 'italic', 'underline', 'anchor'] 29 | } 30 | }); 31 | }, 32 | 33 | // Link Editor 34 | linkEditor: function (event) { 35 | var $target = $(event.target), // Current Link (target element) 36 | modalContainer = $('#linkeditor'), // Modal Container 37 | targetLinkText = $.trim($target.html()), // Currrent Link Text 38 | targetLinkURL = $target.attr('href'); // Current Link URL 39 | 40 | // Fill-in Modal Form with Link attributes 41 | $('[data-link="text"]', modalContainer).val(targetLinkText); 42 | $('[data-link="url"]', modalContainer).val(targetLinkURL); 43 | 44 | $.fancybox.open({ 45 | src : '#linkeditor', 46 | type : 'inline', 47 | opts : { 48 | afterClose: function(){ 49 | // Clear the modal form 50 | $('[data-link="text"]', modalContainer).val(''); 51 | $('[data-link="url"]', modalContainer).val(''); 52 | }, 53 | onComplete : function() { 54 | // Remove previous on-click event listeners 55 | $('.modal-btn-cancel', modalContainer).off('click'); 56 | $('.modal-btn-ok', modalContainer).off('click'); 57 | 58 | $('.modal-btn-cancel', modalContainer).on('click', function (event) { 59 | // Close Modal 60 | $.fancybox.close(); 61 | },); 62 | $('.modal-btn-ok', modalContainer).on('click', function(event){ 63 | // Get new link attributes 64 | var linkText = $('[data-link="text"]', modalContainer).val(); 65 | var linkURL = $('[data-link="url"]', modalContainer).val(); 66 | // Assign new link attributes to target element 67 | $target.html(linkText); 68 | $target.attr('href', linkURL); 69 | 70 | // Close Modal 71 | $.fancybox.close(); 72 | }); 73 | } 74 | } 75 | }); 76 | return false; 77 | }, 78 | 79 | imageEditor: function (event) { 80 | // Destroy the uploader 81 | if(ContentEditor.imageUploader){ 82 | ContentEditor.imageUploader.slim('destroy'); 83 | ContentEditor.imageUploader=null; 84 | } 85 | 86 | var $target = $(event.target), // Current Image (target element) 87 | modalContainer = $('#imageeditor'), // Modal Container 88 | targetImageSrc = $target.attr('src'), // Currrent Image Src 89 | targetImageAlt = $target.attr('alt'), // Currrent Image Alt 90 | targetImageWidth = $target.width(), // Currrent Image Width 91 | targetImageHeight = $target.height(), // Currrent Image Height 92 | targetLinkURL = ''; // Current Image Link 93 | 94 | if ($target.parent().is( 'a' )) { 95 | targetLinkURL = $target.parent('a').attr('href'); 96 | } 97 | 98 | // Fill-in Modal Form with Image attributes 99 | $('[data-image="url"]', modalContainer).val(targetLinkURL); 100 | $('[data-image="alt"]', modalContainer).val(targetImageAlt); 101 | $('[data-image="width"]', modalContainer).val(targetImageWidth); 102 | $('[data-image="height"]', modalContainer).val(targetImageHeight); 103 | $('[data-image="src"]', modalContainer).attr('src', targetImageSrc); 104 | 105 | $.fancybox.open({ 106 | src : '#imageeditor', 107 | type : 'inline', 108 | opts : { 109 | afterClose: function(){ 110 | // Clear the modal form 111 | $('[data-image="url"]', modalContainer).val(''); 112 | $('[data-image="alt"]', modalContainer).val(''); 113 | $('[data-image="width"]', modalContainer).val(''); 114 | $('[data-image="height"]', modalContainer).val(''); 115 | $('[data-image="src"]', modalContainer).attr('src', ''); 116 | }, 117 | onComplete : function() { 118 | // Remove previous on-click event listeners 119 | $('.modal-btn-cancel', modalContainer).off('click'); 120 | $('.modal-btn-ok', modalContainer).off('click'); 121 | 122 | // Init image uploader 123 | ContentEditor.imageUploader = $('#modal-image-uploader').slim({ 124 | fetcher: 'server/fetch.php', 125 | service: 'server/async.php', 126 | push: true, 127 | instantEdit: true, 128 | didUpload: function(){ 129 | var $data = ContentEditor.imageUploader.slim('data')[0], 130 | imageSrc = config.uploads + '/' + $data.server.file, 131 | imageWidth = $data.output.width, 132 | imageHeight = $data.output.height; 133 | 134 | $('[data-image="width"]', modalContainer).val(imageWidth); 135 | $('[data-image="height"]', modalContainer).val(imageHeight); 136 | $('[data-image="src"]', modalContainer).attr('src', imageSrc); 137 | } 138 | }); 139 | 140 | 141 | $('.modal-btn-cancel', modalContainer).on('click', function (event) { 142 | // Close Modal 143 | $.fancybox.close(); 144 | }); 145 | $('.modal-btn-ok', modalContainer).on('click', function(event){ 146 | // Get new link attributes 147 | var imageURL = $('[data-image="url"]', modalContainer).val(), 148 | imageAlt = $('[data-image="alt"]', modalContainer).val(), 149 | imageWidth = $('[data-image="width"]', modalContainer).val(), 150 | imageHeight = $('[data-image="height"]', modalContainer).val(), 151 | imageSrc = $('[data-image="src"]', modalContainer).attr('src'); 152 | 153 | // Assign new link attributes to target element 154 | $target.attr('src', imageSrc); // Image Src 155 | $target.attr('alt', imageAlt); // Image Alt 156 | $target.attr('width', imageWidth); // Image Width 157 | $target.attr('height', imageHeight); // Image Height 158 | 159 | $target.unwrap('a'); // Unwrap image 160 | if(imageURL){ //Wrap image with link if URL is present 161 | $target.wrap(''); 162 | } 163 | 164 | // Close Modal 165 | $.fancybox.close(); 166 | }); 167 | } 168 | } 169 | }); 170 | return false; 171 | } 172 | 173 | 174 | }; 175 | 176 | emitter.on('update_template', function () { 177 | // Init the content editor 178 | ContentEditor.init(); 179 | }); 180 | 181 | // Export ContentEditor object 182 | module.exports = ContentEditor; 183 | 184 | 185 | -------------------------------------------------------------------------------- /src/js/_emitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Custom events emitter 4 | // Used to hook different events and actions inside of Email Builder 5 | function Emitter() { 6 | this.events = {}; 7 | } 8 | 9 | Emitter.prototype.on = function (type, listener) { 10 | this.events[type] = this.events[type] || []; 11 | this.events[type].push(listener); 12 | 13 | } 14 | 15 | Emitter.prototype.emit = function (type, atts = null) { 16 | if (this.events[type]) { 17 | this.events[type].forEach(function (listener) { 18 | listener(atts); 19 | }); 20 | } 21 | } 22 | 23 | 24 | // Export ContentEditor object 25 | module.exports = new Emitter(); -------------------------------------------------------------------------------- /src/js/_module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Global Event Emitter 4 | var emitter = window.top.jsEmailBuilderEmitter; 5 | 6 | // Module 7 | var Module = { 8 | 9 | confirm: function (title, callback) { 10 | swal({ 11 | title: title, 12 | type: "warning", 13 | showCancelButton: true, 14 | confirmButtonColor: "#DD6B55", 15 | confirmButtonText: "Yes", 16 | closeOnConfirm: false 17 | }, 18 | function (isConfirm) { 19 | if (isConfirm) { 20 | swal("Deleted!", "The module has been deleted", "success"); 21 | callback(); 22 | } 23 | }); 24 | }, 25 | current: null, // current selected module 26 | imageUploader: null, 27 | controls: '
' + 28 | '
' + 29 | '
' + 30 | '
' + 31 | '
' + 32 | '
', 33 | codeControls: '
' + 34 | '
' + 35 | '
' + 36 | '
', 37 | options: { 38 | 'color': [], 39 | 'bgcolor': [], 40 | 'bg': [] 41 | }, 42 | init: function () { 43 | var self = this; 44 | $('.module-container', '[data-type="editor"]').off('mouseenter mouseleave'); 45 | 46 | $('.module-container', '[data-type="editor"]').on('mouseenter', function (event) { 47 | $(this).children('table').append(self.controls); 48 | self.eventsInit(); 49 | $('[data-type="editor"]').sortable("refresh"); 50 | }); 51 | 52 | $('.module-container', '[data-type="editor"]').on('mouseleave', function (event) { 53 | $('.module-controls-container', this).remove(); 54 | }); 55 | 56 | $('.module-container').on('click', function (event) { 57 | 58 | var currentModule = this; 59 | if (self.current !== currentModule) { 60 | self.current = currentModule; 61 | emitter.emit('module_change'); 62 | } 63 | }); 64 | }, 65 | rgb2hex: function (rgb) { // transform RGB to HEX 66 | if (/^#[0-9A-F]{6}$/i.test(rgb)) return rgb; 67 | 68 | rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/); 69 | function hex(x) { 70 | return ("0" + parseInt(x).toString(16)).slice(-2); 71 | } 72 | 73 | return "#" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); 74 | }, 75 | eventsInit: function () { 76 | this.controlsDuplicate(); 77 | this.controlsCode(); 78 | this.controlsDelete(); 79 | }, 80 | 81 | controlsDuplicate: function () { 82 | var self = this; 83 | $('.btn-module-duplicate', '.module-controls-container').off('click'); 84 | $('.btn-module-duplicate', '.module-controls-container').on('click', function (event) { 85 | var module = $(this).closest('.module-container'); 86 | module.clone().insertAfter(module).find('.module-controls-container').remove(); 87 | emitter.emit('update_template'); 88 | }); 89 | }, 90 | 91 | controlsCode: function () { 92 | 93 | var self = this; 94 | 95 | $('.btn-module-code', '.module-controls-container').on('click', function (event) { 96 | 97 | var module = $(this).closest('.module-container'); 98 | 99 | var tmpModule = $(module).clone(); 100 | 101 | // FIX TinyMCE style changes 102 | $('.mce-content-body', tmpModule).each(function () { 103 | var cleanStyle = $(this).attr('style').replace(/"/g, "'"); 104 | $(this).attr('style', cleanStyle); 105 | }); 106 | 107 | tmpModule.find('.module-controls-container').remove(); 108 | tmpModule.find('.mce-content-body').removeClass('mce-content-body'); 109 | 110 | 111 | var moduleTmpHTML = $(tmpModule).html(); 112 | 113 | moduleTmpHTML = moduleTmpHTML.replace(//g, '').replace(/<\/tbody>/g, ''); 114 | 115 | moduleTmpHTML = moduleTmpHTML.replace(/(v:)+([\w])|(xmlns)+(:v)/gi, function vml_pre_replace(x) { 116 | return x.replace(/:/g, "_"); 117 | }); 118 | 119 | moduleTmpHTML = $.htmlClean(moduleTmpHTML, { 120 | format: true, 121 | allowEmpty: [["td"], ["link"], ["meta"], ["table"], ["tr"], ["p"], ["b"], ["i"], ["font"], ["div"], ["a"], ["li"], ["ul"]], 122 | formatIndent: 1, 123 | allowComments: true, 124 | allowedAttributes: [["id"], ["map"], ["area"], ["class"], ["border"], ["style"], ["width"], ["height"], ["bgcolor"], ["shape"], ["coords"], ["cellspacing"], ["cellpadding"], ["mso-table-lspace"], ["mso-table-rspace"], ["align"], ["bgcolor"], ["valign"], ["name"], ["data-module"], ["data-color"], ["data-size"], ["data-editable"], ["contenteditable"], ["href"], ["data-min"], ["data-max"], ["data-crop"], ["data-link-style"], ["data-link-size"], ["data-link-color"], ["data-bg"], ["data-bgcolor"], ["data-border-top-color"], ["data-border-bottom-color"], ["data-border-left-color"], ["data-border-right-color"], ["background"], ["itemscope"], ["itemtype"], ["itemprop"], ["datetime"], ["fill", ["v_rect"]], ["stroke", ["v_rect"]], ["type", ["v_fill"]], ["src", ["v_fill"]], ["color", ["v_fill"]], ["inset", ["v_textbox"]], ["xmlns_v", ["v_rect"]]] 125 | }); 126 | 127 | moduleTmpHTML = moduleTmpHTML.replace(/(v_)+([\w])|(xmlns)+(_v)/gi, function vml_aft_replace(x) { 128 | return x.replace(/_/g, ":"); 129 | }); 130 | 131 | $('#code').remove(); 132 | $(module).after(self.getCodeEditorHTML(moduleTmpHTML)); 133 | 134 | var editor = CodeMirror.fromTextArea(document.getElementById('code'), { 135 | mode: "xml", 136 | htmlMode: true, 137 | lineNumbers: true, 138 | lineWrapping: true, 139 | styleActiveLine: true 140 | }); 141 | 142 | editor.setSize('100%', '100%'); 143 | editor.setOption('theme', 'tomorrow-night-bright'); 144 | 145 | $('.btn-code-update').on('click', function (event) { 146 | var moduleNewHTML = editor.getValue(); 147 | 148 | module.html(moduleNewHTML); 149 | $('.module-code-container', module.parent()).remove(); 150 | emitter.emit('update_template'); 151 | 152 | }); 153 | $('.btn-code-cancel').on('click', function (event) { 154 | $('.module-code-container', module.parent()).remove(); 155 | }); 156 | }); 157 | }, 158 | 159 | controlsDelete: function () { 160 | var self = this; 161 | $('.btn-module-delete', '.module-controls-container').off('click'); 162 | $('.btn-module-delete', '.module-controls-container').on('click', function (event) { 163 | var module = $(this).closest('.module-container'); 164 | self.confirm('Remove this module?', function () { 165 | module.remove(); 166 | emitter.emit('update_template'); 167 | }); 168 | 169 | }); 170 | }, 171 | 172 | optionActions: function () { 173 | var self = this; 174 | // Re-init option fields 175 | // Colorpicker 176 | $('[data-control="colorpicker"]').minicolors({position: 'bottom right'}); 177 | 178 | // Colorpicker live update 179 | $('[data-control="colorpicker"]').on('change', function (event) { 180 | var targetOption = $(this).data('target-option'); 181 | var targetAttr = $(this).data('target-attr'); 182 | var currentVal = $(this).val(); 183 | $('[data-' + targetAttr + '="' + targetOption + '"]', self.current).css(targetAttr, currentVal); 184 | $('[data-' + targetAttr + '="' + targetOption + '"]', self.current).attr(targetAttr, currentVal); 185 | emitter.emit('update_template'); 186 | }); 187 | 188 | // Background image live update 189 | $('[data-control="background-image"]').on('click', function (event) { 190 | 191 | // Destroy the uploader 192 | if(Module.imageUploader){ 193 | Module.imageUploader.slim('destroy'); 194 | Module.imageUploader=null; 195 | } 196 | 197 | var $target = $(event.target), // clicked button 198 | modalContainer = $('#bgeditor'), // Modal Container 199 | targetImageSrc = $(this).data('bg-image'), // Currrent Image Src 200 | option_id = $(this).data('target-option'); // Option Id to update 201 | 202 | // Fill-in Modal Form with Image attributes 203 | $('[data-image="src"]', modalContainer).attr('src', targetImageSrc); 204 | 205 | $.fancybox.open({ 206 | src : '#bgeditor', 207 | type : 'inline', 208 | opts : { 209 | afterClose: function(){ 210 | // Clear the modal form 211 | $('[data-image="src"]', modalContainer).attr('src', ''); 212 | }, 213 | onComplete : function() { 214 | // Remove previous on-click event listeners 215 | $('.modal-btn-cancel', modalContainer).off('click'); 216 | $('.modal-btn-ok', modalContainer).off('click'); 217 | 218 | // Init image uploader 219 | Module.imageUploader = $('#modal-bg-uploader').slim({ 220 | fetcher: 'server/fetch.php', 221 | service: 'server/async.php', 222 | push: true, 223 | instantEdit: true, 224 | didUpload: function(){ 225 | var $data = Module.imageUploader.slim('data')[0], 226 | imageSrc = config.uploads + '/' + $data.server.file; 227 | 228 | $('[data-image="src"]', modalContainer).attr('src', imageSrc); 229 | } 230 | }); 231 | 232 | $('.modal-btn-cancel', modalContainer).on('click', function (event) { 233 | // Close Modal 234 | $.fancybox.close(); 235 | }); 236 | $('.modal-btn-ok', modalContainer).on('click', function(event){ 237 | // Get new link attributes 238 | var imageSrc = $('[data-image="src"]', modalContainer).attr('src'); 239 | 240 | // Update background label 241 | $('[data-bg-option-label="' + option_id + '"]', $target.parent()).html(imageSrc); 242 | // Update module background 243 | $('[data-bg="' + option_id + '"]', Module.current).css('background-image', "url('" + imageSrc + "')"); 244 | 245 | // Close Modal 246 | $.fancybox.close(); 247 | }); 248 | } 249 | } 250 | }); 251 | 252 | emitter.emit('update_template'); 253 | }); 254 | // Background appearance live update 255 | $('[data-control="background-appearance"]').on('change', function (event) { 256 | var targetOption = $(this).data('target-option'); 257 | var currentVal = $(this).val(); 258 | // Reset background appearance 259 | $('[data-bg="' + targetOption + '"]', self.current).css('background-position', ''); 260 | $('[data-bg="' + targetOption + '"]', self.current).css('background-size', ''); 261 | $('[data-bg="' + targetOption + '"]', self.current).css('background-attachment', ''); 262 | switch (currentVal) { 263 | case 'original': 264 | $('[data-bg="' + targetOption + '"]', self.current).css('background-position', 'center center'); 265 | break; 266 | case '100%': 267 | $('[data-bg="' + targetOption + '"]', self.current).css('background-size', '100%'); 268 | break; 269 | case 'fluid': 270 | $('[data-bg="' + targetOption + '"]', self.current).css('background-position', 'center center'); 271 | $('[data-bg="' + targetOption + '"]', self.current).css('background-size', 'cover'); 272 | break; 273 | case 'fixed': 274 | $('[data-bg="' + targetOption + '"]', self.current).css('background-position', 'center center'); 275 | $('[data-bg="' + targetOption + '"]', self.current).css('background-size', 'cover'); 276 | $('[data-bg="' + targetOption + '"]', self.current).css('background-attachment', 'fixed'); 277 | break; 278 | } 279 | 280 | }); 281 | }, 282 | 283 | getCodeEditorHTML: function (moduleHTML = '') { 284 | var codeEditorHTML = '
' + this.codeControls + '
'; 285 | return codeEditorHTML; 286 | }, 287 | 288 | getOptions: function () { 289 | var self = this; 290 | 291 | // Reset module options 292 | self.options = { 293 | 'color': [], 294 | 'bgcolor': [], 295 | 'bg': [] 296 | }; 297 | 298 | var colorOptions = [], 299 | bgcolorOptions = [], 300 | bgOptions = []; 301 | 302 | // Looking for 'color' option in selected module 303 | $('[data-color]', self.current).each(function () { 304 | var optionName = $(this).data('color'); 305 | colorOptions.push(optionName); 306 | }); 307 | // Looking for 'bgcolor' option in selected module 308 | $('[data-bgcolor]', self.current).each(function () { 309 | var optionName = $(this).data('bgcolor'); 310 | bgcolorOptions.push(optionName); 311 | }); 312 | // Looking for 'bg' option in selected module 313 | $('[data-bg]', self.current).each(function () { 314 | var optionName = $(this).data('bg'); 315 | bgOptions.push(optionName); 316 | }); 317 | // Remove repeatable options 318 | self.options['color'] = colorOptions.filter((v, i, a) => a.indexOf(v) === i); 319 | self.options['bgcolor'] = bgcolorOptions.filter((v, i, a) => a.indexOf(v) === i); 320 | self.options['bg'] = bgOptions.filter((v, i, a) => a.indexOf(v) === i); 321 | 322 | }, 323 | 324 | getOptionsHTML: function(){ 325 | var self = this; 326 | 327 | var optionsHTML = ''; 328 | // Render 'color' options 329 | if (self.options['color'].length > 0) { 330 | optionsHTML += '
'; 331 | optionsHTML += '
Font colors
'; 332 | optionsHTML += '
'; 333 | self.options['color'].forEach(function (option) { 334 | // get option value 335 | var color = $('[data-color="' + option + '"]', self.current).css('color'); 336 | var val = self.rgb2hex(color); 337 | optionsHTML += '
'; 338 | optionsHTML += '
' + option + '
'; 339 | optionsHTML += '
'; 340 | optionsHTML += '
'; 341 | }); 342 | optionsHTML += '
'; 343 | optionsHTML += '
'; 344 | } 345 | 346 | // Render 'bgcolor' options 347 | if (self.options['bgcolor'].length > 0) { 348 | optionsHTML += '
'; 349 | optionsHTML += '
Background colors
'; 350 | optionsHTML += '
'; 351 | self.options['bgcolor'].forEach(function (option) { 352 | // get option value 353 | var color = $('[data-bgcolor="' + option + '"]', self.current).css('background-color'); 354 | var val = self.rgb2hex(color); 355 | optionsHTML += '
'; 356 | optionsHTML += '
' + option + '
'; 357 | optionsHTML += '
'; 358 | optionsHTML += '
'; 359 | }); 360 | optionsHTML += '
'; 361 | optionsHTML += '
'; 362 | } 363 | 364 | // Render 'bg' options 365 | if (self.options['bg'].length > 0) { 366 | optionsHTML += '
'; 367 | optionsHTML += '
Background images
'; 368 | optionsHTML += '
'; 369 | self.options['bg'].forEach(function (option) { 370 | var bgImage = $('[data-bg="' + option + '"]', self.current).css('background-image').replace(/^url\(["']?/, '').replace(/["']?\)$/, ''); 371 | optionsHTML += '
'; 372 | optionsHTML += '
' + option + '
'; 373 | optionsHTML += '
' + bgImage + '
'; 374 | optionsHTML += '
'; 375 | optionsHTML += '
'; 376 | optionsHTML += '
Position
'; 377 | optionsHTML += '
'; 378 | optionsHTML += ' '; 384 | optionsHTML += '
'; 385 | optionsHTML += '
'; 386 | 387 | }); 388 | optionsHTML += '
'; 389 | optionsHTML += '
'; 390 | } 391 | 392 | return optionsHTML; 393 | } 394 | 395 | }; 396 | 397 | emitter.on('module_added', function () { 398 | // Update template event 399 | emitter.emit('update_template'); 400 | }); 401 | 402 | emitter.on('module_change', function () { 403 | // Hide the content of the sidebar 404 | $('[data-type="sidebar-second"]').find('[data-sidebar]').hide(); 405 | Module.getOptions(); 406 | var moduleOptionsHTML = Module.getOptionsHTML(); 407 | $('[data-type="module-options"]').html(moduleOptionsHTML); 408 | Module.optionActions(); 409 | 410 | }); 411 | 412 | emitter.on('update_template', function () { 413 | // Init the Module controls 414 | Module.init(); 415 | }); 416 | 417 | 418 | 419 | 420 | 421 | // Export Module object 422 | module.exports = Module; 423 | 424 | 425 | -------------------------------------------------------------------------------- /src/js/_nav.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Global Event Emitter 4 | var emitter = window.top.jsEmailBuilderEmitter; 5 | 6 | // Navigation & Sidebar 7 | var Nav = { 8 | 9 | // Init 10 | init: function(){ 11 | emitter.emit('show_nav'); 12 | this.bindAccordion(); 13 | this.bindSidebar(); 14 | }, 15 | 16 | // Show Modules on Load 17 | showModulesSidebar: function () { 18 | // Show modules sidebar 19 | var target = $('[data-type="nav"] [data-target="modules"]').get(0); // Get DOM element 20 | this.toggleSidebar(target); 21 | 22 | // Make modules menu item active 23 | var modulesNav = $('[data-type="nav"] [data-nav="has-child"]').find('[data-target="modules"]'); 24 | modulesNav.closest('[data-type="subnav"]').slideDown(200, function () { 25 | modulesNav.addClass('active'); 26 | }); 27 | }, 28 | 29 | // Accordion navigation 30 | bindAccordion: function () { 31 | $('[data-nav="has-child"]').on('click', function (event) { 32 | var target = event.currentTarget; 33 | 34 | // Exit if there is no child items 35 | if (!$(target).data('nav') == 'has-child') { 36 | return false; 37 | } 38 | 39 | // Exit if item is already active 40 | if ($(target).hasClass('active')) { 41 | return false; 42 | } 43 | 44 | // Hide previous items 45 | $('[data-type="nav"]').find('[data-type="subnav"]').slideUp(200, function () { 46 | $('[data-nav="has-child"]').removeClass('active'); 47 | }); 48 | 49 | // Show selected item 50 | $(target).find('[data-type="subnav"]').slideDown(200, function () { 51 | $(target).addClass('active'); 52 | }); 53 | }); 54 | }, 55 | 56 | // Second sidebar actions 57 | bindSidebar: function () { 58 | var self = this; 59 | $('[data-type="nav"] [data-target]').on('click', function (event) { 60 | var 61 | // Reference to event listener target 62 | target = event.currentTarget; 63 | self.toggleSidebar(target); 64 | }); 65 | }, 66 | 67 | toggleSidebar: function(target){ 68 | 69 | var relatedModule = $(target).data('target'); 70 | 71 | $('[data-type="sidebar-second"]').effect('slide', { direction: 'left', mode: 'hide' }, 500, function () { 72 | $('[data-type="nav"] [data-target]').removeClass('active'); 73 | $(target).addClass('active'); 74 | // Hide the content of the sidebar 75 | $('[data-type="sidebar-second"]').find('[data-sidebar]').hide(); 76 | }).promise().done(function(){ 77 | // Show the content of the sidebar 78 | $('[data-type="sidebar-second"]').find('[data-sidebar="' + relatedModule + '"]').show(); 79 | // Show the sidebar itself 80 | $('[data-type="sidebar-second"]').effect('slide', { direction: 'left', mode: 'show' }, 500); 81 | }); 82 | } 83 | 84 | }; 85 | 86 | emitter.on('init', function () { 87 | Nav.init(); 88 | // Show Modules Sidebar 89 | Nav.showModulesSidebar(); 90 | }); 91 | 92 | emitter.on('module_change', function () { 93 | // Show the sidebar 94 | var target = $('[data-type="nav"] [data-target="styles"]').get(0); // Get DOM element 95 | Nav.toggleSidebar(target); 96 | 97 | }); 98 | 99 | // Export Nav object 100 | module.exports = Nav; 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/js/_theme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Global Event Emitter 3 | var emitter = window.top.jsEmailBuilderEmitter; 4 | 5 | // Current theme 6 | var Theme = { 7 | 8 | // Private attributes 9 | // Shouldn't be called directly! 10 | name: '', // Theme name (slug) 11 | path: '', // Theme path 12 | template: {}, // Theme template 13 | modulesHTML: {}, // Theme modules module_uid => module_html, used in getModuleHTML(uid) method 14 | // Private methods 15 | renderModulePreview: function (uid, title, thumb) { 16 | return '
' + title + '
'; 17 | }, 18 | // Public methods 19 | load: function (themeName) { // Load the theme 20 | var self = this; 21 | self.name = themeName; 22 | self.path = 'templates/' + self.name; 23 | return $.getJSON(self.getPath() + '/template.json', function (data) { 24 | self.template = data; 25 | var fonts = self.getFonts(); 26 | $("head").append(fonts); 27 | $.each(self.template.modules, function (index, module) { 28 | self.modulesHTML[module.uid] = module.html; 29 | }); 30 | }); 31 | }, 32 | getPath: function () { // Return theme path / string 33 | return this.path; 34 | }, 35 | getName: function () { // Return theme name (slug) / string 36 | return this.name; 37 | }, 38 | getTitle: function () { // Return theme title / string 39 | return this.template.template; 40 | }, 41 | getTemplate: function () { // Return theme template / object 42 | return this.template; 43 | }, 44 | getModules: function () { // Return the list of theme modules / object 45 | return this.template.modules; 46 | }, 47 | getModuleHTML: function (uid) { // Return the module HTML 48 | return this.modulesHTML[uid]; 49 | }, 50 | getModulesPreview: function () { // Return ready-to-display list of theme modules / string 51 | var self = this; 52 | var path = self.getPath(); 53 | var modules = self.getModules(); 54 | var modulesPreview = ''; 55 | $.each(modules, function (index, module) { 56 | var moduleThumb = path + '/' + module.thumb; 57 | modulesPreview += self.renderModulePreview(module.uid, module.title, moduleThumb); 58 | }); 59 | emitter.emit('modules_preview'); 60 | return modulesPreview; 61 | }, 62 | getHeader: function () { // Return theme header / string 63 | return this.template.header; 64 | }, 65 | getFooter: function () { // Return theme footer / string 66 | return this.template.footer; 67 | }, 68 | getFonts: function () { 69 | var fontsHTML = ''; 70 | if('fonts' in this.template) { 71 | var fonts = this.template.fonts; 72 | $.each(fonts, function (index, font) { 73 | fontsHTML += ""; 74 | }); 75 | } 76 | return fontsHTML; 77 | }, 78 | actionModuleDrag: function () { 79 | $('[data-type="modules-container"] .module-item').draggable({ 80 | appendTo: "body", 81 | helper: "clone", 82 | connectToSortable: '[data-type="editor"]', 83 | scroll: false, 84 | zIndex: 1000, 85 | delay: 100, 86 | revert: 'invalid', 87 | 88 | }); 89 | }, 90 | 91 | actionModuleAdd: function () { 92 | var self = this; 93 | 94 | $('[data-type="editor"]').sortable({ 95 | handle: '.btn-module-sort', 96 | items: '.module-container', 97 | placeholder: 'module-placeholder', 98 | axis: 'y', 99 | opacity: 0.9, 100 | scroll: false, 101 | zIndex: 1000, 102 | refreshPositions: true, 103 | beforeStop: function (event, ui) { 104 | // Editor.moduleControlsEvent(); 105 | var uid = ui.item.data('module'); 106 | var moduleHTML = $('
').html(self.getModuleHTML(uid)); 107 | $('img', moduleHTML).each(function () { 108 | var host = config.host; 109 | var theme = self.name; 110 | var image = $(this).attr('src'); 111 | var imagePath = host + '/' + 'templates' + '/' + theme + '/' + image; 112 | $(this).attr('src', imagePath); 113 | }); 114 | $(ui.item).replaceWith('
' + moduleHTML.html() + '
'); 115 | 116 | 117 | }, 118 | receive: function (event, ui) { 119 | 120 | // Editor.moduleControlsEvent(); 121 | 122 | }, 123 | activate: function (event, ui) { 124 | // Show only the thumbnail when dragging module 125 | $(ui.helper).html($('.module-thumb', ui.item).html()); 126 | }, 127 | start: function (event, ui) { 128 | }, 129 | update: function (event, ui) { 130 | emitter.emit('module_added'); 131 | }, 132 | }); 133 | } 134 | }; 135 | 136 | emitter.on('theme_load', function (theme) { 137 | $.when(Theme.load(theme)).done(function (data) { 138 | emitter.emit('theme_loaded'); 139 | 140 | }); 141 | }); 142 | 143 | emitter.on('theme_loaded', function () { 144 | var modulesPreview = Theme.getModulesPreview(); 145 | // Render sidebar 146 | $('[data-type="modules-container"]').html(modulesPreview); 147 | 148 | Theme.actionModuleDrag(); 149 | Theme.actionModuleAdd(); 150 | }); 151 | 152 | emitter.on('update_template', function () { 153 | // Update template HTML 154 | var templateHeader = Theme.getHeader(); 155 | var templateFooter = Theme.getFooter(); 156 | var curTemplateHTML = $('[data-type="editor"]').html(); 157 | var templateHTML = templateHeader + curTemplateHTML + templateFooter; 158 | $('#templateHTML').val(btoa(templateHTML)); 159 | }); 160 | 161 | 162 | 163 | // Export Theme object 164 | module.exports = Theme; 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/js/_utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Global Event Emitter 4 | var emitter = window.top.jsEmailBuilderEmitter; 5 | 6 | // Utils, e.g.: export and send 7 | var Utils = { 8 | showPreview(w = 375, h = 559){ 9 | var previewWindow = window.open(null, 'Preview HTML', 'width=' + w + ',height=' + h + ',resizeable,scrollbars'); 10 | var html = $('#templateHTML').val(); 11 | previewWindow.document.write(atob(html)); 12 | previewWindow.document.close(); // needed for chrome and safari 13 | }, 14 | templatePreviewMobile: function () { 15 | this.showPreview(375, 559); 16 | }, 17 | 18 | templatePreviewTablet: function () { 19 | this.showPreview(768, 600); 20 | }, 21 | // Clear template 22 | templateClear: function () { 23 | swal({ 24 | title: 'Clear the template?', 25 | type: "warning", 26 | showCancelButton: true, 27 | confirmButtonColor: "#DD6B55", 28 | confirmButtonText: "Yes", 29 | closeOnConfirm: false 30 | }, 31 | function (isConfirm) { 32 | if (isConfirm) { 33 | swal("All done!", "The template has been cleared", "success"); 34 | $('[data-type="editor"]').html(''); 35 | } 36 | }); 37 | }, 38 | }; 39 | 40 | 41 | emitter.on('init', function () { 42 | // Mobile Preview 43 | $('.template-preview-mobile').on('click', function (event) { 44 | Utils.templatePreviewMobile(); 45 | }); 46 | // Tablet Preview 47 | $('.template-preview-tablet').on('click', function (event) { 48 | Utils.templatePreviewTablet(); 49 | }); 50 | // Clear the template 51 | $('.template-clear').on('click', function (event) { 52 | Utils.templateClear(); 53 | }); 54 | 55 | 56 | }); 57 | 58 | // Export Utils object 59 | module.exports = Utils; -------------------------------------------------------------------------------- /src/js/custom.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var emitter = window.top.jsEmailBuilderEmitter; 4 | 5 | function dialogExportHTML() { 6 | swal({ 7 | title: "Export to HTML", 8 | text: "Export template to single HTML file", 9 | type: "info", 10 | showCancelButton: true, 11 | showLoaderOnConfirm: true, 12 | }, 13 | function () { 14 | setTimeout(function () { 15 | swal("Template has been exported successfully"); 16 | $('#export-form [name="type"]').val('html'); 17 | $('#export-form').submit(); 18 | }, 1000); 19 | }); 20 | } 21 | 22 | function dialogExportZip() { 23 | swal({ 24 | title: "Export to ZIP", 25 | text: "Export template to zip-archive", 26 | type: "info", 27 | showCancelButton: true, 28 | showLoaderOnConfirm: true, 29 | }, 30 | function () { 31 | setTimeout(function () { 32 | swal("Template has been exported successfully"); 33 | $('#export-form [name="type"]').val('zip'); 34 | $('#export-form').submit(); 35 | }, 1000); 36 | }); 37 | 38 | } 39 | 40 | function dialogCampaign(){ 41 | var modalContainer = $('#campaignmodal'); 42 | $.fancybox.open({ 43 | src : '#campaignmodal', 44 | type : 'inline', 45 | opts : { 46 | onComplete : function() { 47 | // Remove previous on-click event listeners 48 | $('.modal-btn-cancel', modalContainer).off('click'); 49 | $('.modal-btn-ok', modalContainer).off('click'); 50 | 51 | $('.modal-btn-cancel', modalContainer).on('click', function (event) { 52 | // Close Modal 53 | $.fancybox.close(); 54 | }); 55 | $('.modal-btn-ok', modalContainer).on('click', function(event){ 56 | var modalTitle = "Save" + ( $('input[name=send_campaign]').is( ":checked" ) ? ' and send' : '') + ' campaign?'; 57 | swal({ 58 | title: modalTitle, 59 | type: "info", 60 | showCancelButton: true, 61 | closeOnConfirm: false, 62 | showLoaderOnConfirm: true 63 | }, 64 | function(){ 65 | var list_ids = []; 66 | $(".list_ids:checked").each(function() { 67 | list_ids.push(this.value); 68 | }); 69 | $.ajax({ 70 | url : '_ajax.php', 71 | type : 'POST', 72 | dataType: 'json', 73 | data : { 74 | 'process_campaign' : 1, 75 | 'from_name' : $('input[name=from_name]').val(), 76 | 'from_email' : $('input[name=from_email]').val(), 77 | 'reply_to' : $('input[name=reply_to]').val(), 78 | 'subject' : $('input[name=subject]').val(), 79 | 'plain_text' : $('input[name=plain_text]').val(), 80 | 'html_text' : $('#templateHTML').val(), 81 | 'brand_id' : $('#brand_id').val(), 82 | 'send_campaign' : $('input[name=send_campaign]').is( ":checked" ) ? 1 : 0, 83 | 'list_ids' : list_ids, 84 | 'query_string' : $('input[name=query_string]').val() 85 | }, 86 | success : function( data ) { 87 | 88 | if ( data['type'] == 'success' ) { 89 | var result = data['message']; 90 | if(result.status === true) { 91 | swal("Success", result.message, "success"); 92 | // Reset campaign form 93 | $('input[name=from_name]').val(''); 94 | $('input[name=from_email]').val(''); 95 | $('input[name=reply_to]').val(''); 96 | $('input[name=subject]').val(''); 97 | $('input[name=plain_text]').val(''); 98 | $('input[name=send_campaign]').prop('checked', false); 99 | $('input[name=query_string]').val(''); 100 | // Close Modal 101 | $.fancybox.close(); 102 | }else{ 103 | swal("Error", result.message, "error"); 104 | } 105 | 106 | 107 | 108 | } 109 | }, 110 | error : function( xhr, err ) { 111 | // Log errors if AJAX call is failed 112 | console.log(xhr); 113 | console.log(err); 114 | } 115 | }); 116 | }); 117 | 118 | 119 | 120 | }); 121 | } 122 | } 123 | }); 124 | 125 | return false; 126 | } 127 | 128 | function dialogSendyHelpers(){ 129 | $.fancybox.open({ 130 | src : '#sendyhelpers', 131 | type : 'inline' 132 | }); 133 | 134 | return false; 135 | } 136 | 137 | emitter.on('init', function () { 138 | $('[data-action="export-html"]').on('click', function (event) { 139 | dialogExportHTML(); 140 | }); 141 | $('[data-action="export-zip"]').on('click', function (event) { 142 | dialogExportZip(); 143 | }); 144 | 145 | $('[data-action="campaign-settings"]').on('click', function (event) { 146 | dialogCampaign(); 147 | }); 148 | 149 | $('[data-action="sendy-helpers"]').on('click', function (event) { 150 | dialogSendyHelpers(); 151 | }); 152 | 153 | $('[data-action="expand-account"]').on('click', function (event) { 154 | var self = this; 155 | $(self).closest('.nav-item').find('.subnav').slideToggle(); 156 | }); 157 | 158 | // Events on Brand ID change 159 | $('#brand_id').on('change', function (event) { 160 | var brand_id = this.value; 161 | // Get Brand subscriber lists 162 | $.ajax({ 163 | url : '_ajax.php', 164 | type : 'POST', 165 | dataType: 'json', 166 | data : { 167 | 'get_lists' : 1, 168 | 'brand_id' : brand_id 169 | }, 170 | success : function( data ) { 171 | 172 | if ( data['type'] != 'success' ) { 173 | // show error message 174 | $('#subscribers-list-container').html(data['message']); 175 | }else{ 176 | // Update Lists 177 | var lists = data['message']; // an array 178 | var listsHTML = ''; 179 | 180 | if(lists instanceof Array ) { 181 | $.each(lists, function (index, list) { 182 | listsHTML += ''; 183 | }); 184 | }else{ 185 | listsHTML = lists; // no subscribers list 186 | } 187 | 188 | $('#subscribers-list-container').html(listsHTML); 189 | 190 | } 191 | 192 | }, 193 | error : function( xhr, err ) { 194 | // Log errors if AJAX call is failed 195 | console.log(xhr); 196 | console.log(err); 197 | } 198 | }); 199 | 200 | // Update From Name and From Email fields 201 | $.ajax({ 202 | url : '_ajax.php', 203 | type : 'POST', 204 | dataType: 'json', 205 | data : { 206 | 'get_brand_info' : 1, 207 | 'brand_id' : brand_id 208 | }, 209 | success : function( data ) { 210 | 211 | if ( data['type'] == 'success' ) { 212 | var brand_info = data['message']; // an array 213 | $('input[name=from_name]').val(brand_info.from_name); 214 | $('input[name=from_email]').val(brand_info.from_email); 215 | $('input[name=reply_to]').val(brand_info.reply_to); 216 | } 217 | }, 218 | error : function( xhr, err ) { 219 | // Log errors if AJAX call is failed 220 | console.log(xhr); 221 | console.log(err); 222 | } 223 | }); 224 | }); 225 | }); 226 | -------------------------------------------------------------------------------- /src/js/editor.js: -------------------------------------------------------------------------------- 1 | // Load jQuery & jQuery UI 2 | import $ from 'jquery'; 3 | window.$ = window.jQuery = $; 4 | 5 | import 'jquery-ui/ui/effects/effect-slide'; 6 | import 'jquery-ui/ui/widgets/draggable.js'; 7 | import 'jquery-ui/ui/widgets/sortable.js'; 8 | 9 | import MediumEditor from 'medium-editor'; 10 | window.MediumEditor = MediumEditor; 11 | 12 | import CodeMirror from 'codemirror'; 13 | window.CodeMirror = CodeMirror; 14 | require('codemirror/mode/xml/xml.js'); 15 | 16 | // Load NPM modules 17 | import swal from 'sweetalert'; 18 | window.swal = swal; 19 | 20 | require('@claviska/jquery-minicolors'); 21 | require('@fancyapps/fancybox'); 22 | 23 | // Load custom modules 24 | require('../libs/jquery.htmlClean.js'); 25 | require('../libs/uploader/slim.jquery.js'); 26 | 27 | 28 | (function($) { 29 | "use strict"; 30 | 31 | // Make a global event emitter 32 | window.top.jsEmailBuilderEmitter = require('./_emitter.js'); 33 | var emitter = window.top.jsEmailBuilderEmitter; 34 | 35 | // Navigation & Sidebar 36 | var Nav = require('./_nav.js'); 37 | // Theme 38 | var Theme = require('./_theme.js'); 39 | // Module 40 | var Module = require('./_module.js'); 41 | // Content Editor 42 | var ContentEditor = require('./_content_editor.js'); 43 | // Utils 44 | var Utils = require('./_utils.js'); 45 | 46 | 47 | // Default options for jQuery plugin 48 | var defaults = { 49 | theme: 'default' 50 | } 51 | 52 | // jQuery plugin 53 | $.EmailBuilder = function (options) { 54 | var settings = $.extend({}, defaults, options); 55 | 56 | // Allow access to Email Builder events through event emitter 57 | this.emitter = emitter; 58 | 59 | // Allow access to custom classes 60 | this.Nav = Nav; 61 | this.Theme = Theme; 62 | this.Module = Module; 63 | this.Module = Module; 64 | this.ContentEditor = ContentEditor; 65 | this.Utils = Utils; 66 | 67 | // Events 68 | this.emitter.on('init', function () { 69 | emitter.emit('theme_load', settings.theme); 70 | $('[data-type="editor"]').on('DOMSubtreeModified', function(){ 71 | emitter.emit('update_template'); 72 | }); 73 | }); 74 | 75 | this.emitter.on('switch_theme', function () { 76 | emitter.emit('theme_load', settings.theme); 77 | }); 78 | 79 | // Public Methods 80 | this.init = function () { 81 | this.emitter.emit('init'); 82 | }; 83 | 84 | this.switchTheme = function (themeName) { 85 | settings.theme = themeName; 86 | this.emitter.emit('switch_theme'); 87 | }; 88 | } 89 | }(jQuery)); -------------------------------------------------------------------------------- /src/libs/uploader/slim.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Slim v4.6.4 - Image Cropping Made Easy 3 | * Copyright (c) 2017 Rik Schennink - http://slimimagecropper.com 4 | */ 5 | .slim-file-hopper { 6 | position: absolute; 7 | left: 0; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; } 11 | 12 | .slim-image-editor { 13 | position: relative; 14 | height: 100%; 15 | text-align: left; 16 | z-index: 1; } 17 | .slim-image-editor .slim-container { 18 | position: relative; 19 | height: calc(100% - 8em); 20 | width: 100%; 21 | z-index: 2; 22 | direction: ltr; } 23 | .slim-image-editor .slim-editor-utils-group, 24 | .slim-image-editor .slim-editor-btn-group { 25 | -webkit-flex-shrink: 0; 26 | -ms-flex-negative: 0; 27 | flex-shrink: 0; } 28 | .slim-image-editor .slim-stage { 29 | position: absolute; 30 | line-height: 0; } 31 | .slim-image-editor .slim-wrapper { 32 | position: absolute; 33 | z-index: 2; } 34 | .slim-image-editor .slim-crop-preview { 35 | position: absolute; 36 | left: 0; 37 | top: 0; 38 | right: 0; 39 | bottom: 0; 40 | line-height: 0; } 41 | .slim-image-editor .slim-stage { 42 | z-index: 4; } 43 | .slim-image-editor .slim-crop-preview { 44 | z-index: 3; 45 | border-radius: 4px; } 46 | .slim-image-editor .slim-crop-preview img, .slim-image-editor .slim-crop-preview::after, 47 | .slim-image-editor .slim-crop-preview canvas { 48 | position: absolute; 49 | display: block; 50 | border-radius: inherit; 51 | left: 0; 52 | top: 0; } 53 | .slim-image-editor .slim-crop-preview .slim-crop { 54 | z-index: 3; } 55 | .slim-image-editor .slim-crop-preview::after { 56 | z-index: 2; 57 | right: 0; 58 | bottom: 0; 59 | content: ''; } 60 | .slim-image-editor .slim-crop-preview .slim-crop-blur { 61 | -webkit-filter: contrast(0.7); 62 | -moz-filter: contrast(0.7); 63 | filter: contrast(0.7); 64 | z-index: 1; } 65 | .slim-image-editor .slim-editor-utils-group { 66 | text-align: center; } 67 | .slim-image-editor .slim-editor-utils-group button { 68 | width: 2.5em; 69 | height: 2.5em; 70 | padding: 0; 71 | font-size: 1em; 72 | cursor: pointer; 73 | outline: none; 74 | box-shadow: inset 0 -1px 2px rgba(0, 0, 0, 0.1), inset 0 1px 0 0 rgba(255, 255, 255, 0.15); 75 | background-color: transparent; 76 | background-size: 50% 50%; 77 | background-position: center center; 78 | background-repeat: no-repeat; } 79 | .slim-image-editor .slim-editor-utils-group button:active { 80 | background-color: rgba(0, 0, 0, 0.1); 81 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15); } 82 | .slim-image-editor .slim-editor-btn-group { 83 | text-align: center; } 84 | .slim-image-editor .slim-editor-btn-group button { 85 | position: relative; 86 | display: inline-block; 87 | vertical-align: top; 88 | font-size: 1em; 89 | margin: 0 .75em; 90 | padding: .75em 1.5em .875em; 91 | cursor: pointer; 92 | overflow: hidden; 93 | -webkit-transition: color .25s, box-shadow .25s, background-color .25s; 94 | transition: color .25s, box-shadow .25s, background-color .25s; 95 | box-shadow: inset 0 -1px 2px rgba(0, 0, 0, 0.1), inset 0 1px 0 0 rgba(255, 255, 255, 0.15); 96 | background-color: transparent; 97 | outline: none; } 98 | .slim-image-editor .slim-editor-btn-group button:active { 99 | padding: .875em 1.5em .75em; 100 | background-color: rgba(0, 0, 0, 0.1); 101 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15); } 102 | 103 | .slim-rotation-disabled .slim-container { 104 | height: calc(100% - 4em); } 105 | 106 | .slim-rotation-disabled .slim-editor-utils-group { 107 | display: none; } 108 | 109 | .slim-editor-utils-btn, 110 | .slim-editor-btn { 111 | color: rgba(255, 255, 255, 0.75); 112 | border: 2px solid rgba(0, 0, 0, 0.25); } 113 | .slim-editor-utils-btn:focus, .slim-editor-utils-btn:hover, 114 | .slim-editor-btn:focus, 115 | .slim-editor-btn:hover { 116 | color: rgba(255, 255, 255, 0.9); } 117 | 118 | .slim-editor-utils-btn { 119 | border-radius: .6875em; } 120 | 121 | .slim-editor-btn { 122 | border-radius: .5em; } 123 | 124 | .slim-image-editor-preview::after { 125 | background-color: rgba(244, 250, 255, 0.4); 126 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.07), 0 1px 5px rgba(0, 0, 0, 0.3); } 127 | 128 | .slim-btn-rotate { 129 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='252' height='287' viewBox='0 0 252 287' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M134.762.626v36.15c65.016 4.594 116.34 58.75 116.34 124.936 0 69.198-56.09 125.288-125.29 125.288C56.616 287 .525 230.91.525 161.71c0-30.036 10.592-57.59 28.215-79.17l31.934 31.934C51.03 127.75 45.27 144.04 45.27 161.71c0 44.485 36.06 80.544 80.544 80.544 44.484 0 80.544-36.058 80.544-80.543 0-41.454-31.327-75.56-71.594-80.017v35.272l-62.646-57.89L134.762.625zm-8.95 196.883c-19.77 0-35.796-16.028-35.796-35.798 0-19.77 16.027-35.796 35.797-35.796 19.77 0 35.797 16.026 35.797 35.796s-16.027 35.797-35.797 35.797z' fill='rgba(255,255,255,.8)' fill-rule='evenodd'/%3E%3C/svg%3E"); } 130 | 131 | .slim-editor-utils-group { 132 | padding: 1em 0 0; } 133 | 134 | .slim-editor-btn-group { 135 | padding: 1em 0 0; } 136 | 137 | @media (min-width: 40em) { 138 | .slim-btn-group { 139 | padding-top: 2em; } } 140 | 141 | .slim-crop-area { 142 | position: absolute; 143 | box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.75), 0 0 0 1px rgba(255, 255, 255, 0.75); } 144 | .slim-crop-area .grid { 145 | overflow: hidden; } 146 | .slim-crop-area .grid::before, .slim-crop-area .grid::after { 147 | position: absolute; 148 | z-index: 2; 149 | content: ''; 150 | opacity: 0; 151 | -webkit-transition: opacity .5s; 152 | transition: opacity .5s; } 153 | .slim-crop-area .grid::before { 154 | top: 33.333%; 155 | bottom: 33.333%; 156 | left: 1px; 157 | right: 1px; 158 | box-shadow: inset 0 -1px 0 0 rgba(255, 255, 255, 0.35), inset 0 1px 0 0 rgba(255, 255, 255, 0.35); } 159 | .slim-crop-area .grid::after { 160 | top: 1px; 161 | bottom: 1px; 162 | left: 33.333%; 163 | right: 33.333%; 164 | box-shadow: inset -1px 0 0 0 rgba(255, 255, 255, 0.35), inset 1px 0 0 0 rgba(255, 255, 255, 0.35); } 165 | .slim-crop-area button { 166 | position: absolute; 167 | background: #fafafa; 168 | box-shadow: inset 0 1px 0 0 #fff, 0 1px 1px rgba(0, 0, 0, 0.15); 169 | border: none; 170 | padding: 0; 171 | margin: 0; 172 | width: 16px; 173 | height: 16px; 174 | margin-top: -8px; 175 | margin-left: -8px; 176 | border-radius: 8px; 177 | z-index: 3; } 178 | .slim-crop-area [class*='n'] { 179 | top: 0; } 180 | .slim-crop-area [class*='s'] { 181 | top: 100%; } 182 | .slim-crop-area [class*='w'] { 183 | left: 0; } 184 | .slim-crop-area [class*='e'] { 185 | left: 100%; } 186 | .slim-crop-area .e, 187 | .slim-crop-area .w { 188 | top: 50%; 189 | cursor: ew-resize; 190 | height: 30px; 191 | margin-top: -15px; } 192 | .slim-crop-area .n, 193 | .slim-crop-area .s { 194 | left: 50%; 195 | cursor: ns-resize; 196 | width: 30px; 197 | margin-left: -15px; } 198 | .slim-crop-area .ne, 199 | .slim-crop-area .sw { 200 | cursor: nesw-resize; } 201 | .slim-crop-area .nw, 202 | .slim-crop-area .se { 203 | cursor: nwse-resize; } 204 | .slim-crop-area .c { 205 | top: 10px; 206 | left: 10px; 207 | width: calc(100% - 20px); 208 | height: calc(100% - 20px); 209 | margin: 0; 210 | border-radius: 0; 211 | border: none; 212 | z-index: 2; 213 | box-shadow: none; 214 | opacity: 0; 215 | cursor: move; } 216 | .slim-crop-area button:not(.c)::after { 217 | content: ''; 218 | position: absolute; 219 | left: -12px; 220 | right: -12px; 221 | top: -12px; 222 | bottom: -12px; } 223 | .slim-crop-area .slim-crop-mask { 224 | position: absolute; 225 | left: 0; 226 | top: 0; 227 | right: 0; 228 | bottom: 0; 229 | overflow: hidden; 230 | z-index: 1; } 231 | .slim-crop-area .slim-crop-mask img { 232 | position: absolute; 233 | -webkit-transform-origin: 0 0; 234 | transform-origin: 0 0; 235 | -webkit-transform: translateZ(0); 236 | transform: translateZ(0); 237 | margin: 0 !important; 238 | width: auto; 239 | height: auto; 240 | max-width: none; 241 | min-width: initial; } 242 | .slim-crop-area[data-dragging='true'] .grid::before, .slim-crop-area[data-dragging='true'] .grid::after { 243 | opacity: 1; } 244 | 245 | .slim-popover { 246 | -ms-touch-action: none; 247 | touch-action: none; 248 | position: fixed; 249 | left: 0; 250 | top: 0; 251 | width: 100%; 252 | height: 100%; 253 | padding: 1em; 254 | font-size: 16px; 255 | background: rgba(25, 27, 29, 0.99); 256 | z-index: 2147483647; 257 | overflow: hidden; } 258 | .slim-popover[data-state='off'] { 259 | left: -100%; } 260 | .slim-popover::after { 261 | position: absolute; 262 | left: 0; 263 | top: 0; 264 | right: 0; 265 | bottom: 0; 266 | content: ''; 267 | background: -webkit-radial-gradient(center ellipse, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0) 80%); 268 | background: radial-gradient(ellipse at center, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0) 80%); } 269 | 270 | @media (min-width: 40em) { 271 | .slim-popover { 272 | padding: 2em; } } 273 | 274 | .slim, 275 | .slim-popover, 276 | .slim-crop-area, 277 | .slim-image-editor { 278 | -webkit-user-select: none; 279 | -moz-user-select: none; 280 | -ms-user-select: none; 281 | user-select: none; 282 | box-sizing: border-box; } 283 | .slim button, 284 | .slim-popover button, 285 | .slim-crop-area button, 286 | .slim-image-editor button { 287 | -webkit-highlight: none; 288 | -webkit-tap-highlight-color: transparent; } 289 | .slim *, 290 | .slim-popover *, 291 | .slim-crop-area *, 292 | .slim-image-editor * { 293 | box-sizing: inherit; } 294 | .slim img, 295 | .slim-popover img, 296 | .slim-crop-area img, 297 | .slim-image-editor img { 298 | background-color: #eee; 299 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAABG2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+Gkqr6gAAAYBpQ0NQc1JHQiBJRUM2MTk2Ni0yLjEAACiRdZHPK0RRFMc/M4gYERaKxUvDamhQExtlJqEmTWOUwWbmzS81P17vzaTJVtlOUWLj14K/gK2yVopISdlZExv0nGfUSObc7rmf+73nnO49F+yhtJoxat2Qyeb14KRXmQ8vKPWP2OjCQRtKRDW08UDAT1V7u5Fosat+q1b1uH+tKRY3VLA1CI+pmp4XnhL2r+Q1izeFO9RUJCZ8LOzS5YLC15YeLfOTxckyf1ish4I+sLcKK8lfHP3FakrPCMvLcWbSBfXnPtZLHPHs3KysPTK7MQgyiReFaSbw4WGQUfEe+hliQHZUyXd/58+Qk1xVvEYRnWWSpMjjErUg1eOyJkSPy0hTtPr/t69GYnioXN3hhboH03zphfoN+CyZ5vu+aX4eQM09nGUr+bk9GHkVvVTRnLvQsgYn5xUtugWn69B5p0X0yLdUI9OeSMDzETSHof0SGhfLPfs55/AWQqvyVRewvQN9Et+y9AUyt2fOEwKMEgAAAAlwSFlzAAALEwAACxMBAJqcGAAAAC9JREFUOI1jZGBgkGIgDjwjRhETkYYRDUYNHDVwMBjISIJaonLU4PfyqIGjBpIBAPvwAUFW9TOIAAAAAElFTkSuQmCC"); } 300 | 301 | .slim img { 302 | width: 100%; 303 | height: auto; } 304 | 305 | span.slim { 306 | display: block; } 307 | 308 | .slim { 309 | position: relative; 310 | font-size: inherit; 311 | background-color: #eee; 312 | -webkit-transition: background-color .25s; 313 | transition: background-color .25s; 314 | padding-bottom: .025px; } 315 | 316 | @-webkit-keyframes rotate { 317 | 0% { 318 | -webkit-transform: rotate(0deg); 319 | transform: rotate(0deg); } 320 | 100% { 321 | -webkit-transform: rotate(360deg); 322 | transform: rotate(360deg); } } 323 | 324 | @keyframes rotate { 325 | 0% { 326 | -webkit-transform: rotate(0deg); 327 | transform: rotate(0deg); } 328 | 100% { 329 | -webkit-transform: rotate(360deg); 330 | transform: rotate(360deg); } } 331 | .slim[data-state*='empty']:hover { 332 | background-color: #ddd; } 333 | .slim[data-state*='empty'] .slim-label { 334 | visibility: visible; 335 | opacity: 1; } 336 | .slim[data-state*='busy'] .slim-label { 337 | opacity: 0; } 338 | .slim[data-state*='loading'] .slim-label { 339 | display: none; } 340 | .slim[data-state*='loading'] .slim-label-loading { 341 | opacity: 1; 342 | display: block; } 343 | .slim[data-state*='preview'] .slim-label { 344 | visibility: hidden; } 345 | .slim[data-state*='error'] { 346 | background-color: #e8a69f !important; 347 | color: #702010; } 348 | .slim > img, 349 | .slim > input[type=file] { 350 | display: block !important; 351 | opacity: 0 !important; 352 | width: 0 !important; 353 | height: 0 !important; 354 | padding: 0 !important; 355 | margin-left: 0 !important; 356 | margin-right: 0 !important; 357 | margin-top: 0 !important; 358 | border: 0 !important; } 359 | .slim > img + input[type=file] { 360 | margin-bottom: 0 !important; } 361 | .slim > input[type=file] + img { 362 | margin-bottom: 0 !important; } 363 | .slim > input[type=hidden] { 364 | position: absolute; 365 | width: 1px; 366 | height: 1px; 367 | margin: -1px; 368 | opacity: 0; } 369 | .slim .slim-label-loading { 370 | display: none; } 371 | .slim .slim-label { 372 | visibility: hidden; 373 | -webkit-transition: opacity .25s; 374 | transition: opacity .25s; } 375 | .slim .slim-label-loading, 376 | .slim .slim-label, 377 | .slim .slim-error { 378 | max-width: 100%; } 379 | .slim .slim-file-hopper { 380 | z-index: 2; 381 | background: rgba(0, 0, 0, 0.0001); } 382 | .slim .slim-ratio, 383 | .slim .slim-drip, 384 | .slim .slim-status, 385 | .slim .slim-result, 386 | .slim .slim-area { 387 | border-radius: inherit; } 388 | .slim .slim-area { 389 | width: 100%; 390 | color: inherit; 391 | overflow: hidden; } 392 | .slim .slim-area *:only-of-type { 393 | margin: 0; } 394 | .slim .slim-area .slim-loader { 395 | pointer-events: none; 396 | position: absolute; 397 | right: .875em; 398 | top: .875em; 399 | width: 23px; 400 | height: 23px; 401 | z-index: 1; } 402 | .slim .slim-area .slim-loader svg { 403 | display: block; 404 | width: 100%; 405 | height: 100%; 406 | opacity: 0; } 407 | .slim .slim-area .slim-upload-status { 408 | position: absolute; 409 | right: 1em; 410 | top: 1em; 411 | z-index: 1; 412 | opacity: 0; 413 | -webkit-transition: opacity .25s; 414 | transition: opacity .25s; 415 | white-space: nowrap; 416 | line-height: 1.65; 417 | font-weight: normal; } 418 | .slim .slim-area .slim-upload-status-icon { 419 | display: inline-block; 420 | opacity: .9; } 421 | .slim .slim-area .slim-drip, 422 | .slim .slim-area .slim-status, 423 | .slim .slim-area .slim-result { 424 | left: 0; 425 | top: 0; 426 | right: 0; 427 | bottom: 0; } 428 | .slim .slim-area .slim-drip, 429 | .slim .slim-area .slim-result { 430 | position: absolute; } 431 | .slim .slim-area .slim-status { 432 | padding: 3em 1.5em; 433 | display: -webkit-box; 434 | display: -webkit-flex; 435 | display: -ms-flexbox; 436 | display: flex; 437 | -webkit-box-align: center; 438 | -webkit-align-items: center; 439 | -ms-flex-align: center; 440 | align-items: center; 441 | -webkit-box-pack: center; 442 | -webkit-justify-content: center; 443 | -ms-flex-pack: center; 444 | justify-content: center; 445 | text-align: center; 446 | -webkit-box-orient: vertical; 447 | -webkit-box-direction: normal; 448 | -webkit-flex-direction: column; 449 | -ms-flex-direction: column; 450 | flex-direction: column; 451 | pointer-events: none; } 452 | .slim .slim-area .slim-drip { 453 | z-index: 1; 454 | overflow: hidden; } 455 | .slim .slim-area .slim-drip > span { 456 | position: absolute; 457 | left: 0; 458 | top: 0; 459 | opacity: 0; 460 | margin-left: -25%; 461 | margin-top: -25%; 462 | width: 50%; 463 | padding-bottom: 50%; } 464 | .slim .slim-area .slim-drip > span > span { 465 | position: absolute; 466 | width: 100%; 467 | height: 100%; 468 | background-color: rgba(0, 0, 0, 0.25); 469 | border-radius: 50%; 470 | opacity: .5; 471 | left: 0; 472 | top: 0; } 473 | .slim .slim-area .slim-result { 474 | overflow: hidden; 475 | -webkit-perspective: 1px; } 476 | .slim .slim-area .slim-result img { 477 | display: block; 478 | width: 100%; 479 | position: absolute; 480 | left: 0; 481 | top: 0; } 482 | .slim .slim-area .slim-result img:not([src]), .slim .slim-area .slim-result img[src=''] { 483 | visibility: hidden; } 484 | .slim .slim-btn-group { 485 | position: absolute; 486 | right: 0; 487 | bottom: 0; 488 | left: 0; 489 | z-index: 3; 490 | overflow: hidden; 491 | pointer-events: none; } 492 | .slim .slim-btn-group button { 493 | pointer-events: all; 494 | cursor: pointer; } 495 | .slim[data-ratio*=':'] { 496 | min-height: initial; } 497 | .slim[data-ratio*=':'] .slim-status { 498 | position: absolute; 499 | padding: 0 1.5em; } 500 | .slim[data-ratio='16:10'] > input[type=file], 501 | .slim[data-ratio='16:10'] > img { 502 | margin-bottom: 62.5%; } 503 | .slim[data-ratio='10:16'] > input[type=file], 504 | .slim[data-ratio='10:16'] > img { 505 | margin-bottom: 160%; } 506 | .slim[data-ratio='16:9'] > input[type=file], 507 | .slim[data-ratio='16:9'] > img { 508 | margin-bottom: 56.25%; } 509 | .slim[data-ratio='9:16'] > input[type=file], 510 | .slim[data-ratio='9:16'] > img { 511 | margin-bottom: 177.77778%; } 512 | .slim[data-ratio='5:3'] > input[type=file], 513 | .slim[data-ratio='5:3'] > img { 514 | margin-bottom: 60%; } 515 | .slim[data-ratio='3:5'] > input[type=file], 516 | .slim[data-ratio='3:5'] > img { 517 | margin-bottom: 166.66667%; } 518 | .slim[data-ratio='5:4'] > input[type=file], 519 | .slim[data-ratio='5:4'] > img { 520 | margin-bottom: 80%; } 521 | .slim[data-ratio='4:5'] > input[type=file], 522 | .slim[data-ratio='4:5'] > img { 523 | margin-bottom: 125%; } 524 | .slim[data-ratio='4:3'] > input[type=file], 525 | .slim[data-ratio='4:3'] > img { 526 | margin-bottom: 75%; } 527 | .slim[data-ratio='3:4'] > input[type=file], 528 | .slim[data-ratio='3:4'] > img { 529 | margin-bottom: 133.33333%; } 530 | .slim[data-ratio='3:2'] > input[type=file], 531 | .slim[data-ratio='3:2'] > img { 532 | margin-bottom: 66.66667%; } 533 | .slim[data-ratio='2:3'] > input[type=file], 534 | .slim[data-ratio='2:3'] > img { 535 | margin-bottom: 150%; } 536 | .slim[data-ratio='1:1'] > input[type=file], 537 | .slim[data-ratio='1:1'] > img { 538 | margin-bottom: 100%; } 539 | 540 | .slim-btn-group { 541 | padding: 1.5em 0; 542 | text-align: center; } 543 | 544 | .slim-btn { 545 | position: relative; 546 | padding: 0; 547 | margin: 0 7.2px; 548 | font-size: 0; 549 | outline: none; 550 | width: 36px; 551 | height: 36px; 552 | border: none; 553 | color: #fff; 554 | background-color: rgba(0, 0, 0, 0.7); 555 | background-repeat: no-repeat; 556 | background-size: 50% 50%; 557 | background-position: center center; } 558 | .slim-btn { 559 | border-radius: 50%; } 560 | .slim-btn::before { 561 | border-radius: inherit; 562 | position: absolute; 563 | box-sizing: border-box; 564 | left: -3px; 565 | right: -3px; 566 | bottom: -3px; 567 | top: -3px; 568 | border: 3px solid white; 569 | content: ''; 570 | -webkit-transform: scale(0.95); 571 | transform: scale(0.95); 572 | opacity: 0; 573 | -webkit-transition: all .25s; 574 | transition: all .25s; 575 | z-index: -1; 576 | pointer-events: none; } 577 | .slim-btn:focus::before, .slim-btn:hover::before { 578 | opacity: 1; 579 | -webkit-transform: scale(1); 580 | transform: scale(1); } 581 | .slim-btn * { 582 | pointer-events: none; } 583 | 584 | .slim-btn-remove { 585 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 269 269' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' clip-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='1.414'%3E%3Cpath d='M63.12 250.254s3.998 18.222 24.582 18.222h93.072c20.583 0 24.582-18.222 24.582-18.222l18.374-178.66H44.746l18.373 178.66zM170.034 98.442c0-4.943 4.006-8.95 8.95-8.95 4.942 0 8.95 4.007 8.95 8.95l-8.95 134.238c0 4.943-4.008 8.95-8.95 8.95-4.942 0-8.95-4.008-8.95-8.95l8.95-134.238zm-44.746 0c0-4.943 4.006-8.95 8.948-8.95 4.943 0 8.95 4.007 8.95 8.95V232.68c0 4.943-4.007 8.95-8.95 8.95s-8.95-4.008-8.95-8.95V98.442zm-35.798-8.95c4.943 0 8.95 4.006 8.95 8.95l8.95 134.237c0 4.942-4.008 8.948-8.95 8.948-4.943 0-8.95-4.007-8.95-8.95l-8.95-134.236c0-4.943 4.008-8.95 8.95-8.95zm128.868-53.68h-39.376V17.898c0-13.578-4.39-17.9-17.898-17.9H107.39C95 0 89.492 6 89.492 17.9V35.81H50.116c-7.914 0-14.32 6.007-14.32 13.43 0 7.424 6.406 13.43 14.32 13.43H218.36c7.914 0 14.32-6.006 14.32-13.43 0-7.423-6.406-13.43-14.32-13.43zm-57.274 0H107.39l.002-17.914h53.695V35.81z' fill='%23fff'/%3E%3C/svg%3E"); } 586 | 587 | .slim-btn-download { 588 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 269 269' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' clip-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='1.414'%3E%3Cpath d='M232.943 223.73H35.533c-12.21 0-22.11 10.017-22.11 22.373 0 12.356 9.9 22.373 22.11 22.373h197.41c12.21 0 22.11-10.017 22.11-22.373 0-12.356-9.9-22.373-22.11-22.373zM117.88 199.136c4.035 4.04 9.216 6.147 14.492 6.508.626.053 1.227.188 1.866.188.633 0 1.228-.135 1.847-.186 5.284-.357 10.473-2.464 14.512-6.51l70.763-70.967c8.86-8.876 8.86-23.268 0-32.143-8.86-8.876-23.225-8.876-32.086 0l-32.662 32.756V22.373C156.612 10.017 146.596 0 134.238 0c-12.356 0-22.372 10.017-22.372 22.373v106.41L79.204 96.027c-8.86-8.876-23.226-8.876-32.086 0-8.86 8.875-8.86 23.267 0 32.142l70.763 70.966z' fill='%23fff'/%3E%3C/svg%3E"); } 589 | 590 | .slim-btn-upload { 591 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='243' height='269' viewBox='0 0 243 269' xmlns='http://www.w3.org/2000/svg'%3E%3Ctitle%3EDownload%3C/title%3E%3Cpath d='M219.943 223.73H22.533c-12.21 0-22.11 10.017-22.11 22.373 0 12.356 9.9 22.373 22.11 22.373h197.41c12.21 0 22.11-10.017 22.11-22.373 0-12.356-9.9-22.373-22.11-22.373zM104.88 6.696c4.035-4.04 9.216-6.147 14.492-6.508C119.998.135 120.6 0 121.238 0c.633 0 1.228.135 1.847.186 5.284.357 10.473 2.464 14.512 6.51l70.763 70.967c8.86 8.875 8.86 23.267 0 32.142-8.86 8.876-23.225 8.876-32.086 0L143.612 77.05v106.41c0 12.355-10.016 22.372-22.374 22.372-12.356 0-22.372-10.017-22.372-22.373V77.05l-32.662 32.755c-8.86 8.876-23.226 8.876-32.086 0-8.86-8.875-8.86-23.267 0-32.142L104.88 6.696z' fill='%23FFF' fill-rule='evenodd'/%3E%3C/svg%3E"); } 592 | 593 | .slim-btn-edit { 594 | background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 269 269' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' clip-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='1.414'%3E%3Cpath d='M161.36 56.337c-7.042-7.05-18.46-7.05-25.5 0l-6.373 6.38-89.243 89.338.023.023-2.812 2.82s-8.968 9.032-29.216 74.4c-.143.456-.284.91-.427 1.373-.36 1.172-.726 2.362-1.094 3.568-.327 1.066-.657 2.154-.988 3.25-.28.922-.556 1.835-.84 2.778-.64 2.14-1.29 4.318-1.954 6.567-1.455 4.937-5.01 16.07-.99 20.1 3.87 3.882 15.12.467 20.043-.993 2.233-.662 4.396-1.31 6.52-1.952.98-.296 1.932-.586 2.89-.878 1.032-.314 2.058-.626 3.063-.935 1.27-.39 2.52-.775 3.75-1.157l1.09-.34c62.193-19.365 73.358-28.453 74.286-29.284l.01-.01.067-.06 2.88-2.886.192.193 89.244-89.336 6.373-6.382c7.04-7.048 7.04-18.476 0-25.525l-50.998-51.05zM103.4 219.782c-.08.053-.185.122-.297.193l-.21.133c-.076.047-.158.098-.245.15l-.243.148c-2.97 1.777-11.682 6.362-32.828 14.017-2.47.894-5.162 1.842-7.98 2.82l-30.06-30.092c.98-2.84 1.928-5.55 2.825-8.04 7.638-21.235 12.22-29.974 13.986-32.94l.12-.2c.063-.1.12-.196.175-.283l.126-.2c.07-.11.14-.217.192-.296l2.2-2.205 54.485 54.542-2.248 2.255zM263.35 56.337l-50.996-51.05c-7.04-7.048-18.456-7.048-25.498 0L174.108 18.05c-7.04 7.048-7.04 18.476 0 25.524l50.996 51.05c7.04 7.048 18.457 7.048 25.498 0l12.75-12.762c7.04-7.05 7.04-18.477 0-25.525z' fill='%23fff'/%3E%3C/svg%3E"); } 595 | 596 | .slim-loader-background { 597 | stroke: rgba(0, 0, 0, 0.15); } 598 | 599 | .slim-loader-foreground { 600 | stroke: rgba(0, 0, 0, 0.65); } 601 | 602 | .slim[data-state*='preview'] .slim-loader-background { 603 | stroke: rgba(255, 255, 255, 0.25); } 604 | 605 | .slim[data-state*='preview'] .slim-loader-foreground { 606 | stroke: #fff; } 607 | 608 | .slim-upload-status { 609 | padding: 0 .5em; 610 | border-radius: .3125em; 611 | font-size: .75em; 612 | box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.25); } 613 | 614 | .slim-upload-status[data-state="success"] { 615 | background-color: #d1ed8f; 616 | color: #323e15; } 617 | .slim-upload-status[data-state="success"] .slim-upload-status-icon { 618 | width: .5em; 619 | height: .75em; 620 | -webkit-transform: rotate(45deg); 621 | transform: rotate(45deg); 622 | border: .1875em solid currentColor; 623 | border-left: none; 624 | border-top: none; 625 | margin-right: .325em; 626 | margin-left: .25em; 627 | margin-bottom: .0625em; } 628 | 629 | .slim-upload-status[data-state="error"] { 630 | background: #efd472; 631 | color: #574016; } 632 | .slim-upload-status[data-state="error"] .slim-upload-status-icon { 633 | margin-left: -.125em; 634 | margin-right: .5em; 635 | width: .5625em; 636 | height: 1em; 637 | position: relative; 638 | -webkit-transform: rotate(45deg); 639 | transform: rotate(45deg); } 640 | .slim-upload-status[data-state="error"] .slim-upload-status-icon:after, .slim-upload-status[data-state="error"] .slim-upload-status-icon:before { 641 | content: ''; 642 | position: absolute; 643 | box-sizing: content-box; 644 | width: 0; 645 | height: 0; 646 | border-width: 0.09em; 647 | border-style: solid; 648 | border-color: currentColor; 649 | background-color: currentColor; 650 | -webkit-transform: translate(-50%, -50%) translate(0.5em, 0.5em); 651 | transform: translate(-50%, -50%) translate(0.5em, 0.5em); } 652 | .slim-upload-status[data-state="error"] .slim-upload-status-icon:before { 653 | width: 0.66666666667em; } 654 | .slim-upload-status[data-state="error"] .slim-upload-status-icon:after { 655 | height: 0.66666666667em; } 656 | -------------------------------------------------------------------------------- /src/sass/_base.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | padding: 0px; 3 | margin: 0px; 4 | height: 100%; 5 | } 6 | 7 | body { 8 | background: $main-bg-color; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | font-family: 'Roboto', sans-serif; 12 | font-weight: 400; 13 | font-size: 14px; 14 | } 15 | 16 | 17 | 18 | #email-builder{ 19 | height: 100%; 20 | } 21 | 22 | #email-builder .main-container { 23 | margin-left: 250px; 24 | background: $main-bg-color; 25 | height: 100%; 26 | 27 | .holder-container { 28 | margin-left: 350px; 29 | width: 700px; 30 | height: 100%; 31 | } 32 | 33 | } 34 | 35 | #email-builder .sidebar-top { 36 | padding: 10px; 37 | text-align: center; 38 | 39 | .item { 40 | display: inline-block; 41 | cursor: pointer; 42 | color: $button-top-sidebar-color; 43 | text-decoration: none; 44 | padding: 5px; 45 | margin-left: 10px; 46 | background: $button-top-sidebar-bg-color; 47 | min-width: 20px; 48 | border-radius: 3px; 49 | } 50 | 51 | .item-last { 52 | display: block; 53 | float: right; 54 | padding: 5px 15px; 55 | text-transform: uppercase; 56 | } 57 | 58 | .template-preview-mobile{ 59 | color: $button-mobile-preview-color; 60 | background: $button-mobile-preview-bg-color; 61 | &:hover{ 62 | color: $button-mobile-preview-active-color; 63 | background: $button-mobile-preview-active-bg-color; 64 | } 65 | } 66 | 67 | .template-preview-tablet{ 68 | color: $button-tablet-preview-color; 69 | background: $button-tablet-preview-bg-color; 70 | &:hover{ 71 | color: $button-tablet-preview-active-color; 72 | background: $button-tablet-preview-active-bg-color; 73 | } 74 | } 75 | 76 | .template-clear{ 77 | color: $button-template-clear-color; 78 | background: $button-template-clear-bg-color; 79 | &:hover{ 80 | color: $button-template-clear-active-color; 81 | background: $button-template-clear-active-bg-color; 82 | } 83 | } 84 | 85 | } 86 | 87 | #email-builder .editor-container { 88 | padding-top: 1rem; 89 | left: 0; 90 | top: 0; 91 | bottom: 0; 92 | right: 0; 93 | 94 | .editor { 95 | min-height: 500px; 96 | background: $editor-bg-color; 97 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06); 98 | position: relative; 99 | } 100 | 101 | } 102 | 103 | .alert{ 104 | padding: 5px 10px; 105 | &.bg-danger{ 106 | color: $alert-danger-color; 107 | background: $alert-danger-bg-color; 108 | } 109 | } 110 | 111 | .main-form-container{ 112 | width:450px; 113 | margin:0px auto; 114 | padding-top: 100px; 115 | 116 | .form-login-container{ 117 | text-align: center; 118 | img{ 119 | max-height: 50px; 120 | width: auto; 121 | } 122 | } 123 | 124 | .btn-form-action{ 125 | margin-top:20px; 126 | float: right; 127 | display: inline-block; 128 | cursor: pointer; 129 | color: $button-top-sidebar-color; 130 | text-decoration: none; 131 | padding: 5px; 132 | margin-left: 10px; 133 | background: $button-top-sidebar-bg-color; 134 | min-width: 20px; 135 | border-radius: 3px; 136 | 137 | &:hover{ 138 | color: $button-template-clear-active-color; 139 | background: $button-template-clear-active-bg-color; 140 | } 141 | } 142 | 143 | form{ 144 | background: $editor-bg-color; 145 | box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.06); 146 | padding:20px; 147 | 148 | .btn-primary{ 149 | cursor: pointer; 150 | width:30%; 151 | display: inline-block; 152 | border: 0px; 153 | padding: 20px 0px; 154 | font-weight: bold; 155 | text-transform: uppercase; 156 | color: $button-modal-apply-color; 157 | background: $button-modal-apply-bg-color; 158 | 159 | &:hover{ 160 | color: $button-modal-apply-active-color; 161 | background: $button-modal-apply-active-bg-color; 162 | } 163 | } 164 | 165 | .form-group{ 166 | padding-top:20px; 167 | 168 | .form-control{ 169 | width: 100%; 170 | box-sizing: border-box; 171 | margin-bottom: 20px; 172 | padding: 10px 5px; 173 | border: 1px solid $main-sidebar-bg-color; 174 | } 175 | } 176 | } 177 | } 178 | 179 | 180 | -------------------------------------------------------------------------------- /src/sass/_colors.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * COLORS 3 | */ 4 | 5 | /* ======= GENERAL ======= */ 6 | 7 | $main-color: #c4c4c4; 8 | $main-bg-color: #f1f1f1; 9 | $editor-bg-color: #fff; 10 | 11 | $placeholder-border-color: #a2a2a2; 12 | $placeholder-color: #a2a2a2; 13 | 14 | /* ======= MAIN SIDEBAR ======= */ 15 | 16 | $main-sidebar-bg-color: #2A2C31; 17 | $main-sidebar-color: #808080; 18 | 19 | /* Logo Container */ 20 | $logo-color: #fff; 21 | $logo-bg-color: #33363B; 22 | 23 | /* Nav Items */ 24 | $nav-item-color: #808080; 25 | $nav-item-active-color: #fff; 26 | 27 | /* Sub Nav Items */ 28 | $subnav-item-color: #808080; 29 | $subnav-item-active-color: #fff; 30 | $subnav-item-border-color: #2A2C31; 31 | $subnav-item-border-active-color: #58C0B0; 32 | $subnav-item-active-bg-color: #202326; 33 | 34 | 35 | /* ======= SECOND SIDEBAR ======= */ 36 | 37 | $second-sidebar-bg-color: #202326; 38 | $second-sidebar-input-border-color: #f1f1f1; 39 | $second-sidebar-header-color: #A3A3A3; 40 | 41 | /* Module List */ 42 | $second-sidebar-module-color: #A3A3A3; 43 | $second-sidebar-module-color: #fff; 44 | 45 | /* Module Options */ 46 | $option-main-color: #fff; 47 | $option-border-color: #35383d; 48 | $option-label-color: #848484; 49 | 50 | 51 | /* ======= BUTTONS ======= */ 52 | 53 | /* === Sidebar Buttons === */ 54 | 55 | /* Main Button */ 56 | // Normal State 57 | $button-main-color: #fff; 58 | $button-main-bg-color: #58C0B0; 59 | // Active / Hover State 60 | $button-main-active-color: #fff; 61 | $button-main-active-bg-color: #499E91; 62 | 63 | /* Secondary Button */ 64 | // Normal State 65 | $button-secondary-color: #fff; 66 | $button-secondary-bg-color: #232629; 67 | // Active / Hover State 68 | $button-secondary-active-color: #fff; 69 | $button-secondary-active-bg-color: #37393C; 70 | 71 | /* Background Option Button */ 72 | // Normal State 73 | $button-module-bgoption-color: #fff; 74 | $button-module-bgoption-bg-color: #58C0B0; 75 | // Active / Hover State 76 | $button-module-bgoption-active-color: #fff; 77 | $button-module-bgoption-active-bg-color: #499E91; 78 | 79 | 80 | /* === Module Buttons === */ 81 | 82 | $button-module-color: #fff; 83 | $button-module-bg-color: #CDCDCD; 84 | 85 | /* Sort/Drag Button */ 86 | // Normal State 87 | $button-module-sort-color: #fff; 88 | $button-module-sort-bg-color: #CDCDCD; 89 | // Active / Hover State 90 | $button-module-sort-active-color: #fff; 91 | $button-module-sort-active-bg-color: #56A0D8; 92 | 93 | /* Copy Button */ 94 | // Normal State 95 | $button-module-copy-color: #fff; 96 | $button-module-copy-bg-color: #CDCDCD; 97 | // Active / Hover State 98 | $button-module-copy-active-color: #fff; 99 | $button-module-copy-active-bg-color: #EA9E2C; 100 | 101 | /* Code Button */ 102 | // Normal State 103 | $button-module-code-color: #fff; 104 | $button-module-code-bg-color: #CDCDCD; 105 | // Active / Hover State 106 | $button-module-code-active-color: #fff; 107 | $button-module-code-active-bg-color: #92BFB1; 108 | 109 | /* Remove Button */ 110 | // Normal State 111 | $button-module-remove-color: #fff; 112 | $button-module-remove-bg-color: #CDCDCD; 113 | // Active / Hover State 114 | $button-module-remove-active-color: #fff; 115 | $button-module-remove-active-bg-color: #F05A5A; 116 | 117 | 118 | /* Code Edit Button */ 119 | $button-code-color: #fff; 120 | $button-code-bg-color: #CDCDCD; 121 | 122 | /* Code Apply Button */ 123 | // Normal State 124 | $button-code-apply-color: #fff; 125 | $button-code-apply-bg-color: #CDCDCD; 126 | // Active / Hover State 127 | $button-code-apply-active-color: #fff; 128 | $button-code-apply-active-bg-color: #58C0B0; 129 | 130 | /* Code Cancel Button */ 131 | // Normal State 132 | $button-code-cancel-color: #fff; 133 | $button-code-cancel-bg-color: #CDCDCD; 134 | // Active / Hover State 135 | $button-code-cancel-active-color: #fff; 136 | $button-code-cancel-active-bg-color: #F05A5A; 137 | 138 | 139 | /* === Modal Buttons === */ 140 | 141 | /* Modal Button */ 142 | $button-modal-color: #fff; 143 | $button-modal-bg-color: #CDCDCD; 144 | 145 | /* Modal Apply Button */ 146 | // Normal State 147 | $button-modal-apply-color: #fff; 148 | $button-modal-apply-bg-color: #58C0B0; 149 | // Active / Hover State 150 | $button-modal-apply-active-color: #fff; 151 | $button-modal-apply-active-bg-color: #499E91; 152 | 153 | /* Modal Cancel Button */ 154 | // Normal State 155 | $button-modal-cancel-color: #fff; 156 | $button-modal-cancel-bg-color: #CDCDCD; 157 | // Active / Hover State 158 | $button-modal-cancel-active-color: #fff; 159 | $button-modal-cancel-active-bg-color: #969696; 160 | 161 | 162 | /* === Top Sidebar Buttons === */ 163 | 164 | /* Main Button */ 165 | $button-top-sidebar-color: #c4c4c4; 166 | $button-top-sidebar-bg-color: #eee; 167 | 168 | /* Template Mobile Preview Button */ 169 | // Normal State 170 | $button-mobile-preview-color: #c4c4c4; 171 | $button-mobile-preview-bg-color: #eee; 172 | // Active / Hover State 173 | $button-mobile-preview-active-color: #c4c4c4; 174 | $button-mobile-preview-active-bg-color: #ECECEC; 175 | 176 | /* Template Tablet Preview Button */ 177 | // Normal State 178 | $button-tablet-preview-color: #c4c4c4; 179 | $button-tablet-preview-bg-color: #eee; 180 | // Active / Hover State 181 | $button-tablet-preview-active-color: #c4c4c4; 182 | $button-tablet-preview-active-bg-color: #ECECEC; 183 | 184 | /* Clear the Template Button */ 185 | // Normal State 186 | $button-template-clear-color: #c4c4c4; 187 | $button-template-clear-bg-color: #eee; 188 | // Active / Hover State 189 | $button-template-clear-active-color: #fff; 190 | $button-template-clear-active-bg-color: #F05A5A; 191 | 192 | // Alert 193 | $alert-danger-color: #fff; 194 | $alert-danger-bg-color: #F05A5A; -------------------------------------------------------------------------------- /src/sass/_external.scss: -------------------------------------------------------------------------------- 1 | @import '~jquery-ui/themes/base/all.css'; 2 | @import '~@claviska/jquery-minicolors/jquery.minicolors.css'; 3 | @import '~codemirror/lib/codemirror.css'; 4 | @import '~codemirror/theme/tomorrow-night-bright.css'; 5 | @import '~sweetalert/dist/sweetalert.css'; 6 | @import '~medium-editor/src/sass/medium-editor.scss'; 7 | @import '~medium-editor/src/sass/themes/beagle.scss'; 8 | @import '~@fancyapps/fancybox/dist/jquery.fancybox.css'; 9 | 10 | @import '../libs/uploader/slim.css'; -------------------------------------------------------------------------------- /src/sass/_main_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar-main { 2 | left: 0; 3 | top: 0; 4 | position: fixed; 5 | height: 100%; 6 | width: 250px; 7 | z-index: 99; 8 | background: $main-sidebar-bg-color; 9 | color: $main-sidebar-color; 10 | .sidebar-header { 11 | background: $logo-bg-color; 12 | padding: 20px 5px; 13 | color: $logo-color; 14 | a { 15 | color: $logo-color; 16 | text-decoration: none; 17 | } 18 | .logo-container { 19 | display: block; 20 | overflow: hidden; 21 | .logo { 22 | max-height: 48px; 23 | width: auto; 24 | display: block; 25 | float: left; 26 | margin-right: 5px; 27 | } 28 | .title-container { 29 | display: block; 30 | float: left; 31 | padding-top: 5px; 32 | .title { 33 | font-size: 16px; 34 | font-family: 'Roboto', sans-serif; 35 | font-weight: 700; 36 | letter-spacing: 1.1px; 37 | display: block; 38 | } 39 | .subtitle { 40 | font-size: 12px; 41 | font-family: 'Roboto', sans-serif; 42 | font-weight: 300; 43 | letter-spacing: 1.1px; 44 | display: block; 45 | } 46 | } 47 | } 48 | } 49 | .account-info { 50 | padding: 5px 0px 0px 0px; 51 | margin: 0; 52 | list-style: none; 53 | overflow: auto; 54 | a { 55 | color: $nav-item-color; 56 | text-decoration: none; 57 | } 58 | .nav-item { 59 | display: block; 60 | font-family: 'Roboto', sans-serif; 61 | text-transform: uppercase; 62 | cursor: pointer; 63 | padding: 0px; 64 | a:hover, &.active, &:hover { 65 | color: $nav-item-active-color; 66 | } 67 | .item { 68 | display: block; 69 | padding: 15px 0px 8px 20px; 70 | .icon { 71 | padding-right: 5px; 72 | } 73 | } 74 | .subnav { 75 | display: none; 76 | margin: 0; 77 | padding: 0; 78 | list-style: none; 79 | text-transform: none; 80 | .subnav-item { 81 | border-left: 4px solid $subnav-item-border-color; 82 | color: $subnav-item-color; 83 | cursor: pointer; 84 | padding: 5px 0px; 85 | padding-left: 40px; 86 | a:hover, &:hover { 87 | color: $subnav-item-active-color; 88 | } 89 | &.active { 90 | color: $subnav-item-active-color; 91 | background: $subnav-item-active-bg-color; 92 | border-color: $subnav-item-border-active-color; 93 | } 94 | } 95 | } 96 | } 97 | } 98 | .nav { 99 | padding: 35px 0px 0px 0px; 100 | margin: 0; 101 | list-style: none; 102 | overflow: auto; 103 | height: 100%; 104 | a { 105 | color: $nav-item-color; 106 | text-decoration: none; 107 | } 108 | .nav-item { 109 | display: block; 110 | font-family: 'Roboto', sans-serif; 111 | text-transform: uppercase; 112 | cursor: pointer; 113 | padding: 0px; 114 | a:hover, &.active, &:hover { 115 | color: $nav-item-active-color; 116 | } 117 | .item { 118 | display: block; 119 | padding: 15px 0px 8px 20px; 120 | .icon { 121 | padding-right: 5px; 122 | } 123 | } 124 | .subnav { 125 | display: none; 126 | margin: 0; 127 | padding: 0; 128 | list-style: none; 129 | text-transform: none; 130 | .subnav-item { 131 | border-left: 4px solid $subnav-item-border-color; 132 | color: $subnav-item-color; 133 | cursor: pointer; 134 | padding: 12px 0px; 135 | padding-left: 40px; 136 | a:hover, &:hover { 137 | color: $subnav-item-active-color; 138 | } 139 | &.active { 140 | color: $subnav-item-active-color; 141 | background: $subnav-item-active-bg-color; 142 | border-color: $subnav-item-border-active-color; 143 | } 144 | } 145 | } 146 | } 147 | } 148 | .sidebar-footer { 149 | position: absolute; 150 | bottom: 0px; 151 | left: 0px; 152 | padding: 20px; 153 | .btn-main { 154 | width: 100%; 155 | text-align: center; 156 | text-decoration: none; 157 | display: block; 158 | padding: 10px 20px; 159 | color: $button-main-color; 160 | background: $button-main-bg-color; 161 | margin-bottom: 10px; 162 | border: none; 163 | cursor: pointer; 164 | 165 | &:hover{ 166 | color: $button-main-active-color; 167 | background: $button-main-active-bg-color; 168 | } 169 | } 170 | .btn-second { 171 | width: 100%; 172 | text-align: center; 173 | text-decoration: none; 174 | display: block; 175 | padding: 10px 20px; 176 | color: $button-secondary-color; 177 | background: $button-secondary-bg-color; 178 | 179 | &:hover{ 180 | color: $button-secondary-active-color; 181 | background: $button-secondary-active-bg-color; 182 | } 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /src/sass/_modal.scss: -------------------------------------------------------------------------------- 1 | .modal-container { 2 | display: none; 3 | position: relative; 4 | 5 | .modal-container-inner{ 6 | overflow: hidden; 7 | } 8 | 9 | .input-group{ 10 | padding-top:20px; 11 | 12 | .input-control{ 13 | width: 100%; 14 | box-sizing: border-box; 15 | margin-bottom: 20px; 16 | padding: 10px 5px; 17 | } 18 | } 19 | 20 | .modal-controls{ 21 | position: absolute; 22 | bottom: 0px; 23 | left:0px; 24 | width: 100%; 25 | 26 | button{ 27 | width:50%; 28 | float:left; 29 | display: inline-block; 30 | border: 0px; 31 | padding: 20px 0px; 32 | font-weight: bold; 33 | text-transform: uppercase; 34 | color: $button-modal-color; 35 | background: $button-modal-color; 36 | } 37 | .modal-btn-cancel{ 38 | color: $button-modal-cancel-color; 39 | background: $button-modal-cancel-bg-color; 40 | &:hover{ 41 | color: $button-modal-cancel-active-color; 42 | background: $button-modal-cancel-active-bg-color; 43 | } 44 | } 45 | .modal-btn-ok{ 46 | float:right; 47 | color: $button-modal-apply-color; 48 | background: $button-modal-apply-bg-color; 49 | &:hover{ 50 | color: $button-modal-apply-active-color; 51 | background: $button-modal-apply-active-bg-color; 52 | } 53 | } 54 | } 55 | 56 | } 57 | 58 | #linkeditor{ 59 | width: 400px; 60 | height: 230px; 61 | } 62 | 63 | #linkeditor{ 64 | width: 400px; 65 | height: 230px; 66 | } 67 | 68 | #imageeditor{ 69 | width: 750px; 70 | height: 500px; 71 | 72 | .input-group{ 73 | width:29%; 74 | float:left; 75 | 76 | .input-control-small{ 77 | width: 30%; 78 | } 79 | } 80 | 81 | .slim{ 82 | width: 69%; 83 | float:right; 84 | height: 370px; 85 | margin-top: 20px 86 | } 87 | } 88 | 89 | #bgeditor{ 90 | width: 750px; 91 | height: 500px; 92 | 93 | .slim{ 94 | height: 370px; 95 | margin-top: 20px 96 | } 97 | } 98 | 99 | #sendyhelpers { 100 | width: 750px; 101 | height: 500px; 102 | 103 | pre{ 104 | padding: 2px 4px; 105 | color: #d14; 106 | background-color: #F9F9F9; 107 | border: 1px solid #f2f2f2; 108 | } 109 | } 110 | 111 | #campaignmodal { 112 | width: 750px; 113 | height: 500px; 114 | 115 | .input-row{ 116 | overflow: hidden; 117 | 118 | .checkbox-wrapper{ 119 | height:100px; 120 | overflow-y: scroll; 121 | 122 | label{ 123 | display: block; 124 | } 125 | } 126 | 127 | .input-group{ 128 | float: left; 129 | width: 49%; 130 | 131 | textarea{ 132 | height: 100px; 133 | resize: none; 134 | width: 100%; 135 | } 136 | 137 | &:first-child{ 138 | float: left; 139 | } 140 | &:last-child{ 141 | float: right; 142 | } 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/sass/_module.scss: -------------------------------------------------------------------------------- 1 | 2 | .module-code-container { 3 | position: relative; 4 | .module-code-controls { 5 | position: absolute; 6 | z-index: 99; 7 | top: 0px; 8 | right: 0px; 9 | display: -webkit-box; 10 | display: -ms-flexbox; 11 | display: flex; 12 | -webkit-box-pack: end; 13 | -ms-flex-pack: end; 14 | justify-content: flex-end; 15 | .btn-code-control { 16 | cursor: pointer; 17 | width: 30px; 18 | height: 30px; 19 | line-height: 30px; 20 | text-align: center; 21 | color: $button-code-color; 22 | background: $button-code-bg-color; 23 | } 24 | 25 | .btn-code-update{ 26 | color: $button-code-apply-color; 27 | background: $button-code-apply-bg-color; 28 | &:hover{ 29 | color: $button-code-apply-active-color; 30 | background: $button-code-apply-active-bg-color; 31 | } 32 | } 33 | 34 | .btn-code-cancel{ 35 | color: $button-code-cancel-color; 36 | background: $button-code-cancel-bg-color; 37 | &:hover{ 38 | color: $button-code-cancel-active-color; 39 | background: $button-code-cancel-active-bg-color; 40 | } 41 | } 42 | 43 | } 44 | } 45 | 46 | .editor .module-placeholder { 47 | padding: 20px; 48 | border: 2px dashed $placeholder-border-color; 49 | display: -webkit-box; 50 | display: -ms-flexbox; 51 | display: flex; 52 | -webkit-box-align: center; 53 | -ms-flex-align: center; 54 | align-items: center; 55 | -webkit-box-pack: center; 56 | -ms-flex-pack: center; 57 | justify-content: center; 58 | min-height: 180px; 59 | &:after { 60 | text-align: center; 61 | font-size: 72px; 62 | color: $placeholder-color; 63 | content: "\f019"; 64 | font-family: 'emailbuilder-icon-font'; 65 | } 66 | } 67 | 68 | 69 | .editor .module-container { 70 | position: relative; 71 | > table { 72 | position: relative; 73 | } 74 | } 75 | .editor .module-controls-container { 76 | position: absolute; 77 | top: 0; 78 | left: 0; 79 | z-index: 99; 80 | .btn-module { 81 | cursor: pointer; 82 | width: 40px; 83 | height: 40px; 84 | line-height: 40px; 85 | text-align: center; 86 | color: $button-module-color; 87 | background: $button-module-bg-color; 88 | } 89 | 90 | .btn-module-sort{ 91 | color: $button-module-sort-color; 92 | background: $button-module-sort-bg-color; 93 | 94 | &:hover{ 95 | color: $button-module-sort-active-color; 96 | background: $button-module-sort-active-bg-color; 97 | } 98 | } 99 | 100 | .btn-module-duplicate{ 101 | color: $button-module-copy-color; 102 | background: $button-module-copy-bg-color; 103 | 104 | &:hover{ 105 | color: $button-module-copy-active-color; 106 | background: $button-module-copy-active-bg-color; 107 | } 108 | } 109 | 110 | .btn-module-code{ 111 | color: $button-module-code-color; 112 | background: $button-module-code-bg-color; 113 | 114 | &:hover{ 115 | color: $button-module-code-active-color; 116 | background: $button-module-code-active-bg-color; 117 | } 118 | } 119 | 120 | .btn-module-delete{ 121 | color: $button-module-remove-color; 122 | background: $button-module-remove-bg-color; 123 | 124 | &:hover{ 125 | color: $button-module-remove-active-color; 126 | background: $button-module-remove-active-bg-color; 127 | } 128 | } 129 | 130 | } -------------------------------------------------------------------------------- /src/sass/_second_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar-second { 2 | display: none; 3 | width: 290px; 4 | padding: 0px 5px; 5 | height: 100%; 6 | position: fixed; 7 | z-index: 100; 8 | background: $second-sidebar-bg-color; 9 | overflow-y: auto; 10 | .sidebar-inner { 11 | display: none; 12 | padding-top: 20px; 13 | width: 250px; 14 | margin: 0 auto; 15 | .inner-header { 16 | padding: 15px 0px 30px 0px; 17 | font-family: 'Roboto', sans-serif; 18 | color: $second-sidebar-header-color; 19 | text-align: center; 20 | } 21 | .inner-search { 22 | padding: 15px 0px 30px 0px; 23 | .search-container { 24 | width: 210px; 25 | margin: 0 auto; 26 | .input-search { 27 | width: 160px; 28 | border: none; 29 | padding: 3px 0px 3px 3px; 30 | outline: none; 31 | border: 2px solid $second-sidebar-input-border-color; 32 | height: 30px; 33 | line-height: 30px; 34 | } 35 | .btn-search { 36 | right: -5px; 37 | top: 0px; 38 | background: none; 39 | border: none; 40 | outline: none; 41 | cursor: pointer; 42 | height: 40px; 43 | width: 40px; 44 | background-color: $button-module-bgoption-bg-color; 45 | color: $button-module-bgoption-color; 46 | 47 | &:hover{ 48 | background-color: $button-module-bgoption-active-color; 49 | color: $button-module-bgoption-active-bg-color; 50 | } 51 | } 52 | } 53 | } 54 | .inner-content { 55 | .module-item { 56 | margin-bottom: 20px; 57 | cursor: pointer; 58 | color: $second-sidebar-module-color; 59 | .module-thumb, .module-title { 60 | text-align: center; 61 | text-transform: uppercase; 62 | } 63 | &:hover { 64 | color: $second-sidebar-module-color; 65 | } 66 | } 67 | .styles-group { 68 | margin-bottom: 20px; 69 | color: $option-main-color; 70 | font-size: 12px; 71 | .group-header { 72 | font-family: 'Roboto', sans-serif; 73 | margin-bottom: 20px; 74 | padding-bottom: 10px; 75 | border-bottom: 1px solid $option-border-color; 76 | } 77 | .group-styles-inner .style-item { 78 | padding: 10px 0px; 79 | display: -webkit-box; 80 | display: -ms-flexbox; 81 | display: flex; 82 | -webkit-box-pack: justify; 83 | -ms-flex-pack: justify; 84 | justify-content: space-between; 85 | .style-label { 86 | color: $option-label-color; 87 | } 88 | .style-control { 89 | color: $option-main-color; 90 | } 91 | .style-control>span{ 92 | padding:5px; 93 | word-break: break-all; 94 | } 95 | .style-control>button{ 96 | border:none; 97 | cursor: pointer; 98 | text-align: center; 99 | text-decoration: none; 100 | padding: 5px 10px; 101 | color: $button-module-bgoption-color; 102 | background: $button-module-bgoption-bg-color; 103 | 104 | &:hover{ 105 | color: $button-module-bgoption-active-color; 106 | background: $button-module-bgoption-active-bg-color; 107 | } 108 | } 109 | .style-control>select{ 110 | padding: 2px 20px; 111 | } 112 | .style-control .minicolors-input{ 113 | border: none; 114 | padding: 5px 10px; 115 | } 116 | .style-control .minicolors-swatch{ 117 | top: 0px; 118 | right: 0px; 119 | height: 30px; 120 | border-right: none; 121 | border-top: none; 122 | border-bottom: none; 123 | width: 30px; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | a { 130 | color: $option-main-color; 131 | text-decoration: none; 132 | } 133 | } -------------------------------------------------------------------------------- /src/sass/_transitions.scss: -------------------------------------------------------------------------------- 1 | .main-form-container, 2 | .sidebar-main, 3 | .sidebar-second, 4 | .sidebar-top, 5 | .module-controls-container, 6 | .module-code-controls, 7 | .modal-controls 8 | { 9 | a, 10 | button, 11 | .item, 12 | .subnav-item, 13 | .btn-code-control, 14 | .btn-module, 15 | .btn-form-action{ 16 | transition: all 0.2s ease; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/sass/_typography.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 14px; 3 | } 4 | 5 | h2, h3, h4, h5, h6 { 6 | font-weight: normal; 7 | } 8 | 9 | h1 { 10 | text-align: center; 11 | font-weight: normal; 12 | margin: 0; 13 | } 14 | 15 | h2 { 16 | text-align: center; 17 | margin-top: 0; 18 | margin-bottom: 14px; 19 | } 20 | 21 | h3 { 22 | font-size: 18px; 23 | } -------------------------------------------------------------------------------- /src/sass/editor.scss: -------------------------------------------------------------------------------- 1 | @import "external"; 2 | 3 | @import "colors"; 4 | 5 | @import "fonts"; 6 | @import "typography"; 7 | 8 | @import "transitions"; 9 | @import "base"; 10 | @import "main_sidebar"; 11 | @import "second_sidebar"; 12 | @import "modal"; 13 | @import "module"; -------------------------------------------------------------------------------- /templates/default/img/bg-main-b.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/bg-main-b.jpg -------------------------------------------------------------------------------- /templates/default/img/bg-sep-a.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/bg-sep-a.jpg -------------------------------------------------------------------------------- /templates/default/img/icon32-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon32-1.png -------------------------------------------------------------------------------- /templates/default/img/icon32-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon32-2.png -------------------------------------------------------------------------------- /templates/default/img/icon32-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon32-3.png -------------------------------------------------------------------------------- /templates/default/img/icon32-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon32-4.png -------------------------------------------------------------------------------- /templates/default/img/icon64-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon64-1.png -------------------------------------------------------------------------------- /templates/default/img/icon64-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon64-2.png -------------------------------------------------------------------------------- /templates/default/img/icon64-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon64-3.png -------------------------------------------------------------------------------- /templates/default/img/icon64-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/icon64-7.png -------------------------------------------------------------------------------- /templates/default/img/img183-7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img183-7.jpg -------------------------------------------------------------------------------- /templates/default/img/img183-8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img183-8.jpg -------------------------------------------------------------------------------- /templates/default/img/img183-9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img183-9.jpg -------------------------------------------------------------------------------- /templates/default/img/img250-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img250-1.jpg -------------------------------------------------------------------------------- /templates/default/img/img250-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img250-2.jpg -------------------------------------------------------------------------------- /templates/default/img/img287-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img287-1.jpg -------------------------------------------------------------------------------- /templates/default/img/img287-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img287-2.jpg -------------------------------------------------------------------------------- /templates/default/img/img287-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img287-5.jpg -------------------------------------------------------------------------------- /templates/default/img/img287-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img287-6.jpg -------------------------------------------------------------------------------- /templates/default/img/img600.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/img/img600.jpg -------------------------------------------------------------------------------- /templates/default/thumbnails/article-2-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-2-alt.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-2-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-2-img.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-2-no-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-2-no-img.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-2.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-3.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-full.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-full.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-left.png -------------------------------------------------------------------------------- /templates/default/thumbnails/article-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/article-right.png -------------------------------------------------------------------------------- /templates/default/thumbnails/contact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/contact.png -------------------------------------------------------------------------------- /templates/default/thumbnails/features-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/features-3.png -------------------------------------------------------------------------------- /templates/default/thumbnails/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/header.png -------------------------------------------------------------------------------- /templates/default/thumbnails/headline-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/headline-content.png -------------------------------------------------------------------------------- /templates/default/thumbnails/line-separator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/line-separator.png -------------------------------------------------------------------------------- /templates/default/thumbnails/pricing-table-2-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/pricing-table-2-alt.png -------------------------------------------------------------------------------- /templates/default/thumbnails/separator-alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/separator-alt.png -------------------------------------------------------------------------------- /templates/default/thumbnails/social-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/social-4.png -------------------------------------------------------------------------------- /templates/default/thumbnails/space.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderbag/Sendy-Email-Builder/f796b73cc965549c9c18869efdf110d5d4399c8c/templates/default/thumbnails/space.png -------------------------------------------------------------------------------- /theme.php: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 |
12 |

Create new campaign

13 |

Please, choose email template

14 | 15 |
16 | 24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 | Logout 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | let path = require('path'); 2 | let glob = require('glob'); 3 | let webpack = require('webpack'); 4 | let Mix = require('laravel-mix').config; 5 | let webpackPlugins = require('laravel-mix').plugins; 6 | 7 | /* 8 | |-------------------------------------------------------------------------- 9 | | Mix Initialization 10 | |-------------------------------------------------------------------------- 11 | | 12 | | As our first step, we'll require the project's Laravel Mix file 13 | | and record the user's requested compilation and build steps. 14 | | Once those steps have been recorded, we may get to work. 15 | | 16 | */ 17 | 18 | Mix.initialize(); 19 | 20 | 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Webpack Context 25 | |-------------------------------------------------------------------------- 26 | | 27 | | This prop will determine the appropriate context, when running Webpack. 28 | | Since you have the option of publishing this webpack.config.js file 29 | | to your project root, we will dynamically set the path for you. 30 | | 31 | */ 32 | 33 | module.exports.context = Mix.Paths.root(); 34 | 35 | 36 | 37 | /* 38 | |-------------------------------------------------------------------------- 39 | | Webpack Entry 40 | |-------------------------------------------------------------------------- 41 | | 42 | | We'll first specify the entry point for Webpack. By default, we'll 43 | | assume a single bundled file, but you may call Mix.extract() 44 | | to make a separate bundle specifically for vendor libraries. 45 | | 46 | */ 47 | 48 | module.exports.entry = Mix.entry().get(); 49 | 50 | 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Webpack Output 55 | |-------------------------------------------------------------------------- 56 | | 57 | | Webpack naturally requires us to specify our desired output path and 58 | | file name. We'll simply echo what you passed to with Mix.js(). 59 | | Note that, for Mix.version(), we'll properly hash the file. 60 | | 61 | */ 62 | 63 | module.exports.output = Mix.output(); 64 | 65 | 66 | 67 | /* 68 | |-------------------------------------------------------------------------- 69 | | Rules 70 | |-------------------------------------------------------------------------- 71 | | 72 | | Webpack rules allow us to register any number of loaders and options. 73 | | Out of the box, we'll provide a handful to get you up and running 74 | | as quickly as possible, though feel free to add to this list. 75 | | 76 | */ 77 | 78 | let plugins = []; 79 | 80 | if (Mix.options.extractVueStyles) { 81 | var vueExtractTextPlugin = Mix.vueExtractTextPlugin(); 82 | 83 | plugins.push(vueExtractTextPlugin); 84 | } 85 | 86 | let rules = [ 87 | { 88 | test: /\.vue$/, 89 | loader: 'vue-loader', 90 | options: { 91 | loaders: Mix.options.extractVueStyles ? { 92 | js: 'babel-loader' + Mix.babelConfig(), 93 | scss: vueExtractTextPlugin.extract({ 94 | use: 'css-loader!sass-loader', 95 | fallback: 'vue-style-loader' 96 | }), 97 | sass: vueExtractTextPlugin.extract({ 98 | use: 'css-loader!sass-loader?indentedSyntax', 99 | fallback: 'vue-style-loader' 100 | }), 101 | stylus: vueExtractTextPlugin.extract({ 102 | use: 'css-loader!stylus-loader?paths[]=node_modules', 103 | fallback: 'vue-style-loader' 104 | }), 105 | css: vueExtractTextPlugin.extract({ 106 | use: 'css-loader', 107 | fallback: 'vue-style-loader' 108 | }) 109 | }: { 110 | js: 'babel-loader' + Mix.babelConfig(), 111 | scss: 'vue-style-loader!css-loader!sass-loader', 112 | sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax', 113 | stylus: 'vue-style-loader!css-loader!stylus-loader?paths[]=node_modules' 114 | }, 115 | 116 | postcss: Mix.options.postCss 117 | } 118 | }, 119 | 120 | { 121 | test: /\.jsx?$/, 122 | exclude: /(node_modules|bower_components)/, 123 | loader: 'babel-loader' + Mix.babelConfig() 124 | }, 125 | 126 | { 127 | test: /\.css$/, 128 | loaders: ['style-loader', 'css-loader'] 129 | }, 130 | 131 | { 132 | test: /\.s[ac]ss$/, 133 | include: /node_modules/, 134 | loaders: ['style-loader', 'css-loader', 'sass-loader'] 135 | }, 136 | 137 | { 138 | test: /\.html$/, 139 | loaders: ['html-loader'] 140 | }, 141 | 142 | { 143 | test: /\.(png|jpe?g|gif)$/, 144 | loaders: [ 145 | { 146 | loader: 'file-loader', 147 | options: { 148 | name: path => { 149 | if (! /node_modules|bower_components/.test(path)) { 150 | return 'images/[name].[ext]?[hash]'; 151 | } 152 | 153 | return 'images/vendor/' + path 154 | .replace(/\\/g, '/') 155 | .replace( 156 | /((.*(node_modules|bower_components))|images|image|img|assets)\//g, '' 157 | ) + '?[hash]'; 158 | }, 159 | publicPath: Mix.options.resourceRoot 160 | } 161 | }, 162 | 'img-loader' 163 | ] 164 | }, 165 | 166 | { 167 | test: /\.(woff2?|ttf|eot|svg|otf)$/, 168 | loader: 'file-loader', 169 | options: { 170 | name: path => { 171 | if (! /node_modules|bower_components/.test(path)) { 172 | return 'fonts/[name].[ext]?[hash]'; 173 | } 174 | 175 | return 'fonts/vendor/' + path 176 | .replace(/\\/g, '/') 177 | .replace( 178 | /((.*(node_modules|bower_components))|fonts|font|assets)\//g, '' 179 | ) + '?[hash]'; 180 | }, 181 | publicPath: Mix.options.resourceRoot 182 | } 183 | }, 184 | 185 | { 186 | test: /\.(cur|ani)$/, 187 | loader: 'file-loader', 188 | options: { 189 | name: '[name].[ext]?[hash]', 190 | publicPath: Mix.options.resourceRoot 191 | } 192 | } 193 | ]; 194 | 195 | if (Mix.preprocessors) { 196 | Mix.preprocessors.forEach(preprocessor => { 197 | rules.push(preprocessor.rules()); 198 | 199 | plugins.push(preprocessor.extractPlugin); 200 | }); 201 | } 202 | 203 | module.exports.module = { rules }; 204 | 205 | 206 | 207 | /* 208 | |-------------------------------------------------------------------------- 209 | | Resolve 210 | |-------------------------------------------------------------------------- 211 | | 212 | | Here, we may set any options/aliases that affect Webpack's resolving 213 | | of modules. To begin, we will provide the necessary Vue alias to 214 | | load the Vue common library. You may delete this, if needed. 215 | | 216 | */ 217 | 218 | module.exports.resolve = { 219 | extensions: ['*', '.js', '.jsx', '.vue'], 220 | 221 | alias: { 222 | 'vue$': 'vue/dist/vue.common.js' 223 | } 224 | }; 225 | 226 | 227 | 228 | /* 229 | |-------------------------------------------------------------------------- 230 | | Stats 231 | |-------------------------------------------------------------------------- 232 | | 233 | | By default, Webpack spits a lot of information out to the terminal, 234 | | each you time you compile. Let's keep things a bit more minimal 235 | | and hide a few of those bits and pieces. Adjust as you wish. 236 | | 237 | */ 238 | 239 | module.exports.stats = { 240 | hash: false, 241 | version: false, 242 | timings: false, 243 | children: false, 244 | errors: false 245 | }; 246 | 247 | process.noDeprecation = true; 248 | 249 | module.exports.performance = { hints: false }; 250 | 251 | 252 | 253 | /* 254 | |-------------------------------------------------------------------------- 255 | | Devtool 256 | |-------------------------------------------------------------------------- 257 | | 258 | | Sourcemaps allow us to access our original source code within the 259 | | browser, even if we're serving a bundled script or stylesheet. 260 | | You may activate sourcemaps, by adding Mix.sourceMaps(). 261 | | 262 | */ 263 | 264 | module.exports.devtool = Mix.options.sourcemaps; 265 | 266 | 267 | 268 | /* 269 | |-------------------------------------------------------------------------- 270 | | Webpack Dev Server Configuration 271 | |-------------------------------------------------------------------------- 272 | | 273 | | If you want to use that flashy hot module replacement feature, then 274 | | we've got you covered. Here, we'll set some basic initial config 275 | | for the Node server. You very likely won't want to edit this. 276 | | 277 | */ 278 | module.exports.devServer = { 279 | historyApiFallback: true, 280 | noInfo: true, 281 | compress: true, 282 | quiet: true 283 | }; 284 | 285 | 286 | 287 | /* 288 | |-------------------------------------------------------------------------- 289 | | Plugins 290 | |-------------------------------------------------------------------------- 291 | | 292 | | Lastly, we'll register a number of plugins to extend and configure 293 | | Webpack. To get you started, we've included a handful of useful 294 | | extensions, for versioning, OS notifications, and much more. 295 | | 296 | */ 297 | 298 | plugins.push( 299 | new webpack.ProvidePlugin(Mix.autoload || {}), 300 | 301 | new webpackPlugins.FriendlyErrorsWebpackPlugin({ clearConsole: Mix.options.clearConsole }), 302 | 303 | new webpackPlugins.StatsWriterPlugin({ 304 | filename: 'mix-manifest.json', 305 | transform: Mix.manifest.transform.bind(Mix.manifest), 306 | }), 307 | 308 | new webpack.LoaderOptionsPlugin({ 309 | minimize: Mix.inProduction, 310 | options: { 311 | postcss: Mix.options.postCss, 312 | context: __dirname, 313 | output: { path: './' } 314 | } 315 | }) 316 | ); 317 | 318 | if (Mix.browserSync) { 319 | plugins.push( 320 | new webpackPlugins.BrowserSyncPlugin( 321 | Object.assign({ 322 | host: 'localhost', 323 | port: 3000, 324 | proxy: 'app.dev', 325 | files: [ 326 | 'app/**/*.php', 327 | 'resources/views/**/*.php', 328 | 'public/js/**/*.js', 329 | 'public/css/**/*.css' 330 | ] 331 | }, Mix.browserSync), 332 | { 333 | reload: false 334 | } 335 | ) 336 | ); 337 | } 338 | 339 | if (Mix.options.notifications) { 340 | plugins.push( 341 | new webpackPlugins.WebpackNotifierPlugin({ 342 | title: 'Laravel Mix', 343 | alwaysNotify: true, 344 | contentImage: Mix.Paths.root('node_modules/laravel-mix/icons/laravel.png') 345 | }) 346 | ); 347 | } 348 | 349 | if (Mix.copy) { 350 | Mix.copy.forEach(copy => { 351 | plugins.push( 352 | new webpackPlugins.CopyWebpackPlugin([copy]) 353 | ); 354 | }); 355 | } 356 | 357 | if (Mix.entry().hasExtractions()) { 358 | plugins.push( 359 | new webpack.optimize.CommonsChunkPlugin({ 360 | names: Mix.entry().getExtractions(), 361 | minChunks: Infinity 362 | }) 363 | ); 364 | } 365 | 366 | if (Mix.options.versioning) { 367 | plugins.push( 368 | new webpack[Mix.inProduction ? 'HashedModuleIdsPlugin': 'NamedModulesPlugin'](), 369 | new webpackPlugins.WebpackChunkHashPlugin() 370 | ); 371 | } 372 | 373 | if (Mix.options.purifyCss) { 374 | let PurifyCSSPlugin = require('purifycss-webpack'); 375 | 376 | // By default, we'll scan all Blade and Vue files in our project. 377 | let paths = glob.sync(Mix.Paths.root('resources/views/**/*.blade.php')).concat( 378 | Mix.entry().scripts.reduce((carry, js) => { 379 | return carry.concat(glob.sync(js.base + '/**/*.vue')); 380 | }, []) 381 | ); 382 | 383 | plugins.push(new PurifyCSSPlugin( 384 | Object.assign({ paths }, Mix.options.purifyCss, { minimize: Mix.inProduction }) 385 | )); 386 | } 387 | 388 | if (Mix.inProduction) { 389 | plugins.push( 390 | new webpack.DefinePlugin({ 391 | 'process.env': { 392 | NODE_ENV: '"production"' 393 | } 394 | }) 395 | ); 396 | 397 | if (Mix.options.uglify) { 398 | plugins.push( 399 | new webpack.optimize.UglifyJsPlugin(Mix.options.uglify) 400 | ); 401 | } 402 | } 403 | 404 | plugins.push( 405 | new webpackPlugins.WebpackOnBuildPlugin( 406 | stats => global.events.fire('build', stats) 407 | ) 408 | ); 409 | 410 | if (! Mix.entry().hasScripts()) { 411 | plugins.push(new webpackPlugins.MockEntryPlugin(Mix.output().path)); 412 | } 413 | 414 | module.exports.plugins = plugins; 415 | 416 | 417 | 418 | /* 419 | |-------------------------------------------------------------------------- 420 | | Mix Finalizing 421 | |-------------------------------------------------------------------------- 422 | | 423 | | Now that we've declared the entirety of our Webpack configuration, the 424 | | final step is to scan for any custom configuration in the Mix file. 425 | | If mix.webpackConfig() is called, we'll merge it in, and build! 426 | | 427 | */ 428 | 429 | if (Mix.webpackConfig) { 430 | module.exports = require('webpack-merge').smart( 431 | module.exports, Mix.webpackConfig 432 | ); 433 | } 434 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | let mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for your application, as well as bundling up your JS files. 11 | | 12 | */ 13 | 14 | mix.js('src/js/editor.js', 'dist/js/') 15 | .js('src/js/custom.js', 'dist/js/') 16 | .sass('src/sass/editor.scss', 'dist/css/') 17 | .copy('src/images/*', 'dist/images/') 18 | .copy('src/fonts/*', 'dist/fonts/') 19 | .options({ 20 | processCssUrls: false 21 | }); 22 | 23 | if (mix.config.inProduction) { 24 | mix.disableNotifications(); 25 | } 26 | 27 | 28 | 29 | // Full API 30 | // mix.js(src, output); 31 | // mix.react(src, output); <-- Identical to mix.js(), but registers React Babel compilation. 32 | // mix.extract(vendorLibs); 33 | // mix.sass(src, output); 34 | // mix.less(src, output); 35 | // mix.stylus(src, output); 36 | // mix.browserSync('my-site.dev'); 37 | // mix.combine(files, destination); 38 | // mix.babel(files, destination); <-- Identical to mix.combine(), but also includes Babel compilation. 39 | // mix.copy(from, to); 40 | // mix.copyDirectory(fromDir, toDir); 41 | // mix.minify(file); 42 | // mix.sourceMaps(); // Enable sourcemaps 43 | // mix.version(); // Enable versioning. 44 | // mix.disableNotifications(); 45 | // mix.setPublicPath('path/to/public'); 46 | // mix.setResourceRoot('prefix/for/resource/locators'); 47 | // mix.autoload({}); <-- Will be passed to Webpack's ProvidePlugin. 48 | // mix.webpackConfig({}); <-- Override webpack.config.js, without editing the file directly. 49 | // mix.then(function () {}) <-- Will be triggered each time Webpack finishes building. 50 | // mix.options({ 51 | // extractVueStyles: false, // Extract .vue component styling to file, rather than inline. 52 | // processCssUrls: true, // Process/optimize relative stylesheet url()'s. Set to false, if you don't want them touched. 53 | // purifyCss: false, // Remove unused CSS selectors. 54 | // uglify: {}, // Uglify-specific options. https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin 55 | // postCss: [] // Post-CSS options: https://github.com/postcss/postcss/blob/master/docs/plugins.md 56 | // }); 57 | --------------------------------------------------------------------------------