stimulus controller reorder

This commit is contained in:
Paul Lützner committed 2026-07-30 11:04:15 +02:00
1 parent 518ea401e1
commit 54e3ae3b3b
83 files changed
+578 -507

No files matched your search

+1 -1
View File
@@ -24,7 +24,7 @@ Variables: message
{{- kb >= 1024 ? (kb / 1024)|number_format(1) ~ ' MB' : kb|number_format(0) ~ ' KB' -}}
</span>
<button type="button"
data-action="click->compose#removeAttachment"
data-action="click->compose--compose#removeAttachment"
data-part-id="{{ part.id }}"
title="{{ 'compose.attachment_remove'|trans }}"
class="w-5 h-5 flex items-center justify-center rounded
+81 -81
View File
@@ -21,17 +21,17 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
{% set ctx = urlParams|default({}) %}
<turbo-frame id="{{ frame }}">
<div data-controller="compose compose-toolbar"
data-compose-inline-value="{{ inline ? 'true' : 'false' }}"
data-compose-min-chars-value="{{ min_chars|default(5) }}"
data-compose-max-attachment-bytes-value="{{ constant('App\\Controller\\Mail\\ComposeController::MAX_ATTACHMENT_BYTES') }}"
{% if thread is not null %}data-compose-thread-value="{{ thread }}"{% endif %}
<div data-controller="compose--compose compose--compose-toolbar"
data-compose--compose-inline-value="{{ inline ? 'true' : 'false' }}"
data-compose--compose-min-chars-value="{{ min_chars|default(5) }}"
data-compose--compose-max-attachment-bytes-value="{{ constant('App\\Controller\\Mail\\ComposeController::MAX_ATTACHMENT_BYTES') }}"
{% if thread is not null %}data-compose--compose-thread-value="{{ thread }}"{% endif %}
{% if null == message.id %}
data-compose-draft-url-value="{{ path('app_compose_form_new', ctx) }}"
data-compose-send-url-value="{{ path('app_compose_mail_send', ctx) }}"
data-compose--compose-draft-url-value="{{ path('app_compose_form_new', ctx) }}"
data-compose--compose-send-url-value="{{ path('app_compose_mail_send', ctx) }}"
{% else %}
data-compose-draft-url-value="{{ path('app_compose_form_edit', ctx|merge({'id': message.id})) }}"
data-compose-send-url-value="{{ path('app_compose_mail_send_draft', ctx|merge({'id': message.id})) }}"
data-compose--compose-draft-url-value="{{ path('app_compose_form_edit', ctx|merge({'id': message.id})) }}"
data-compose--compose-send-url-value="{{ path('app_compose_mail_send_draft', ctx|merge({'id': message.id})) }}"
{% endif %}
class="{% if inline %}
w-full rounded-xl border border-line bg-surface shadow-sm mt-3
@@ -46,7 +46,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
<div class="flex items-center justify-between px-4 py-2.5
{{ inline ? 'border-b border-line' : 'bg-sunken rounded-t-2xl' }}
select-none shrink-0"
{% if not inline %}data-action="dblclick->compose#toggleMinimize"{% endif %}>
{% if not inline %}data-action="dblclick->compose--compose#toggleMinimize"{% endif %}>
{% if inline %}
{# Replying rarely means retargeting: the address and subject
rows stay folded behind this summary. #}
@@ -56,29 +56,29 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
{% endfor %}
<button type="button"
data-action="click->compose#toggleFields"
data-action="click->compose--compose#toggleFields"
class="flex items-center gap-2 min-w-0 text-sm text-ink-soft
hover:text-ink transition-colors cursor-pointer">
<span class="text-xs text-ink-faint shrink-0">{{ 'message.details.to_label'|trans }}</span>
<span class="truncate max-w-[420px]">
{{- recipients is empty ? ('compose.no_recipients'|trans) : recipients|join(', ') -}}
</span>
<i data-compose-target="fieldsChevron"
<i data-compose--compose-target="fieldsChevron"
class="fa-solid fa-chevron-down text-[10px] text-ink-faint shrink-0 transition-transform duration-150"></i>
</button>
<span class="hidden" data-compose-target="title"></span>
<span class="hidden" data-compose--compose-target="title"></span>
{% else %}
<span class="text-sm font-semibold text-ink truncate max-w-[260px] cursor-pointer"
data-compose-target="title"
data-action="click->compose#toggleMinimize">New Message</span>
data-compose--compose-target="title"
data-action="click->compose--compose#toggleMinimize">New Message</span>
{% endif %}
<div class="flex items-center gap-1">
<span data-compose-target="saveStatus"
<span data-compose--compose-target="saveStatus"
class="text-xs text-ink-muted mr-2 transition-colors"></span>
{% if inline %}
<button type="button" title="{{ 'compose.pop_out'|trans }}"
data-action="click->compose#popOut"
data-action="click->compose--compose#popOut"
class="w-7 h-7 flex items-center justify-center rounded-lg
text-ink-muted hover:text-ink
hover:bg-hover transition-colors">
@@ -86,24 +86,24 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</button>
{% else %}
<button type="button" title="Minimize"
data-action="click->compose#toggleMinimize"
data-action="click->compose--compose#toggleMinimize"
class="w-7 h-7 flex items-center justify-center rounded-lg
text-ink-muted hover:text-ink
hover:bg-hover transition-colors">
<i data-compose-target="minimizeIcon" class="fa-solid fa-minus text-xs"></i>
<i data-compose--compose-target="minimizeIcon" class="fa-solid fa-minus text-xs"></i>
</button>
<button type="button" title="Expand"
data-action="click->compose#toggleExpand"
data-action="click->compose--compose#toggleExpand"
class="w-7 h-7 flex items-center justify-center rounded-lg
text-ink-muted hover:text-ink
hover:bg-hover transition-colors">
<i data-compose-target="expandIcon"
<i data-compose--compose-target="expandIcon"
class="fa-solid fa-up-right-and-down-left-from-center text-[10px]"></i>
</button>
{% endif %}
<button type="button" title="{{ 'compose.save_and_close'|trans }}"
data-action="compose#saveDraft compose#close"
data-action="compose--compose#saveDraft compose--compose#close"
class="w-7 h-7 flex items-center justify-center rounded-lg
text-ink-muted hover:text-ink
hover:bg-hover transition-colors">
@@ -113,13 +113,13 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</div>
{# ── Collapsible body ─────────────────────────────────────── #}
<div data-compose-target="collapsible"
<div data-compose--compose-target="collapsible"
class="flex flex-col flex-1 min-h-0">
{# ── Hidden account select (submitted with form) ───────── #}
<select id="{{ form.account.vars.id }}"
name="{{ form.account.vars.full_name }}"
data-compose-target="accountSelect"
data-compose--compose-target="accountSelect"
class="sr-only">
{% for choice in form.account.vars.choices %}
<option value="{{ choice.value }}"
@@ -130,29 +130,29 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</select>
{# ── Addresses + subject (folded away inline) ──────────── #}
<div data-compose-target="fields" class="{{ inline ? 'hidden' : '' }}">
<div data-compose--compose-target="fields" class="{{ inline ? 'hidden' : '' }}">
{# ── From ─────────────────────────────────────────────── #}
<div class="relative shrink-0">
<div class="flex items-center gap-3 px-4 pt-3 pb-1"
data-compose-target="fromRow">
data-compose--compose-target="fromRow">
<span class="text-xs font-medium text-ink-faint w-10 shrink-0 text-right">From</span>
<button type="button"
data-action="click->compose#toggleFromDropdown"
data-compose-target="fromBtn"
data-action="click->compose--compose#toggleFromDropdown"
data-compose--compose-target="fromBtn"
class="flex-1 flex items-center justify-between gap-2 text-sm
text-ink-soft text-left">
<span data-compose-target="fromLabel" class="truncate">
<span data-compose--compose-target="fromLabel" class="truncate">
{% for choice in form.account.vars.choices %}
{% if form.account.vars.value == choice.value %}{{ choice.label }}{% endif %}
{% endfor %}
</span>
<i class="fa-solid fa-chevron-down text-[10px] text-ink-faint shrink-0 transition-transform duration-150"
data-compose-target="fromChevron"></i>
data-compose--compose-target="fromChevron"></i>
</button>
</div>
<div data-compose-target="fromDropdown"
<div data-compose--compose-target="fromDropdown"
class="hidden absolute left-4 right-4 top-full z-20 rounded-xl overflow-hidden
bg-pane-soft
backdrop-blur-sm
@@ -160,7 +160,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
shadow-pop">
{% for choice in form.account.vars.choices %}
<button type="button"
data-action="click->compose#selectAccount"
data-action="click->compose--compose#selectAccount"
data-value="{{ choice.value }}"
data-label="{{ choice.label }}"
class="w-full text-left px-4 py-2.5 text-sm
@@ -178,7 +178,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</div>
{# ── To ───────────────────────────────────────────────── #}
<div data-compose-target="toField"
<div data-compose--compose-target="toField"
class="flex items-center gap-3 px-4 py-1.5 shrink-0 min-h-[36px]">
<span class="text-xs font-medium text-ink-faint w-10 shrink-0 text-right">To</span>
<div class="flex-1 min-w-0">
@@ -186,14 +186,14 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</div>
<div class="flex gap-2 shrink-0">
<button type="button"
data-compose-target="ccBtn"
data-action="click->compose#showCc"
data-compose--compose-target="ccBtn"
data-action="click->compose--compose#showCc"
class="text-xs text-ink-faint hover:text-accent transition-colors">
Cc
</button>
<button type="button"
data-compose-target="bccBtn"
data-action="click->compose#showBcc"
data-compose--compose-target="bccBtn"
data-action="click->compose--compose#showBcc"
class="text-xs text-ink-faint hover:text-accent transition-colors">
Bcc
</button>
@@ -201,7 +201,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</div>
{# ── Cc ───────────────────────────────────────────────── #}
<div data-compose-target="ccField"
<div data-compose--compose-target="ccField"
class="flex hidden items-center gap-3 px-4 py-1.5 shrink-0 min-h-[36px]">
<span class="text-xs font-medium text-ink-faint w-10 shrink-0 text-right">Cc</span>
<div class="flex-1 min-w-0">
@@ -210,7 +210,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</div>
{# ── Bcc ──────────────────────────────────────────────── #}
<div data-compose-target="bccField"
<div data-compose--compose-target="bccField"
class="flex hidden items-center gap-3 px-4 py-1.5 shrink-0 min-h-[36px]">
<span class="text-xs font-medium text-ink-faint w-10 shrink-0 text-right">Bcc</span>
<div class="flex-1 min-w-0">
@@ -227,7 +227,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
<input type="text"
id="{{ form.subject.vars.id }}"
name="{{ form.subject.vars.full_name }}"
data-compose-target="subject"
data-compose--compose-target="subject"
value="{{ form.subject.vars.value ?: '' }}"
placeholder="Subject"
class="flex-1 bg-transparent outline-none text-sm
@@ -242,13 +242,13 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
<input type="hidden"
id="{{ form.bodyHtml.vars.id }}"
name="{{ form.bodyHtml.vars.full_name }}"
data-compose-toolbar-target="hiddenInput"
data-compose--compose-toolbar-target="hiddenInput"
value="{{ form.bodyHtml.vars.value|e('html_attr') ?: '' }}">
<div class="flex-1 overflow-y-auto min-h-[180px] px-4 py-3">
<div id="{{ form.bodyHtml.vars.id }}_editor"
data-compose-toolbar-target="editor"
data-compose-target="body"
data-compose--compose-toolbar-target="editor"
data-compose--compose-target="body"
contenteditable="true"
data-placeholder="Write your message…"
class="w-full min-h-[170px] bg-transparent outline-none
@@ -260,7 +260,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
</div>
{# ── Attachments ──────────────────────────────────────── #}
<div data-compose-target="attachments" class="shrink-0">
<div data-compose--compose-target="attachments" class="shrink-0">
{{ include('compose/_attachments.html.twig', { message: message }) }}
</div>
@@ -271,8 +271,8 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
bg-sunken
rounded-full px-2 py-1">
<select data-compose-toolbar-target="fontFamily"
data-action="change->compose-toolbar#changeFontFamily"
<select data-compose--compose-toolbar-target="fontFamily"
data-action="change->compose--compose-toolbar#changeFontFamily"
class="bg-transparent border-none outline-none text-xs cursor-pointer px-1 py-0.5 rounded
text-ink-muted
hover:bg-hover transition-colors">
@@ -283,8 +283,8 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
<div class="w-px h-4 bg-line mx-1 shrink-0"></div>
<select data-compose-toolbar-target="fontSize"
data-action="change->compose-toolbar#changeFontSize"
<select data-compose--compose-toolbar-target="fontSize"
data-action="change->compose--compose-toolbar#changeFontSize"
class="bg-transparent border-none outline-none text-xs cursor-pointer px-1 py-0.5 rounded
text-ink-muted
hover:bg-hover transition-colors">
@@ -296,59 +296,59 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
<div class="w-px h-4 bg-line mx-1 shrink-0"></div>
<button type="button" data-compose-toolbar-action data-compose-toolbar-target="boldBtn"
data-action="click->compose-toolbar#bold"
<button type="button" data-compose--compose-toolbar-action data-compose--compose-toolbar-target="boldBtn"
data-action="click->compose--compose-toolbar#bold"
class="px-1.5 py-0.5 rounded font-bold text-sm transition-colors text-ink-soft hover:bg-hover">B</button>
<button type="button" data-compose-toolbar-action data-compose-toolbar-target="italicBtn"
data-action="click->compose-toolbar#italic"
<button type="button" data-compose--compose-toolbar-action data-compose--compose-toolbar-target="italicBtn"
data-action="click->compose--compose-toolbar#italic"
class="px-1.5 py-0.5 rounded italic text-sm transition-colors text-ink-soft hover:bg-hover">I</button>
<button type="button" data-compose-toolbar-action data-compose-toolbar-target="underlineBtn"
data-action="click->compose-toolbar#underline"
<button type="button" data-compose--compose-toolbar-action data-compose--compose-toolbar-target="underlineBtn"
data-action="click->compose--compose-toolbar#underline"
class="px-1.5 py-0.5 rounded underline text-sm transition-colors text-ink-soft hover:bg-hover">U</button>
<label class="px-1.5 py-0.5 rounded text-sm flex flex-col items-center leading-none gap-0.5
transition-colors text-ink-soft
hover:bg-hover cursor-pointer relative">
<span class="font-semibold">A</span>
<span data-compose-toolbar-target="colorSwatch"
<span data-compose--compose-toolbar-target="colorSwatch"
class="block w-3.5 h-[3px] bg-accent rounded-sm"></span>
<input type="color" data-action="input->compose-toolbar#changeColor"
<input type="color" data-action="input->compose--compose-toolbar#changeColor"
class="absolute opacity-0 w-0 h-0 pointer-events-none" tabindex="-1">
</label>
<div class="w-px h-4 bg-line mx-1 shrink-0"></div>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#alignLeft"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#alignLeft"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Align left">
<i class="fa-solid fa-align-left"></i></button>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#alignCenter"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#alignCenter"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Align center">
<i class="fa-solid fa-align-center"></i></button>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#alignRight"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#alignRight"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Align right">
<i class="fa-solid fa-align-right"></i></button>
<div class="w-px h-4 bg-line mx-1 shrink-0"></div>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#orderedList"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#orderedList"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Numbered list">
<i class="fa-solid fa-list-ol"></i></button>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#unorderedList"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#unorderedList"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Bullet list">
<i class="fa-solid fa-list-ul"></i></button>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#outdent"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#outdent"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Outdent">
<i class="fa-solid fa-outdent"></i></button>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#indent"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#indent"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Indent">
<i class="fa-solid fa-indent"></i></button>
<div class="w-px h-4 bg-line mx-1 shrink-0"></div>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#undo"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#undo"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Undo">
<i class="fa-solid fa-rotate-left"></i></button>
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#redo"
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#redo"
class="px-1.5 py-0.5 rounded text-sm transition-colors text-ink-muted hover:bg-hover" title="Redo">
<i class="fa-solid fa-rotate-right"></i></button>
</div>
@@ -369,15 +369,15 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
{% set ic = "w-8 h-8 flex items-center justify-center rounded-full transition-colors text-ink-muted hover:text-ink hover:bg-hover" %}
<button type="button" class="{{ ic }} text-sm font-bold tracking-tighter">Aa</button>
<button type="button"
data-action="click->compose#openFilePicker"
data-action="click->compose--compose#openFilePicker"
title="{{ 'compose.attach'|trans }}"
class="{{ ic }}"><i class="fa-solid fa-paperclip"></i></button>
{# No name: never submitted with the form, only read by the
controller and POSTed to the attachment endpoint. #}
<input type="file" multiple class="hidden"
data-compose-target="fileInput"
data-action="change->compose#uploadFiles">
<button type="button" data-compose-toolbar-action data-action="click->compose-toolbar#insertLink" class="{{ ic }}"><i class="fa-solid fa-link"></i></button>
data-compose--compose-target="fileInput"
data-action="change->compose--compose#uploadFiles">
<button type="button" data-compose--compose-toolbar-action data-action="click->compose--compose-toolbar#insertLink" class="{{ ic }}"><i class="fa-solid fa-link"></i></button>
<button type="button" class="{{ ic }}"><i class="fa-regular fa-face-smile"></i></button>
{# Attach from a connected service. Sized to what the user
actually has: nothing at all with no connections, the
@@ -386,28 +386,28 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
{% if pickerIntegrations|length == 1 %}
{% set only = pickerIntegrations|first %}
<button type="button"
data-controller="modal"
data-modal-title-value="{{ only.name }}"
data-modal-size-value="{{ only.provider.isMediaLibrary ? 'full' : 'wide' }}"
data-controller="ui--modal"
data-ui--modal-title-value="{{ only.name }}"
data-ui--modal-size-value="{{ only.provider.isMediaLibrary ? 'full' : 'wide' }}"
data-integration-id="{{ only.id }}"
data-action="click->compose#openIntegrationPicker"
data-action="click->compose--compose#openIntegrationPicker"
title="{{ 'integration.picker.attach_from'|trans({ '%name%': only.name }) }}"
class="{{ ic }}"><i class="{{ only.provider.icon }}"></i></button>
{% elseif pickerIntegrations|length > 1 %}
<div class="relative" data-controller="dropdown">
<div class="relative" data-controller="ui--dropdown">
<button type="button"
data-action="click->dropdown#toggle"
data-action="click->ui--dropdown#toggle"
title="{{ 'integration.picker.attach_from_service'|trans }}"
class="{{ ic }}"><i class="fa-solid fa-cloud-arrow-down"></i></button>
<div data-dropdown-target="menu" hidden
<div data-ui--dropdown-target="menu" hidden
class="absolute bottom-full mb-1 left-0 z-20 min-w-48 popover py-1">
{% for integration in pickerIntegrations %}
<button type="button"
data-controller="modal"
data-modal-title-value="{{ integration.name }}"
data-modal-size-value="{{ integration.provider.isMediaLibrary ? 'full' : 'wide' }}"
data-controller="ui--modal"
data-ui--modal-title-value="{{ integration.name }}"
data-ui--modal-size-value="{{ integration.provider.isMediaLibrary ? 'full' : 'wide' }}"
data-integration-id="{{ integration.id }}"
data-action="click->compose#openIntegrationPicker click->dropdown#close"
data-action="click->compose--compose#openIntegrationPicker click->ui--dropdown#close"
class="w-full flex items-center gap-2.5 px-3 py-2 text-sm text-ink-muted hover:bg-hover hover:text-ink transition-colors text-left">
<i class="{{ integration.provider.icon }} w-4 text-center" aria-hidden="true"></i>
<span class="truncate">{{ integration.name }}</span>
@@ -424,7 +424,7 @@ Variables: form, message, inline (bool), frame (string), thread (int|null)
<div class="flex-1"></div>
<button type="button"
data-action="compose#discard"
data-action="compose--compose#discard"
title="{{ 'compose.discard'|trans }}"
class="{{ ic }} hover:text-danger">
<i class="fa-regular fa-trash-can"></i>