feat: instant preview of edited email template

This commit is contained in:
Björn Fromme
2026-08-17 12:17:05 +02:00
parent 489aafed3e
commit 0e1df6da7c
12 changed files with 250 additions and 58 deletions
@@ -1,4 +1,4 @@
{{ form_start(form) }}
{{ form_start(form, { 'attr': { 'id': 'email-text-form' } }) }}
<div class="flex flex-col space-y-4 pb-8">
{{ form_row(form.subject) }}
{{ form_row(form.headline) }}
@@ -20,8 +20,22 @@
</div>
</div>
</div>
<button type="submit" class="btn">
Speichern
</button>
<div class="flex items-center space-x-2">
<button type="submit" class="btn">
Speichern
</button>
<a href="{{ path('app_admin_system_email_text_index') }}" class="btn btn--secondary">
Abbrechen
</a>
{% if emailText.id is not null %}
<button type="button"
class="btn btn--secondary text-red-500"
hx-get="{{ path('app_admin_system_email_text_reset', { 'key': definition.key.value }) }}"
hx-target="body"
hx-swap="beforeend">
Auf Standard zurücksetzen
</button>
{% endif %}
</div>
{{ form_rest(form) }}
{{ form_end(form) }}
@@ -0,0 +1,21 @@
<div class="pb-4">
<span class="font-bold">Betreff:</span> {{ text.subject }}
</div>
{# The renderer escaped everything before it added a tag, see RenderedEmailTextDto. #}
<div class="border border-gray-200 rounded-md p-4">
{% 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">
{{ text.bodyHtml | raw }}
</div>
<p>
<span class="btn inline-block">Zum Portal</span>
</p>
</div>
<div class="pt-4 text-sm text-gray-500">
Platzhalter sind als <code>[name]</code> dargestellt und werden beim Versand durch
die echten Werte ersetzt. Logo und Fußzeile der E-Mail sind hier nicht abgebildet.
</div>
@@ -0,0 +1,36 @@
{% extends 'admin/layout.html.twig' %}
{% block title %}Einstellungen - E-Mail-Text bearbeiten{% endblock %}
{% block content %}
<h1 class="text-2xl font-bold pb-8">
{{ definition.label }}
</h1>
<div class="grid lg:grid-cols-2 gap-8 items-start">
{% include 'admin/system/email_text/_form.html.twig' %}
<div class="lg:sticky lg:top-4">
<div class="font-bold pb-2">
Vorschau
</div>
{# The pane refreshes itself from the form while the admin types, so the
wording is checked before it is saved rather than after. #}
<div class="relative">
<div id="email-text-preview"
hx-post="{{ path('app_admin_system_email_text_preview_draft', { 'key': definition.key.value }) }}"
hx-trigger="input from:#email-text-form delay:500ms"
hx-include="#email-text-form"
hx-target="this"
hx-swap="innerHTML"
hx-indicator="#email-text-preview-indicator">
{% include 'admin/system/email_text/_preview.html.twig' %}
</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="email-text-preview-indicator">
{% include '_partials/_spinner.html.twig' with { 'class': 'w-8 h-8' } %}
</div>
</div>
</div>
</div>
{% endblock %}
@@ -59,13 +59,10 @@
hx-swap="beforeend">
{{ icon('eye') }}
</button>
<button type="button"
title="E-Mail-Text bearbeiten"
hx-get="{{ path('app_admin_system_email_text_edit', { 'key': key }) }}"
hx-target="body"
hx-swap="beforeend">
<a href="{{ path('app_admin_system_email_text_edit', { 'key': key }) }}"
title="E-Mail-Text bearbeiten">
{{ icon('edit') }}
</button>
</a>
</div>
</td>
</tr>
@@ -1,7 +0,0 @@
{% extends 'htmx_modal.html.twig' %}
{% block title %}{{ definition.label }}{% endblock %}
{% block content %}
{% include 'admin/system/email_text/_form.html.twig' %}
{% endblock %}
@@ -3,25 +3,5 @@
{% block title %}Vorschau: {{ definition.label }}{% endblock %}
{% block content %}
<div class="pb-4">
<span class="font-bold">Betreff:</span> {{ text.subject }}
</div>
{# The renderer escaped everything before it added a tag, see RenderedEmailTextDto. #}
<div class="border border-gray-200 rounded-md p-4">
{% 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">
{{ text.bodyHtml | raw }}
</div>
<p>
<span class="btn inline-block">Zum Portal</span>
</p>
</div>
<div class="pt-4 text-sm text-gray-500">
Platzhalter sind als <code>[name]</code> dargestellt und werden beim Versand durch
die echten Werte ersetzt. Logo und Fußzeile der E-Mail sind hier nicht abgebildet.
</div>
{% include 'admin/system/email_text/_preview.html.twig' %}
{% endblock %}