فهرست منبع

Improve themes handling

- Store user theme in session
- Logged out users will keep their theme
- Add theme background color to web manifest and meta tag
Frédéric Guillot 7 سال پیش
والد
کامیت
a291d8a38b

+ 9 - 0
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)

+ 1 - 0
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))
 	})

+ 3 - 0
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"}
 

+ 3 - 2
model/session.go → 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.

+ 12 - 0
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() {

+ 6 - 8
template/common.go

@@ -77,8 +77,9 @@ var templateCommonMap = map[string]string{
 <html>
 <head>
     <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>{{template "title" .}} - Miniflux</title>
 
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
     <meta name="mobile-web-app-capable" content="yes">
     <meta name="apple-mobile-web-app-title" content="Miniflux">
@@ -104,12 +105,9 @@ var templateCommonMap = map[string]string{
     {{ if .csrf }}
         <meta name="X-CSRF-Token" value="{{ .csrf }}">
     {{ end }}
-    <title>{{template "title" .}} - Miniflux</title>
-    {{ if .user }}
-        <link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .user.Theme }}">
-    {{ else }}
-        <link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "default" }}">
-    {{ end }}
+
+    <meta name="theme-color" content="{{ theme_color .theme }}">
+    <link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme }}">
 
     <script type="text/javascript" src="{{ route "javascript" "name" "app" }}" defer></script>
     <script type="text/javascript" src="{{ route "javascript" "name" "sw" }}" defer id="service-worker-script"></script>
@@ -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",
 }

+ 4 - 0
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 {

+ 5 - 7
template/html/common/layout.html

@@ -3,8 +3,9 @@
 <html>
 <head>
     <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>{{template "title" .}} - Miniflux</title>
 
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
     <meta name="mobile-web-app-capable" content="yes">
     <meta name="apple-mobile-web-app-title" content="Miniflux">
@@ -30,12 +31,9 @@
     {{ if .csrf }}
         <meta name="X-CSRF-Token" value="{{ .csrf }}">
     {{ end }}
-    <title>{{template "title" .}} - Miniflux</title>
-    {{ if .user }}
-        <link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .user.Theme }}">
-    {{ else }}
-        <link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "default" }}">
-    {{ end }}
+
+    <meta name="theme-color" content="{{ theme_color .theme }}">
+    <link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme }}">
 
     <script type="text/javascript" src="{{ route "javascript" "name" "app" }}" defer></script>
     <script type="text/javascript" src="{{ route "javascript" "name" "sw" }}" defer id="service-worker-script"></script>

+ 3 - 2
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,

+ 1 - 0
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)

+ 1 - 0
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,

+ 6 - 1
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)

+ 1 - 0
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"))
 }

+ 20 - 11
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"},

+ 1 - 0
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
 }