mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
Issue #25. Filing mail the way Gmail does took two actions here: the label button adds a label and leaves the mail where it is, and a drop on a folder needs a pointer and a sidebar in reach. "Move to" is one click, beside the label button, in the list toolbar and in the reading pane: the target goes on and the label of the list being looked at comes off. The client names the view; the server decides what that removes. A body that lists labels to strip is not an instruction the route has. Inbox target on, archived (Archive on, Inbox off) — or into the folder, where the target is one a label that label swapped for the target Trash, Spam target on, out of the bin, not into the inbox anywhere else target on, Inbox off where it was on to Inbox restore(), then the view's label off to Trash, Spam the existing trash and spam paths, not a label swap MoveToService composes ThreadStatusUpdater's operations rather than writing labels itself, inside one transaction, because each of those carries provider behaviour that took a bug report to get right. Only the messages that carry the leaving label lose it: a sent reply in an inbox thread gains the target and is not archived. The issue asks for "tag(s)", plural. This is Gmail's single target; several labels are still what the label button is for. Undo came with it, for archive and labels as the issue asks, and for the drag onto a folder since the mechanism was there. It remembers each message's labels before the action, in the session, and puts exactly those back — not the inverse of the action, which is wrong for a mixed selection. A whole-view action runs in a worker and has none. Three things fell out: - The toolbar's label menu posted once per ticked row. With a toast and an Undo on the answer that was five of each, so it posts the selection once now, to /status/bulk/label, which learned `attach`. - Inbox, Spam and Trash are asked for by role. Their labels are made lazily, so a picker that carried ids had no Trash row for anybody who had never deleted anything. - Font Awesome keeps its folder-with-arrow in Pro, so the button is an inline SVG. Not verified against a live Gmail or Exchange account: the tests pin local label state and the IMAP jobs that are queued. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.5 KiB
Twig
75 lines
3.5 KiB
Twig
{#
|
|
_toast.html.twig
|
|
|
|
A pane with a coloured edge, not a coloured slab.
|
|
|
|
The filled version could not follow a theme: success painted bg-inverse, but
|
|
error and info were `text-white` over a fully saturated fill, and --rgb-info
|
|
existed only in :root — so on any theme the informational toast was the same
|
|
hard blue, and on a light-inked dark theme white-on-danger was barely legible.
|
|
Every colour here now comes from a token, so a new theme gets matching toasts
|
|
without touching this file.
|
|
|
|
The edge and the icon carry the type; the surface and the text are the same as
|
|
every other panel, which is also why a toast no longer shouts over the UI it is
|
|
reporting on.
|
|
#}
|
|
|
|
{% set tone = {
|
|
success: { edge: 'border-success', icon: 'fa-circle-check', text: 'text-success' },
|
|
error: { edge: 'border-danger', icon: 'fa-circle-exclamation', text: 'text-danger' },
|
|
info: { edge: 'border-info', icon: 'fa-circle-info', text: 'text-info' },
|
|
}[type ?? 'success'] %}
|
|
|
|
<div
|
|
data-controller="ui--toast"
|
|
data-ui--toast-duration-value="{{ extra.hide_after ?? 4000 }}"
|
|
{# It travels up into place rather than merely fading: the dock is at the
|
|
bottom edge, so "rise" is the direction the toast actually came from, and
|
|
a thing that moves the way it appears to have moved is a thing the eye
|
|
tracks instead of noticing. #}
|
|
data-enter="rise"
|
|
role="status"
|
|
class="relative pane-flat shadow-float overflow-hidden min-w-[280px] max-w-sm
|
|
border-l-4 {{ tone.edge }}"
|
|
>
|
|
<div class="flex items-center gap-3 px-4 py-3.5 text-sm font-medium text-ink">
|
|
<i class="fa-solid {{ tone.icon }} shrink-0 {{ tone.text }}" aria-hidden="true"></i>
|
|
|
|
<span class="min-w-0" data-toast-message>{{ message }}</span>
|
|
|
|
{% if extra.undo_url is defined %}
|
|
{# Which controller answers the click. A send is called off by
|
|
compose--undo-send, which is what this button was built for and
|
|
stays the default; a filing action is put back by
|
|
mail--status-undo, which also has to re-read the list. Both take
|
|
the same two values and the same action name, so the choice is
|
|
one identifier rather than a second button. #}
|
|
{% set undo = extra.undo_controller ?? 'compose--undo-send' %}
|
|
<span data-ui--toast-target="countdown" class="text-ink-faint tabular-nums"></span>
|
|
<button
|
|
data-controller="{{ undo }}"
|
|
data-{{ undo }}-url-value="{{ extra.undo_url }}"
|
|
data-{{ undo }}-hide-after-value="{{ extra.hide_after ?? 4000 }}"
|
|
data-action="click->{{ undo }}#abort"
|
|
class="text-accent underline underline-offset-2 font-semibold
|
|
hover:opacity-70 transition-opacity cursor-pointer whitespace-nowrap"
|
|
>
|
|
{# "Undo" is right for a send already on its way; for a hold set
|
|
for Monday it is the wrong verb — there is nothing to undo
|
|
yet, only something to call off. Callers that mean the other
|
|
thing pass their own key. #}
|
|
{{ (extra.undo_label ?? 'toast.undo')|trans }}
|
|
</button>
|
|
{% endif %}
|
|
|
|
<button
|
|
data-action="click->ui--toast#dismiss"
|
|
class="ml-auto shrink-0 text-ink-faint hover:text-ink transition-colors cursor-pointer"
|
|
aria-label="{{ 'toast.dismiss'|trans }}"
|
|
>
|
|
<i class="fa-solid fa-xmark text-sm" aria-hidden="true"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|