Просмотр исходного кода

Add option to enable/disable keyboard shortcuts

Frédéric Guillot 7 лет назад
Родитель
Сommit
4295a86e55

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

+ 2 - 0
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",

+ 1 - 0
database/sql/schema_version_23.sql

@@ -0,0 +1 @@
+alter table users add column keyboard_shortcuts boolean default 't';

+ 18 - 9
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",
 }

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

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

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

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

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

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

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

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

+ 1 - 0
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 用户名",

+ 11 - 10
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.

+ 70 - 36
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,
 		)

+ 4 - 2
template/common.go

@@ -112,7 +112,9 @@ var templateCommonMap = map[string]string{
     <script type="text/javascript" src="{{ route "javascript" "name" "app" }}?{{ .app_js_checksum }}" defer></script>
     <script type="text/javascript" src="{{ route "javascript" "name" "sw" }}?{{ .sw_js_checksum }}" defer id="service-worker-script"></script>
 </head>
-<body data-entries-status-url="{{ route "updateEntriesStatus" }}">
+<body
+    data-entries-status-url="{{ route "updateEntriesStatus" }}"
+    {{ if .user }}{{ if not .user.KeyboardShortcuts }}data-disable-keyboard-shortcuts="true"{{ end }}{{ end }}>
     {{ if .user }}
     <header class="header">
         <nav>
@@ -245,6 +247,6 @@ var templateCommonMap = map[string]string{
 var templateCommonMapChecksums = map[string]string{
 	"entry_pagination": "4faa91e2eae150c5e4eab4d258e039dfdd413bab7602f0009360e6d52898e353",
 	"item_meta":        "34deb081a054f2948ad808bdb2c8603d6ab00c58f2f50c4ead0b47ae092888eb",
-	"layout":           "4a5339267f67b5999a22ece7584df4c75785bc3bf95d44e1891da763aaea7991",
+	"layout":           "838fb8ec4df4120ff63168c15d900e3734f52e4b7473fb1d45695e6b27540d11",
 	"pagination":       "3386e90c6e1230311459e9a484629bc5d5bf39514a75ef2e73bbbc61142f7abb",
 }

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

@@ -38,7 +38,9 @@
     <script type="text/javascript" src="{{ route "javascript" "name" "app" }}?{{ .app_js_checksum }}" defer></script>
     <script type="text/javascript" src="{{ route "javascript" "name" "sw" }}?{{ .sw_js_checksum }}" defer id="service-worker-script"></script>
 </head>
-<body data-entries-status-url="{{ route "updateEntriesStatus" }}">
+<body
+    data-entries-status-url="{{ route "updateEntriesStatus" }}"
+    {{ if .user }}{{ if not .user.KeyboardShortcuts }}data-disable-keyboard-shortcuts="true"{{ end }}{{ end }}>
     {{ if .user }}
     <header class="header">
         <nav>

+ 2 - 0
template/html/settings.html

@@ -64,6 +64,8 @@
         <option value="desc" {{ if eq "desc" $.form.EntryDirection }}selected="selected"{{ end }}>{{ t "form.prefs.select.recent_first" }}</option>
     </select>
 
+    <label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
+
     <div class="buttons">
         <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
     </div>

+ 3 - 1
template/views.go

@@ -1282,6 +1282,8 @@ var templateViewsMap = map[string]string{
         <option value="desc" {{ if eq "desc" $.form.EntryDirection }}selected="selected"{{ end }}>{{ t "form.prefs.select.recent_first" }}</option>
     </select>
 
+    <label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
+
     <div class="buttons">
         <button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
     </div>
@@ -1437,7 +1439,7 @@ var templateViewsMapChecksums = map[string]string{
 	"login":               "2e72d2d4b9786641b696bedbed5e10b04bdfd68254ddbbdb0a53cca621d200c7",
 	"search_entries":      "d71849a4f2b0573c7c76ad0ea941812009e9f022de60895987a781d3e6f08a01",
 	"sessions":            "1b3ec0970a4111b81f86d6ed187bb410f88972e2ede6723b9febcc4c7e5fc921",
-	"settings":            "bc04faf83dd977306825973375954600bd014619340188e1243fd9e2f5d5e1a9",
+	"settings":            "152143e58d057ea6ab3bfd8dd947bfd70685843ca40e40542484b23849746df4",
 	"unread_entries":      "880018cbc59ec09b23dd800c4010fadad944d7023e0d36a3872c09b5d4952799",
 	"users":               "4b56cc76fbcc424e7c870d0efca93bb44dbfcc2a08b685cf799c773fbb8dfb2f",
 }

+ 17 - 14
ui/form/settings.go

@@ -13,13 +13,14 @@ import (
 
 // SettingsForm represents the settings form.
 type SettingsForm struct {
-	Username       string
-	Password       string
-	Confirmation   string
-	Theme          string
-	Language       string
-	Timezone       string
-	EntryDirection string
+	Username          string
+	Password          string
+	Confirmation      string
+	Theme             string
+	Language          string
+	Timezone          string
+	EntryDirection    string
+	KeyboardShortcuts bool
 }
 
 // Merge updates the fields of the given user.
@@ -29,6 +30,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
 	user.Language = s.Language
 	user.Timezone = s.Timezone
 	user.EntryDirection = s.EntryDirection
+	user.KeyboardShortcuts = s.KeyboardShortcuts
 
 	if s.Password != "" {
 		user.Password = s.Password
@@ -64,12 +66,13 @@ func (s *SettingsForm) Validate() error {
 // NewSettingsForm returns a new SettingsForm.
 func NewSettingsForm(r *http.Request) *SettingsForm {
 	return &SettingsForm{
-		Username:       r.FormValue("username"),
-		Password:       r.FormValue("password"),
-		Confirmation:   r.FormValue("confirmation"),
-		Theme:          r.FormValue("theme"),
-		Language:       r.FormValue("language"),
-		Timezone:       r.FormValue("timezone"),
-		EntryDirection: r.FormValue("entry_direction"),
+		Username:          r.FormValue("username"),
+		Password:          r.FormValue("password"),
+		Confirmation:      r.FormValue("confirmation"),
+		Theme:             r.FormValue("theme"),
+		Language:          r.FormValue("language"),
+		Timezone:          r.FormValue("timezone"),
+		EntryDirection:    r.FormValue("entry_direction"),
+		KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
 	}
 }

+ 6 - 5
ui/settings_show.go

@@ -27,11 +27,12 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
 	}
 
 	settingsForm := form.SettingsForm{
-		Username:       user.Username,
-		Theme:          user.Theme,
-		Language:       user.Language,
-		Timezone:       user.Timezone,
-		EntryDirection: user.EntryDirection,
+		Username:          user.Username,
+		Theme:             user.Theme,
+		Language:          user.Language,
+		Timezone:          user.Timezone,
+		EntryDirection:    user.EntryDirection,
+		KeyboardShortcuts: user.KeyboardShortcuts,
 	}
 
 	timezones, err := h.store.Timezones()

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
ui/static/js.go


+ 33 - 30
ui/static/js/bootstrap.js

@@ -2,36 +2,39 @@ document.addEventListener("DOMContentLoaded", function() {
     FormHandler.handleSubmitButtons();
 
     let navHandler = new NavHandler();
-    let keyboardHandler = new KeyboardHandler();
-    keyboardHandler.on("g u", () => navHandler.goToPage("unread"));
-    keyboardHandler.on("g b", () => navHandler.goToPage("starred"));
-    keyboardHandler.on("g h", () => navHandler.goToPage("history"));
-    keyboardHandler.on("g f", () => navHandler.goToFeedOrFeeds());
-    keyboardHandler.on("g c", () => navHandler.goToPage("categories"));
-    keyboardHandler.on("g s", () => navHandler.goToPage("settings"));
-    keyboardHandler.on("ArrowLeft", () => navHandler.goToPrevious());
-    keyboardHandler.on("ArrowRight", () => navHandler.goToNext());
-    keyboardHandler.on("j", () => navHandler.goToPrevious());
-    keyboardHandler.on("p", () => navHandler.goToPrevious());
-    keyboardHandler.on("k", () => navHandler.goToNext());
-    keyboardHandler.on("n", () => navHandler.goToNext());
-    keyboardHandler.on("h", () => navHandler.goToPage("previous"));
-    keyboardHandler.on("l", () => navHandler.goToPage("next"));
-    keyboardHandler.on("o", () => navHandler.openSelectedItem());
-    keyboardHandler.on("v", () => navHandler.openOriginalLink());
-    keyboardHandler.on("m", () => navHandler.toggleEntryStatus());
-    keyboardHandler.on("A", () => {
-        let element = document.querySelector("a[data-on-click=markPageAsRead]");
-        navHandler.markPageAsRead(element.dataset.showOnlyUnread || false);
-    });
-    keyboardHandler.on("s", () => navHandler.saveEntry());
-    keyboardHandler.on("d", () => navHandler.fetchOriginalContent());
-    keyboardHandler.on("f", () => navHandler.toggleBookmark());
-    keyboardHandler.on("?", () => navHandler.showKeyboardShortcuts());
-    keyboardHandler.on("#", () => navHandler.unsubscribeFromFeed());
-    keyboardHandler.on("/", (e) => navHandler.setFocusToSearchInput(e));
-    keyboardHandler.on("Escape", () => ModalHandler.close());
-    keyboardHandler.listen();
+
+    if (! document.querySelector("body[data-disable-keyboard-shortcuts=true]")) {
+        let keyboardHandler = new KeyboardHandler();
+        keyboardHandler.on("g u", () => navHandler.goToPage("unread"));
+        keyboardHandler.on("g b", () => navHandler.goToPage("starred"));
+        keyboardHandler.on("g h", () => navHandler.goToPage("history"));
+        keyboardHandler.on("g f", () => navHandler.goToFeedOrFeeds());
+        keyboardHandler.on("g c", () => navHandler.goToPage("categories"));
+        keyboardHandler.on("g s", () => navHandler.goToPage("settings"));
+        keyboardHandler.on("ArrowLeft", () => navHandler.goToPrevious());
+        keyboardHandler.on("ArrowRight", () => navHandler.goToNext());
+        keyboardHandler.on("j", () => navHandler.goToPrevious());
+        keyboardHandler.on("p", () => navHandler.goToPrevious());
+        keyboardHandler.on("k", () => navHandler.goToNext());
+        keyboardHandler.on("n", () => navHandler.goToNext());
+        keyboardHandler.on("h", () => navHandler.goToPage("previous"));
+        keyboardHandler.on("l", () => navHandler.goToPage("next"));
+        keyboardHandler.on("o", () => navHandler.openSelectedItem());
+        keyboardHandler.on("v", () => navHandler.openOriginalLink());
+        keyboardHandler.on("m", () => navHandler.toggleEntryStatus());
+        keyboardHandler.on("A", () => {
+            let element = document.querySelector("a[data-on-click=markPageAsRead]");
+            navHandler.markPageAsRead(element.dataset.showOnlyUnread || false);
+        });
+        keyboardHandler.on("s", () => navHandler.saveEntry());
+        keyboardHandler.on("d", () => navHandler.fetchOriginalContent());
+        keyboardHandler.on("f", () => navHandler.toggleBookmark());
+        keyboardHandler.on("?", () => navHandler.showKeyboardShortcuts());
+        keyboardHandler.on("#", () => navHandler.unsubscribeFromFeed());
+        keyboardHandler.on("/", (e) => navHandler.setFocusToSearchInput(e));
+        keyboardHandler.on("Escape", () => ModalHandler.close());
+        keyboardHandler.listen();
+    }
 
     let touchHandler = new TouchHandler(navHandler);
     touchHandler.listen();

+ 0 - 1
ui/static/js/keyboard_handler.js

@@ -64,5 +64,4 @@ class KeyboardHandler {
 
         return event.key;
     }
-
 }

Некоторые файлы не были показаны из-за большого количества измененных файлов