Email styling using email.less (#3704)

* DRY font-family declarations

* Add email.less, inline styles using premailer

* min-width 100% for mobile email clients

* Emails without header have default 100% width (like before)

* Include email.css for all apps

* Keep !important declarations

* Add test case for inlining css

* Ignore important rules in css

* minor
This commit is contained in:
Faris Ansari 2017-07-19 16:21:06 +05:30 committed by Makarand Bauskar
parent 4f7feddb6a
commit db22aa1c87
12 changed files with 206 additions and 46 deletions

View file

@ -247,7 +247,27 @@ def get_formatted_html(subject, message, footer=None, print_html=None, email_acc
"subject": subject
})
return scrub_urls(rendered_email)
sanitized_html = scrub_urls(rendered_email)
transformed_html = inline_style_in_html(sanitized_html)
return transformed_html
def inline_style_in_html(html):
''' Convert email.css and html to inline-styled html
'''
from premailer import Premailer
apps = frappe.get_installed_apps()
css_files = []
for app in apps:
path = 'assets/{0}/css/email.css'.format(app)
if os.path.exists(os.path.abspath(path)):
css_files.append(path)
p = Premailer(html=html, external_styles=css_files, strip_important=False)
return p.transform()
def add_attachment(fname, fcontent, content_type=None,
parent=None, content_id=None, inline=False):
@ -407,7 +427,6 @@ def get_header():
else:
email_brand_image = default_brand_image
email_brand_image = default_brand_image
brand_text = frappe.get_hooks('app_title')[-1]
email_header, text = get_email_from_template('email_header', {

View file

@ -3,7 +3,8 @@
from __future__ import unicode_literals
import frappe, unittest, os, base64
from frappe.email.email_body import replace_filename_with_cid, get_email
from frappe.email.email_body import (replace_filename_with_cid,
get_email, inline_style_in_html)
class TestEmailBody(unittest.TestCase):
def setUp(self):
@ -95,6 +96,17 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
'''.format(inline_images[0].get('content_id'))
self.assertEquals(message, processed_message)
def test_inline_styling(self):
html = '''
<h3>Hi John</h3>
<p>This is a test email</p>
'''
transformed_html = '''
<h3>Hi John</h3>
<p style="margin:1em 0 !important">This is a test email</p>
'''
self.assertTrue(transformed_html in inline_style_in_html(html))
def fixed_column_width(string, chunk_size):
parts = [string[0+i:chunk_size+i] for i in range(0, len(string), chunk_size)]

View file

@ -0,0 +1,64 @@
/* csslint ignore:start */
body {
line-height: 1.5;
color: #36414C;
}
p {
margin: 1em 0 !important;
}
.body-table,
.email-body tr,
.email-footer tr {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.email-header,
.email-body,
.email-footer {
width: 100% !important;
min-width: 100% !important;
}
.email-body tr {
font-size: 14px;
}
.email-footer {
border-top: 1px solid #d1d8dd;
}
.email-footer tr {
font-size: 12px;
}
.email-header {
background: #fafbfc;
border: 1px solid #d1d8dd;
border-radius: 3px 3px 0 0;
}
.email-header .brand-image {
width: 24px;
height: 24px;
display: block;
}
.body-table.has-header .email-body {
border: 1px solid #d1d8dd;
border-radius: 0 0 3px 3px;
border-top: none;
}
.body-table.has-header .email-footer {
border-top: none;
}
.btn {
text-decoration: none;
padding: 7px 10px;
font-size: 12px;
border: 1px solid;
border-radius: 3px;
}
.btn.btn-default {
color: #fff;
background-color: #f0f4f7;
border-color: transparent;
}
.btn.btn-primary {
color: #fff;
background-color: #5E64FF;
border-color: #444bff;
}
/* csslint ignore:end */

View file

@ -1,21 +1,8 @@
@import "variables.less";
@import "mixins.less";
// @import url(https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700);
//
// body {
// font-family: "Open Sans", "Helvetica", Arial, "sans-serif";
// }
html {
// overflow-x: hidden;
}
body {
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
font-family: @font-stack;
}
a {

View file

@ -8,10 +8,7 @@ body {
// position: relative;
-webkit-font-smoothing: antialiased;
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
font-family: @font-stack;
}

View file

@ -0,0 +1,78 @@
/* csslint ignore:start */
@import "variables.less";
body {
line-height: 1.5;
color: @text-color;
}
p {
margin: 1em 0 !important;
}
.body-table, .email-body tr, .email-footer tr {
font-family: @font-stack;
}
.email-header, .email-body, .email-footer {
width: 100% !important;
min-width: 100% !important;
}
.email-body tr {
font-size: @text-regular;
}
.email-footer {
border-top: 1px solid @border-color;
tr {
font-size: @text-medium;
}
}
.email-header {
background: @light-bg;
border: 1px solid @border-color;
border-radius: 3px 3px 0 0;
.brand-image {
width: 24px;
height: 24px;
display: block;
}
}
.body-table.has-header {
.email-body {
border: 1px solid @border-color;
border-radius: 0 0 3px 3px;
border-top: none;
}
.email-footer {
border-top: none;
}
}
.btn {
text-decoration: none;
padding: 7px 10px;
font-size: 12px;
border: 1px solid;
border-radius: 3px;
&.btn-default {
color: #fff;
background-color: #f0f4f7;
border-color: transparent;
}
&.btn-primary {
color: #fff;
background-color: @brand-primary;
border-color: #444bff;
}
}
/* csslint ignore:end */

View file

@ -49,6 +49,10 @@
@screen-sm: 991px;
@screen-md: 1199px;
@font-stack: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
// palette colors
@red: #FC4F51;
@red-light: #FD8B8B;

View file

@ -3,17 +3,9 @@
@import "avatar.less";
@import "indicator.less";
// html, body {
// font-family: "Open Sans", "Helvetica Neue", Serif;
// color: @text-light;
// }
body {
font-family: -apple-system, BlinkMacSystemFont,
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
"Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
color: @text-color;
font-family: @font-stack;
color: @text-color;
}
a& {

View file

@ -1,10 +1,12 @@
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="email_header">
<table class="email-header" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td width="35" height="50" style="border-bottom: 1px solid #d1d8dd;">
<img embed="{{brand_image}}" width="24" height="24" style="display: block;" alt="{{brand_text}}">
<td width="15"></td>
<td width="35" height="50">
<img class="brand-image" embed="{{brand_image}}" alt="{{brand_text}}">
</td>
<td style="border-bottom: 1px solid #d1d8dd;">
<td>
<p>{{ brand_text }}</p>
</td>
<td width="15"></td>
</tr>
</table>

View file

@ -1,7 +1,9 @@
<h3>{{_("Password Reset")}}</h3>
<br>
<p>{{_("Dear")}} {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},</p>
<p>{{_("Please click on the following link to set your new password")}}:</p>
<p><a href="{{ link }}">{{ link }}</a></p>
<p>{{_("Thank you")}},<br>
{{ user_fullname }}</p>
<p><a class="btn btn-primary" href="{{ link }}">Reset your password</a></p>
<p>
{{_("Thank you")}},<br>
{{ user_fullname }}
</p>

View file

@ -7,11 +7,12 @@
<title>{{ subject or "" }}</title>
</head>
<body style="line-height: 1.5; color: #36414C;">
<table border="0" cellpadding="0" cellspacing="0" height="100%" width="100%" id="body_table" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; font-size: 14px;">
<body>
<table class="body-table {% if header %}has-header{% endif %}" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%">
<tr>
<td align="center" valign="top">
<table border="0" cellpadding="0" cellspacing="0" width="600" id="email_container">
<table class="email-container" border="0" cellpadding="0" cellspacing="0"
width="{% if header %}600{% else %}100%{% endif %}">
<tr>
<td valign="top">
{{ header or "" }}
@ -19,22 +20,23 @@
</tr>
<tr>
<td valign="top">
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="email_body">
<table class="email-body" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td width="15"></td>
<td valign="top">
<p>{{ content }}</p>
<p>{{ signature }}</p>
</td>
<td width="15"></td>
</tr>
</table>
</td>
</tr>
<tr>
<td valign="top">
<table border="0" cellpadding="0" cellspacing="0" width="100%" id="email_footer">
<table class="email-footer" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" style="font-family: Helvetica, Arial, sans-serif; font-size: 11px;
margin-bottom: 15px; border-top: 1px solid #d1d8dd;" data-email-footer="true">
<td valign="top" data-email-footer="true">
{{ footer }}
</td>
</tr>

View file

@ -41,3 +41,4 @@ oauthlib
PyJWT
pypdf
openpyxl
premailer