Make web app display mode configurable
The change is visible after reinstalling the web app. It's not compatible with all browsers. See https://developer.mozilla.org/en-US/docs/Web/Manifest/display
This commit is contained in:
parent
053b1d0f8d
commit
0d935a863f
23 changed files with 182 additions and 12 deletions
|
@ -34,6 +34,7 @@ type User struct {
|
|||
ShowReadingTime bool `json:"show_reading_time"`
|
||||
EntrySwipe bool `json:"entry_swipe"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
DisplayMode string `json:"display_mode"`
|
||||
}
|
||||
|
||||
func (u User) String() string {
|
||||
|
@ -65,6 +66,7 @@ type UserModificationRequest struct {
|
|||
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
||||
ShowReadingTime *bool `json:"show_reading_time"`
|
||||
EntrySwipe *bool `json:"entry_swipe"`
|
||||
DisplayMode *string `json:"display_mode"`
|
||||
}
|
||||
|
||||
// Users represents a list of users.
|
||||
|
|
|
@ -521,4 +521,12 @@ var migrations = []func(tx *sql.Tx) error{
|
|||
`)
|
||||
return err
|
||||
},
|
||||
func(tx *sql.Tx) (err error) {
|
||||
sql := `
|
||||
CREATE TYPE webapp_display_mode AS enum('fullscreen', 'standalone', 'minimal-ui', 'browser');
|
||||
ALTER TABLE users ADD COLUMN display_mode webapp_display_mode default 'standalone';
|
||||
`
|
||||
_, err = tx.Exec(sql)
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "Ungültige Sprache.",
|
||||
"error.invalid_timezone": "Ungültige Zeitzone.",
|
||||
"error.invalid_entry_direction": "Ungültige Sortierreihenfolge.",
|
||||
"error.invalid_display_mode": "Ungültiger Web-App-Anzeigemodus.",
|
||||
"form.feed.label.title": "Titel",
|
||||
"form.feed.label.site_url": "Webseite-URL",
|
||||
"form.feed.label.feed_url": "Abonnement-URL",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Thema",
|
||||
"form.prefs.label.entry_sorting": "Sortierung der Artikel",
|
||||
"form.prefs.label.entries_per_page": "Einträge pro Seite",
|
||||
"form.prefs.label.display_mode": "Anzeigemodus der Web-App (muss neu installiert werden)",
|
||||
"form.prefs.select.older_first": "Älteste Artikel zuerst",
|
||||
"form.prefs.select.recent_first": "Neueste Artikel zuerst",
|
||||
"form.prefs.select.fullscreen": "Vollbildschirm",
|
||||
"form.prefs.select.standalone": "Eigenständige",
|
||||
"form.prefs.select.minimal_ui": "Minimal",
|
||||
"form.prefs.select.browser": "Browser",
|
||||
"form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
|
||||
"form.prefs.label.entry_swipe": "Wischgeste für Einträge auf dem Handy aktivieren",
|
||||
"form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
|
||||
|
|
|
@ -233,6 +233,7 @@
|
|||
"error.invalid_language": "Invalid language.",
|
||||
"error.invalid_timezone": "Invalid timezone.",
|
||||
"error.invalid_entry_direction": "Invalid entry direction.",
|
||||
"error.invalid_display_mode": "Invalid web app display mode.",
|
||||
"error.empty_file": "This file is empty.",
|
||||
"error.bad_credentials": "Invalid username or password.",
|
||||
"error.fields_mandatory": "All fields are mandatory.",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Theme",
|
||||
"form.prefs.label.entry_sorting": "Entry Sorting",
|
||||
"form.prefs.label.entries_per_page": "Entries per page",
|
||||
"form.prefs.label.display_mode": "Web app display mode (needs reinstalling)",
|
||||
"form.prefs.select.older_first": "Older entries first",
|
||||
"form.prefs.select.recent_first": "Recent entries first",
|
||||
"form.prefs.select.fullscreen": "Fullscreen",
|
||||
"form.prefs.select.standalone": "Standalone",
|
||||
"form.prefs.select.minimal_ui": "Minimal",
|
||||
"form.prefs.select.browser": "Browser",
|
||||
"form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
|
||||
"form.prefs.label.entry_swipe": "Enable swipe gesture on entries on mobile",
|
||||
"form.prefs.label.show_reading_time": "Show estimated reading time for articles",
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "Idioma no válido.",
|
||||
"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.",
|
||||
"form.feed.label.title": "Título",
|
||||
"form.feed.label.site_url": "URL del sitio",
|
||||
"form.feed.label.feed_url": "URL de la fuente",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Tema",
|
||||
"form.prefs.label.entry_sorting": "Clasificación de entradas",
|
||||
"form.prefs.label.entries_per_page": "Entradas por página",
|
||||
"form.prefs.label.display_mode": "Modo de visualización de la aplicación web (necesita reinstalación)",
|
||||
"form.prefs.select.older_first": "Entradas más viejas primero",
|
||||
"form.prefs.select.recent_first": "Entradas recientes primero",
|
||||
"form.prefs.select.fullscreen": "Pantalla completa",
|
||||
"form.prefs.select.standalone": "Ser único",
|
||||
"form.prefs.select.minimal_ui": "Mínimo",
|
||||
"form.prefs.select.browser": "Navegador",
|
||||
"form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado",
|
||||
"form.prefs.label.entry_swipe": "Habilitar el gesto de deslizar el dedo en las entradas en el móvil",
|
||||
"form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "Langue non valide.",
|
||||
"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.",
|
||||
"form.feed.label.title": "Titre",
|
||||
"form.feed.label.site_url": "URL du site web",
|
||||
"form.feed.label.feed_url": "URL du flux",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Thème",
|
||||
"form.prefs.label.entry_sorting": "Ordre des éléments",
|
||||
"form.prefs.label.entries_per_page": "Entrées par page",
|
||||
"form.prefs.label.display_mode": "Mode d'affichage de l'application web (doit être réinstallé)",
|
||||
"form.prefs.select.older_first": "Ancien éléments en premier",
|
||||
"form.prefs.select.recent_first": "Éléments récents en premier",
|
||||
"form.prefs.select.fullscreen": "Plein écran",
|
||||
"form.prefs.select.standalone": "Autonome",
|
||||
"form.prefs.select.minimal_ui": "Minimal",
|
||||
"form.prefs.select.browser": "Navigateur",
|
||||
"form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier",
|
||||
"form.prefs.label.entry_swipe": "Activer le geste de balayage sur les entrées sur mobile",
|
||||
"form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "Lingua non valida.",
|
||||
"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.",
|
||||
"form.feed.label.title": "Titolo",
|
||||
"form.feed.label.site_url": "URL del sito",
|
||||
"form.feed.label.feed_url": "URL del feed",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Tema",
|
||||
"form.prefs.label.entry_sorting": "Ordinamento articoli",
|
||||
"form.prefs.label.entries_per_page": "Articoli per pagina",
|
||||
"form.prefs.label.display_mode": "Modalità di visualizzazione web app (necessita la reinstallazione)",
|
||||
"form.prefs.select.older_first": "Prima i più vecchi",
|
||||
"form.prefs.select.recent_first": "Prima i più recenti",
|
||||
"form.prefs.select.fullscreen": "Schermo intero",
|
||||
"form.prefs.select.standalone": "Autonoma",
|
||||
"form.prefs.select.minimal_ui": "Minimale",
|
||||
"form.prefs.select.browser": "Browser",
|
||||
"form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
|
||||
"form.prefs.label.entry_swipe": "Abilita il gesto di scorrimento sulle voci sul cellulare",
|
||||
"form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "言語が無効です。",
|
||||
"error.invalid_timezone": "タイムゾーンが無効です。",
|
||||
"error.invalid_entry_direction": "ソート順が無効です。",
|
||||
"error.invalid_display_mode": "Webアプリの表示モードが無効です。",
|
||||
"form.feed.label.title": "タイトル",
|
||||
"form.feed.label.site_url": "サイト URL",
|
||||
"form.feed.label.feed_url": "フィード URL",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "テーマ",
|
||||
"form.prefs.label.entry_sorting": "記事の並べ替え",
|
||||
"form.prefs.label.entries_per_page": "ページあたりのエントリ",
|
||||
"form.prefs.label.display_mode": "Webアプリの表示モード (再インストールが必要)",
|
||||
"form.prefs.select.older_first": "古い記事を最初に",
|
||||
"form.prefs.select.recent_first": "新しい記事を最初に",
|
||||
"form.prefs.select.fullscreen": "全画面表示",
|
||||
"form.prefs.select.standalone": "スタンドアロン",
|
||||
"form.prefs.select.minimal_ui": "最小限",
|
||||
"form.prefs.select.browser": "ブラウザ",
|
||||
"form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする",
|
||||
"form.prefs.label.entry_swipe": "モバイルのエントリでスワイプジェスチャーを有効にする",
|
||||
"form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "Ongeldige taal.",
|
||||
"error.invalid_timezone": "Ongeldige tijdzone.",
|
||||
"error.invalid_entry_direction": "Ongeldige sorteervolgorde.",
|
||||
"error.invalid_display_mode": "Ongeldige weergavemodus voor webapp.",
|
||||
"form.feed.label.title": "Naam",
|
||||
"form.feed.label.site_url": "Website URL",
|
||||
"form.feed.label.feed_url": "Feed URL",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Skin",
|
||||
"form.prefs.label.entry_sorting": "Volgorde van items",
|
||||
"form.prefs.label.entries_per_page": "Inzendingen per pagina",
|
||||
"form.prefs.label.display_mode": "Weergavemodus voor webapp (moet opnieuw worden geïnstalleerd)",
|
||||
"form.prefs.select.older_first": "Oudere items eerst",
|
||||
"form.prefs.select.recent_first": "Recente items eerst",
|
||||
"form.prefs.select.fullscreen": "Volledig scherm",
|
||||
"form.prefs.select.standalone": "Standalone",
|
||||
"form.prefs.select.minimal_ui": "Minimaal",
|
||||
"form.prefs.select.browser": "Browser",
|
||||
"form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in",
|
||||
"form.prefs.label.entry_swipe": "Schakel veegbewegingen in voor items op mobiel",
|
||||
"form.prefs.label.show_reading_time": "Toon geschatte leestijd voor artikelen",
|
||||
|
|
|
@ -256,6 +256,7 @@
|
|||
"error.invalid_language": "Nieprawidłowy język.",
|
||||
"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.",
|
||||
"form.feed.label.title": "Tytuł",
|
||||
"form.feed.label.site_url": "URL strony",
|
||||
"form.feed.label.feed_url": "URL kanału",
|
||||
|
@ -282,11 +283,16 @@
|
|||
"form.prefs.label.theme": "Wygląd",
|
||||
"form.prefs.label.entry_sorting": "Sortowanie artykułów",
|
||||
"form.prefs.label.entries_per_page": "Wpisy na stronie",
|
||||
"form.prefs.label.display_mode": "Tryb wyświetlania aplikacji internetowej (wymaga ponownej instalacji)",
|
||||
"form.prefs.select.older_first": "Najstarsze wpisy jako pierwsze",
|
||||
"form.prefs.label.keyboard_shortcuts": "Włącz skróty klawiaturowe",
|
||||
"form.prefs.label.entry_swipe": "Włącz gest przesuwania na wpisach na telefonie komórkowym",
|
||||
"form.prefs.label.show_reading_time": "Pokaż szacowany czas czytania artykułów",
|
||||
"form.prefs.select.recent_first": "Najnowsze wpisy jako pierwsze",
|
||||
"form.prefs.select.fullscreen": "Pełny ekran",
|
||||
"form.prefs.select.standalone": "Samodzielny",
|
||||
"form.prefs.select.minimal_ui": "Minimalny",
|
||||
"form.prefs.select.browser": "Przeglądarka",
|
||||
"form.prefs.label.custom_css": "Niestandardowy CSS",
|
||||
"form.import.label.file": "Plik OPML",
|
||||
"form.import.label.url": "URL",
|
||||
|
|
|
@ -254,6 +254,7 @@
|
|||
"error.invalid_language": "Idioma inválido.",
|
||||
"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.",
|
||||
"form.feed.label.title": "Título",
|
||||
"form.feed.label.site_url": "URL do site",
|
||||
"form.feed.label.feed_url": "URL da fonte",
|
||||
|
@ -280,8 +281,13 @@
|
|||
"form.prefs.label.theme": "Tema",
|
||||
"form.prefs.label.entry_sorting": "Ordenação dos itens",
|
||||
"form.prefs.label.entries_per_page": "Itens por página",
|
||||
"form.prefs.label.display_mode": "Modo de exibição do aplicativo Web (precisa ser reinstalado)",
|
||||
"form.prefs.select.older_first": "Itens mais velhos primeiro",
|
||||
"form.prefs.select.recent_first": "Itens mais recentes",
|
||||
"form.prefs.select.fullscreen": "Tela completa",
|
||||
"form.prefs.select.standalone": "Autônomo",
|
||||
"form.prefs.select.minimal_ui": "Mínimo",
|
||||
"form.prefs.select.browser": "Navegador",
|
||||
"form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
|
||||
"form.prefs.label.entry_swipe": "Ativar gesto de deslizar nas entradas no celular",
|
||||
"form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
|
||||
|
|
|
@ -256,6 +256,7 @@
|
|||
"error.invalid_language": "Неверный язык.",
|
||||
"error.invalid_timezone": "Неверный часовой пояс.",
|
||||
"error.invalid_entry_direction": "Неверное направление входа.",
|
||||
"error.invalid_display_mode": "Недопустимый режим отображения веб-приложения.",
|
||||
"form.feed.label.title": "Название",
|
||||
"form.feed.label.site_url": "URL сайта",
|
||||
"form.feed.label.feed_url": "URL подписки",
|
||||
|
@ -282,8 +283,13 @@
|
|||
"form.prefs.label.theme": "Тема",
|
||||
"form.prefs.label.entry_sorting": "Сортировка записей",
|
||||
"form.prefs.label.entries_per_page": "Записи на странице",
|
||||
"form.prefs.label.display_mode": "Режим отображения веб-приложения (требуется переустановка)",
|
||||
"form.prefs.select.older_first": "Сначала старые записи",
|
||||
"form.prefs.select.recent_first": "Сначала последние записи",
|
||||
"form.prefs.select.fullscreen": "Полноэкранный",
|
||||
"form.prefs.select.standalone": "Автономный",
|
||||
"form.prefs.select.minimal_ui": "Минимальный",
|
||||
"form.prefs.select.browser": "Браузер",
|
||||
"form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш",
|
||||
"form.prefs.label.entry_swipe": "Включить жест смахивания для записей на мобильном устройстве",
|
||||
"form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
|
||||
|
|
|
@ -252,6 +252,7 @@
|
|||
"error.invalid_language": "语言无效。",
|
||||
"error.invalid_timezone": "无效的时区。",
|
||||
"error.invalid_entry_direction": "无效的输入方向。",
|
||||
"error.invalid_display_mode": "无效的Web应用显示模式。",
|
||||
"form.feed.label.title": "标题",
|
||||
"form.feed.label.site_url": "站点 URL",
|
||||
"form.feed.label.feed_url": "源 URL",
|
||||
|
@ -278,8 +279,13 @@
|
|||
"form.prefs.label.theme": "主题",
|
||||
"form.prefs.label.entry_sorting": "内容排序",
|
||||
"form.prefs.label.entries_per_page": "每页条目",
|
||||
"form.prefs.label.display_mode": "Web应用程序显示模式 (需要重新安装)",
|
||||
"form.prefs.select.older_first": "旧->新",
|
||||
"form.prefs.select.recent_first": "新->旧",
|
||||
"form.prefs.select.fullscreen": "全屏",
|
||||
"form.prefs.select.standalone": "单机版",
|
||||
"form.prefs.select.minimal_ui": "最小的",
|
||||
"form.prefs.select.browser": "浏览器",
|
||||
"form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
|
||||
"form.prefs.label.entry_swipe": "在移动设备上的条目上启用滑动手势",
|
||||
"form.prefs.label.show_reading_time": "显示文章的预计阅读时间",
|
||||
|
|
|
@ -28,6 +28,7 @@ type User struct {
|
|||
ShowReadingTime bool `json:"show_reading_time"`
|
||||
EntrySwipe bool `json:"entry_swipe"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
DisplayMode string `json:"display_mode"`
|
||||
}
|
||||
|
||||
// UserCreationRequest represents the request to create a user.
|
||||
|
@ -55,6 +56,7 @@ type UserModificationRequest struct {
|
|||
KeyboardShortcuts *bool `json:"keyboard_shortcuts"`
|
||||
ShowReadingTime *bool `json:"show_reading_time"`
|
||||
EntrySwipe *bool `json:"entry_swipe"`
|
||||
DisplayMode *string `json:"display_mode"`
|
||||
}
|
||||
|
||||
// Patch updates the User object with the modification request.
|
||||
|
@ -114,6 +116,10 @@ func (u *UserModificationRequest) Patch(user *User) {
|
|||
if u.EntrySwipe != nil {
|
||||
user.EntrySwipe = *u.EntrySwipe
|
||||
}
|
||||
|
||||
if u.DisplayMode != nil {
|
||||
user.DisplayMode = *u.DisplayMode
|
||||
}
|
||||
}
|
||||
|
||||
// UseTimezone converts last login date to the given timezone.
|
||||
|
|
|
@ -83,7 +83,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
|||
entry_swipe,
|
||||
stylesheet,
|
||||
google_id,
|
||||
openid_connect_id
|
||||
openid_connect_id,
|
||||
display_mode
|
||||
`
|
||||
|
||||
tx, err := s.db.Begin()
|
||||
|
@ -114,6 +115,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
|||
&user.Stylesheet,
|
||||
&user.GoogleID,
|
||||
&user.OpenIDConnectID,
|
||||
&user.DisplayMode,
|
||||
)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
|
@ -162,9 +164,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
entry_swipe=$11,
|
||||
stylesheet=$12,
|
||||
google_id=$13,
|
||||
openid_connect_id=$14
|
||||
openid_connect_id=$14,
|
||||
display_mode=$15
|
||||
WHERE
|
||||
id=$15
|
||||
id=$16
|
||||
`
|
||||
|
||||
_, err = s.db.Exec(
|
||||
|
@ -183,6 +186,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
user.Stylesheet,
|
||||
user.GoogleID,
|
||||
user.OpenIDConnectID,
|
||||
user.DisplayMode,
|
||||
user.ID,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -203,9 +207,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
entry_swipe=$10,
|
||||
stylesheet=$11,
|
||||
google_id=$12,
|
||||
openid_connect_id=$13
|
||||
openid_connect_id=$13,
|
||||
display_mode=$14
|
||||
WHERE
|
||||
id=$14
|
||||
id=$15
|
||||
`
|
||||
|
||||
_, err := s.db.Exec(
|
||||
|
@ -223,6 +228,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
user.Stylesheet,
|
||||
user.GoogleID,
|
||||
user.OpenIDConnectID,
|
||||
user.DisplayMode,
|
||||
user.ID,
|
||||
)
|
||||
|
||||
|
@ -262,7 +268,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
|||
last_login_at,
|
||||
stylesheet,
|
||||
google_id,
|
||||
openid_connect_id
|
||||
openid_connect_id,
|
||||
display_mode
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
|
@ -289,7 +296,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
|||
last_login_at,
|
||||
stylesheet,
|
||||
google_id,
|
||||
openid_connect_id
|
||||
openid_connect_id,
|
||||
display_mode
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
|
@ -316,7 +324,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
|
|||
last_login_at,
|
||||
stylesheet,
|
||||
google_id,
|
||||
openid_connect_id
|
||||
openid_connect_id,
|
||||
display_mode
|
||||
FROM
|
||||
users
|
||||
WHERE
|
||||
|
@ -350,7 +359,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
|||
u.last_login_at,
|
||||
u.stylesheet,
|
||||
u.google_id,
|
||||
u.openid_connect_id
|
||||
u.openid_connect_id,
|
||||
u.display_mode
|
||||
FROM
|
||||
users u
|
||||
LEFT JOIN
|
||||
|
@ -379,6 +389,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
|||
&user.Stylesheet,
|
||||
&user.GoogleID,
|
||||
&user.OpenIDConnectID,
|
||||
&user.DisplayMode,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
|
@ -442,7 +453,8 @@ func (s *Storage) Users() (model.Users, error) {
|
|||
last_login_at,
|
||||
stylesheet,
|
||||
google_id,
|
||||
openid_connect_id
|
||||
openid_connect_id,
|
||||
display_mode
|
||||
FROM
|
||||
users
|
||||
ORDER BY username ASC
|
||||
|
@ -472,6 +484,7 @@ func (s *Storage) Users() (model.Users, error) {
|
|||
&user.Stylesheet,
|
||||
&user.GoogleID,
|
||||
&user.OpenIDConnectID,
|
||||
&user.DisplayMode,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
|
|
@ -43,6 +43,14 @@
|
|||
{{ end }}
|
||||
</select>
|
||||
|
||||
<label for="form-display-mode">{{ t "form.prefs.label.display_mode" }}</label>
|
||||
<select id="form-display-mode" name="display_mode">
|
||||
<option value="fullscreen" {{ if eq "fullscreen" $.form.DisplayMode }}selected="selected"{{ end }}>{{ t "form.prefs.select.fullscreen" }}</option>
|
||||
<option value="standalone" {{ if eq "standalone" $.form.DisplayMode }}selected="selected"{{ end }}>{{ t "form.prefs.select.standalone" }}</option>
|
||||
<option value="minimal-ui" {{ if eq "minimal-ui" $.form.DisplayMode }}selected="selected"{{ end }}>{{ t "form.prefs.select.minimal_ui" }}</option>
|
||||
<option value="browser" {{ if eq "browser" $.form.DisplayMode }}selected="selected"{{ end }}>{{ t "form.prefs.select.browser" }}</option>
|
||||
</select>
|
||||
|
||||
<label for="form-entry-direction">{{ t "form.prefs.label.entry_sorting" }}</label>
|
||||
<select id="form-entry-direction" name="entry_direction">
|
||||
<option value="asc" {{ if eq "asc" $.form.EntryDirection }}selected="selected"{{ end }}>{{ t "form.prefs.select.older_first" }}</option>
|
||||
|
|
|
@ -82,6 +82,10 @@ func TestGetUsers(t *testing.T) {
|
|||
if users[0].EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, users[0].EntriesPerPage)
|
||||
}
|
||||
|
||||
if users[0].DisplayMode != "standalone" {
|
||||
t.Fatalf(`Invalid web app display mode, got "%v"`, users[0].DisplayMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateStandardUser(t *testing.T) {
|
||||
|
@ -127,6 +131,10 @@ func TestCreateStandardUser(t *testing.T) {
|
|||
if user.EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, user.EntriesPerPage)
|
||||
}
|
||||
|
||||
if user.DisplayMode != "standalone" {
|
||||
t.Fatalf(`Invalid web app display mode, got "%v"`, user.DisplayMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveUser(t *testing.T) {
|
||||
|
@ -195,6 +203,10 @@ func TestGetUserByID(t *testing.T) {
|
|||
if user.EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, user.EntriesPerPage)
|
||||
}
|
||||
|
||||
if user.DisplayMode != "standalone" {
|
||||
t.Fatalf(`Invalid web app display mode, got "%v"`, user.DisplayMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserByUsername(t *testing.T) {
|
||||
|
@ -250,6 +262,10 @@ func TestGetUserByUsername(t *testing.T) {
|
|||
if user.EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, user.EntriesPerPage)
|
||||
}
|
||||
|
||||
if user.DisplayMode != "standalone" {
|
||||
t.Fatalf(`Invalid web app display mode, got "%v"`, user.DisplayMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserTheme(t *testing.T) {
|
||||
|
@ -282,10 +298,12 @@ func TestUpdateUserFields(t *testing.T) {
|
|||
stylesheet := "body { color: red }"
|
||||
swipe := false
|
||||
entriesPerPage := 5
|
||||
displayMode := "fullscreen"
|
||||
user, err = client.UpdateUser(user.ID, &miniflux.UserModificationRequest{
|
||||
Stylesheet: &stylesheet,
|
||||
EntrySwipe: &swipe,
|
||||
EntriesPerPage: &entriesPerPage,
|
||||
DisplayMode: &displayMode,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -302,6 +320,10 @@ func TestUpdateUserFields(t *testing.T) {
|
|||
if user.EntriesPerPage != entriesPerPage {
|
||||
t.Fatalf(`Unable to update user EntriesPerPage: got %q instead of %q`, user.EntriesPerPage, entriesPerPage)
|
||||
}
|
||||
|
||||
if user.DisplayMode != displayMode {
|
||||
t.Fatalf(`Unable to update user DisplayMode: got %q instead of %q`, user.DisplayMode, displayMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserThemeWithInvalidValue(t *testing.T) {
|
||||
|
@ -394,6 +416,21 @@ func TestUpdateUserPasswordWithInvalidValue(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserDisplayModeWithInvalidValue(t *testing.T) {
|
||||
username := getRandomUsername()
|
||||
client := miniflux.New(testBaseURL, testAdminUsername, testAdminPassword)
|
||||
user, err := client.CreateUser(username, testStandardPassword, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
displayMode := "invalid"
|
||||
_, err = client.UpdateUser(user.ID, &miniflux.UserModificationRequest{DisplayMode: &displayMode})
|
||||
if err == nil {
|
||||
t.Fatal(`Updating a user web app display mode with an invalid value should raise an error`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserWithEmptyUsernameValue(t *testing.T) {
|
||||
username := getRandomUsername()
|
||||
client := miniflux.New(testBaseURL, testAdminUsername, testAdminPassword)
|
||||
|
|
|
@ -26,6 +26,7 @@ type SettingsForm struct {
|
|||
ShowReadingTime bool
|
||||
CustomCSS string
|
||||
EntrySwipe bool
|
||||
DisplayMode string
|
||||
}
|
||||
|
||||
// Merge updates the fields of the given user.
|
||||
|
@ -40,6 +41,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
|||
user.ShowReadingTime = s.ShowReadingTime
|
||||
user.Stylesheet = s.CustomCSS
|
||||
user.EntrySwipe = s.EntrySwipe
|
||||
user.DisplayMode = s.DisplayMode
|
||||
|
||||
if s.Password != "" {
|
||||
user.Password = s.Password
|
||||
|
@ -50,7 +52,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 == "" {
|
||||
if s.Username == "" || s.Theme == "" || s.Language == "" || s.Timezone == "" || s.EntryDirection == "" || s.DisplayMode == "" {
|
||||
return errors.NewLocalizedError("error.settings_mandatory_fields")
|
||||
}
|
||||
|
||||
|
@ -87,5 +89,6 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
|||
ShowReadingTime: r.FormValue("show_reading_time") == "1",
|
||||
CustomCSS: r.FormValue("custom_css"),
|
||||
EntrySwipe: r.FormValue("entry_swipe") == "1",
|
||||
DisplayMode: r.FormValue("display_mode"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ func TestValid(t *testing.T) {
|
|||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 50,
|
||||
DisplayMode: "standalone",
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
|
@ -32,6 +33,7 @@ func TestConfirmationEmpty(t *testing.T) {
|
|||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 50,
|
||||
DisplayMode: "standalone",
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
|
@ -54,6 +56,7 @@ func TestConfirmationIncorrect(t *testing.T) {
|
|||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 50,
|
||||
DisplayMode: "standalone",
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
|
|
|
@ -37,6 +37,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
|||
ShowReadingTime: user.ShowReadingTime,
|
||||
CustomCSS: user.Stylesheet,
|
||||
EntrySwipe: user.EntrySwipe,
|
||||
DisplayMode: user.DisplayMode,
|
||||
}
|
||||
|
||||
timezones, err := h.store.Timezones()
|
||||
|
|
|
@ -60,6 +60,7 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
|||
Timezone: model.OptionalString(settingsForm.Timezone),
|
||||
EntryDirection: model.OptionalString(settingsForm.EntryDirection),
|
||||
EntriesPerPage: model.OptionalInt(settingsForm.EntriesPerPage),
|
||||
DisplayMode: model.OptionalString(settingsForm.DisplayMode),
|
||||
}
|
||||
|
||||
if validationErr := validator.ValidateUserModification(h.store, loggedUser.ID, userModificationRequest); validationErr != nil {
|
||||
|
|
|
@ -44,12 +44,21 @@ func (h *handler) showWebManifest(w http.ResponseWriter, r *http.Request) {
|
|||
BackgroundColor string `json:"background_color"`
|
||||
}
|
||||
|
||||
displayMode := "standalone"
|
||||
if request.IsAuthenticated(r) {
|
||||
user, err := h.store.UserByID(request.UserID(r))
|
||||
if err != nil {
|
||||
json.ServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
displayMode = user.DisplayMode
|
||||
}
|
||||
themeColor := model.ThemeColor(request.UserTheme(r))
|
||||
manifest := &webManifest{
|
||||
Name: "Miniflux",
|
||||
ShortName: "Miniflux",
|
||||
Description: "Minimalist Feed Reader",
|
||||
Display: "standalone",
|
||||
Display: displayMode,
|
||||
StartURL: route.Path(h.router, "unread"),
|
||||
ThemeColor: themeColor,
|
||||
BackgroundColor: themeColor,
|
||||
|
|
|
@ -73,6 +73,12 @@ func ValidateUserModification(store *storage.Storage, userID int64, changes *mod
|
|||
}
|
||||
}
|
||||
|
||||
if changes.DisplayMode != nil {
|
||||
if err := validateDisplayMode(*changes.DisplayMode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -124,3 +130,10 @@ func validateEntriesPerPage(entriesPerPage int) *ValidationError {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateDisplayMode(displayMode string) *ValidationError {
|
||||
if displayMode != "fullscreen" && displayMode != "standalone" && displayMode != "minimal-ui" && displayMode != "browser" {
|
||||
return NewValidationError("error.invalid_display_mode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue