Quellcode durchsuchen

Make default home page configurable

Romain de Laage vor 3 Jahren
Ursprung
Commit
03a1cfcd5e

+ 2 - 0
client/model.go

@@ -38,6 +38,7 @@ type User struct {
 	DisplayMode         string     `json:"display_mode"`
 	DefaultReadingSpeed int        `json:"default_reading_speed"`
 	CJKReadingSpeed     int        `json:"cjk_reading_speed"`
+	DefaultHomePage     string     `json:"default_home_page"`
 }
 
 func (u User) String() string {
@@ -73,6 +74,7 @@ type UserModificationRequest struct {
 	DisplayMode         *string `json:"display_mode"`
 	DefaultReadingSpeed *int    `json:"default_reading_speed"`
 	CJKReadingSpeed     *int    `json:"cjk_reading_speed"`
+	DefaultHomePage     *string `json:"default_home_page"`
 }
 
 // Users represents a list of users.

+ 6 - 0
database/migrations.go

@@ -604,4 +604,10 @@ var migrations = []func(tx *sql.Tx) error{
 		`)
 		return
 	},
+	func(tx *sql.Tx) (err error) {
+		_, err = tx.Exec(`
+			ALTER TABLE users ADD COLUMN default_home_page text default 'unread';
+		`)
+		return
+	},
 }

+ 2 - 0
locale/translations/de_DE.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "Ungültige Zeitzone.",
     "error.invalid_entry_direction": "Ungültige Sortierreihenfolge.",
     "error.invalid_display_mode": "Ungültiger Web-App-Anzeigemodus.",
+    "error.invalid_default_home_page": "Ungültige Standard-Startseite!",
     "form.feed.label.title": "Titel",
     "form.feed.label.site_url": "Webseite-URL",
     "form.feed.label.feed_url": "Abonnement-URL",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
     "form.prefs.label.custom_css": "Benutzerdefiniertes CSS",
     "form.prefs.label.entry_order": "Eintrag Sortierspalte",
+    "form.prefs.label.default_home_page": "Standard Startseite",
     "form.import.label.file": "OPML Datei",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Fever API aktivieren",

+ 2 - 0
locale/translations/el_EL.json

@@ -241,6 +241,7 @@
     "error.invalid_timezone": "Μη έγκυρη ζώνη ώρας.",
     "error.invalid_entry_direction": "Μη έγκυρη κατεύθυνση ταξινόμησης άρθρων.",
     "error.invalid_display_mode": "Μη έγκυρη λειτουργία εμφάνισης εφαρμογών ιστού.",
+    "error.invalid_default_home_page": "Μη έγκυρη προεπιλεγμένη αρχική σελίδα!",
     "error.empty_file": "Αυτό το αρχείο είναι κενό.",
     "error.bad_credentials": "Μη έγκυρο όνομα χρήστη ή κωδικό πρόσβασης.",
     "error.fields_mandatory": "Όλα τα πεδία είναι υποχρεωτικά.",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Εμφάνιση εκτιμώμενου χρόνου ανάγνωσης για άρθρα",
     "form.prefs.label.custom_css": "Προσαρμοσμένο CSS",
     "form.prefs.label.entry_order": "Στήλη ταξινόμησης εισόδου",
+    "form.prefs.label.default_home_page": "Προεπιλεγμένη αρχική σελίδα",
     "form.import.label.file": "Αρχείο OPML",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Ενεργοποιήστε το Fever API",

+ 2 - 0
locale/translations/en_US.json

@@ -241,6 +241,7 @@
     "error.invalid_timezone": "Invalid timezone.",
     "error.invalid_entry_direction": "Invalid entry direction.",
     "error.invalid_display_mode": "Invalid web app display mode.",
+    "error.invalid_default_home_page": "Invalid default homepage!",
     "error.empty_file": "This file is empty.",
     "error.bad_credentials": "Invalid username or password.",
     "error.fields_mandatory": "All fields are mandatory.",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Show estimated reading time for articles",
     "form.prefs.label.custom_css": "Custom CSS",
     "form.prefs.label.entry_order": "Entry Sorting Column",
+    "form.prefs.label.default_home_page": "Default home page",
     "form.import.label.file": "OPML file",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Activate Fever API",

+ 2 - 0
locale/translations/es_ES.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "Zona horaria no válida.",
     "error.invalid_entry_direction": "Dirección de entrada no válida.",
     "error.invalid_display_mode": "Modo de visualización de la aplicación web no válido.",
+    "error.invalid_default_home_page": "¡Página de inicio por defecto inválida!",
     "form.feed.label.title": "Título",
     "form.feed.label.site_url": "URL del sitio",
     "form.feed.label.feed_url": "URL de la fuente",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
     "form.prefs.label.custom_css": "CSS personalizado",
     "form.prefs.label.entry_order": "Columna de clasificación de entradas",
+    "form.prefs.label.default_home_page": "Página de inicio por defecto",
     "form.import.label.file": "Archivo OPML",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Activar API de Fever",

+ 2 - 0
locale/translations/fi_FI.json

@@ -241,6 +241,7 @@
     "error.invalid_timezone": "Virheellinen aikavyöhyke.",
     "error.invalid_entry_direction": "Invalid entry direction.",
     "error.invalid_display_mode": "Virheellinen verkkosovelluksen näyttötila.",
+    "error.invalid_default_home_page": "Väärä oletusarvoinen kotisivu!",
     "error.empty_file": "Tiedosto on tyhjä.",
     "error.bad_credentials": "Virheellinen käyttäjänimi tai salasana.",
     "error.fields_mandatory": "Kaikki kentät ovat pakollisia.",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Näytä artikkeleiden arvioitu lukuaika",
     "form.prefs.label.custom_css": "Mukautettu CSS",
     "form.prefs.label.entry_order": "Lajittele sarakkeen mukaan",
+    "form.prefs.label.default_home_page": "Oletusarvoinen etusivu",
     "form.import.label.file": "OPML-tiedosto",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Ota Fever API käyttöön",

+ 2 - 0
locale/translations/fr_FR.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "Fuseau horaire non valide.",
     "error.invalid_entry_direction": "Ordre de trie non valide.",
     "error.invalid_display_mode": "Mode d'affichage de l'application web non valide.",
+    "error.invalid_default_home_page": "Page d'accueil par défaut invalide !",
     "form.feed.label.title": "Titre",
     "form.feed.label.site_url": "URL du site web",
     "form.feed.label.feed_url": "URL du flux",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
     "form.prefs.label.custom_css": "CSS personnalisé",
     "form.prefs.label.entry_order": "Colonne de tri des entrées",
+    "form.prefs.label.default_home_page": "Page d'accueil par défaut",
     "form.import.label.file": "Fichier OPML",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Activer l'API de Fever",

+ 2 - 0
locale/translations/hi_IN.json

@@ -241,6 +241,7 @@
     "error.invalid_timezone": "अमान्य समयक्षेत्र.",
     "error.invalid_entry_direction": "अमान्य प्रवेश दिशा।",
     "error.invalid_display_mode": "अमान्य वेब ऐप्लिकेशन प्रदर्शन मोड.",
+    "error.invalid_default_home_page": "अमान्य डिफ़ॉल्ट मुखपृष्ठ!",
     "error.empty_file": "यह फ़ाइल खाली है।",
     "error.bad_credentials": "अमान्य उपयोगकर्ता नाम या पासवर्ड।",
     "error.fields_mandatory": "सभी फील्ड अनिवार्य।",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "विषय के लिए अनुमानित पढ़ने का समय दिखाएं",
     "form.prefs.label.custom_css": "कस्टम सीएसएस",
     "form.prefs.label.entry_order": "प्रवेश छँटाई कॉलम",
+    "form.prefs.label.default_home_page": "डिफ़ॉल्ट होमपेज़",
     "form.import.label.file": "ओपीएमएल फ़ाइल",
     "form.import.label.url": "यूआरएल",
     "form.integration.fever_activate": "फीवर एपीआई सक्रिय करें",

+ 2 - 0
locale/translations/it_IT.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "Fuso orario non valido.",
     "error.invalid_entry_direction": "Ordinamento non valido.",
     "error.invalid_display_mode": "Modalità di visualizzazione web app non valida.",
+    "error.invalid_default_home_page": "Pagina iniziale predefinita non valida!",
     "form.feed.label.title": "Titolo",
     "form.feed.label.site_url": "URL del sito",
     "form.feed.label.feed_url": "URL del feed",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
     "form.prefs.label.custom_css": "CSS personalizzati",
     "form.prefs.label.entry_order": "Colonna di ordinamento delle voci",
+    "form.prefs.label.default_home_page": "Pagina iniziale predefinita",
     "form.import.label.file": "File OPML",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Abilita l'API di Fever",

+ 2 - 0
locale/translations/ja_JP.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "タイムゾーンが無効です。",
     "error.invalid_entry_direction": "ソート順が無効です。",
     "error.invalid_display_mode": "Webアプリの表示モードが無効です。",
+    "error.invalid_default_home_page": "デフォルトのホームページが無効です",
     "form.feed.label.title": "タイトル",
     "form.feed.label.site_url": "サイト URL",
     "form.feed.label.feed_url": "フィード URL",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
     "form.prefs.label.custom_css": "カスタムCSS",
     "form.prefs.label.entry_order": "エントリーソートカラム",
+    "form.prefs.label.default_home_page": "デフォルトのトップページ",
     "form.import.label.file": "OPML ファイル",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Fever API を有効にする",

+ 2 - 0
locale/translations/nl_NL.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "Ongeldige tijdzone.",
     "error.invalid_entry_direction": "Ongeldige sorteervolgorde.",
     "error.invalid_display_mode": "Ongeldige weergavemodus voor webapp.",
+    "error.invalid_default_home_page": "Ongeldige standaard homepage!",
     "form.feed.label.title": "Naam",
     "form.feed.label.site_url": "Website URL",
     "form.feed.label.feed_url": "Feed URL",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Toon geschatte leestijd voor artikelen",
     "form.prefs.label.custom_css": "Aangepaste CSS",
     "form.prefs.label.entry_order": "Ingang Sorteerkolom",
+    "form.prefs.label.default_home_page": "Standaard startpagina",
     "form.import.label.file": "OPML-bestand",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Activeer Fever API",

+ 2 - 0
locale/translations/pl_PL.json

@@ -265,6 +265,7 @@
     "error.invalid_timezone": "Nieprawidłowa strefa czasowa.",
     "error.invalid_entry_direction": "Nieprawidłowa kolejność sortowania.",
     "error.invalid_display_mode": "Nieprawidłowy tryb wyświetlania aplikacji internetowej.",
+    "error.invalid_default_home_page": "Nieprawidłowa domyślna strona główna!",
     "form.feed.label.title": "Tytuł",
     "form.feed.label.site_url": "URL strony",
     "form.feed.label.feed_url": "URL kanału",
@@ -311,6 +312,7 @@
     "form.prefs.select.created_time": "Czas utworzenia wpisu",
     "form.prefs.label.custom_css": "Niestandardowy CSS",
     "form.prefs.label.entry_order": "Kolumna sortowania wpisów",
+    "form.prefs.label.default_home_page": "Domyślna strona główna",
     "form.import.label.file": "Plik OPML",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Aktywuj Fever API",

+ 2 - 0
locale/translations/pt_BR.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "Fuso horário inválido.",
     "error.invalid_entry_direction": "Direção de entrada inválida.",
     "error.invalid_display_mode": "Modo de exibição de aplicativo inválido da web.",
+    "error.invalid_default_home_page": "Página inicial por defeito inválida!",
     "form.feed.label.title": "Título",
     "form.feed.label.site_url": "URL do site",
     "form.feed.label.feed_url": "URL da fonte",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
     "form.prefs.label.custom_css": "CSS customizado",
     "form.prefs.label.entry_order": "Coluna de Ordenação de Entrada",
+    "form.prefs.label.default_home_page": "Página inicial predefinida",
     "form.import.label.file": "Arquivo OPML",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Ativar API do Fever",

+ 2 - 0
locale/translations/ru_RU.json

@@ -265,6 +265,7 @@
     "error.invalid_timezone": "Неверный часовой пояс.",
     "error.invalid_entry_direction": "Неверное направление входа.",
     "error.invalid_display_mode": "Недопустимый режим отображения веб-приложения.",
+    "error.invalid_default_home_page": "Неверная домашняя страница по умолчанию!",
     "form.feed.label.title": "Название",
     "form.feed.label.site_url": "URL сайта",
     "form.feed.label.feed_url": "URL подписки",
@@ -311,6 +312,7 @@
     "form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
     "form.prefs.label.custom_css": "Пользовательские CSS",
     "form.prefs.label.entry_order": "Колонка сортировки ввода",
+    "form.prefs.label.default_home_page": "Домашняя страница по умолчанию",
     "form.import.label.file": "OPML файл",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Активировать Fever API",

+ 2 - 0
locale/translations/tr_TR.json

@@ -241,6 +241,7 @@
     "error.invalid_timezone": "Geçersiz saat dilimi",
     "error.invalid_entry_direction": "Geçersiz giriş yönü.",
     "error.invalid_display_mode": "Geçersiz web uygulaması görüntüleme modu.",
+    "error.invalid_default_home_page": "Geçersiz varsayılan ana sayfa!",
     "error.empty_file": "Bu dosya boş.",
     "error.bad_credentials": "Geçersiz kullanıcı veya parola.",
     "error.fields_mandatory": "Tüm alanlar zorunlu.",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "Makaleler için tahmini okuma süresini göster",
     "form.prefs.label.custom_css": "Özel CSS",
     "form.prefs.label.entry_order": "Giriş Sıralama Sütunu",
+    "form.prefs.label.default_home_page": "Varsayılan ana sayfa",
     "form.import.label.file": "OPML dosyası",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "Fever API'yi Etkinleştir",

+ 2 - 0
locale/translations/zh_CN.json

@@ -261,6 +261,7 @@
     "error.invalid_timezone": "无效的时区。",
     "error.invalid_entry_direction": "无效的输入方向。",
     "error.invalid_display_mode": "无效的网页应用显示模式。",
+    "error.invalid_default_home_page": "无效的默认主页!",
     "form.feed.label.title": "标题",
     "form.feed.label.site_url": "源网站 URL",
     "form.feed.label.feed_url": "订阅源 URL",
@@ -307,6 +308,7 @@
     "form.prefs.label.show_reading_time": "显示文章的预计阅读时间",
     "form.prefs.label.custom_css": "自定义 CSS",
     "form.prefs.label.entry_order": "文章排序依据",
+    "form.prefs.label.default_home_page": "默认主页",
     "form.import.label.file": "OPML 文件",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "启用 Fever API",

+ 2 - 0
locale/translations/zh_TW.json

@@ -263,6 +263,7 @@
     "error.invalid_timezone": "無效的時區。",
     "error.invalid_entry_direction": "無效的輸入方向。",
     "error.invalid_display_mode": "無效的網頁應用顯示模式。",
+    "error.invalid_default_home_page": "默認主頁無效!",
     "form.feed.label.title": "標題",
     "form.feed.label.site_url": "網站 URL",
     "form.feed.label.feed_url": "訂閱Feed URL",
@@ -309,6 +310,7 @@
     "form.prefs.label.show_reading_time": "顯示文章的預計閱讀時間",
     "form.prefs.label.custom_css": "自定義 CSS",
     "form.prefs.label.entry_order": "文章排序依據",
+    "form.prefs.label.default_home_page": "默認主頁",
     "form.import.label.file": "OPML 檔案",
     "form.import.label.url": "URL",
     "form.integration.fever_activate": "啟用 Fever API",

+ 16 - 0
model/home_page.go

@@ -0,0 +1,16 @@
+// Copyright 2017 Frédéric Guillot. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package model // import "miniflux.app/model"
+
+// HomePages returns the list of available home pages.
+func HomePages() map[string]string {
+	return map[string]string{
+		"unread":     "menu.unread",
+		"starred":    "menu.starred",
+		"history":    "menu.history",
+		"feeds":      "menu.feeds",
+		"categories": "menu.categories",
+	}
+}

+ 6 - 0
model/user.go

@@ -32,6 +32,7 @@ type User struct {
 	DisplayMode         string     `json:"display_mode"`
 	DefaultReadingSpeed int        `json:"default_reading_speed"`
 	CJKReadingSpeed     int        `json:"cjk_reading_speed"`
+	DefaultHomePage     string     `json:"default_home_page"`
 }
 
 // UserCreationRequest represents the request to create a user.
@@ -63,6 +64,7 @@ type UserModificationRequest struct {
 	DisplayMode         *string `json:"display_mode"`
 	DefaultReadingSpeed *int    `json:"default_reading_speed"`
 	CJKReadingSpeed     *int    `json:"cjk_reading_speed"`
+	DefaultHomePage     *string `json:"default_home_page"`
 }
 
 // Patch updates the User object with the modification request.
@@ -138,6 +140,10 @@ func (u *UserModificationRequest) Patch(user *User) {
 	if u.CJKReadingSpeed != nil {
 		user.CJKReadingSpeed = *u.CJKReadingSpeed
 	}
+
+	if u.DefaultHomePage != nil {
+		user.DefaultHomePage = *u.DefaultHomePage
+	}
 }
 
 // UseTimezone converts last login date to the given timezone.

+ 23 - 10
storage/user.go

@@ -87,7 +87,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
 			display_mode,
 			entry_order,
 		    default_reading_speed,
-		    cjk_reading_speed
+		    cjk_reading_speed,
+		    default_home_page
 	`
 
 	tx, err := s.db.Begin()
@@ -122,6 +123,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
 		&user.EntryOrder,
 		&user.DefaultReadingSpeed,
 		&user.CJKReadingSpeed,
+		&user.DefaultHomePage,
 	)
 	if err != nil {
 		tx.Rollback()
@@ -174,9 +176,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
 				display_mode=$15,
 				entry_order=$16,
 				default_reading_speed=$17,
-				cjk_reading_speed=$18
+				cjk_reading_speed=$18,
+				default_home_page=$19
 			WHERE
-				id=$19
+				id=$20
 		`
 
 		_, err = s.db.Exec(
@@ -199,6 +202,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
 			user.EntryOrder,
 			user.DefaultReadingSpeed,
 			user.CJKReadingSpeed,
+			user.DefaultHomePage,
 			user.ID,
 		)
 		if err != nil {
@@ -223,9 +227,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
 				display_mode=$14,
 				entry_order=$15,
 				default_reading_speed=$16,
-				cjk_reading_speed=$17
+				cjk_reading_speed=$17,
+				default_home_page=$18
 			WHERE
-				id=$18
+				id=$19
 		`
 
 		_, err := s.db.Exec(
@@ -247,6 +252,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
 			user.EntryOrder,
 			user.DefaultReadingSpeed,
 			user.CJKReadingSpeed,
+			user.DefaultHomePage,
 			user.ID,
 		)
 
@@ -290,7 +296,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
 			display_mode,
 			entry_order,
 			default_reading_speed,
-			cjk_reading_speed
+			cjk_reading_speed,
+			default_home_page
 		FROM
 			users
 		WHERE
@@ -321,7 +328,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
 			display_mode,
 			entry_order,
 			default_reading_speed,
-			cjk_reading_speed
+			cjk_reading_speed,
+			default_home_page
 		FROM
 			users
 		WHERE
@@ -352,7 +360,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
 			display_mode,
 			entry_order,
 			default_reading_speed,
-			cjk_reading_speed
+			cjk_reading_speed,
+			default_home_page
 		FROM
 			users
 		WHERE
@@ -390,7 +399,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
 			u.display_mode,
 			u.entry_order,
 			u.default_reading_speed,
-			u.cjk_reading_speed
+			u.cjk_reading_speed,
+			u.default_home_page
 		FROM
 			users u
 		LEFT JOIN
@@ -423,6 +433,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
 		&user.EntryOrder,
 		&user.DefaultReadingSpeed,
 		&user.CJKReadingSpeed,
+		&user.DefaultHomePage,
 	)
 
 	if err == sql.ErrNoRows {
@@ -516,7 +527,8 @@ func (s *Storage) Users() (model.Users, error) {
 			display_mode,
 			entry_order,
 			default_reading_speed,
-			cjk_reading_speed
+			cjk_reading_speed,
+			default_home_page
 		FROM
 			users
 		ORDER BY username ASC
@@ -550,6 +562,7 @@ func (s *Storage) Users() (model.Users, error) {
 			&user.EntryOrder,
 			&user.DefaultReadingSpeed,
 			&user.CJKReadingSpeed,
+			&user.DefaultHomePage,
 		)
 
 		if err != nil {

+ 1 - 1
template/templates/common/layout.html

@@ -56,7 +56,7 @@
     <header class="header">
         <nav>
             <div class="logo">
-                <a href="{{ route "unread" }}">Mini<span>flux</span></a>
+                <a href="{{ route .user.DefaultHomePage }}">Mini<span>flux</span></a>
             </div>
             <ul>
                 <li {{ if eq .menu "unread" }}class="active"{{ end }} title="{{ t "tooltip.keyboard_shortcuts" "g u" }}">

+ 7 - 0
template/templates/views/settings.html

@@ -63,6 +63,13 @@
         <option value="created_at" {{ if eq "created_at" $.form.EntryOrder }}selected="selected"{{ end }}>{{ t "form.prefs.select.created_time" }}</option>
     </select>
 
+    <label for="form-default-home-page">{{ t "form.prefs.label.default_home_page" }}</label>
+    <select id="form-default-home-page" name="default_home_page">
+    {{ range $key, $value := .default_home_pages }}
+        <option value="{{ $key }}" {{ if eq $key $.form.DefaultHomePage }}selected="selected"{{ end }}>{{ t $value }}</option>
+    {{ end }}
+    </select>
+
     <label for="form-entries-per-page">{{ t "form.prefs.label.entries_per_page" }}</label>
     <input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">
 

+ 4 - 1
ui/form/settings.go

@@ -30,6 +30,7 @@ type SettingsForm struct {
 	DisplayMode         string
 	DefaultReadingSpeed int
 	CJKReadingSpeed     int
+	DefaultHomePage     string
 }
 
 // Merge updates the fields of the given user.
@@ -48,6 +49,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
 	user.DisplayMode = s.DisplayMode
 	user.CJKReadingSpeed = s.CJKReadingSpeed
 	user.DefaultReadingSpeed = s.DefaultReadingSpeed
+	user.DefaultHomePage = s.DefaultHomePage
 
 	if s.Password != "" {
 		user.Password = s.Password
@@ -58,7 +60,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
 
 // Validate makes sure the form values are valid.
 func (s *SettingsForm) Validate() error {
-	if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" {
+	if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" || s.DefaultHomePage == "" {
 		return errors.NewLocalizedError("error.settings_mandatory_fields")
 	}
 
@@ -111,5 +113,6 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
 		DisplayMode:         r.FormValue("display_mode"),
 		DefaultReadingSpeed: int(defaultReadingSpeed),
 		CJKReadingSpeed:     int(cjkReadingSpeed),
+		DefaultHomePage:     r.FormValue("default_home_page"),
 	}
 }

+ 3 - 0
ui/form/settings_test.go

@@ -17,6 +17,7 @@ func TestValid(t *testing.T) {
 		DisplayMode:         "standalone",
 		DefaultReadingSpeed: 35,
 		CJKReadingSpeed:     25,
+		DefaultHomePage:     "unread",
 	}
 
 	err := settings.Validate()
@@ -38,6 +39,7 @@ func TestConfirmationEmpty(t *testing.T) {
 		DisplayMode:         "standalone",
 		DefaultReadingSpeed: 35,
 		CJKReadingSpeed:     25,
+		DefaultHomePage:     "unread",
 	}
 
 	err := settings.Validate()
@@ -63,6 +65,7 @@ func TestConfirmationIncorrect(t *testing.T) {
 		DisplayMode:         "standalone",
 		DefaultReadingSpeed: 35,
 		CJKReadingSpeed:     25,
+		DefaultHomePage:     "unread",
 	}
 
 	err := settings.Validate()

+ 1 - 1
ui/login_check.go

@@ -60,5 +60,5 @@ func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) {
 		config.Opts.BasePath(),
 	))
 
-	html.Redirect(w, r, route.Path(h.router, "unread"))
+	html.Redirect(w, r, route.Path(h.router, user.DefaultHomePage))
 }

