feat: markdown in email body for mailing and transactional
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{# Shown under both mail editors, so an admin meets the same syntax in each of them. #}
|
||||
<div class="text-sm text-gray-500">
|
||||
<div class="pb-2">
|
||||
Formatierung der Nachricht:
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for example, label in {
|
||||
'**fett**': 'fett',
|
||||
'*kursiv*': 'kursiv',
|
||||
'## Überschrift': 'Überschrift',
|
||||
'- Punkt': 'Aufzählung',
|
||||
'1. Punkt': 'Nummerierung',
|
||||
} %}
|
||||
<span class="inline-flex items-center space-x-1 py-1 px-2 bg-gray-50 border border-gray-200 rounded">
|
||||
<code>{{ example }}</code>
|
||||
<span>{{ label }}</span>
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="pt-2">
|
||||
Eine Leerzeile beginnt einen neuen Absatz, Links und E-Mail-Adressen werden
|
||||
automatisch verlinkt.
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,6 +4,8 @@
|
||||
{{ form_row(form.headline) }}
|
||||
{{ form_row(form.body) }}
|
||||
|
||||
{{ include('_partials/_markdown_hint.html.twig') }}
|
||||
|
||||
<div class="bg-gray-100 rounded-md p-4 text-sm">
|
||||
<div class="font-bold pb-2">
|
||||
Verfügbare Platzhalter
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
{% if text.headline is not empty %}
|
||||
<h1 class="text-xl font-bold pb-2">{{ text.headline | raw }}</h1>
|
||||
{% endif %}
|
||||
{# The paragraphs come from the mail body, where email/layout.html.twig styles every
|
||||
<p> with padding-bottom:16px - pb-4 is the same spacing, so the preview matches. #}
|
||||
<div class="[&>p]:pb-4 [&_a]:underline">
|
||||
{# .mail-body carries the spacing and list styling of email/layout.html.twig, so this
|
||||
pane and the mailing pane both show what the mail will look like. #}
|
||||
<div class="mail-body">
|
||||
{{ text.bodyHtml | raw }}
|
||||
</div>
|
||||
<p>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<div class="pb-4">
|
||||
<span class="font-bold">Betreff:</span> {{ subject }}
|
||||
</div>
|
||||
{# App\Email\MailBodyRenderer escaped everything before it introduced a tag, which is what
|
||||
makes |raw safe here - see the contract on that class. #}
|
||||
<div class="mail-body border border-gray-200 rounded-md p-4">
|
||||
{{ bodyHtml | raw }}
|
||||
<p>
|
||||
<span class="btn inline-block">Zum Portal</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="pt-4 text-sm text-gray-500">
|
||||
Platzhalter sind mit deinem eigenen Namen gefüllt, beim Versand steht dort der Name der
|
||||
jeweiligen Person. Logo und Fußzeile der E-Mail sind hier nicht abgebildet.
|
||||
</div>
|
||||
@@ -27,29 +27,64 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# the draft is parked on every keystroke so that a trip to the filter and back keeps it,
|
||||
hx-trigger replaces htmx' default submit trigger, the preview button still posts natively #}
|
||||
{{ form_start(form, { 'attr': {
|
||||
'hx-post': path('app_admin_teamer_mailing_draft'),
|
||||
'hx-trigger': 'input changed delay:500ms',
|
||||
'hx-swap': 'none',
|
||||
} }) }}
|
||||
<div class="flex flex-col space-y-4 pb-4 max-w-3xl">
|
||||
{{ form_row(form.subject) }}
|
||||
{{ form_row(form.message) }}
|
||||
</div>
|
||||
{# One request per keystroke parks the draft - so that a trip to the filter and back
|
||||
keeps it - and answers with the preview, which is why the response is swapped into
|
||||
the pane instead of being thrown away. hx-trigger replaces htmx' default submit
|
||||
trigger, the "Vorschau an mich senden" button still posts natively.
|
||||
|
||||
<div class="pb-8 max-w-3xl text-sm text-gray-500">
|
||||
<div class="pb-2">
|
||||
Platzhalter für Betreff und Nachricht, sie werden beim Senden pro Person ersetzt:
|
||||
Deliberately without htmx' "changed" modifier: it compares the listening element's
|
||||
.value between events, and a <form> has none, so every event compares undefined to
|
||||
undefined and the request is never sent. The 500ms delay is what keeps the typing
|
||||
from flooding the route. #}
|
||||
{{ form_start(form, { 'attr': {
|
||||
'id': 'mailing-form',
|
||||
'hx-post': path('app_admin_teamer_mailing_draft'),
|
||||
'hx-trigger': 'input delay:500ms',
|
||||
'hx-target': '#mailing-preview',
|
||||
'hx-swap': 'innerHTML',
|
||||
'hx-indicator': '#mailing-preview-indicator',
|
||||
} }) }}
|
||||
<div class="grid lg:grid-cols-2 gap-8 items-start">
|
||||
<div>
|
||||
<div class="flex flex-col space-y-4 pb-4">
|
||||
{{ form_row(form.subject) }}
|
||||
{{ form_row(form.message) }}
|
||||
</div>
|
||||
|
||||
<div class="pb-6">
|
||||
{{ include('_partials/_markdown_hint.html.twig') }}
|
||||
</div>
|
||||
|
||||
<div class="pb-8 text-sm text-gray-500">
|
||||
<div class="pb-2">
|
||||
Platzhalter für Betreff und Nachricht, sie werden beim Senden pro Person ersetzt:
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for placeholder, label in placeholders %}
|
||||
<span class="inline-flex items-center space-x-1 py-1 px-2 bg-gray-50 border border-gray-200 rounded">
|
||||
<code>{{ placeholder }}</code>
|
||||
<span>{{ label }}</span>
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for placeholder, label in placeholders %}
|
||||
<span class="inline-flex items-center space-x-1 py-1 px-2 bg-gray-50 border border-gray-200 rounded">
|
||||
<code>{{ placeholder }}</code>
|
||||
<span>{{ label }}</span>
|
||||
</span>
|
||||
{% endfor %}
|
||||
|
||||
<div class="lg:sticky lg:top-4">
|
||||
<div class="font-bold pb-2">
|
||||
Vorschau
|
||||
</div>
|
||||
<div class="relative">
|
||||
<div id="mailing-preview">
|
||||
{{ include('admin/teamer/_mailing_preview.html.twig', preview) }}
|
||||
</div>
|
||||
{# Deliberately a sibling of the swap target, not a child: hx-swap replaces
|
||||
the target's contents, which would carry the indicator away with them. #}
|
||||
<div class="htmx-indicator absolute inset-0 bg-white/75 flex items-center justify-center"
|
||||
id="mailing-preview-indicator">
|
||||
{{ include('_partials/_spinner.html.twig', { 'class': 'w-8 h-8' }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<html lang="und" dir="auto" xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
|
||||
<head>
|
||||
<title>MyE&P Team</title>
|
||||
@@ -116,7 +116,16 @@
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
/* Was 0 while an h2 only ever preceded a p, which brings its own spacing. */
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -152,12 +161,28 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
/* Margin, not padding: Outlook on Windows renders list padding unreliably. */
|
||||
margin: 0 0 16px 24px;
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
li {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="word-spacing:normal;background-color:#666666;">
|
||||
<div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0px;max-width:0px;opacity:0;overflow:hidden;">MyE&P Team</div>
|
||||
<div style="background-color:#666666;">
|
||||
<div aria-label="MyE&P Team" aria-roledescription="email" style="background-color:#666666;" role="article" lang="und" dir="auto">
|
||||
<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:600px;" width="600" ><tr><td style="line-height:0;font-size:0;mso-line-height-rule:exactly;"><v:image style="border:0;mso-position-horizontal:center;position:absolute;top:0;width:600px;z-index:-3;" xmlns:v="urn:schemas-microsoft-com:vml" /><![endif]-->
|
||||
<div style="margin:0 auto;max-width:600px;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" role="presentation" style="width:100%;">
|
||||
@@ -215,16 +240,22 @@
|
||||
<div style="font-family:Lato, Verdana, Arial, 'Helvetica neue', sans-serif;font-size:16px;line-height:24px;text-align:left;color:#666666;">{% block body %} <h1> Lorem ipsum dolor sit amet </h1>
|
||||
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto culpa delectus dolores earum eius fugiat in nesciunt quas quidem vitae? </p>
|
||||
<h2> Lorem ipsum dolor </h2>
|
||||
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto culpa delectus dolores <strong>earum eius fugiat in nesciunt quas quidem vitae</strong>? </p>
|
||||
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Architecto culpa delectus dolores <strong>earum eius fugiat in nesciunt quas quidem vitae</strong>, <em>eius fugiat</em>? </p>
|
||||
<ul>
|
||||
<li>Lorem ipsum dolor</li>
|
||||
<li>Consectetur adipisicing elit</li>
|
||||
</ul>
|
||||
<h3> Lorem ipsum </h3>
|
||||
<ol>
|
||||
<li>Lorem ipsum dolor</li>
|
||||
<li>Consectetur adipisicing elit</li>
|
||||
</ol>
|
||||
<p>
|
||||
<a href="#" class="button"> Button </a>
|
||||
</p>
|
||||
<p class="small"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus, eveniet! </p> {% endblock %}
|
||||
{% block button %}
|
||||
<p>
|
||||
<a href="{{ url('app_index') }}" class="button">Zum Portal</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
<p class="small"> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus, eveniet! </p> {% endblock %} {% block button %} <p>
|
||||
<a href="{{ url('app_index') }}" class="button">Zum Portal</a>
|
||||
</p> {% endblock %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{% extends 'email/layout.html.twig' %}
|
||||
|
||||
{# Everything on `bodyHtml` was escaped by App\Email\MailBodyRenderer before any tag was
|
||||
introduced, which is what makes |raw safe here - same contract as email/generic.html.twig.
|
||||
Do not pass anything into this template that did not come out of that renderer. #}
|
||||
{% block body %}
|
||||
<p>
|
||||
{{ message | nl2br }}
|
||||
</p>
|
||||
{{ bodyHtml | raw }}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user