client: Support Dark Mode

Refs #714

Co-authored-by: Niels De Graef <nielsdegraef@gmail.com>
This commit is contained in:
Serhii Tereshchenko 2025-05-31 13:04:02 +03:00
parent 2ef32d2d2d
commit dbfb7f40da
11 changed files with 196 additions and 57 deletions

View file

@ -37,6 +37,13 @@
<default>true</default>
<summary>Display message previews</summary>
<description>True if we should display a short preview of each message.</description>
</key>
<key name="unset-html-colors" type="b">
<default>false</default>
<summary>Unset colors provided in HTML emails</summary>
<summary>Override colors in HTML emails</summary>
<description>Overrides the original colors in HTML messages to integrate better with the app theme.</description>
</key>
<key name="move-messages-on-tag" type="b">

View file

@ -12,23 +12,18 @@ public class Accounts.SignatureWebView : Components.WebView {
private static WebKit.UserScript? app_script = null;
private static WebKit.UserStyleSheet? app_stylesheet = null;
public static new void load_resources()
throws GLib.Error {
SignatureWebView.app_script = Components.WebView.load_app_script(
"signature-web-view.js"
);
SignatureWebView.app_stylesheet = Components.WebView.load_app_stylesheet(
"signature-web-view.css"
);
}
public SignatureWebView(Application.Configuration config) {
base(config);
this.user_content_manager.add_script(SignatureWebView.app_script);
this.user_content_manager.add_style_sheet(SignatureWebView.app_stylesheet);
}
}

View file

