"""Version HTML des emails — même thème que l'appli (couleurs/typographie de `frontend/tailwind.config.js`), logo de l'édition en en-tête si défini. Chaque email reste envoyé en `multipart/alternative` (texte + HTML, voir `app/email/sender.py:build_message`) : le texte brut reste la source de vérité (accessibilité, clients sans HTML, RGPD — rien de plus dans le HTML que dans le texte déjà journalisable). Pas de CSS externe ni de police web (rarement chargées par les clients mail) : mise en page par tableau, styles en ligne, polices système en repli du style « Archivo »/« DM Mono ».""" import html as _html _INK = "#14110E" _CREAM = "#F5EFE1" _PAPER = "#FFFDF7" _YELLOW = "#FFD23F" _FONT_DISPLAY = "Arial, Helvetica, sans-serif" _FONT_MONO = "'Courier New', Courier, monospace" # Identifiant de la pièce jointe « inline » du logo (voir # `app/email/sender.py:build_message`) — jamais une URL externe avec le # `access_token` du formulaire public en requête : n'importe quel # destinataire d'un email (lien magique, confirmation d'inscription…) aurait # pu lire ce jeton depuis la source du message, pas seulement la coordination. LOGO_CID = "edition-logo" def _paragraphs(body: str) -> str: """`\n\n` sépare les paragraphes, un simple `\n` reste un retour à la ligne — même convention que les modèles texte (`app/email/templates.py`).""" blocks = body.strip().split("\n\n") return "".join( f'

{_html.escape(block).replace(chr(10), "
")}

' for block in blocks if block.strip() ) def render_html(*, subject: str, body: str, has_logo: bool) -> str: """`has_logo` : voir `app/email/branding.py:edition_logo` — `False` tant que l'édition n'a pas importé de logo, l'en-tête retombe alors sur un simple bandeau texte (le sujet, en capitales).""" if has_logo: header = ( f'' ) else: header = ( f'{_html.escape(subject)}' ) return f"""
{header}
{_paragraphs(body)}
"""