forge
ExploreLog in

Tahinli/forge

public
0 watching
★ 0
2.7 KiB
1//! The colour theme for a request. A signed-in user's stored preference wins. Anonymous
2//! visitors use the `forge_theme` cookie, which the footer form sets. `auto`, or no choice at
3//! all, renders no `data-theme`, so the stylesheet follows the operating system.
4
5use std::time::Duration;
6
7use forge_core::User;
8use topcoat::context::Cx;
9use topcoat::cookie::{Cookie, Cookies, SameSite, cookies, time};
10
11/// Name of the anonymous theme cookie.
12pub const THEME_COOKIE: &str = "forge_theme";
13
14/// Lifetime of the anonymous theme cookie.
15pub const THEME_COOKIE_TTL: Duration = Duration::from_secs(365 * 24 * 60 * 60);
16
17/// The stored colour preference for this request: `"auto"`, `"light"` or `"dark"`. A signed-in
18/// user's stored preference wins. Anonymous visitors use the cookie. Anything else reads as
19/// `"auto"`.
20pub fn preference(cx: &Cx, viewer: Option<&User>) -> &'static str {
21 let stored = match viewer {
22 Some(user) => user.theme.clone(),
23 None => cookies(cx)
24 .get(THEME_COOKIE)
25 .map(|cookie| cookie.value().to_owned())
26 .unwrap_or_default(),
27 };
28 match stored.as_str() {
29 "light" => "light",
30 "dark" => "dark",
31 _ => "auto",
32 }
33}
34
35/// The `data-theme` value for this request: `Some("light")` or `Some("dark")`, or `None` when the
36/// page should follow the operating system.
37pub fn data_theme(cx: &Cx, viewer: Option<&User>) -> Option<&'static str> {
38 match preference(cx, viewer) {
39 "auto" => None,
40 theme => Some(theme),
41 }
42}
43
44/// The preference the top-bar button switches to from `current`: auto, then light, then dark,
45/// then back to auto.
46pub fn next_preference(current: &str) -> &'static str {
47 match current {
48 "auto" => "light",
49 "light" => "dark",
50 _ => "auto",
51 }
52}
53
54/// Sets the anonymous theme cookie. The caller has already checked that `theme` is valid.
55pub fn set_theme_cookie(cx: &Cx, theme: &str) {
56 let max_age =
57 time::Duration::seconds(i64::try_from(THEME_COOKIE_TTL.as_secs()).unwrap_or(i64::MAX));
58 cookies(cx).add(
59 Cookie::build((THEME_COOKIE, theme.to_owned()))
60 .path("/")
61 .secure(true)
62 .http_only(true)
63 .same_site(SameSite::Lax)
64 .max_age(max_age)
65 .build(),
66 );
67}
68
69#[cfg(test)]
70mod tests {
71 use super::next_preference;
72
73 #[test]
74 fn the_toggle_cycles_auto_light_dark_auto() {
75 assert_eq!(next_preference("auto"), "light");
76 assert_eq!(next_preference("light"), "dark");
77 assert_eq!(next_preference("dark"), "auto");
78 }
79
80 #[test]
81 fn unknown_preferences_start_the_cycle_at_auto() {
82 assert_eq!(next_preference(""), "auto");
83 assert_eq!(next_preference("neon"), "auto");
84 }
85}