Files
pl_mail/templates/compose/_unschedule.stream.html.twig
Paul LütznerandClaude Opus 5 a28f9da47c feat(compose): call a scheduled send off from the row it sits on
The hold was already announced — the composer closes and a toast names the
time — but calling it off meant opening the draft and then opening the send
menu, and the Drafts row itself offered no way out. On a phone that was the
only way out at all: the send pill is desktop-only, so the row is the sole
surface a scheduled draft has there.

  • The scheduled badge grows a cancel. Badge and × are one pill rather than
    a badge with a button parked beside it — the × means nothing on its own.
    It sits ON TOP of the row's overlay anchor exactly as the star and the
    hover actions do (z-10 on a flex item, plus stopPropagation in
    mail--message-row), so no nested <form> in a shared partial and no click
    reaching the link underneath.

  • POST /compose/unschedule/{id} answers with the row redrawn, not with the
    composer reopened: undo() exists to catch a send you regretted while you
    were still looking at it, and a hold set on Friday is called off on
    Monday from a list of forty rows. Both now share callOffSend(); the
    sentAt guard is in one place instead of two. A message that has already
    gone is left entirely alone — submissionSendAt is by then the record of
    when it was due — and the row is redrawn anyway, because what was on
    screen was stale.

  • The toast's action is relabelled "Cancel send". "Undo" is the wrong verb
    for something that has not happened yet, and the long form pushed the
    message onto four lines; _toast.html.twig takes an optional undo_label.

  • On a stacked row a scheduled draft shows the badge instead of the
    preview, which at 393px was two characters and an ellipsis. `hidden
    @xl:inline-block`, not `@xl:block`: Tailwind emits `.hidden` after
    `.block`, so the container variant does not outrank it.

Every string translated (en/de/en_PI), including the sr-only label. The
mobile spec measures the pill's bounding box and the tap target rather than
asserting presence, after the target came out 11px tall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 21:32:51 +02:00

48 lines
2.0 KiB
Twig

{#
compose/_unschedule.stream.html.twig
─────────────────────────────────────────────────────────────────────────────
Answer to POST /compose/unschedule/{id}: the row that was clicked, redrawn
without its hold, plus a toast saying so.
`replace` on the row's own id, exactly as thread/status/_star.stream.html.twig
does — the list rows are patched one at a time and never reloaded whole, and
the row carries its own Stimulus controller, so replacing the <li> re-connects
it rather than leaving a dead badge behind.
The include is NOT `only`: _thread_row.html.twig reads `accounts` (the account
wedge is drawn only on a multi-account install) off the globals, the same way
the star and read streams reach it.
Variables: row (MessageThread|Message), type ('thread'|'message'),
draft_scope (bool), cancelled (bool — false when the mail had
already gone out, in which case nothing was written)
#}
<turbo-stream action="replace" target="thread_{{ row.id }}">
<template>
{% if 'thread' == type %}
{% include '_partials/_thread_row.html.twig' with {
thread: row,
draft_scope: draft_scope,
} %}
{% else %}
{% include '_partials/_thread_row.html.twig' with {
message: row,
draft_scope: draft_scope,
} %}
{% endif %}
</template>
</turbo-stream>
{# Silent on a message that had already gone: the row redraws itself into the
truth, and "cancelled" would be the one thing that did not happen. #}
{% if cancelled %}
<turbo-stream action="append" target="toast-region">
<template>
{% include '_partials/_toast.html.twig' with {
type: 'info',
message: 'toast.schedule_cancelled'|trans,
} only %}
</template>
</turbo-stream>
{% endif %}