mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 10:04:18 +00:00
153 lines
7.4 KiB
Twig
153 lines
7.4 KiB
Twig
{#
|
|
mail/_thread_message.html.twig
|
|
─────────────────────────────────────────────────────────────────────────────
|
|
One message inside the conversation view. Extracted so a Turbo Stream can
|
|
append or remove exactly one message (see compose/_inline_send.stream.html.twig).
|
|
|
|
Variables:
|
|
message Message
|
|
expanded bool — render open (newest message / unread) or collapsed
|
|
draft bool — override; a just-sent message still carries the DRAFT
|
|
flag for a moment, but belongs in the thread as a message
|
|
#}
|
|
{% set expanded = expanded|default(false) %}
|
|
{% set isDraft = draft is defined ? draft : message.isDraft %}
|
|
|
|
{# A draft has no body worth reading — clicking it opens the editor in the
|
|
reply zone, which then hides this row for as long as it is open. #}
|
|
{% if isDraft %}
|
|
{# The row is its own turbo-frame, so the editor opens right here rather
|
|
than at the foot of the thread. compose#close navigates it back. #}
|
|
{% set threaded = message.thread is not null %}
|
|
{% set draftFrame = threaded ? 'compose_draft_' ~ message.id : 'compose_dock' %}
|
|
{% set composeParams = threaded
|
|
? { frame: draftFrame, thread: message.thread.id }
|
|
: {} %}
|
|
{% set snippet = message.bodyText ? message.bodyText|striptags|trim : '' %}
|
|
|
|
<div id="thread_message_{{ message.id }}" class="border-b border-line last:border-b-0 py-4 first:pt-0">
|
|
<turbo-frame id="{{ draftFrame }}">
|
|
<a
|
|
href="{{ path('app_compose_edit', composeParams|merge({ id: message.id })) }}"
|
|
data-turbo-frame="{{ draftFrame }}"
|
|
class="flex items-center gap-3 cursor-pointer group"
|
|
>
|
|
<div class="w-9 h-9 rounded-full bg-sunken border border-line flex-shrink-0 flex items-center justify-center text-ink-faint text-sm">
|
|
<i class="fa-regular fa-pen-to-square text-xs" aria-hidden="true"></i>
|
|
</div>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<div class="flex items-baseline gap-2">
|
|
<span class="text-sm font-medium text-danger">{{ 'message.draft'|trans }}</span>
|
|
<span class="text-xs text-ink-muted truncate">
|
|
{{ snippet ?: (message.subject ?: ('compose.empty_draft'|trans)) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<span class="text-xs text-ink-faint whitespace-nowrap group-hover:text-ink-soft transition-colors">
|
|
{{ 'compose.continue_editing'|trans }}
|
|
</span>
|
|
</a>
|
|
</turbo-frame>
|
|
</div>
|
|
{% else %}
|
|
|
|
<div
|
|
id="thread_message_{{ message.id }}"
|
|
class="border-b border-line last:border-b-0 py-4 first:pt-0"
|
|
data-controller="thread-message"
|
|
data-thread-message-expanded-value="{{ expanded ? 'true' : 'false' }}"
|
|
>
|
|
{# ── Header row ─────────────────────────────────────────────────────── #}
|
|
<div class="flex items-start gap-3">
|
|
<div class="w-9 h-9 rounded-full bg-gradient-to-br from-blue-500 to-indigo-600 flex-shrink-0 flex items-center justify-center text-white text-sm font-medium shadow-sm">
|
|
{{ message.fromName|default(message.fromAddress)|slice(0,1)|upper }}
|
|
</div>
|
|
|
|
<div
|
|
class="flex-1 min-w-0 cursor-pointer"
|
|
data-action="click->thread-message#toggle"
|
|
>
|
|
<div class="flex items-baseline gap-2 flex-wrap">
|
|
<span class="text-sm font-semibold text-ink truncate">
|
|
{{ message.fromName ?: message.fromAddress }}
|
|
</span>
|
|
{% if message.fromName %}
|
|
<span class="text-xs text-ink-faint truncate"><{{ message.fromAddress }}></span>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{# The details popover must not toggle the message, so it stops
|
|
the click itself (see message_details#toggle). #}
|
|
<div data-thread-message-target="recipients" class="{{ expanded ? '' : 'hidden' }}">
|
|
{% include 'mail/_message_details.html.twig' with { message: message } only %}
|
|
</div>
|
|
|
|
{# Snippet — collapsed #}
|
|
<div
|
|
data-thread-message-target="snippet"
|
|
class="text-xs text-ink-muted truncate py-0.5 {{ expanded ? 'hidden' : '' }}"
|
|
>
|
|
{{ message.bodyText|striptags|slice(0, 200) }}…
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center gap-1 flex-shrink-0">
|
|
<span class="text-xs text-ink-faint whitespace-nowrap">
|
|
{{ message.receivedAt ? message.receivedAt|date('M j, g:i a') : '' }}
|
|
</span>
|
|
|
|
{% include 'mail/_message_actions.html.twig' with { message: message } only %}
|
|
|
|
<button
|
|
type="button"
|
|
data-action="click->thread-message#toggle:stop"
|
|
data-thread-message-target="toggleBtn"
|
|
aria-label="{{ 'message.actions.expand'|trans }}"
|
|
class="flex items-center justify-center w-8 h-8 rounded-full text-ink-muted hover:text-ink hover:bg-hover transition-colors"
|
|
>
|
|
<i
|
|
data-thread-message-target="chevron"
|
|
class="fa-solid fa-chevron-down text-xs transition-transform duration-150 {{ expanded ? 'rotate-180' : '' }}"
|
|
aria-hidden="true"
|
|
></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{# ── Body — full pane width ─────────────────────────────────────────── #}
|
|
<div data-thread-message-target="body" class="{{ expanded ? '' : 'hidden' }} mt-4">
|
|
{% if message.bodyHtmlSafe %}
|
|
<div class="text-sm overflow-x-auto">
|
|
{{ message.bodyHtmlSafe|raw }}
|
|
</div>
|
|
{% else %}
|
|
<pre class="whitespace-pre-wrap break-words font-sans text-sm text-ink-soft">{{ message.bodyText }}</pre>
|
|
{% endif %}
|
|
|
|
{% if message.hasAttachments %}
|
|
<div class="mt-4 pt-4 border-t border-line">
|
|
<p class="text-xs font-medium text-ink-faint mb-2">{{ 'message.attachments'|trans }}</p>
|
|
<div class="flex flex-wrap gap-2">
|
|
{% for part in message.messageParts %}
|
|
{% if not part.isInline %}
|
|
|
|
<a
|
|
href="{{ path('app_mail_attachment', { id: part.id }) }}?download=1"
|
|
class="inline-flex items-center gap-2 px-3 py-2 rounded-lg text-xs
|
|
bg-sunken border border-line text-ink-soft
|
|
hover:bg-hover transition-colors"
|
|
>
|
|
<i class="fa-solid fa-paperclip text-[11px]" aria-hidden="true"></i>
|
|
<span class="truncate max-w-[12rem]">{{ part.filename ?: 'attachment' }}</span>
|
|
</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|