diff --git a/http/context/context.go b/http/context/context.go index f0fed23f..4ca95c5c 100644 --- a/http/context/context.go +++ b/http/context/context.go @@ -48,6 +48,15 @@ func (c *Context) UserLanguage() string { return language } +// UserTheme get the theme used by the current logged user. +func (c *Context) UserTheme() string { + theme := c.getContextStringValue(middleware.UserThemeContextKey) + if theme == "" { + theme = "default" + } + return theme +} + // CSRF returns the current CSRF token. func (c *Context) CSRF() string { return c.getContextStringValue(middleware.CSRFContextKey) diff --git a/middleware/app_session.go b/middleware/app_session.go index 806debd0..ae1d8e92 100644 --- a/middleware/app_session.go +++ b/middleware/app_session.go @@ -55,6 +55,7 @@ func (m *Middleware) AppSession(next http.Handler) http.Handler { ctx = context.WithValue(ctx, FlashMessageContextKey, session.Data.FlashMessage) ctx = context.WithValue(ctx, FlashErrorMessageContextKey, session.Data.FlashErrorMessage) ctx = context.WithValue(ctx, UserLanguageContextKey, session.Data.Language) + ctx = context.WithValue(ctx, UserThemeContextKey, session.Data.Theme) ctx = context.WithValue(ctx, PocketRequestTokenContextKey, session.Data.PocketRequestToken) next.ServeHTTP(w, r.WithContext(ctx)) }) diff --git a/middleware/context_keys.go b/middleware/context_keys.go index 026da051..03a3e2bf 100644 --- a/middleware/context_keys.go +++ b/middleware/context_keys.go @@ -32,6 +32,9 @@ var ( // UserLanguageContextKey is the context key to store user language. UserLanguageContextKey = &ContextKey{"UserLanguageContextKey"} + // UserThemeContextKey is the context key to store user theme. + UserThemeContextKey = &ContextKey{"UserThemeContextKey"} + // SessionIDContextKey is the context key used to store the session ID. SessionIDContextKey = &ContextKey{"SessionID"} diff --git a/model/session.go b/model/app_session.go similarity index 89% rename from model/session.go rename to model/app_session.go index 763f7097..e9ee06e8 100644 --- a/model/session.go +++ b/model/app_session.go @@ -18,12 +18,13 @@ type SessionData struct { FlashMessage string `json:"flash_message"` FlashErrorMessage string `json:"flash_error_message"` Language string `json:"language"` + Theme string `json:"Theme"` PocketRequestToken string `json:"pocket_request_token"` } func (s SessionData) String() string { - return fmt.Sprintf(`CSRF="%s", "OAuth2State="%s", FlashMessage="%s", FlashErrorMessage="%s", Lang="%s"`, - s.CSRF, s.OAuth2State, s.FlashMessage, s.FlashErrorMessage, s.Language) + return fmt.Sprintf(`CSRF=%q, "OAuth2State=%q, FlashMsg=%q, FlashErrorMsg=%q, Lang=%q, Theme=%q`, + s.CSRF, s.OAuth2State, s.FlashMessage, s.FlashErrorMessage, s.Language, s.Theme) } // Value converts the session data to JSON. diff --git a/model/theme.go b/model/theme.go index 5d32df45..f58f91c8 100644 --- a/model/theme.go +++ b/model/theme.go @@ -15,6 +15,18 @@ func Themes() map[string]string { } } +// ThemeColor returns the color for the address bar or/and the browser color. +// https://developer.mozilla.org/en-US/docs/Web/Manifest#theme_color +// https://developers.google.com/web/tools/lighthouse/audits/address-bar +func ThemeColor(theme string) string { + switch theme { + case "black": + return "#222" + default: + return "#fff" + } +} + // ValidateTheme validates theme value. func ValidateTheme(theme string) error { for key := range Themes() { diff --git a/template/common.go b/template/common.go index 02b67488..2db5d448 100644 --- a/template/common.go +++ b/template/common.go @@ -77,8 +77,9 @@ var templateCommonMap = map[string]string{ - + {{template "title" .}} - Miniflux + @@ -104,12 +105,9 @@ var templateCommonMap = map[string]string{ {{ if .csrf }} {{ end }} - {{template "title" .}} - Miniflux - {{ if .user }} - - {{ else }} - - {{ end }} + + + @@ -241,6 +239,6 @@ var templateCommonMap = map[string]string{ var templateCommonMapChecksums = map[string]string{ "entry_pagination": "756ef122f3ebc73754b5fc4304bf05e59da0ab4af030b2509ff4c9b4a74096ce", "item_meta": "2da78476f6c7fb8742c969ad1bfc20b7b61fddf97d79a77baf3cabda52f6fb49", - "layout": "0d226847454115497b3ef7d67381ae231459c8dcde974eb1a7f4a115957c0e86", + "layout": "16658c13e91cab88ba4c49f14654a95b1db12054cc96def3e40360a52acc6c54", "pagination": "b592d58ea9d6abf2dc0b158621404cbfaeea5413b1c8b8b9818725963096b196", } diff --git a/template/functions.go b/template/functions.go index 46734876..225984b9 100644 --- a/template/functions.go +++ b/template/functions.go @@ -15,6 +15,7 @@ import ( "github.com/miniflux/miniflux/config" "github.com/miniflux/miniflux/filter" "github.com/miniflux/miniflux/http/route" + "github.com/miniflux/miniflux/model" "github.com/miniflux/miniflux/url" ) @@ -90,6 +91,9 @@ func (f *funcMap) Map() template.FuncMap { return str }, + "theme_color": func(theme string) string { + return model.ThemeColor(theme) + }, // These functions are overrided at runtime after the parsing. "elapsed": func(timezone string, t time.Time) string { diff --git a/template/html/common/layout.html b/template/html/common/layout.html index e3968962..dbf70796 100644 --- a/template/html/common/layout.html +++ b/template/html/common/layout.html @@ -3,8 +3,9 @@ - + {{template "title" .}} - Miniflux + @@ -30,12 +31,9 @@ {{ if .csrf }} {{ end }} - {{template "title" .}} - Miniflux - {{ if .user }} - - {{ else }} - - {{ end }} + + + diff --git a/ui/login_check.go b/ui/login_check.go index 71e78541..36fafea6 100644 --- a/ui/login_check.go +++ b/ui/login_check.go @@ -47,13 +47,14 @@ func (c *Controller) CheckLogin(w http.ResponseWriter, r *http.Request) { logger.Info("[Controller:CheckLogin] username=%s just logged in", authForm.Username) c.store.SetLastLogin(userID) - userLanguage, err := c.store.UserLanguage(userID) + user, err := c.store.UserByID(userID) if err != nil { html.ServerError(w, err) return } - sess.SetLanguage(userLanguage) + sess.SetLanguage(user.Language) + sess.SetTheme(user.Theme) http.SetCookie(w, cookie.New( cookie.CookieUserSessionID, diff --git a/ui/logout.go b/ui/logout.go index 2946d1a0..0c777cef 100644 --- a/ui/logout.go +++ b/ui/logout.go @@ -28,6 +28,7 @@ func (c *Controller) Logout(w http.ResponseWriter, r *http.Request) { } sess.SetLanguage(user.Language) + sess.SetTheme(user.Theme) if err := c.store.RemoveUserSessionByToken(user.ID, ctx.UserSessionToken()); err != nil { logger.Error("[Controller:Logout] %v", err) diff --git a/ui/oauth2_callback.go b/ui/oauth2_callback.go index 23e379b2..a39c0ac7 100644 --- a/ui/oauth2_callback.go +++ b/ui/oauth2_callback.go @@ -114,6 +114,7 @@ func (c *Controller) OAuth2Callback(w http.ResponseWriter, r *http.Request) { logger.Info("[Controller:OAuth2Callback] username=%s just logged in", user.Username) c.store.SetLastLogin(user.ID) sess.SetLanguage(user.Language) + sess.SetTheme(user.Theme) http.SetCookie(w, cookie.New( cookie.CookieUserSessionID, diff --git a/ui/session/session.go b/ui/session/session.go index 3d630a81..474bd187 100644 --- a/ui/session/session.go +++ b/ui/session/session.go @@ -51,11 +51,16 @@ func (s *Session) FlashErrorMessage() string { return message } -// SetLanguage updates language field in session. +// SetLanguage updates the language field in session. func (s *Session) SetLanguage(language string) { s.store.UpdateSessionField(s.ctx.SessionID(), "language", language) } +// SetTheme updates the theme field in session. +func (s *Session) SetTheme(theme string) { + s.store.UpdateSessionField(s.ctx.SessionID(), "theme", theme) +} + // SetPocketRequestToken updates Pocket Request Token. func (s *Session) SetPocketRequestToken(requestToken string) { s.store.UpdateSessionField(s.ctx.SessionID(), "pocket_request_token", requestToken) diff --git a/ui/settings_update.go b/ui/settings_update.go index f78b2901..229042d7 100644 --- a/ui/settings_update.go +++ b/ui/settings_update.go @@ -68,6 +68,7 @@ func (c *Controller) UpdateSettings(w http.ResponseWriter, r *http.Request) { } sess.SetLanguage(user.Language) + sess.SetTheme(user.Theme) sess.NewFlashMessage(c.translator.GetLanguage(ctx.UserLanguage()).Get("Preferences saved!")) response.Redirect(w, r, route.Path(c.router, "settings")) } diff --git a/ui/static_manifest.go b/ui/static_manifest.go index 47de9f3a..27abaec6 100644 --- a/ui/static_manifest.go +++ b/ui/static_manifest.go @@ -7,8 +7,10 @@ package ui import ( "net/http" + "github.com/miniflux/miniflux/http/context" "github.com/miniflux/miniflux/http/response/json" "github.com/miniflux/miniflux/http/route" + "github.com/miniflux/miniflux/model" ) // WebManifest renders web manifest file. @@ -20,20 +22,27 @@ func (c *Controller) WebManifest(w http.ResponseWriter, r *http.Request) { } type webManifest struct { - Name string `json:"name"` - Description string `json:"description"` - ShortName string `json:"short_name"` - StartURL string `json:"start_url"` - Icons []webManifestIcon `json:"icons"` - Display string `json:"display"` + Name string `json:"name"` + Description string `json:"description"` + ShortName string `json:"short_name"` + StartURL string `json:"start_url"` + Icons []webManifestIcon `json:"icons"` + Display string `json:"display"` + ThemeColor string `json:"theme_color"` + BackgroundColor string `json:"background_color"` } + ctx := context.New(r) + themeColor := model.ThemeColor(ctx.UserTheme()) + manifest := &webManifest{ - Name: "Miniflux", - ShortName: "Miniflux", - Description: "Minimalist Feed Reader", - Display: "minimal-ui", - StartURL: route.Path(c.router, "unread"), + Name: "Miniflux", + ShortName: "Miniflux", + Description: "Minimalist Feed Reader", + Display: "minimal-ui", + StartURL: route.Path(c.router, "unread"), + ThemeColor: themeColor, + BackgroundColor: themeColor, Icons: []webManifestIcon{ webManifestIcon{Source: route.Path(c.router, "appIcon", "filename", "icon-120.png"), Sizes: "120x120", Type: "image/png"}, webManifestIcon{Source: route.Path(c.router, "appIcon", "filename", "icon-192.png"), Sizes: "192x192", Type: "image/png"}, diff --git a/ui/view/view.go b/ui/view/view.go index a1c66462..08542228 100644 --- a/ui/view/view.go +++ b/ui/view/view.go @@ -35,5 +35,6 @@ func New(tpl *template.Engine, ctx *context.Context, sess *session.Session) *Vie b.params["csrf"] = ctx.CSRF() b.params["flashMessage"] = sess.FlashMessage() b.params["flashErrorMessage"] = sess.FlashErrorMessage() + b.params["theme"] = ctx.UserTheme() return b }