Make "Show original" in the headers dialog the action, not a description

The hint at the foot of Message headers named an action and left you to go
find it. Requested in #236, and the reporter is right that it is the shape of
the thing rather than the size: telling somebody a feature exists is half a
job when the other half is one element away.

Clicking it now closes the headers dialog and opens the original, so it reads
as going deeper rather than as opening a second window.

`tNode` rather than a sentence chopped either side of a button: the sentence
stays whole for whoever translates it, and a language that puts the verb
somewhere else can move the hole rather than being handed two fragments.

The link style needed unscoping to work, which turned out to be a bug of its
own. `.link-btn` was written for the composer's To and Cc labels and scoped to
`.composer-field label`, so the two callers outside it -- the trusted-domain
list in Privacy settings, and now this -- rendered as default button chrome in
the middle of a sentence. The rule is now unscoped and Privacy is fixed by the
same change.

Checked in a browser: the hint reads as a sentence with a dotted-underlined
link in it, clicking swaps one dialog for the other, and the raw message is
there.

Closes #236.
This commit is contained in:
2026-09-02 13:17:10 -07:00
parent 45f1309d06
commit b10ce2f9dd
11 changed files with 40 additions and 13 deletions
+1 -1
View File
@@ -225,7 +225,7 @@ export const catalog: Catalog = {
"Never send one": "Nie senden",
"Not this time": "Diesmal nicht",
"It would go to {address}, which is not where the message came from.": "Sie ginge an {address} — nicht dorthin, woher die Nachricht kam.",
"Use “Show original” for the complete raw message.": "Nutzen Sie „Original anzeigen“ für die vollständige Rohnachricht.",
"Use {action} for the complete raw message.": "{action} für die vollständige Rohnachricht.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Ordner",
+1 -1
View File
@@ -217,7 +217,7 @@ export const catalog: Catalog = {
"Never send one": "No enviar nunca",
"Not this time": "Esta vez no",
"It would go to {address}, which is not where the message came from.": "Iría a {address}, que no es de donde vino el mensaje.",
"Use “Show original” for the complete raw message.": "Use «Mostrar el original» para ver el mensaje sin procesar completo.",
"Use {action} for the complete raw message.": "{action} para ver el mensaje sin procesar completo.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Carpeta",
+1 -1
View File
@@ -222,7 +222,7 @@ export const catalog: Catalog = {
"Never send one": "Ne jamais en envoyer",
"Not this time": "Pas cette fois",
"It would go to {address}, which is not where the message came from.": "Il irait à {address}, qui n'est pas l'origine du message.",
"Use “Show original” for the complete raw message.": "Utilisez « Afficher l'original » pour le message brut complet.",
"Use {action} for the complete raw message.": "{action} pour le message brut complet.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Dossier",
+1 -1
View File
@@ -216,7 +216,7 @@ export const catalog: Catalog = {
"Never send one": "送信しない",
"Not this time": "今回は送信しない",
"It would go to {address}, which is not where the message came from.": "送信先は {address} で、メールの送信元とは異なります。",
"Use “Show original” for the complete raw message.": "完全な原文は「元のメールを表示」でご覧いただけます。",
"Use {action} for the complete raw message.": "完全な生メッセージは {action} から。",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "フォルダー",
+1 -1
View File
@@ -213,7 +213,7 @@ export const catalog: Catalog = {
"Never send one": "Nooit versturen",
"Not this time": "Deze keer niet",
"It would go to {address}, which is not where the message came from.": "Die zou naar {address} gaan, en daar kwam het bericht niet vandaan.",
"Use “Show original” for the complete raw message.": "Gebruik “Origineel tonen” voor het volledige ruwe bericht.",
"Use {action} for the complete raw message.": "{action} voor het volledige onbewerkte bericht.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Map",
+1 -1
View File
@@ -220,7 +220,7 @@ export const catalog: Catalog = {
"Never send one": "Nunca enviar",
"Not this time": "Desta vez não",
"It would go to {address}, which is not where the message came from.": "Ela iria para {address}, que não é de onde a mensagem veio.",
"Use “Show original” for the complete raw message.": "Use “Mostrar o original” para ver a mensagem bruta completa.",
"Use {action} for the complete raw message.": "{action} para a mensagem bruta completa.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Pasta",
+1 -1
View File
@@ -219,7 +219,7 @@ export const catalog: Catalog = {
"Never send one": "Никогда не отправлять",
"Not this time": "Не в этот раз",
"It would go to {address}, which is not where the message came from.": "Оно ушло бы на {address}, а письмо пришло не оттуда.",
"Use “Show original” for the complete raw message.": "Нажмите «Показать исходник», чтобы увидеть письмо целиком.",
"Use {action} for the complete raw message.": "{action} — полное исходное сообщение.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Папка",
+1 -1
View File
@@ -213,7 +213,7 @@ export const catalog: Catalog = {
"Never send one": "Ніколи не надсилати",
"Not this time": "Не цього разу",
"It would go to {address}, which is not where the message came from.": "Воно пішло б на {address}, а лист надійшов не звідти.",
"Use “Show original” for the complete raw message.": "Натисніть «Показати оригінал», щоб побачити лист повністю.",
"Use {action} for the complete raw message.": "{action} — повне вихідне повідомлення.",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "Тека",
+1 -1
View File
@@ -215,7 +215,7 @@ export const catalog: Catalog = {
"Never send one": "从不发送",
"Not this time": "这次不发送",
"It would go to {address}, which is not where the message came from.": "回执会发往 {address},而邮件并非来自该地址。",
"Use “Show original” for the complete raw message.": "使用「显示原始邮件」查看完整原。",
"Use {action} for the complete raw message.": "{action} 可查看完整原始邮件。",
// ── Folders, calendar, contacts, files ─────────────────────────────
"Folder": "文件夹",
+9 -3
View File
@@ -1743,9 +1743,15 @@ button.dp-open:disabled { cursor: default; opacity: .5; }
@media (prefers-reduced-motion: reduce) { .spin { animation: none; } }
/* The composer's To label doubles as the way into the address books. */
.composer-field label .link-btn { background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; }
.composer-field label .link-btn:hover { color: var(--accent); }
.composer-field label .link-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
/* A button that reads as part of a sentence.
Unscoped: it was written for the composer's To/Cc labels and scoped to them,
which left every other caller -- the trusted-domain list in Privacy, and the
"Show original" link in the headers dialog -- rendering as default button
chrome in the middle of a line. `font` and `color` inherit, so it takes the
voice of whatever sentence it sits in. */
.link-btn { background: none; border: 0; padding: 0; font: inherit; color: inherit; cursor: pointer; text-decoration: underline; text-decoration-style: dotted; text-underline-offset: 3px; }
.link-btn:hover { color: var(--accent); }
.link-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
/* The spam filter's own working, in the message details. */
.spam-summary { display: flex; flex-direction: column; gap: 6px; }
+22 -1
View File
@@ -436,7 +436,28 @@ export const MessageView = memo(function MessageView({ email: e, expanded, wasUn
{e.inReplyTo?.length ? <><dt>{translate("In-Reply-To")}</dt><dd className="mono small">{e.inReplyTo.join(" ")}</dd></> : null}
{e.references?.length ? <><dt>{translate("References")}</dt><dd className="mono small">{e.references.join(" ")}</dd></> : null}
</dl>
<p className="hint">{translate("Use “Show original” for the complete raw message.")}</p>
{/*
* The action, not a description of where to find it. Telling somebody
* an action exists and leaving them to hunt for it is half a job
* (#236) -- and one dialog replaces the other, so it reads as going
* deeper rather than as opening a second window.
*
* `tNode` rather than two `translate` calls around a button: the
* sentence stays whole for whoever translates it, and languages that
* put the verb elsewhere can move the hole.
*/}
<p className="hint">
{tNode("Use {action} for the complete raw message.", {
action: (
<button
className="link-btn"
onClick={() => { setShowHeaders(false); void openSource(); }}
>
{translate("Show original")}
</button>
),
})}
</p>
</Dialog>
</article>
);