fix(webmail): acertos de interface e mover mensagens entre pastas

Cc/Cco. Os dois rótulos do compositor eram o último texto em inglês
que restava, e "Bcc" tem nome próprio em português - quem não conhece
a abreviatura inglesa não adivinha o que o campo faz.

Larguras. A barra lateral tinha 244px fixos e nela não cabia "Console
de administração", que transbordava no telemóvel e no computador: o
botão passa a mostrar um rótulo curto e guarda o nome completo no
tooltip. O endereço da conta usava break-words e partia-se a meio do
domínio, deixando um "br" sozinho na linha de baixo; passa a truncar
com o valor inteiro no tooltip.

A lista tinha 404px fixos e o leitor 760px centrados, portanto num ecrã
largo os assuntos eram espremidos numa coluna estreita enquanto o
leitor ficava entre duas margens enormes. Ambos crescem agora com a
janela. O assunto ganha overflow-wrap: os Message-ID dos relatórios do
Google são uma única "palavra" sem espaços, mais longa que a coluna, e
sem sítio onde quebrar saíam da linha.

Imagens remotas. A escolha era reposta sempre que uma mensagem era
aberta, por isso a mesma newsletter tinha de ser desbloqueada em cada
leitura. A decisão passa a ser lembrada por remetente, com um segundo
botão para a retirar. Bloqueado continua a ser o que acontece por
omissão: uma imagem remota é um aviso de leitura que o remetente não
pediu licença para receber.

Assinatura. Era escrita num textarea - texto simples com quebras de
linha reais - e injetada no compositor com innerHTML, onde uma quebra
de linha é espaço em branco: todas desapareciam. Passa a ser um campo
rico, com negrito, itálico, links e imagens, guardado como HTML e
sanitizado nos dois sentidos, já que este valor é injetado como
marcação na origem do webmail. Assinaturas guardadas pelo campo antigo
continuam a funcionar: as suas quebras de linha são convertidas.

Mover entre pastas. Faltava a única ação corrente que o leitor não
tinha: arquivar uma mensagem para fora da caixa de entrada era
impossível. Enviados e Rascunhos nunca são destino - descrevem como a
mensagem veio a existir, não onde o leitor a arrumou - mas sair deles é
permitido. Pastas criadas pelo utilizador, que não têm papel
special-use, são destino como qualquer outra.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5 committed 2026-08-22 13:18:08 -03:00
1 parent ac0521cb9b
commit f179cb1f27
20 files changed
+911 -37

No files matched your search

@@ -207,6 +207,47 @@ func (uc *WebmailUseCase) Send(ctx context.Context, input ComposeInput) error {
return nil
}
// ErrFolderNotATarget refuses a move into a folder that describes how a
// message came to exist rather than where its reader filed it.
var ErrFolderNotATarget = errors.New("webmail: messages cannot be moved into this folder")
// ErrNoTargetFolder rejects a move with no destination named.
var ErrNoTargetFolder = errors.New("webmail: a destination folder is required")
// notAMoveTarget are the folders a message may never be moved into.
//
// Sent and Drafts are not places a reader files things; they record what a
// message is. Moving arbitrary mail into Sent would make it claim the message
// was sent from here, and a message in Drafts that cannot be edited would sit
// among the unfinished ones forever.
var notAMoveTarget = map[string]bool{"sent": true, "drafts": true}
// Move files one message into another folder.
//
// The webmail could delete a message and it could read one, but it had no way
// to put it anywhere: archiving something out of the inbox was impossible.
func (uc *WebmailUseCase) Move(ctx context.Context, address, folder string, uid uint32, target string) error {
target = strings.TrimSpace(target)
if target == "" {
return ErrNoTargetFolder
}
if notAMoveTarget[strings.ToLower(target)] {
return ErrFolderNotATarget
}
// Moving a message where it already is is what a second click on the same
// target produces; it is a no-op rather than an error.
if strings.EqualFold(target, folder) {
return nil
}
mailboxID, err := uc.mailboxID(ctx, address)
if err != nil {
return err
}
_, err = uc.repo.MoveToFolder(ctx, mailboxID, folder, uid, target)
return err
}
// Delete removes one message the way a mail client does: from any ordinary
// folder it goes to Trash, and from Trash it is really expunged.
//