diff --git a/docs/dev-setup-non-vagrant.md b/docs/dev-setup-non-vagrant.md index 4bc45d5def..e528ffc926 100644 --- a/docs/dev-setup-non-vagrant.md +++ b/docs/dev-setup-non-vagrant.md @@ -322,6 +322,7 @@ yarn install sudo mkdir /srv/zulip-emoji-cache sudo chown -R `whoami`:`whoami` /srv/zulip-emoji-cache ./tools/setup/emoji/build_emoji +./tools/inline-email-css ./tools/setup/build_pygments_data.py ./scripts/setup/generate_secrets.py --development if [ $(uname) = "OpenBSD" ]; then diff --git a/tools/inline-email-css b/tools/inline-email-css new file mode 100755 index 0000000000..18e4c8019a --- /dev/null +++ b/tools/inline-email-css @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import os + +from premailer import Premailer +from cssutils import profile +from cssutils.profiles import Profiles, properties, macros + +ZULIP_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../') + +if __name__ == "__main__": + escaped_jinja2_characters = [('%7B%7B%20', '{{ '), ('%20%7D%7D', ' }}')] + + templates_to_inline = set() + for f in os.listdir(os.path.join(ZULIP_PATH, 'templates', 'zerver', 'emails')): + template = f.split('.source.html')[0] + if template != f: + templates_to_inline.add(template) + + # These properties are not supported by cssutils by default and will + # result in warnings when premailer package is run. + properties[Profiles.CSS_LEVEL_2]['-ms-interpolation-mode'] = r'none|bicubic|nearest-neighbor' + properties[Profiles.CSS_LEVEL_2]['-ms-text-size-adjust'] = r'none|auto|{percentage}' + properties[Profiles.CSS_LEVEL_2]['mso-table-lspace'] = r'0|{num}(pt)' + properties[Profiles.CSS_LEVEL_2]['mso-table-rspace'] = r'0|{num}(pt)' + properties[Profiles.CSS_LEVEL_2]['-webkit-text-size-adjust'] = r'none|auto|{percentage}' + properties[Profiles.CSS_LEVEL_2]['-webkit-font-smoothing'] = r'none|antialiased|subpixel-antialiased' + properties[Profiles.CSS_LEVEL_2]['mso-hide'] = 'all' + properties[Profiles.CSS_LEVEL_2]['pointer-events'] = (r'auto|none|visiblePainted|visibleFill|visibleStroke|' + r'visible|painted|fill|stroke|all|inherit') + + profile.addProfiles([(Profiles.CSS_LEVEL_2, properties[Profiles.CSS_LEVEL_2], macros[Profiles.CSS_LEVEL_2])]) + + os.chdir(os.path.join(ZULIP_PATH, 'templates', 'zerver', 'emails')) + + for template in templates_to_inline: + template_html_source = template + ".source.html" + final_template = template + ".html" + + with open(template_html_source) as template_source_file: + template_str = template_source_file.read() + output = Premailer(template_str, + external_styles=["email.css"]).transform() + + for escaped, original in escaped_jinja2_characters: + output = output.replace(escaped, original) + + with open(final_template, 'w') as final_template_file: + final_template_file.write(output) diff --git a/tools/lib/provision.py b/tools/lib/provision.py index 66cfe8e1c9..2aa563f3df 100755 --- a/tools/lib/provision.py +++ b/tools/lib/provision.py @@ -279,6 +279,7 @@ def main(options): run(["tools/setup/build_pygments_data.py"]) run(["scripts/setup/generate_secrets.py", "--development"]) run(["tools/update-authors-json", "--use-fixture"]) + run(["tools/inline-email-css"]) if options.is_travis and not options.is_production_travis: run(["sudo", "service", "rabbitmq-server", "restart"]) run(["sudo", "service", "redis-server", "restart"]) diff --git a/tools/update-prod-static b/tools/update-prod-static index 1d0c2cd2e2..43e0c12c7e 100755 --- a/tools/update-prod-static +++ b/tools/update-prod-static @@ -41,6 +41,10 @@ setup_node_modules(production=True, stdout=fp, stderr=fp) subprocess.check_call(['./tools/setup/emoji/build_emoji'], stdout=fp, stderr=fp) +# Inline CSS in emails +subprocess.check_call(['./tools/inline-email-css'], + stdout=fp, stderr=fp) + # Build pygment data subprocess.check_call(['./tools/setup/build_pygments_data.py'], stdout=fp, stderr=fp)