From db22aa1c871fc5fbbf757e8106dc111e4790537b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 19 Jul 2017 16:21:06 +0530 Subject: [PATCH] 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 --- frappe/email/email_body.py | 23 +++++- frappe/email/test_email_body.py | 14 +++- frappe/public/css/email.css | 64 +++++++++++++++++ frappe/public/less/common.less | 15 +--- frappe/public/less/docs.less | 5 +- frappe/public/less/email.less | 78 +++++++++++++++++++++ frappe/public/less/variables.less | 4 ++ frappe/public/less/website.less | 12 +--- frappe/templates/emails/email_header.html | 10 +-- frappe/templates/emails/password_reset.html | 10 +-- frappe/templates/emails/standard.html | 16 +++-- requirements.txt | 1 + 12 files changed, 206 insertions(+), 46 deletions(-) create mode 100644 frappe/public/css/email.css create mode 100644 frappe/public/less/email.less diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 41753dbaf7..830895a41d 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -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', { diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py index fda4646b68..a1f1758e5d 100644 --- a/frappe/email/test_email_body.py +++ b/frappe/email/test_email_body.py @@ -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 = ''' +

Hi John

+

This is a test email

+''' + transformed_html = ''' +

Hi John

+

This is a test email

+''' + 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)] diff --git a/frappe/public/css/email.css b/frappe/public/css/email.css new file mode 100644 index 0000000000..57aeb6cb66 --- /dev/null +++ b/frappe/public/css/email.css @@ -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 */ diff --git a/frappe/public/less/common.less b/frappe/public/less/common.less index 5c5238d30b..38bcbf7f62 100644 --- a/frappe/public/less/common.less +++ b/frappe/public/less/common.less @@ -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 { diff --git a/frappe/public/less/docs.less b/frappe/public/less/docs.less index eef77ce552..f84b0fe960 100644 --- a/frappe/public/less/docs.less +++ b/frappe/public/less/docs.less @@ -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; } diff --git a/frappe/public/less/email.less b/frappe/public/less/email.less new file mode 100644 index 0000000000..d14cbe4561 --- /dev/null +++ b/frappe/public/less/email.less @@ -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 */ \ No newline at end of file diff --git a/frappe/public/less/variables.less b/frappe/public/less/variables.less index 7babbd06d3..7e427863df 100644 --- a/frappe/public/less/variables.less +++ b/frappe/public/less/variables.less @@ -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; diff --git a/frappe/public/less/website.less b/frappe/public/less/website.less index 6158fee65e..55b70f2d86 100644 --- a/frappe/public/less/website.less +++ b/frappe/public/less/website.less @@ -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& { diff --git a/frappe/templates/emails/email_header.html b/frappe/templates/emails/email_header.html index 5ddd436bc4..93c4794e2f 100644 --- a/frappe/templates/emails/email_header.html +++ b/frappe/templates/emails/email_header.html @@ -1,10 +1,12 @@ - +
- + - +
- {{brand_text}} + + {{brand_text}} +

{{ brand_text }}

\ No newline at end of file diff --git a/frappe/templates/emails/password_reset.html b/frappe/templates/emails/password_reset.html index 927c4c0bdd..586badec5a 100644 --- a/frappe/templates/emails/password_reset.html +++ b/frappe/templates/emails/password_reset.html @@ -1,7 +1,9 @@

{{_("Password Reset")}}

-
+

{{_("Dear")}} {{ first_name }}{% if last_name %} {{ last_name}}{% endif %},

{{_("Please click on the following link to set your new password")}}:

-

{{ link }}

-

{{_("Thank you")}},
-{{ user_fullname }}

\ No newline at end of file +

Reset your password

+

+ {{_("Thank you")}},
+ {{ user_fullname }} +

\ No newline at end of file diff --git a/frappe/templates/emails/standard.html b/frappe/templates/emails/standard.html index 5bc623d253..affabbc4c6 100644 --- a/frappe/templates/emails/standard.html +++ b/frappe/templates/emails/standard.html @@ -7,11 +7,12 @@ {{ subject or "" }} - - + +
- +