From 4295a86e55e765e3e396a03c090bd08be9e0a390 Mon Sep 17 00:00:00 2001 From: Frédéric Guillot Date: Sun, 28 Apr 2019 18:20:46 -0700 Subject: Add option to enable/disable keyboard shortcuts --- database/migration.go | 2 +- database/sql.go | 2 + database/sql/schema_version_23.sql | 1 + locale/translations.go | 27 ++++++---- locale/translations/de_DE.json | 1 + locale/translations/en_US.json | 1 + locale/translations/es_ES.json | 1 + locale/translations/fr_FR.json | 1 + locale/translations/it_IT.json | 1 + locale/translations/nl_NL.json | 1 + locale/translations/pl_PL.json | 1 + locale/translations/ru_RU.json | 1 + locale/translations/zh_CN.json | 1 + model/user.go | 21 ++++---- storage/user.go | 106 ++++++++++++++++++++++++------------- template/common.go | 6 ++- template/html/common/layout.html | 4 +- template/html/settings.html | 2 + template/views.go | 4 +- ui/form/settings.go | 31 ++++++----- ui/settings_show.go | 11 ++-- ui/static/js.go | 5 +- ui/static/js/bootstrap.js | 63 +++++++++++----------- ui/static/js/keyboard_handler.js | 1 - 24 files changed, 183 insertions(+), 112 deletions(-) create mode 100644 database/sql/schema_version_23.sql diff --git a/database/migration.go b/database/migration.go index 7e37dc6..b48fbf6 100644 --- a/database/migration.go +++ b/database/migration.go @@ -12,7 +12,7 @@ import ( "miniflux.app/logger" ) -const schemaVersion = 22 +const schemaVersion = 23 // Migrate executes database migrations. func Migrate(db *sql.DB) { diff --git a/database/sql.go b/database/sql.go index d4002c8..b7acfc5 100644 --- a/database/sql.go +++ b/database/sql.go @@ -146,6 +146,7 @@ update entries set document_vectors = to_tsvector(substring(title || ' ' || coal create index document_vectors_idx on entries using gin(document_vectors);`, "schema_version_21": `alter table feeds add column user_agent text default '';`, "schema_version_22": `update entries set document_vectors = setweight(to_tsvector(substring(coalesce(title, '') for 1000000)), 'A') || setweight(to_tsvector(substring(coalesce(content, '') for 1000000)), 'B');`, + "schema_version_23": `alter table users add column keyboard_shortcuts boolean default 't';`, "schema_version_3": `create table tokens ( id text not null, value text not null, @@ -196,6 +197,7 @@ var SqlMapChecksums = map[string]string{ "schema_version_20": "5d414c0cfc0da2863c641079afa58b7ff42dccb0f0e01c822ad435c3e3aa9201", "schema_version_21": "77da01ee38918ff4fe33985fbb20ed3276a717a7584c2ca9ebcf4d4ab6cb6910", "schema_version_22": "51ed5fbcae9877e57274511f0ef8c61d254ebd78dfbcbc043a2acd30f4c93ca3", + "schema_version_23": "cb3512d328436447f114e305048c0daa8af7505cfe5eab02778b0de1156081b2", "schema_version_3": "a54745dbc1c51c000f74d4e5068f1e2f43e83309f023415b1749a47d5c1e0f12", "schema_version_4": "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9", "schema_version_5": "46397e2f5f2c82116786127e9f6a403e975b14d2ca7b652a48cd1ba843e6a27c", diff --git a/database/sql/schema_version_23.sql b/database/sql/schema_version_23.sql new file mode 100644 index 0000000..2afb962 --- /dev/null +++ b/database/sql/schema_version_23.sql @@ -0,0 +1 @@ +alter table users add column keyboard_shortcuts boolean default 't'; \ No newline at end of file diff --git a/locale/translations.go b/locale/translations.go index 15ceefe..b161251 100644 --- a/locale/translations.go +++ b/locale/translations.go @@ -221,6 +221,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Sortierung der Artikel", "form.prefs.select.older_first": "Älteste Artikel zuerst", "form.prefs.select.recent_first": "Neueste Artikel zuerst", + "form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren", "form.import.label.file": "OPML Datei", "form.integration.fever_activate": "Fever API aktivieren", "form.integration.fever_username": "Fever Benutzername", @@ -515,6 +516,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Entry Sorting", "form.prefs.select.older_first": "Older entries first", "form.prefs.select.recent_first": "Recent entries first", + "form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts", "form.import.label.file": "OPML file", "form.integration.fever_activate": "Activate Fever API", "form.integration.fever_username": "Fever Username", @@ -789,6 +791,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Clasificación de entradas", "form.prefs.select.older_first": "Entradas más viejas primero", "form.prefs.select.recent_first": "Entradas recientes primero", + "form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado", "form.import.label.file": "Archivo OPML", "form.integration.fever_activate": "Activar API de Fever", "form.integration.fever_username": "Nombre de usuario de Fever", @@ -1063,6 +1066,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Ordre des éléments", "form.prefs.select.older_first": "Ancien éléments en premier", "form.prefs.select.recent_first": "Éléments récents en premier", + "form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier", "form.import.label.file": "Fichier OPML", "form.integration.fever_activate": "Activer l'API de Fever", "form.integration.fever_username": "Nom d'utilisateur pour l'API de Fever", @@ -1357,6 +1361,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Ordinamento articoli", "form.prefs.select.older_first": "Prima i più recenti", "form.prefs.select.recent_first": "Prima i più vecchi", + "form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera", "form.import.label.file": "File OPML", "form.integration.fever_activate": "Abilita l'API di Fever", "form.integration.fever_username": "Nome utente dell'account Fever", @@ -1631,6 +1636,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Volgorde van items", "form.prefs.select.older_first": "Oudere items eerst", "form.prefs.select.recent_first": "Recente items eerst", + "form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in", "form.import.label.file": "OPML-bestand", "form.integration.fever_activate": "Activeer Fever API", "form.integration.fever_username": "Fever gebruikersnaam", @@ -1924,6 +1930,7 @@ var translations = map[string]string{ "form.prefs.label.theme": "Wygląd", "form.prefs.label.entry_sorting": "Sortowanie artykułów", "form.prefs.select.older_first": "Najstarsze wpisy jako pierwsze", + "form.prefs.label.keyboard_shortcuts": "Włącz skróty klawiaturowe", "form.prefs.select.recent_first": "Najnowsze wpisy jako pierwsze", "form.import.label.file": "Plik OPML", "form.integration.fever_activate": "Aktywuj Fever API", @@ -2225,6 +2232,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "Сортировка записей", "form.prefs.select.older_first": "Сначала старые записи", "form.prefs.select.recent_first": "Сначала последние записи", + "form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш", "form.import.label.file": "OPML файл", "form.integration.fever_activate": "Активировать Fever API", "form.integration.fever_username": "Имя пользователя Fever", @@ -2503,6 +2511,7 @@ var translations = map[string]string{ "form.prefs.label.entry_sorting": "内容排序", "form.prefs.select.older_first": "旧->新", "form.prefs.select.recent_first": "新->旧", + "form.prefs.label.keyboard_shortcuts": "启用键盘快捷键", "form.import.label.file": "OPML 文件", "form.integration.fever_activate": "启用 Fever API", "form.integration.fever_username": "Fever 用户名", @@ -2574,13 +2583,13 @@ var translations = map[string]string{ } var translationsChecksums = map[string]string{ - "de_DE": "99892a072e1052e288165485cc0d3d4bdbce5edc237bb4686e6747b26681131c", - "en_US": "223117ae0315b5190e57a45a402bb63f17d5abdb4f904e9fd9cb7e91a921caab", - "es_ES": "e9f14f655d3dbf524d5a95a317cda969210df81b874749c393e475298739b4b9", - "fr_FR": "c4be5bb9addb3f42db66782e3f1b5a9b16b4dae4b070032b2caeb926a15ac0a3", - "it_IT": "a60374bc34e7a0769ea764a7b053c2b47feedd6a677ed0e4b5cd16bb98369d7f", - "nl_NL": "28dfa405a69e7864e687ca5359ca0359de76aa1b4c5d01b9495cd98824f147a2", - "pl_PL": "032b7ad1f29e42376bcbadcddc00ffbedfa61247e3e04c3bbe2c4796fc306cea", - "ru_RU": "ff03f2945a8e453a499394220b83b95199b976ba5351d0fbf726f1d1b0a9a6e5", - "zh_CN": "2440ad576beb46783f8f5d553bf842748fd3b665132b95834f745e49456a0021", + "de_DE": "24812dcba285e56e042486c7f3d6fd9b915b37dc8473640d5099bade94e702b2", + "en_US": "491b3765c7e7b1a3e49b265bf3358a48b6d9aeea5340777a38957ad216101b9d", + "es_ES": "89b84d2505fc27d3f75b2622eae78373c6ce465dc180e1e5bf2a4aca25f73f2e", + "fr_FR": "ac80056831e39c48b47d54299ff112fd9f5e35d14fa248b6a5ae049045cf1537", + "it_IT": "b3521ffb2f56810568bc2317846f2dd16dad77b76dadec5990598af2a6e49403", + "nl_NL": "7d095d9c8915e7ae79d28d35793cb6fce04c867e35b9b4a956da45a7f1a0925c", + "pl_PL": "d99b8dcf56f5672e261b231f01cdf5e17f9c3aa422c798f994aa480a1d9a92e6", + "ru_RU": "4c135a56164be9223d87c50d738d8096c9e732c8ec51ee4a7c7db09ee634837a", + "zh_CN": "c1481025b98c282b284aae12e61d5f2356f4d83797d25a596afb67c3e170c6bf", } diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index 8692235..bf5722d 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -216,6 +216,7 @@ "form.prefs.label.entry_sorting": "Sortierung der Artikel", "form.prefs.select.older_first": "Älteste Artikel zuerst", "form.prefs.select.recent_first": "Neueste Artikel zuerst", + "form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren", "form.import.label.file": "OPML Datei", "form.integration.fever_activate": "Fever API aktivieren", "form.integration.fever_username": "Fever Benutzername", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index de8ae5b..df73f46 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -216,6 +216,7 @@ "form.prefs.label.entry_sorting": "Entry Sorting", "form.prefs.select.older_first": "Older entries first", "form.prefs.select.recent_first": "Recent entries first", + "form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts", "form.import.label.file": "OPML file", "form.integration.fever_activate": "Activate Fever API", "form.integration.fever_username": "Fever Username", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index 1b0e082..b181d88 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -216,6 +216,7 @@ "form.prefs.label.entry_sorting": "Clasificación de entradas", "form.prefs.select.older_first": "Entradas más viejas primero", "form.prefs.select.recent_first": "Entradas recientes primero", + "form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado", "form.import.label.file": "Archivo OPML", "form.integration.fever_activate": "Activar API de Fever", "form.integration.fever_username": "Nombre de usuario de Fever", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index ffd1aa3..7fe7544 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -216,6 +216,7 @@ "form.prefs.label.entry_sorting": "Ordre des éléments", "form.prefs.select.older_first": "Ancien éléments en premier", "form.prefs.select.recent_first": "Éléments récents en premier", + "form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier", "form.import.label.file": "Fichier OPML", "form.integration.fever_activate": "Activer l'API de Fever", "form.integration.fever_username": "Nom d'utilisateur pour l'API de Fever", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index e35a807..26e4350 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -216,6 +216,7 @@ "form.prefs.label.entry_sorting": "Ordinamento articoli", "form.prefs.select.older_first": "Prima i più recenti", "form.prefs.select.recent_first": "Prima i più vecchi", + "form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera", "form.import.label.file": "File OPML", "form.integration.fever_activate": "Abilita l'API di Fever", "form.integration.fever_username": "Nome utente dell'account Fever", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index ea665ba..d0b9e1f 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -216,6 +216,7 @@ "form.prefs.label.entry_sorting": "Volgorde van items", "form.prefs.select.older_first": "Oudere items eerst", "form.prefs.select.recent_first": "Recente items eerst", + "form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in", "form.import.label.file": "OPML-bestand", "form.integration.fever_activate": "Activeer Fever API", "form.integration.fever_username": "Fever gebruikersnaam", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index e80690e..3cf5aba 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -217,6 +217,7 @@ "form.prefs.label.theme": "Wygląd", "form.prefs.label.entry_sorting": "Sortowanie artykułów", "form.prefs.select.older_first": "Najstarsze wpisy jako pierwsze", + "form.prefs.label.keyboard_shortcuts": "Włącz skróty klawiaturowe", "form.prefs.select.recent_first": "Najnowsze wpisy jako pierwsze", "form.import.label.file": "Plik OPML", "form.integration.fever_activate": "Aktywuj Fever API", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index b7b726e..e6d4e98 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -218,6 +218,7 @@ "form.prefs.label.entry_sorting": "Сортировка записей", "form.prefs.select.older_first": "Сначала старые записи", "form.prefs.select.recent_first": "Сначала последние записи", + "form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш", "form.import.label.file": "OPML файл", "form.integration.fever_activate": "Активировать Fever API", "form.integration.fever_username": "Имя пользователя Fever", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index be25db6..8b7eaaf 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -214,6 +214,7 @@ "form.prefs.label.entry_sorting": "内容排序", "form.prefs.select.older_first": "旧->新", "form.prefs.select.recent_first": "新->旧", + "form.prefs.label.keyboard_shortcuts": "启用键盘快捷键", "form.import.label.file": "OPML 文件", "form.integration.fever_activate": "启用 Fever API", "form.integration.fever_username": "Fever 用户名", diff --git a/model/user.go b/model/user.go index 6c537b2..fd19d9f 100644 --- a/model/user.go +++ b/model/user.go @@ -13,16 +13,17 @@ import ( // User represents a user in the system. type User struct { - ID int64 `json:"id"` - Username string `json:"username"` - Password string `json:"password,omitempty"` - IsAdmin bool `json:"is_admin"` - Theme string `json:"theme"` - Language string `json:"language"` - Timezone string `json:"timezone"` - EntryDirection string `json:"entry_sorting_direction"` - LastLoginAt *time.Time `json:"last_login_at,omitempty"` - Extra map[string]string `json:"extra"` + ID int64 `json:"id"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + IsAdmin bool `json:"is_admin"` + Theme string `json:"theme"` + Language string `json:"language"` + Timezone string `json:"timezone"` + EntryDirection string `json:"entry_sorting_direction"` + KeyboardShortcuts bool `json:"keyboard_shortcuts"` + LastLoginAt *time.Time `json:"last_login_at,omitempty"` + Extra map[string]string `json:"extra"` } // NewUser returns a new User. diff --git a/storage/user.go b/storage/user.go index e91e3b8..9e236f7 100644 --- a/storage/user.go +++ b/storage/user.go @@ -67,11 +67,14 @@ func (s *Storage) CreateUser(user *model.User) (err error) { } } - query := `INSERT INTO users - (username, password, is_admin, extra) + query := ` + INSERT INTO users + (username, password, is_admin, extra) VALUES - (LOWER($1), $2, $3, $4) - RETURNING id, username, is_admin, language, theme, timezone, entry_direction` + (LOWER($1), $2, $3, $4) + RETURNING + id, username, is_admin, language, theme, timezone, entry_direction, keyboard_shortcuts + ` err = s.db.QueryRow(query, user.Username, password, user.IsAdmin, extra).Scan( &user.ID, @@ -81,6 +84,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) { &user.Theme, &user.Timezone, &user.EntryDirection, + &user.KeyboardShortcuts, ) if err != nil { return fmt.Errorf("unable to create user: %v", err) @@ -121,15 +125,19 @@ func (s *Storage) UpdateUser(user *model.User) error { return err } - query := `UPDATE users SET - username=LOWER($1), - password=$2, - is_admin=$3, - theme=$4, - language=$5, - timezone=$6, - entry_direction=$7 - WHERE id=$8` + query := ` + UPDATE users SET + username=LOWER($1), + password=$2, + is_admin=$3, + theme=$4, + language=$5, + timezone=$6, + entry_direction=$7, + keyboard_shortcuts=$8 + WHERE + id=$9 + ` _, err = s.db.Exec( query, @@ -140,20 +148,25 @@ func (s *Storage) UpdateUser(user *model.User) error { user.Language, user.Timezone, user.EntryDirection, + user.KeyboardShortcuts, user.ID, ) if err != nil { return fmt.Errorf("unable to update user: %v", err) } } else { - query := `UPDATE users SET - username=LOWER($1), - is_admin=$2, - theme=$3, - language=$4, - timezone=$5, - entry_direction=$6 - WHERE id=$7` + query := ` + UPDATE users SET + username=LOWER($1), + is_admin=$2, + theme=$3, + language=$4, + timezone=$5, + entry_direction=$6, + keyboard_shortcuts=$7 + WHERE + id=$8 + ` _, err := s.db.Exec( query, @@ -163,6 +176,7 @@ func (s *Storage) UpdateUser(user *model.User) error { user.Language, user.Timezone, user.EntryDirection, + user.KeyboardShortcuts, user.ID, ) @@ -188,10 +202,15 @@ func (s *Storage) UserLanguage(userID int64) (language string) { // UserByID finds a user by the ID. func (s *Storage) UserByID(userID int64) (*model.User, error) { defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByID] userID=%d", userID)) - query := `SELECT - id, username, is_admin, theme, language, timezone, entry_direction, last_login_at, extra - FROM users - WHERE id = $1` + query := ` + SELECT + id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, + last_login_at, extra + FROM + users + WHERE + id = $1 + ` return s.fetchUser(query, userID) } @@ -199,10 +218,15 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) { // UserByUsername finds a user by the username. func (s *Storage) UserByUsername(username string) (*model.User, error) { defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByUsername] username=%s", username)) - query := `SELECT - id, username, is_admin, theme, language, timezone, entry_direction, last_login_at, extra - FROM users - WHERE username=LOWER($1)` + query := ` + SELECT + id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, + last_login_at, extra + FROM + users + WHERE + username=LOWER($1) + ` return s.fetchUser(query, username) } @@ -210,10 +234,15 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) { // UserByExtraField finds a user by an extra field value. func (s *Storage) UserByExtraField(field, value string) (*model.User, error) { defer timer.ExecutionTime(time.Now(), fmt.Sprintf("[Storage:UserByExtraField] field=%s", field)) - query := `SELECT - id, username, is_admin, theme, language, timezone, entry_direction, last_login_at, extra - FROM users - WHERE extra->$1=$2` + query := ` + SELECT + id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, + last_login_at, extra + FROM + users + WHERE + extra->$1=$2 + ` return s.fetchUser(query, field, value) } @@ -230,6 +259,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err &user.Language, &user.Timezone, &user.EntryDirection, + &user.KeyboardShortcuts, &user.LastLoginAt, &extra, ) @@ -275,9 +305,12 @@ func (s *Storage) Users() (model.Users, error) { defer timer.ExecutionTime(time.Now(), "[Storage:Users]") query := ` SELECT - id, username, is_admin, theme, language, timezone, entry_direction, last_login_at, extra - FROM users - ORDER BY username ASC` + id, username, is_admin, theme, language, timezone, entry_direction, keyboard_shortcuts, + last_login_at, extra + FROM + users + ORDER BY username ASC + ` rows, err := s.db.Query(query) if err != nil { @@ -297,6 +330,7 @@ func (s *Storage) Users() (model.Users, error) { &user.Language, &user.Timezone, &user.EntryDirection, + &user.KeyboardShortcuts, &user.LastLoginAt, &extra, ) diff --git a/template/common.go b/template/common.go index 379f7f9..94ccd04 100644 --- a/template/common.go +++ b/template/common.go @@ -112,7 +112,9 @@ var templateCommonMap = map[string]string{ - + {{ if .user }}