Improve outreach compose, contact merge, and email assets.
Deploy Beta / unit-tests (push) Successful in 10s
Deploy Beta / docker (push) Successful in 14s
Deploy Beta / deploy-beta (push) Successful in 1m40s

Add a Quill email editor with DB-backed image storage, selectable PCM designs, postcard defaults for addressed contacts, and merge-by-phone/address on the contact form.
This commit is contained in:
2026-08-09 10:42:22 -05:00
parent d3db6eed76
commit b3a6ee0cd0
19 changed files with 1158 additions and 129 deletions
+63
View File
@@ -39,6 +39,27 @@ def _absolute_static_url(site_url: str, relative: str) -> str:
return f"{site_url}{path}"
def _tagline_with_site_link(tagline: str, site_url: str) -> str:
"""Turn leading MKDRealtor.com (or similar) into a link to the public site."""
raw = (tagline or "").strip()
if not raw:
return ""
# Match "MKDRealtor.com" (any case) at the start, optional trailing " · rest"
match = re.match(
r"(?i)^(MKDRealtor\.com)(\s*[·•\-–—]\s*.*)?$",
raw,
)
if not match:
return html.escape(raw)
label = html.escape(match.group(1))
rest = html.escape(match.group(2) or "")
href = html.escape(site_url, quote=True)
return (
f'<a href="{href}" style="color:#00626c;text-decoration:none;">{label}</a>'
f"{rest}"
)
def email_brand_context(**extra):
site_url = (getattr(settings, "PUBLIC_SITE_URL", None) or "").rstrip("/")
if not site_url:
@@ -57,6 +78,7 @@ def email_brand_context(**extra):
"brand_name": brand_name,
"brand_legal": brand_legal,
"brand_tagline": tagline,
"brand_tagline_html": _tagline_with_site_link(tagline, site_url),
"host_label": host_label,
**extra,
}
@@ -81,3 +103,44 @@ def plain_text_to_email_html(text: str) -> str:
f'line-height:1.6;">{joined}</p>'
)
return "\n".join(blocks)
_HTML_TAG_RE = re.compile(
r"<\s*(p|div|br|span|strong|em|b|i|u|a|img|h[1-6]|ul|ol|li|font|table)\b",
re.I,
)
def sanitize_email_html(raw: str) -> str:
"""Light cleanup for staff-authored HTML (Quill) before sending."""
text = raw or ""
text = re.sub(r"(?is)<script[^>]*>.*?</script>", "", text)
text = re.sub(r"(?is)<iframe[^>]*>.*?</iframe>", "", text)
text = re.sub(r"(?is)<object[^>]*>.*?</object>", "", text)
text = re.sub(r"(?i)\son\w+\s*=\s*([\"']).*?\1", "", text)
text = re.sub(r"(?i)\son\w+\s*=\s*[^\s>]+", "", text)
text = re.sub(r"(?i)javascript:", "", text)
return text.strip()
def campaign_body_to_email_html(body: str) -> str:
"""Render campaign body for email — HTML as-is when Quill markup, else plain."""
raw = (body or "").strip()
if not raw:
return ""
if _HTML_TAG_RE.search(raw):
return sanitize_email_html(raw)
return plain_text_to_email_html(raw)
def campaign_body_to_plain_text(body: str) -> str:
"""Plain-text alternative for multipart emails."""
from django.utils.html import strip_tags
raw = (body or "").strip()
if not raw:
return ""
if _HTML_TAG_RE.search(raw):
text = strip_tags(sanitize_email_html(raw))
return html.unescape(re.sub(r"[ \t]+\n", "\n", text)).strip()
return raw