[IMP] mass_mailing: add preview block for mailings

PURPOSE

When displaying an email in a list the mail client (gmail, outlook...) computes
a preview based on the content. This preview is generally not well computed as
it contains a lot of garbage and tags while it should contain only relevant
text.

SPECIFICATIONS

To build this preview, all mail clients read the content of the email.
The only way to be able to customize the preview is to add an invisible
HTML element at the beginning of the email with the wanted preview text.

We add at the end of the preview `‌` (zero-width non-joiner) to
fill the end of the preview in order to not have the beginning of the
mail at the end of the preview. It doesn't work with simple space as
the mail clients trim each HTML element content.

Task ID-2172125
PR #49886
This commit is contained in:
std-odoo
2020-08-05 13:55:04 +00:00
committed by Thibault Delavallée
parent ac0579263b
commit ed4846dd9d
6 changed files with 113 additions and 4 deletions
+2 -2
View File
@@ -23,7 +23,7 @@ class MockLinkTracker(common.BaseCase):
def _get_href_from_anchor_id(self, body, anchor_id):
html = etree.fromstring(body)
return html.xpath("*[@id='%s']" % anchor_id)[0].attrib.get('href')
return html.xpath("//*[@id='%s']" % anchor_id)[0].attrib.get('href')
def _get_tracker_from_short_url(self, short_url):
code = self.env['link.tracker.code'].sudo().search([
@@ -41,7 +41,7 @@ class MockLinkTracker(common.BaseCase):
)
"""
(anchor_id, url, is_shortened) = link_info
anchor_href = self._get_href_from_anchor_id(body, anchor_id)
anchor_href = self._get_href_from_anchor_id("<div>%s</div>" % body, anchor_id)
if is_shortened:
self.assertTrue('/r/' in anchor_href, '%s should be shortened: %s' % (anchor_id, anchor_href))
link_tracker = self._get_tracker_from_short_url(anchor_href)
+41
View File
@@ -206,6 +206,47 @@ class MailRenderMixin(models.AbstractModel):
html = self.env['mail.render.mixin']._replace_local_links(html)
return html
@api.model
def _generate_preview(self, html):
""" Generate a preview of html, to used for example in marketing emails.
We add the character `&zwnj;` (zero-width non-joiner) to fill the end of
the preview in order to not have the beginning of the mail at the end of
the preview (don't work with simple space as the content is trimmed).
https://litmus.com/blog/the-ultimate-guide-to-preview-text-support
:param html: html content for which we generate the preview
:return: html to use as preview
"""
preview = ""
for element in re.finditer(r"\>([^<>]+)\<", html):
element = element.group(1).strip()
if element:
preview += " %s" % element
if len(preview) > 500:
break
html_preview = f"""
<div style="display:none;font-size:1px;height:0px;width:0px;opacity:0;">
{tools.html_escape(preview)} {'&zwnj;&nbsp;' * 500}
</div>
""" if preview else ''
return html_preview
@api.model
def _prepend_preview(self, html):
""" Prepare the email body before sending. Add the text preview at the
beginning of the mail. The preview text is displayed bellow the mail
subject of most mail client (gmail, outlook...).
See ``_generate_preview()`` for more details about the preview computation.
:param html: html content for which we want to prepend a preview
:return: html with preprended preview
"""
return tools.prepend_html_content(html, self._generate_preview(html))
# ------------------------------------------------------------
# RENDERING
# ------------------------------------------------------------
+1 -1
View File
@@ -569,7 +569,7 @@ class MassMailing(models.Model):
composer_values = {
'author_id': author_id,
'attachment_ids': [(4, attachment.id) for attachment in mailing.attachment_ids],
'body': mailing.body_html,
'body': self._prepend_preview(self.body_html),
'subject': mailing.subject,
'model': mailing.mailing_model_real,
'email_from': mailing.email_from,
@@ -24,7 +24,8 @@ class TestMassMailing(models.TransientModel):
mass_mail_layout = self.env.ref('mass_mailing.mass_mailing_mail_layout')
for test_mail in test_emails:
# Convert links in absolute URLs before the application of the shortener
body = self.env['mail.render.mixin']._replace_local_links(mailing.body_html)
body = mailing._prepend_preview(mailing.body_html)
body = self.env['mail.render.mixin']._replace_local_links(body)
body = tools.html_sanitize(body, sanitize_attributes=True, sanitize_style=True)
mail_values = {
'email_from': mailing.email_from,
+53
View File
@@ -5,6 +5,7 @@
from unittest.mock import patch
import email.policy
import email.message
import re
import threading
from odoo.tests.common import BaseCase, SavepointCase, TransactionCase
@@ -12,6 +13,7 @@ from odoo.tools import (
is_html_empty, html_sanitize, append_content_to_html, plaintext2html,
email_split,
misc, formataddr,
prepend_html_content,
)
from . import test_mail_examples
@@ -334,6 +336,57 @@ class TestHtmlTools(BaseCase):
for content in valid_html_samples:
self.assertFalse(is_html_empty(content))
def test_prepend_html_content(self):
body = """
<html>
<body>
<div>test</div>
</body>
</html>
"""
content = "<span>content</span>"
result = prepend_html_content(body, content)
result = re.sub(r'[\s\t]', '', result)
self.assertEqual(result, "<html><body><span>content</span><div>test</div></body></html>")
body = "<div>test</div>"
content = "<span>content</span>"
result = prepend_html_content(body, content)
result = re.sub(r'[\s\t]', '', result)
self.assertEqual(result, "<span>content</span><div>test</div>")
body = """
<body>
<div>test</div>
</body>
"""
result = prepend_html_content(body, content)
result = re.sub(r'[\s\t]', '', result)
self.assertEqual(result, "<body><span>content</span><div>test</div></body>")
body = """
<html>
<body>
<div>test</div>
</body>
</html>
"""
content = """
<html>
<body>
<div>test</div>
</body>
</html>
"""
result = prepend_html_content(body, content)
result = re.sub(r'[\s\t]', '', result)
self.assertEqual(result, "<html><body><div>test</div><div>test</div></body></html>")
class TestEmailTools(BaseCase):
""" Test some of our generic utility functions for emails """
+14
View File
@@ -428,6 +428,20 @@ def append_content_to_html(html, content, plaintext=True, preserve=False, contai
return '%s%s' % (html, content)
return '%s%s%s' % (html[:insert_location], content, html[insert_location:])
def prepend_html_content(html_body, html_content):
"""Prepend some HTML content at the beginning of an other HTML content."""
html_content = re.sub(r'(?i)(</?(?:html|body|head|!\s*DOCTYPE)[^>]*>)', '', html_content)
html_content = html_content.strip()
insert_index = next(re.finditer(r'<body[^>]*>', html_body), None)
if insert_index is None:
insert_index = next(re.finditer(r'<html[^>]*>', html_body), None)
insert_index = insert_index.end() if insert_index else 0
return html_body[:insert_index] + html_content + html_body[insert_index:]
#----------------------------------------------------------
# Emails
#----------------------------------------------------------