Add a Monospace messages toggle in Appearance settings

Message text goes back to the proportional UI font by default; the new
switch renders it in a fixed-width font (via html[data-mono]) so ASCII
art, tables and box-drawing line up on demand. Persisted per user and
translated across all locales.
This commit is contained in:
Jean Chevronnet 2026-07-12 18:26:10 +00:00
parent e2abcce3d9
commit 322505a5b5
No known key found for this signature in database
14 changed files with 39 additions and 15 deletions

View file

@ -2,7 +2,7 @@ import { useActiveChat } from '@/core/networks';
// One preference toggle row: icon · label/hint · switch. Shared by the
// Appearance and Notifications sections.
export function ToggleRow({ icon, label, hint, prefKey }: { icon: string; label: string; hint?: string; prefKey: 'sound' | 'hideJoinQuit' | 'compact' | 'linkPreviews' | 'hoverActions' | 'confirmClose' }) {
export function ToggleRow({ icon, label, hint, prefKey }: { icon: string; label: string; hint?: string; prefKey: 'sound' | 'hideJoinQuit' | 'compact' | 'linkPreviews' | 'hoverActions' | 'confirmClose' | 'monoMessages' }) {
const value = useActiveChat((s) => s.prefs[prefKey]);
const setPref = useActiveChat((s) => s.setPref);
return (

View file

@ -56,6 +56,7 @@ export function AppearanceSection() {
</div>
<ToggleRow icon="🗜️" label={t('settings.appearance.compact')} hint={t('settings.appearance.compactHint')} prefKey="compact" />
<ToggleRow icon="🖱️" label={t('settings.appearance.hoverActions')} hint={t('settings.appearance.hoverActionsHint')} prefKey="hoverActions" />
<ToggleRow icon="⌨️" label={t('settings.appearance.mono')} hint={t('settings.appearance.monoHint')} prefKey="monoMessages" />
<div className="srow">
<span className="srow__ic" aria-hidden>🕓</span>
<div className="srow__txt"><div className="srow__label">{t('settings.appearance.timeFormat')}</div></div>

View file

@ -300,7 +300,9 @@
"textSize": "Textgröße",
"compactHint": "Dichtere Nachrichten, mehr auf dem Bildschirm.",
"hoverActions": "Aktionen beim Überfahren",
"hoverActionsHint": "Reaktion, Antwort und Pin beim Überfahren einer Nachricht."
"hoverActionsHint": "Reaktion, Antwort und Pin beim Überfahren einer Nachricht.",
"mono": "Nachrichten in Monospace",
"monoHint": "Feste Zeichenbreite, damit ASCII-Art und Tabellen ausgerichtet bleiben."
},
"notifications": {
"browser": "Browser-Benachrichtigungen",

View file

@ -320,7 +320,9 @@
"textSize": "Text size",
"compactHint": "Denser messages, more on screen.",
"hoverActions": "Hover actions",
"hoverActionsHint": "Quick react, reply and pin buttons when you hover a message."
"hoverActionsHint": "Quick react, reply and pin buttons when you hover a message.",
"mono": "Monospace messages",
"monoHint": "Fixed-width font so ASCII art and tables line up."
},
"notifications": {
"browser": "Browser notifications",

View file

@ -300,7 +300,9 @@
"textSize": "Tamaño de texto",
"compactHint": "Mensajes más densos, más en pantalla.",
"hoverActions": "Acciones al pasar el cursor",
"hoverActionsHint": "Botones de reacción, respuesta y fijar al pasar el cursor por un mensaje."
"hoverActionsHint": "Botones de reacción, respuesta y fijar al pasar el cursor por un mensaje.",
"mono": "Mensajes monoespaciados",
"monoHint": "Fuente de ancho fijo para alinear el arte ASCII y las tablas."
},
"notifications": {
"browser": "Notificaciones del navegador",

View file

@ -320,7 +320,9 @@
"textSize": "Taille du texte",
"compactHint": "Messages plus denses, plus dinfos à lécran.",
"hoverActions": "Actions au survol",
"hoverActionsHint": "Boutons de réaction, réponse et épingle au survol d'un message."
"hoverActionsHint": "Boutons de réaction, réponse et épingle au survol d'un message.",
"mono": "Messages à chasse fixe",
"monoHint": "Police à largeur fixe pour aligner l'art ASCII et les tableaux."
},
"notifications": {
"browser": "Notifications navigateur",

View file

@ -300,7 +300,9 @@
"textSize": "Dimensione testo",
"compactHint": "Messaggi più compatti, più contenuti a schermo.",
"hoverActions": "Azioni al passaggio",
"hoverActionsHint": "Pulsanti di reazione, risposta e fissaggio al passaggio su un messaggio."
"hoverActionsHint": "Pulsanti di reazione, risposta e fissaggio al passaggio su un messaggio.",
"mono": "Messaggi a spaziatura fissa",
"monoHint": "Carattere a larghezza fissa per allineare ASCII art e tabelle."
},
"notifications": {
"browser": "Notifiche del browser",

View file

@ -300,7 +300,9 @@
"textSize": "पाठ आकार",
"compactHint": "घना सन्देश, स्क्रिनमा बढी।",
"hoverActions": "होभर कार्यहरू",
"hoverActionsHint": "सन्देशमाथि माउस लैजाँदा द्रुत प्रतिक्रिया, जवाफ र पिन बटनहरू।"
"hoverActionsHint": "सन्देशमाथि माउस लैजाँदा द्रुत प्रतिक्रिया, जवाफ र पिन बटनहरू।",
"mono": "मोनोस्पेस सन्देशहरू",
"monoHint": "ASCII कला र तालिकाहरू पङ्क्तिबद्ध हुन स्थिर-चौडाइको फन्ट।"
},
"notifications": {
"browser": "ब्राउजर सूचनाहरू",

View file

@ -300,7 +300,9 @@
"textSize": "Tamanho do texto",
"compactHint": "Mensagens mais densas, mais na tela.",
"hoverActions": "Ações ao passar o mouse",
"hoverActionsHint": "Botões de reação, resposta e fixar ao passar o mouse sobre uma mensagem."
"hoverActionsHint": "Botões de reação, resposta e fixar ao passar o mouse sobre uma mensagem.",
"mono": "Mensagens monoespaçadas",
"monoHint": "Fonte de largura fixa para alinhar arte ASCII e tabelas."
},
"notifications": {
"browser": "Notificações do navegador",

View file

@ -300,7 +300,9 @@
"textSize": "Tamanho do texto",
"compactHint": "Mensagens mais densas, mais no ecrã.",
"hoverActions": "Ações ao passar o rato",
"hoverActionsHint": "Botões de reação, resposta e fixar ao passar o rato sobre uma mensagem."
"hoverActionsHint": "Botões de reação, resposta e fixar ao passar o rato sobre uma mensagem.",
"mono": "Mensagens monoespaçadas",
"monoHint": "Tipo de letra de largura fixa para alinhar arte ASCII e tabelas."
},
"notifications": {
"browser": "Notificações do navegador",

View file

@ -300,7 +300,9 @@
"textSize": "Размер текста",
"compactHint": "Более плотные сообщения, больше на экране.",
"hoverActions": "Действия при наведении",
"hoverActionsHint": "Кнопки реакции, ответа и закрепления при наведении на сообщение."
"hoverActionsHint": "Кнопки реакции, ответа и закрепления при наведении на сообщение.",
"mono": "Моноширинные сообщения",
"monoHint": "Моноширинный шрифт, чтобы ASCII-арт и таблицы выравнивались."
},
"notifications": {
"browser": "Уведомления браузера",

View file

@ -300,7 +300,9 @@
"textSize": "Metin boyutu",
"compactHint": "Daha sık mesajlar, ekranda daha fazlası.",
"hoverActions": "Fareyle eylemler",
"hoverActionsHint": "Bir mesajın üzerine gelince hızlı tepki, yanıt ve sabitleme düğmeleri."
"hoverActionsHint": "Bir mesajın üzerine gelince hızlı tepki, yanıt ve sabitleme düğmeleri.",
"mono": "Eş aralıklı mesajlar",
"monoHint": "ASCII sanatı ve tabloların hizalanması için sabit genişlikli yazı tipi."
},
"notifications": {
"browser": "Tarayıcı bildirimleri",

View file

@ -531,9 +531,10 @@ input, textarea { font-family: inherit; }
.group__nick { font-weight: 800; font-size: .94rem; }
.group__time { font-size: .7rem; color: var(--faint); font-weight: 600; }
.group--cont .group__avatar.group__time-rail { height: auto; }
/* Monospace + the existing pre-wrap keeps ASCII art, tables and box-drawing
aligned (every glyph one cell wide, spaces preserved). */
.line { font-family: var(--mono); font-size: .92rem; line-height: 1.5; color: var(--ink-2); word-wrap: break-word; overflow-wrap: anywhere; white-space: pre-wrap; }
.line { font-size: .96rem; line-height: 1.5; color: var(--ink-2); word-wrap: break-word; overflow-wrap: anywhere; white-space: pre-wrap; }
/* "Monospace messages" pref: a fixed-width font + the existing pre-wrap keeps
ASCII art, tables and box-drawing aligned (every glyph one cell wide). */
html[data-mono="on"] .line { font-family: var(--mono); font-size: .92rem; }
.line--action { font-style: italic; color: var(--accent-d); }
.line--notice { color: var(--muted); }
.line--notice::before { content: "-notice- "; font-weight: 700; opacity: .7; font-size: .82em; }

View file

@ -10,6 +10,7 @@ export interface Prefs {
linkPreviews: boolean; // show OpenGraph link-preview cards (off = never fetch)
hoverActions: boolean; // quick react/reply/pin toolbar on message hover (off = classic)
confirmClose: boolean; // warn (beforeunload) before closing/reloading the tab while connected
monoMessages: boolean; // render message text in a fixed-width font so ASCII art/tables line up
}
const KEY = 'tchatou-prefs';
@ -17,7 +18,7 @@ const KEY = 'tchatou-prefs';
// Defaults come from config.json (so a deployment can preset compact/sound/etc.).
function defaults(): Prefs {
const d = getConfig().defaults;
return { sound: d.sound, hideJoinQuit: d.hideJoinQuit, compact: d.compact, clock24: d.clock24, textScale: 1, linkPreviews: true, hoverActions: true, confirmClose: false };
return { sound: d.sound, hideJoinQuit: d.hideJoinQuit, compact: d.compact, clock24: d.clock24, textScale: 1, linkPreviews: true, hoverActions: true, confirmClose: false, monoMessages: false };
}
export function getPrefs(): Prefs {
@ -38,6 +39,7 @@ export function savePrefs(p: Prefs): void {
export function applyPrefs(p: Prefs): void {
document.documentElement.dataset.density = p.compact ? 'compact' : 'comfortable';
document.documentElement.dataset.actions = p.hoverActions ? 'on' : 'off';
document.documentElement.dataset.mono = p.monoMessages ? 'on' : 'off';
const scale = Math.min(1.4, Math.max(0.8, p.textScale || 1));
document.documentElement.style.fontSize = Math.round(scale * 100) + '%';
}