From 0d935a863ff4e53b98c23d2956fc5e8beac1853d Mon Sep 17 00:00:00 2001 From: 1pav <60271007+1pav@users.noreply.github.com> Date: Sun, 28 Feb 2021 22:29:51 +0100 Subject: [PATCH] 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 --- client/model.go | 2 ++ database/migrations.go | 8 ++++++ locale/translations/de_DE.json | 6 +++++ locale/translations/en_US.json | 6 +++++ locale/translations/es_ES.json | 6 +++++ locale/translations/fr_FR.json | 6 +++++ locale/translations/it_IT.json | 6 +++++ locale/translations/ja_JP.json | 6 +++++ locale/translations/nl_NL.json | 6 +++++ locale/translations/pl_PL.json | 6 +++++ locale/translations/pt_BR.json | 6 +++++ locale/translations/ru_RU.json | 6 +++++ locale/translations/zh_CN.json | 6 +++++ model/user.go | 6 +++++ storage/user.go | 33 ++++++++++++++++------- template/templates/views/settings.html | 8 ++++++ tests/user_test.go | 37 ++++++++++++++++++++++++++ ui/form/settings.go | 5 +++- ui/form/settings_test.go | 3 +++ ui/settings_show.go | 1 + ui/settings_update.go | 1 + ui/static_manifest.go | 11 +++++++- validator/user.go | 13 +++++++++ 23 files changed, 182 insertions(+), 12 deletions(-) diff --git a/client/model.go b/client/model.go index 938b13c6..0b9cdeb9 100644 --- a/client/model.go +++ b/client/model.go @@ -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. diff --git a/database/migrations.go b/database/migrations.go index 2f108874..c7dfc8d9 100644 --- a/database/migrations.go +++ b/database/migrations.go @@ -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 + }, } diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 478a66ba..64184b99 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -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", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index 75e7e1b0..6cd654a6 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -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", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index ab2f5e1d..1944f31a 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -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", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index 708f9d8f..09dd3b10 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -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", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index f857d563..34e10156 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -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", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 4ac53262..a4a8fb79 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -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": "記事の推定読書時間を表示する", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index fdc86657..aafb6955 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -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", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 71f00fbf..fd3f1b90 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -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", diff --git a/locale/translations/pt_BR.json b/locale/translations/pt_BR.json index 35bda31b..360f1f3c 100644 --- a/locale/translations/pt_BR.json +++ b/locale/translations/pt_BR.json @@ -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", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index 770f3bee..0a5a3822 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -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": "Показать примерное время чтения статей", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index 29ee1e51..eeb6f312 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -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": "显示文章的预计阅读时间", diff --git a/model/user.go b/model/user.go index 5453a41a..347104b7 100644 --- a/model/user.go +++ b/model/user.go @@ -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. diff --git a/storage/user.go b/storage/user.go index 2d539e1b..e86f4bb2 100644 --- a/storage/user.go +++ b/storage/user.go @@ -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 { diff --git a/template/templates/views/settings.html b/template/templates/views/settings.html index 3caf517b..f8725f09 100644 --- a/template/templates/views/settings.html +++ b/template/templates/views/settings.html @@ -43,6 +43,14 @@ {{ end }} + + +