@ -19,6 +19,7 @@ public class Application.Configuration : Geary.BaseObject {
public const string COMPOSE_AS_HTML_KEY = "compose-as-html";
public const string CONVERSATION_VIEWER_ZOOM_KEY = "conversation-viewer-zoom";
public const string DISPLAY_PREVIEW_KEY = "display-preview";
public const string UNSET_HTML_COLORS = "unset-html-colors";
public const string FORMATTING_TOOLBAR_VISIBLE = "formatting-toolbar-visible";
public const string OPTIONAL_PLUGINS = "optional-plugins";
public const string SEARCH_STRATEGY_KEY = "search-strategy";
@ -99,6 +100,10 @@ public class Application.Configuration : Geary.BaseObject {
get { return settings.get_boolean(DISPLAY_PREVIEW_KEY); }
}
public bool unset_html_colors {
get { return settings.get_boolean(UNSET_HTML_COLORS); }
}
public bool single_key_shortcuts { get; set; default = false; }
public bool run_in_background {

View file

@ -175,6 +175,17 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow {
trust_images_row.activatable_widget = trust_images;
trust_images_row.add(trust_images);
var unset_html_colors = new Gtk.Switch();
unset_html_colors.valign = CENTER;
var unset_html_colors_row = new Hdy.ActionRow();
/// Translators: Preferences label
unset_html_colors_row.title = _("_Override the original colors in HTML emails");
unset_html_colors_row.subtitle = _("Overrides the original colors in HTML messages to integrate better with the app theme. Requires restart.");
unset_html_colors_row.use_underline = true;
unset_html_colors_row.activatable_widget = unset_html_colors;
unset_html_colors_row.add(unset_html_colors);
var group = new Hdy.PreferencesGroup();
/// Translators: Preferences group title
//group.title = _("General");
@ -185,6 +196,7 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow {
group.add(single_key_shortucts_row);
group.add(startup_notifications_row);
group.add(trust_images_row);
group.add(unset_html_colors_row);
var page = new Hdy.PreferencesPage();
/// Translators: Preferences page title
@ -229,6 +241,11 @@ public class Components.PreferencesWindow : Hdy.PreferencesWindow {
(GLib.SettingsBindGetMappingShared) settings_trust_images_getter,
(GLib.SettingsBindSetMappingShared) settings_trust_images_setter
);
config.bind(
Application.Configuration.UNSET_HTML_COLORS,
unset_html_colors,
"state"
);
}
}

View file

@ -67,9 +67,9 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
private static WebKit.WebContext? default_context = null;
private static WebKit.UserStyleSheet? user_stylesheet = null;
private static List<WebKit.UserStyleSheet> styles = new List<WebKit.UserStyleSheet>();
private static WebKit.UserScript? script = null;
private static List<WebKit.UserScript> scripts = new List<WebKit.UserScript>();
/**
@ -126,14 +126,13 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
*/
public static void load_resources(GLib.File user_dir)
throws GLib.Error {
WebView.script = load_app_script(
"components-web-view.js"
);
WebView.scripts.append(load_app_script("components-web-view.js"));
WebView.styles.append(load_app_stylesheet("components-web-view.css"));
foreach (string name in new string[] { USER_CSS, USER_CSS_LEGACY }) {
GLib.File stylesheet = user_dir.get_child(name);
try {
WebView.user_stylesheet = load_user_stylesheet(stylesheet);
WebView.styles.append(load_user_stylesheet(stylesheet));
break;
} catch (GLib.IOError.NOT_FOUND err) {
// All good, try the next one or just exit
@ -350,11 +349,22 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
WebKit.UserContentManager content_manager =
custom_manager ?? new WebKit.UserContentManager();
content_manager.add_script(WebView.script);
if (WebView.user_stylesheet != null) {
content_manager.add_style_sheet(WebView.user_stylesheet);
if (config.unset_html_colors) {
WebView.scripts.append(
new WebKit.UserScript(
"window.UNSET_HTML_COLORS = true;",
WebKit.UserContentInjectedFrames.TOP_FRAME,
WebKit.UserScriptInjectionTime.START,
null,
null
)
);
}
WebView.scripts.foreach(script => content_manager.add_script(script));
WebView.styles.foreach(style => content_manager.add_style_sheet(style));
Object(
settings: setts,
user_content_manager: content_manager,

View file

@ -0,0 +1,59 @@
/*
* Shared styles for Components.WebView and subclasses.
*/
body {
/* Variables */
--bg-color: white;
--fg-color: black;
--quote-container-bg: #e8e8e8;
--quote-container-fg: #303030;
/* The following was taken from GTK+4 trunk Adwaita theme:
* gtk/theme/Adwaita/gtk-contained.css */
--button-color: #2e3436;
--button-outline-color: rgba(46, 52, 54, 0.3);
--button-border-color: #b6b6b3;
--button-border-bottom-color: #91918c;
--button-background-image: linear-gradient(to bottom, #e8e8e7, #dededd 60%, #cfcfcd);
--button-text-shadow-color: rgba(255, 255, 255, 0.76923);
--button-box-shadow-color: rgba(255, 255, 255, 0.8);
--hover-button-color: #2e3436;
--hover-button-outline-color: rgba(46, 52, 54, 0.3);
--hover-button-border-color: #b6b6b3;
--hover-button-border-bottom-color: #91918c;
--hover-button-background-image: linear-gradient(to bottom, #f7f7f7, #e8e8e7 60%, #dededd);
--hover-button-box-shadow-color: rgba(255, 255, 255, 1);
/* Adjust default controls and system colors for light and dark mode.
See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */
color-scheme: light dark;
color: var(--fg-color);
background-color: var(--bg-color);
}
@media (prefers-color-scheme: dark) {
body[data-geary-theme=dark] {
--bg-color: #2a2d30;
--fg-color: #eaeaea;
--quote-container-bg: #36383b;
--quote-container-fg: #e3e3e3;
/* Adwaita Dark inspired button colors */
--button-color: #eeeeec;
--button-outline-color: rgba(0, 0, 0, 0.3);
--button-border-color: #202020;
--button-border-bottom-color: #151515;
--button-background-image: linear-gradient(to bottom, #4a4a4a, #3c3c3c 60%, #303030);
--button-text-shadow-color: rgba(0, 0, 0, 0.4);
--button-box-shadow-color: rgba(0, 0, 0, 0.3);
--hover-button-color: #ffffff;
--hover-button-outline-color: rgba(0, 0, 0, 0.4);
--hover-button-border-color: #2c2c2c;
--hover-button-border-bottom-color: #1e1e1e;
--hover-button-background-image: linear-gradient(to bottom, #5a5a5a, #4c4c4c 60%, #404040);
--hover-button-box-shadow-color: rgba(0, 0, 0, 0.4);
}
}

View file

@ -71,6 +71,10 @@ PageState.prototype = {
// completing.
this.updatePreferredHeight();
this._contentLoaded();
if (window.UNSET_HTML_COLORS) {
document.body.setAttribute("data-geary-theme", "dark")
unsetHTMLColors(document);
}
},
loadRemoteResources: function() {
const TYPES = "*[src], *[srcset]";
@ -182,3 +186,61 @@ let MessageSender = function(name) {
_GearyWebExtension.send(name, Array.from(arguments));
};
};
/**
* Unsets inline and stylesheet colors from the given document.
* @param {Document} doc The HTML document to process.
* @returns {void}
*/
function unsetHTMLColors(doc) {
// Slightly modified copy from Evolution
// https://gitlab.gnome.org/GNOME/evolution/-/blob/94510bed94e8de641a8d54f2adaec6f02a8972de/data/webkit/e-web-view.js#L1169
Array.from(doc.styleSheets).forEach(sheet => {
Array.from(sheet.cssRules).forEach(rule => {
if (!rule.style || !rule.selectorText )
return;
if (rule.style.color)
rule.style.removeProperty("color");
if (rule.style.backgroundColor)
rule.style.removeProperty("background-color");
})
})
doc.querySelectorAll("[style],[color],[bgcolor]").forEach(elem => {
if (["HTML", "IFRAME", "INPUT", "BUTTON", "IMG"].includes(elem.tagName)) {
return;
}
if (elem.style) {
if (elem.style.color)
elem.style.removeProperty("color");
if (elem.style.backgroundColor)
elem.style.removeProperty("background-color");
if (elem.style.backgroundImage)
elem.style.removeProperty("background-image");
if (!elem.style.length)
elem.removeAttribute("style");
}
elem.removeAttribute("color");
elem.removeAttribute("bgcolor");
});
doc.querySelectorAll("body").forEach(elem => {
elem.removeAttribute("bgcolor");
elem.removeAttribute("text");
elem.removeAttribute("link");
elem.removeAttribute("alink");
elem.removeAttribute("vlink");
});
}

View file

@ -3,27 +3,20 @@
* Copyright 2017 Michael Gratton <mike@vee.net>
*/
:root {
/* Adjust default controls and system colors for light and dark mode.
See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */
color-scheme: light dark;
/* Apply canvas background to the root element, so body can darken
it using alpha. */
background-color: canvas;
}
body {
background-color: rgb(50% 50% 50% / .05);
margin: 0 !important;
border: 0 !important;
padding: 0 !important;
font-size: medium !important;
}
body.plain {
font-family: monospace;
body.plain, body.plain * {
font-family: monospace !important;
font-weight: normal;
font-style: normal;
font-size: medium !important;
color: var(--fg-color) !important;
background-color: var(--bg-color) !important;
text-decoration: none;
}
@ -50,7 +43,7 @@ body > div#geary-quote {
}
body > div:focus-within {
background-color: canvas;
background-color: white;
}
body > div#geary-signature:focus-within,

View file

@ -13,11 +13,7 @@
transition: height 0.25s !important;
}
:root {
/* Adjust default controls and system colors for light and dark mode.
See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */
color-scheme: light dark;
html {
/* Width must always be defined by the viewport so content doesn't
overflow inside the WebView, height must always be defined by the
content so the WebView can be sized to fit exactly. */
@ -105,11 +101,11 @@ pre {
margin: 0.5em 0;
border-radius: 4px;
padding: 0.5em 0;
color: #303030;
background-color: #e8e8e8;/* recv-quoted */
color: var(--quote-container-fg);
background-color: var(--quote-container-bg);/* recv-quoted */
}
.geary-sent .geary-quote-container {
background-color: #e8e8e8;/* sent-quoted */
background-color: var(--quote-container-bg);/* sent-quoted */
}
.geary-quote-container > .geary-quote {
@ -172,24 +168,24 @@ pre {
border: 1px solid;
border-radius: 3px;
transition: all 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
color: #2e3436;
outline-color: rgba(46, 52, 54, 0.3);
border-color: #b6b6b3;
border-bottom-color: #91918c;
background-image: linear-gradient(to bottom, #e8e8e7, #dededd 60%, #cfcfcd);
text-shadow: 0 1px rgba(255, 255, 255, 0.76923);
box-shadow: inset 0 1px rgba(255, 255, 255, 0.8);
color: var(--button-color);
outline-color: var(--button-outline-color);
border-color: var(--button-border-color);
border-bottom-color: var(--button-border-bottom-color);
background-image: var(--button-background-image);
text-shadow: 0 1px var(--button-text-shadow-color);
box-shadow: inset 0 1px var(--button-box-shadow-color);
}
.geary-quote-container .geary-button:hover {
/* Likewise the properties below also workaround WK Bug 166648,
* and taken from gtk/theme/Adwaita/gtk-contained.css. */
color: #2e3436;
outline-color: rgba(46, 52, 54, 0.3);
border-color: #b6b6b3;
border-bottom-color: #91918c;
text-shadow: 0 1px rgba(255, 255, 255, 0.76923);
box-shadow: inset 0 1px white;
background-image: linear-gradient(to bottom, #f7f7f7, #e8e8e7 60%, #dededd);
color: var(--hover-button-color);
outline-color: var(--hover-button-outline-color);
border-color: var(--hover-button-border-color);
border-bottom-color: var(--hover-button-border-bottom-color);
text-shadow: 0 1px var(--button-text-shadow-color);
box-shadow: inset 0 1px var(--hover-button-box-shadow-color);
background-image: var(--hover-button-background-image);
}
/* Highlight search terms */

View file

@ -9,6 +9,7 @@
<file compressed="true" preprocess="xml-stripblanks">application-main-window.ui</file>
<file compressed="true" preprocess="xml-stripblanks">certificate_warning_dialog.glade</file>
<file compressed="true">components-web-view.js</file>
<file compressed="true">components-web-view.css</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-pane.ui</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-pane-menus.ui</file>
<file compressed="true" preprocess="xml-stripblanks">components-attachment-view.ui</file>
@ -49,7 +50,6 @@
<file compressed="true" preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
<file compressed="true" preprocess="xml-stripblanks">password-dialog.glade</file>
<file compressed="true" preprocess="xml-stripblanks">problem-details-dialog.ui</file>
<file compressed="true">signature-web-view.css</file>
<file compressed="true">signature-web-view.js</file>
<file compressed="true">geary.css</file>
<file compressed="true">single-key-shortcuts.css</file>

View file

@ -1,5 +0,0 @@
:root {
/* Adjust default controls and system colors for light and dark mode.
See https://www.w3.org/TR/css-color-adjust-1/#color-scheme-effect */
color-scheme: light dark;
}