//! The colour theme for a request. A signed-in user's stored preference wins. Anonymous //! visitors use the `forge_theme` cookie, which the footer form sets. `auto`, or no choice at //! all, renders no `data-theme`, so the stylesheet follows the operating system. use std::time::Duration; use forge_core::User; use topcoat::context::Cx; use topcoat::cookie::{Cookie, Cookies, SameSite, cookies, time}; /// Name of the anonymous theme cookie. pub const THEME_COOKIE: &str = "forge_theme"; /// Lifetime of the anonymous theme cookie. pub const THEME_COOKIE_TTL: Duration = Duration::from_secs(365 * 24 * 60 * 60); /// The stored colour preference for this request: `"auto"`, `"light"` or `"dark"`. A signed-in /// user's stored preference wins. Anonymous visitors use the cookie. Anything else reads as /// `"auto"`. pub fn preference(cx: &Cx, viewer: Option<&User>) -> &'static str { let stored = match viewer { Some(user) => user.theme.clone(), None => cookies(cx) .get(THEME_COOKIE) .map(|cookie| cookie.value().to_owned()) .unwrap_or_default(), }; match stored.as_str() { "light" => "light", "dark" => "dark", _ => "auto", } } /// The `data-theme` value for this request: `Some("light")` or `Some("dark")`, or `None` when the /// page should follow the operating system. pub fn data_theme(cx: &Cx, viewer: Option<&User>) -> Option<&'static str> { match preference(cx, viewer) { "auto" => None, theme => Some(theme), } } /// The preference the top-bar button switches to from `current`: auto, then light, then dark, /// then back to auto. pub fn next_preference(current: &str) -> &'static str { match current { "auto" => "light", "light" => "dark", _ => "auto", } } /// Sets the anonymous theme cookie. The caller has already checked that `theme` is valid. pub fn set_theme_cookie(cx: &Cx, theme: &str) { let max_age = time::Duration::seconds(i64::try_from(THEME_COOKIE_TTL.as_secs()).unwrap_or(i64::MAX)); cookies(cx).add( Cookie::build((THEME_COOKIE, theme.to_owned())) .path("/") .secure(true) .http_only(true) .same_site(SameSite::Lax) .max_age(max_age) .build(), ); } #[cfg(test)] mod tests { use super::next_preference; #[test] fn the_toggle_cycles_auto_light_dark_auto() { assert_eq!(next_preference("auto"), "light"); assert_eq!(next_preference("light"), "dark"); assert_eq!(next_preference("dark"), "auto"); } #[test] fn unknown_preferences_start_the_cycle_at_auto() { assert_eq!(next_preference(""), "auto"); assert_eq!(next_preference("neon"), "auto"); } }