From 548b647f1563644e932f9f081f1933d03925afff Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 24 Sep 2026 19:06:53 +0200 Subject: [PATCH] feat: add background colour and the webmail's 16 text colours to the composer The composer offered 8 text colours and no background colour. It now has the webmail's 2 x 8 palette (7e0a86a6, 3957b358) for text colour, and a background colour button with the same palette and a "Remove background color" action, applied with the editor's `hiliteColor`. The webmail's font-size picker is not ported: the editor has no mark model to scope a size to, so it needs a selection-rewriting command in the page script that can only be checked on a device. --- locales/rn/en.json | 4 +++ src/components/RichTextEditor.tsx | 4 ++- src/screens/ComposeScreen.tsx | 47 ++++++++++++++++++++++++------- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/locales/rn/en.json b/locales/rn/en.json index 0fd2d22e..415b2bdd 100644 --- a/locales/rn/en.json +++ b/locales/rn/en.json @@ -136,6 +136,10 @@ "send_now": "Send now", "send_timeout_body": "The message may already have gone out. Check your Sent folder before sending it again.", "send_timeout_title": "No answer from the server", + "toolbar": { + "background_color": "Background color", + "remove_background_color": "Remove background color" + }, "undo_send_label": "Sending in {seconds}s…", "uploading": "Uploading...", "validation": { diff --git a/src/components/RichTextEditor.tsx b/src/components/RichTextEditor.tsx index 2590edfb..ce416986 100644 --- a/src/components/RichTextEditor.tsx +++ b/src/components/RichTextEditor.tsx @@ -23,7 +23,9 @@ export type RichTextCommand = | 'undo' | 'redo' // Text colour: a CSS colour after the colon, e.g. 'foreColor:#d97706'. - | `foreColor:${string}`; + | `foreColor:${string}` + // Background colour, the same way; 'hiliteColor:transparent' removes it. + | `hiliteColor:${string}`; export interface RichTextSelectionState { bold: boolean; diff --git a/src/screens/ComposeScreen.tsx b/src/screens/ComposeScreen.tsx index b8166204..e489750c 100644 --- a/src/screens/ComposeScreen.tsx +++ b/src/screens/ComposeScreen.tsx @@ -11,7 +11,7 @@ import { List, ListOrdered, Link2, Link2Off, Image as ImageIcon, Quote, Heading1, Heading2, AlignLeft, AlignCenter, AlignRight, RemoveFormatting, Undo2, Redo2, FileText, Clock, Check, Palette, Table, LayoutTemplate, MailCheck, - Users, Tag, Type, + Users, Tag, Type, Highlighter, } from 'lucide-react-native'; import DateTimePicker, { type DateTimePickerEvent } from '@react-native-community/datetimepicker'; import * as ImagePicker from 'expo-image-picker'; @@ -103,7 +103,11 @@ type Field = 'to' | 'cc' | 'bcc'; const URL_RE = /^https?:\/\/.+/i; -const TEXT_COLORS = ['#000000', '#6b7280', '#dc2626', '#ea580c', '#ca8a04', '#16a34a', '#2563eb', '#7c3aed']; +// The webmail's 2 x 8 palette, used for text and background colour alike. +const TEXT_COLORS = [ + '#000000', '#5f6368', '#9aa0a6', '#c5221f', '#e8710a', '#f9ab00', '#188038', '#1967d2', + '#7627bb', '#c2185b', '#795548', '#fa5252', '#fd7e14', '#40c057', '#4dabf7', '#e64980', +]; function formatBytes(n: number): string { if (n < 1024) return `${n} B`; @@ -1759,7 +1763,7 @@ export default function ComposeScreen({ route, navigation }: Props) { }; // ── Colour / table ─────────────────────────────────────────────────── - const [colorPickerOpen, setColorPickerOpen] = React.useState(false); + const [colorTarget, setColorTarget] = React.useState<'text' | 'background' | null>(null); const [tableMenuOpen, setTableMenuOpen] = React.useState(false); const insertTable = (rows: number, cols: number) => { const cell = ' '; @@ -2437,8 +2441,12 @@ export default function ComposeScreen({ route, navigation }: Props) { icon={} /> editorRef.current?.exec('strikeThrough')} icon={} /> - setColorPickerOpen(true)} + setColorTarget('text')} + label={t('email_composer.toolbar.text_color', 'Text color')} icon={} /> + setColorTarget('background')} + label={t('email_composer.toolbar.background_color', 'Background color')} + icon={} /> @@ -2539,21 +2547,40 @@ export default function ComposeScreen({ route, navigation }: Props) { - {/* Text colour palette */} - setColorPickerOpen(false)}> - setColorPickerOpen(false)}> + {/* Text / background colour palette */} + setColorTarget(null)}> + setColorTarget(null)}> {}}> - {t('email_composer.toolbar.text_color', 'Text color')} + + {colorTarget === 'background' + ? t('email_composer.toolbar.background_color', 'Background color') + : t('email_composer.toolbar.text_color', 'Text color')} + {TEXT_COLORS.map((color) => ( { setColorPickerOpen(false); editorRef.current?.exec(`foreColor:${color}`); }} + accessibilityLabel={color} + onPress={() => { + setColorTarget(null); + editorRef.current?.exec(colorTarget === 'background' ? `hiliteColor:${color}` : `foreColor:${color}`); + }} style={[styles.swatch, { backgroundColor: color }]} /> ))} - setColorPickerOpen(false)}> + {colorTarget === 'background' && ( + { setColorTarget(null); editorRef.current?.exec('hiliteColor:transparent'); }} + > + + + {t('email_composer.toolbar.remove_background_color', 'Remove background color')} + + + )} + setColorTarget(null)}> {t('email_composer.cancel', 'Cancel')}