+ 7 - 1
ui/login_show.go

@@ -16,7 +16,13 @@ import (
 
 func (h *handler) showLoginPage(w http.ResponseWriter, r *http.Request) {
 	if request.IsAuthenticated(r) {
-		html.Redirect(w, r, route.Path(h.router, "unread"))
+		user, err := h.store.UserByID(request.UserID(r))
+		if err != nil {
+			html.ServerError(w, r, err)
+			return
+		}
+
+		html.Redirect(w, r, route.Path(h.router, user.DefaultHomePage))
 		return
 	}
 

+ 2 - 0
ui/settings_show.go

@@ -41,6 +41,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
 		DisplayMode:         user.DisplayMode,
 		DefaultReadingSpeed: user.DefaultReadingSpeed,
 		CJKReadingSpeed:     user.CJKReadingSpeed,
+		DefaultHomePage:     user.DefaultHomePage,
 	}
 
 	timezones, err := h.store.Timezones()
@@ -57,6 +58,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
 	view.Set("user", user)
 	view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
 	view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(user.ID))
+	view.Set("default_home_pages", model.HomePages())
 
 	html.OK(w, r, view.Render("settings"))
 }

+ 1 - 0
ui/settings_update.go

@@ -63,6 +63,7 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
 		DisplayMode:         model.OptionalString(settingsForm.DisplayMode),
 		DefaultReadingSpeed: model.OptionalInt(settingsForm.DefaultReadingSpeed),
 		CJKReadingSpeed:     model.OptionalInt(settingsForm.CJKReadingSpeed),
+		DefaultHomePage:     model.OptionalString(settingsForm.DefaultHomePage),
 	}
 
 	if validationErr := validator.ValidateUserModification(h.store, loggedUser.ID, userModificationRequest); validationErr != nil {

+ 14 - 0
validator/user.go

@@ -91,6 +91,12 @@ func ValidateUserModification(store *storage.Storage, userID int64, changes *mod
 		}
 	}
 
+	if changes.DefaultHomePage != nil {
+		if err := validateDefaultHomePage(*changes.DefaultHomePage); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -156,3 +162,11 @@ func validateDisplayMode(displayMode string) *ValidationError {
 	}
 	return nil
 }
+
+func validateDefaultHomePage(defaultHomePage string) *ValidationError {
+	defaultHomePages := model.HomePages()
+	if _, found := defaultHomePages[defaultHomePage]; !found {
+		return NewValidationError("error.invalid_default_home_page")
+	}
+	return nil
+}