Add option to change the number of entries per page (fixes #40)
This commit is contained in:
parent
e32fa059e5
commit
5f266319a3
35 changed files with 174 additions and 52 deletions
|
@ -109,6 +109,7 @@ type userModification struct {
|
|||
Language *string `json:"language"`
|
||||
Timezone *string `json:"timezone"`
|
||||
EntryDirection *string `json:"entry_sorting_direction"`
|
||||
EntriesPerPage *int `json:"entries_per_page"`
|
||||
}
|
||||
|
||||
func (u *userModification) Update(user *model.User) {
|
||||
|
@ -139,6 +140,10 @@ func (u *userModification) Update(user *model.User) {
|
|||
if u.EntryDirection != nil {
|
||||
user.EntryDirection = *u.EntryDirection
|
||||
}
|
||||
|
||||
if u.EntriesPerPage != nil {
|
||||
user.EntriesPerPage = *u.EntriesPerPage
|
||||
}
|
||||
}
|
||||
|
||||
func decodeUserModificationPayload(r io.ReadCloser) (*userModification, error) {
|
||||
|
|
|
@ -26,6 +26,7 @@ type User struct {
|
|||
Language string `json:"language"`
|
||||
Timezone string `json:"timezone"`
|
||||
EntryDirection string `json:"entry_sorting_direction"`
|
||||
EntriesPerPage int `json:"entries_per_page"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
}
|
||||
|
@ -43,6 +44,7 @@ type UserModification struct {
|
|||
Language *string `json:"language"`
|
||||
Timezone *string `json:"timezone"`
|
||||
EntryDirection *string `json:"entry_sorting_direction"`
|
||||
EntriesPerPage *int `json:"entries_per_page"`
|
||||
}
|
||||
|
||||
// Users represents a list of users.
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"miniflux.app/logger"
|
||||
)
|
||||
|
||||
const schemaVersion = 31
|
||||
const schemaVersion = 32
|
||||
|
||||
// Migrate executes database migrations.
|
||||
func Migrate(db *sql.DB) {
|
||||
|
|
|
@ -183,6 +183,8 @@ create unique index entries_share_code_idx on entries using btree(share_code) wh
|
|||
create index entries_user_feed_idx on entries (user_id, feed_id);
|
||||
`,
|
||||
"schema_version_31": `alter table feeds add column ignore_http_cache bool default false;`,
|
||||
"schema_version_32": `alter table users add column entries_per_page int default 100;
|
||||
`,
|
||||
"schema_version_4": `create type entry_sorting_direction as enum('asc', 'desc');
|
||||
alter table users add column entry_direction entry_sorting_direction default 'asc';
|
||||
`,
|
||||
|
@ -237,6 +239,7 @@ var SqlMapChecksums = map[string]string{
|
|||
"schema_version_3": "a54745dbc1c51c000f74d4e5068f1e2f43e83309f023415b1749a47d5c1e0f12",
|
||||
"schema_version_30": "3ec48a9b2e7a0fc32c85f31652f723565c34213f5f2d7e5e5076aad8f0b40d23",
|
||||
"schema_version_31": "9290ef295731b03ddfe32dcaded0be70d41b63572420ad379cf2874a9b54581c",
|
||||
"schema_version_32": "5b4de8dd2d7e3c6ae4150e0e3931df2ee989f2c667145bd67294e5a5f3fae456",
|
||||
"schema_version_4": "216ea3a7d3e1704e40c797b5dc47456517c27dbb6ca98bf88812f4f63d74b5d9",
|
||||
"schema_version_5": "46397e2f5f2c82116786127e9f6a403e975b14d2ca7b652a48cd1ba843e6a27c",
|
||||
"schema_version_6": "9d05b4fb223f0e60efc716add5048b0ca9c37511cf2041721e20505d6d798ce4",
|
||||
|
|
1
database/sql/schema_version_32.sql
Normal file
1
database/sql/schema_version_32.sql
Normal file
|
@ -0,0 +1 @@
|
|||
alter table users add column entries_per_page int default 100;
|
|
@ -234,6 +234,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Passwörter stimmen nicht überein.",
|
||||
"error.password_min_length": "Wenigstens 6 Zeichen müssen genutzt werden.",
|
||||
"error.settings_mandatory_fields": "Die Felder für Benutzername, Thema, Sprache und Zeitzone sind obligatorisch.",
|
||||
"error.entries_per_page_invalid": "Die Anzahl der Einträge pro Seite ist ungültig.",
|
||||
"error.feed_mandatory_fields": "Die URL und die Kategorie sind obligatorisch.",
|
||||
"error.user_mandatory_fields": "Der Benutzername ist obligatorisch.",
|
||||
"error.api_key_already_exists": "Dieser API-Schlüssel ist bereits vorhanden.",
|
||||
|
@ -259,6 +260,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Zeitzone",
|
||||
"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.select.older_first": "Älteste Artikel zuerst",
|
||||
"form.prefs.select.recent_first": "Neueste Artikel zuerst",
|
||||
"form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
|
||||
|
@ -572,6 +574,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Passwords are not the same.",
|
||||
"error.password_min_length": "The password must have at least 6 characters.",
|
||||
"error.settings_mandatory_fields": "The username, theme, language and timezone fields are mandatory.",
|
||||
"error.entries_per_page_invalid": "The number of entries per page is not valid.",
|
||||
"error.feed_mandatory_fields": "The URL and the category are mandatory.",
|
||||
"error.user_mandatory_fields": "The username is mandatory.",
|
||||
"error.api_key_already_exists": "This API Key already exists.",
|
||||
|
@ -597,6 +600,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Timezone",
|
||||
"form.prefs.label.theme": "Theme",
|
||||
"form.prefs.label.entry_sorting": "Entry Sorting",
|
||||
"form.prefs.label.entries_per_page": "Entries per page",
|
||||
"form.prefs.select.older_first": "Older entries first",
|
||||
"form.prefs.select.recent_first": "Recent entries first",
|
||||
"form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
|
||||
|
@ -890,6 +894,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Las contraseñas no son las mismas.",
|
||||
"error.password_min_length": "La contraseña debería tener al menos 6 caracteres.",
|
||||
"error.settings_mandatory_fields": "Los campos de nombre de usuario, tema, idioma y zona horaria son obligatorios.",
|
||||
"error.entries_per_page_invalid": "El número de entradas por página no es válido.",
|
||||
"error.feed_mandatory_fields": "Los campos de URL y categoría son obligatorios.",
|
||||
"error.user_mandatory_fields": "El nombre de usuario es obligatorio.",
|
||||
"error.api_key_already_exists": "Esta clave API ya existe.",
|
||||
|
@ -915,6 +920,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Zona horaria",
|
||||
"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.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",
|
||||
|
@ -1208,6 +1214,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Les mots de passe ne sont pas les mêmes.",
|
||||
"error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
|
||||
"error.settings_mandatory_fields": "Le nom d'utilisateur, le thème, la langue et le fuseau horaire sont obligatoire.",
|
||||
"error.entries_per_page_invalid": "Le nombre d'entrées par page n'est pas valide.",
|
||||
"error.feed_mandatory_fields": "L'URL et la catégorie sont obligatoire.",
|
||||
"error.user_mandatory_fields": "Le nom d'utilisateur est obligatoire.",
|
||||
"error.api_key_already_exists": "Cette clé d'API existe déjà.",
|
||||
|
@ -1233,6 +1240,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Fuseau horaire",
|
||||
"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.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",
|
||||
|
@ -1546,6 +1554,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Le password non coincidono.",
|
||||
"error.password_min_length": "La password deve contenere almeno 6 caratteri.",
|
||||
"error.settings_mandatory_fields": "Il nome utente, il tema, la lingua ed il fuso orario sono campi obbligatori.",
|
||||
"error.entries_per_page_invalid": "Il numero di articoli per pagina non è valido.",
|
||||
"error.feed_mandatory_fields": "L'URL e la categoria sono obbligatori.",
|
||||
"error.user_mandatory_fields": "Il nome utente è obbligatorio.",
|
||||
"error.api_key_already_exists": "Questa chiave API esiste già.",
|
||||
|
@ -1571,6 +1580,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Fuso orario",
|
||||
"form.prefs.label.theme": "Tema",
|
||||
"form.prefs.label.entry_sorting": "Ordinamento articoli",
|
||||
"form.prefs.label.entries_per_page": "Articoli per pagina",
|
||||
"form.prefs.select.older_first": "Prima i più vecchi",
|
||||
"form.prefs.select.recent_first": "Prima i più recenti",
|
||||
"form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
|
||||
|
@ -1864,6 +1874,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "パスワードが一致しません。",
|
||||
"error.password_min_length": "パスワードは6文字以上である必要があります。",
|
||||
"error.settings_mandatory_fields": "ユーザー名、テーマ、言語、タイムゾーンの全てが必要です。",
|
||||
"error.entries_per_page_invalid": "ページあたりのエントリ数が無効です。",
|
||||
"error.feed_mandatory_fields": "URL と カテゴリが必要です。",
|
||||
"error.user_mandatory_fields": "ユーザー名が必要です。",
|
||||
"error.api_key_already_exists": "このAPIキーは既に存在します。",
|
||||
|
@ -1889,6 +1900,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "タイムゾーン",
|
||||
"form.prefs.label.theme": "テーマ",
|
||||
"form.prefs.label.entry_sorting": "記事の並べ替え",
|
||||
"form.prefs.label.entries_per_page": "ページあたりのエントリ",
|
||||
"form.prefs.select.older_first": "古い記事を最初に",
|
||||
"form.prefs.select.recent_first": "新しい記事を最初に",
|
||||
"form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする",
|
||||
|
@ -2182,6 +2194,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Wachtwoorden zijn niet hetzelfde.",
|
||||
"error.password_min_length": "Je moet minstens 6 tekens gebruiken.",
|
||||
"error.settings_mandatory_fields": "Gebruikersnaam, skin, taal en tijdzone zijn verplicht.",
|
||||
"error.entries_per_page_invalid": "Het aantal inzendingen per pagina is niet geldig.",
|
||||
"error.feed_mandatory_fields": "The URL en de categorie zijn verplicht.",
|
||||
"error.user_mandatory_fields": "Gebruikersnaam is verplicht",
|
||||
"error.api_key_already_exists": "This API Key already exists.",
|
||||
|
@ -2207,6 +2220,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Tijdzone",
|
||||
"form.prefs.label.theme": "Skin",
|
||||
"form.prefs.label.entry_sorting": "Volgorde van items",
|
||||
"form.prefs.label.entries_per_page": "Inzendingen per pagina",
|
||||
"form.prefs.select.older_first": "Oudere items eerst",
|
||||
"form.prefs.select.recent_first": "Recente items eerst",
|
||||
"form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in",
|
||||
|
@ -2520,6 +2534,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Hasła nie są identyczne.",
|
||||
"error.password_min_length": "Musisz użyć co najmniej 6 znaków.",
|
||||
"error.settings_mandatory_fields": "Pola nazwy użytkownika, tematu, języka i strefy czasowej są obowiązkowe.",
|
||||
"error.entries_per_page_invalid": "Liczba wpisów na stronę jest nieprawidłowa.",
|
||||
"error.feed_mandatory_fields": "URL i kategoria są obowiązkowe.",
|
||||
"error.user_mandatory_fields": "Nazwa użytkownika jest obowiązkowa.",
|
||||
"error.api_key_already_exists": "Deze API-sleutel bestaat al.",
|
||||
|
@ -2545,6 +2560,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Strefa czasowa",
|
||||
"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.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",
|
||||
|
@ -2864,6 +2880,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "Пароли не совпадают.",
|
||||
"error.password_min_length": "Вы должны использовать минимум 6 символов.",
|
||||
"error.settings_mandatory_fields": "Имя пользователя, тема, язык и часовой пояс обязательны.",
|
||||
"error.entries_per_page_invalid": "Количество записей на странице недействительно.",
|
||||
"error.feed_mandatory_fields": "URL и категория обязательны.",
|
||||
"error.user_mandatory_fields": "Имя пользователя обязательно.",
|
||||
"error.api_key_already_exists": "Этот ключ API уже существует.",
|
||||
|
@ -2889,6 +2906,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "Часовой пояс",
|
||||
"form.prefs.label.theme": "Тема",
|
||||
"form.prefs.label.entry_sorting": "Сортировка записей",
|
||||
"form.prefs.label.entries_per_page": "Записи на странице",
|
||||
"form.prefs.select.older_first": "Сначала старые записи",
|
||||
"form.prefs.select.recent_first": "Сначала последние записи",
|
||||
"form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш",
|
||||
|
@ -3186,6 +3204,7 @@ var translations = map[string]string{
|
|||
"error.different_passwords": "两次输入的密码不同",
|
||||
"error.password_min_length": "请至少使用6个字符",
|
||||
"error.settings_mandatory_fields": "必须填写用户名、主题、语言以及时区",
|
||||
"error.entries_per_page_invalid": "每页的条目数无效。",
|
||||
"error.feed_mandatory_fields": "必须填写 URL 和分类",
|
||||
"error.user_mandatory_fields": "必须填写用户名",
|
||||
"error.api_key_already_exists": "此API密钥已存在。",
|
||||
|
@ -3211,6 +3230,7 @@ var translations = map[string]string{
|
|||
"form.prefs.label.timezone": "时区",
|
||||
"form.prefs.label.theme": "主题",
|
||||
"form.prefs.label.entry_sorting": "内容排序",
|
||||
"form.prefs.label.entries_per_page": "每页条目",
|
||||
"form.prefs.select.older_first": "旧->新",
|
||||
"form.prefs.select.recent_first": "新->旧",
|
||||
"form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
|
||||
|
@ -3289,14 +3309,14 @@ var translations = map[string]string{
|
|||
}
|
||||
|
||||
var translationsChecksums = map[string]string{
|
||||
"de_DE": "ddb063682852c86361af350be616d3bd328373ecb927804824008d016aa7c67c",
|
||||
"en_US": "350b835f759212abd2110322394aa00b666fbf27d752532a7700fb52d5af3f02",
|
||||
"es_ES": "26efc79faaf35efe5a33528cedc2522496987d290c9e86d8fff3a9bcbed3e441",
|
||||
"fr_FR": "e8736791d5373b955cacce215b3ae67d56280bfa5d4596899e4e5e37ff962afd",
|
||||
"it_IT": "8ec2311e00c45b4d2b939ad0280fe49277f5c851a4cd521f42be1a88baef4c34",
|
||||
"ja_JP": "237f49939be015b509d4b3a02890691c3766df8878109114493624cfd13c0cad",
|
||||
"nl_NL": "c70e1eaa3c2e8c0130522189c3932b52ee6e9ff91c91b0090eb9178f2f23c588",
|
||||
"pl_PL": "1d5e05789a3150a8f1ddbe57616d509d1d33c61b60200c563a5e23571671209e",
|
||||
"ru_RU": "b0408b7a150bd79e411376ced3acb706a12e6b28e564a6abfedbdebc2d552915",
|
||||
"zh_CN": "7732905e498d087c9a11ecc3eae8736e758c6b053da13de64fd6599ca40d8ee6",
|
||||
"de_DE": "e986a40b1748968725ddede18ae6451e4d1ae270b9c4c033daa81ee50b1d306e",
|
||||
"en_US": "b27169fc7767e51e6f7610ff1844708e8111e527c7931e3f888864a66826e293",
|
||||
"es_ES": "20a713468ca6ce00e899a80354912e927ded61cf8a79ad9d976c78f515e242dd",
|
||||
"fr_FR": "251eb14fe8521bde772d293fa748307ecd4cae4b0597da03aad39e745a382f11",
|
||||
"it_IT": "8ab664ec8d826aa3702a4f5294c3a3e87193437e64b0ef4990a3a9609b782786",
|
||||
"ja_JP": "7dc146dc5815a8d6dbae2f7f467deea598a85099bbee63e92bf3862d445519af",
|
||||
"nl_NL": "fd106f08b2f8902712a68716a0e33b063bdce32a8440f7a2b296b4f822088403",
|
||||
"pl_PL": "85de665d29e873f6099ef5ea40efe569a05ec3cbf08e4ca7741778bf3d5c8593",
|
||||
"ru_RU": "6e765e44e250469fe1c5666f8ff24e5e07e6b04098c1325c2663a1f722e0bfe9",
|
||||
"zh_CN": "0dc8c5b86a03f0ce58f6d2633ab3011d9bc8004af18f922944a65d151e54beda",
|
||||
}
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "Passwörter stimmen nicht überein.",
|
||||
"error.password_min_length": "Wenigstens 6 Zeichen müssen genutzt werden.",
|
||||
"error.settings_mandatory_fields": "Die Felder für Benutzername, Thema, Sprache und Zeitzone sind obligatorisch.",
|
||||
"error.entries_per_page_invalid": "Die Anzahl der Einträge pro Seite ist ungültig.",
|
||||
"error.feed_mandatory_fields": "Die URL und die Kategorie sind obligatorisch.",
|
||||
"error.user_mandatory_fields": "Der Benutzername ist obligatorisch.",
|
||||
"error.api_key_already_exists": "Dieser API-Schlüssel ist bereits vorhanden.",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "Zeitzone",
|
||||
"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.select.older_first": "Älteste Artikel zuerst",
|
||||
"form.prefs.select.recent_first": "Neueste Artikel zuerst",
|
||||
"form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "Passwords are not the same.",
|
||||
"error.password_min_length": "The password must have at least 6 characters.",
|
||||
"error.settings_mandatory_fields": "The username, theme, language and timezone fields are mandatory.",
|
||||
"error.entries_per_page_invalid": "The number of entries per page is not valid.",
|
||||
"error.feed_mandatory_fields": "The URL and the category are mandatory.",
|
||||
"error.user_mandatory_fields": "The username is mandatory.",
|
||||
"error.api_key_already_exists": "This API Key already exists.",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "Timezone",
|
||||
"form.prefs.label.theme": "Theme",
|
||||
"form.prefs.label.entry_sorting": "Entry Sorting",
|
||||
"form.prefs.label.entries_per_page": "Entries per page",
|
||||
"form.prefs.select.older_first": "Older entries first",
|
||||
"form.prefs.select.recent_first": "Recent entries first",
|
||||
"form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "Las contraseñas no son las mismas.",
|
||||
"error.password_min_length": "La contraseña debería tener al menos 6 caracteres.",
|
||||
"error.settings_mandatory_fields": "Los campos de nombre de usuario, tema, idioma y zona horaria son obligatorios.",
|
||||
"error.entries_per_page_invalid": "El número de entradas por página no es válido.",
|
||||
"error.feed_mandatory_fields": "Los campos de URL y categoría son obligatorios.",
|
||||
"error.user_mandatory_fields": "El nombre de usuario es obligatorio.",
|
||||
"error.api_key_already_exists": "Esta clave API ya existe.",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "Zona horaria",
|
||||
"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.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",
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "Les mots de passe ne sont pas les mêmes.",
|
||||
"error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
|
||||
"error.settings_mandatory_fields": "Le nom d'utilisateur, le thème, la langue et le fuseau horaire sont obligatoire.",
|
||||
"error.entries_per_page_invalid": "Le nombre d'entrées par page n'est pas valide.",
|
||||
"error.feed_mandatory_fields": "L'URL et la catégorie sont obligatoire.",
|
||||
"error.user_mandatory_fields": "Le nom d'utilisateur est obligatoire.",
|
||||
"error.api_key_already_exists": "Cette clé d'API existe déjà.",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "Fuseau horaire",
|
||||
"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.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",
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "Le password non coincidono.",
|
||||
"error.password_min_length": "La password deve contenere almeno 6 caratteri.",
|
||||
"error.settings_mandatory_fields": "Il nome utente, il tema, la lingua ed il fuso orario sono campi obbligatori.",
|
||||
"error.entries_per_page_invalid": "Il numero di articoli per pagina non è valido.",
|
||||
"error.feed_mandatory_fields": "L'URL e la categoria sono obbligatori.",
|
||||
"error.user_mandatory_fields": "Il nome utente è obbligatorio.",
|
||||
"error.api_key_already_exists": "Questa chiave API esiste già.",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "Fuso orario",
|
||||
"form.prefs.label.theme": "Tema",
|
||||
"form.prefs.label.entry_sorting": "Ordinamento articoli",
|
||||
"form.prefs.label.entries_per_page": "Articoli per pagina",
|
||||
"form.prefs.select.older_first": "Prima i più vecchi",
|
||||
"form.prefs.select.recent_first": "Prima i più recenti",
|
||||
"form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "パスワードが一致しません。",
|
||||
"error.password_min_length": "パスワードは6文字以上である必要があります。",
|
||||
"error.settings_mandatory_fields": "ユーザー名、テーマ、言語、タイムゾーンの全てが必要です。",
|
||||
"error.entries_per_page_invalid": "ページあたりのエントリ数が無効です。",
|
||||
"error.feed_mandatory_fields": "URL と カテゴリが必要です。",
|
||||
"error.user_mandatory_fields": "ユーザー名が必要です。",
|
||||
"error.api_key_already_exists": "このAPIキーは既に存在します。",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "タイムゾーン",
|
||||
"form.prefs.label.theme": "テーマ",
|
||||
"form.prefs.label.entry_sorting": "記事の並べ替え",
|
||||
"form.prefs.label.entries_per_page": "ページあたりのエントリ",
|
||||
"form.prefs.select.older_first": "古い記事を最初に",
|
||||
"form.prefs.select.recent_first": "新しい記事を最初に",
|
||||
"form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする",
|
||||
|
|
|
@ -229,6 +229,7 @@
|
|||
"error.different_passwords": "Wachtwoorden zijn niet hetzelfde.",
|
||||
"error.password_min_length": "Je moet minstens 6 tekens gebruiken.",
|
||||
"error.settings_mandatory_fields": "Gebruikersnaam, skin, taal en tijdzone zijn verplicht.",
|
||||
"error.entries_per_page_invalid": "Het aantal inzendingen per pagina is niet geldig.",
|
||||
"error.feed_mandatory_fields": "The URL en de categorie zijn verplicht.",
|
||||
"error.user_mandatory_fields": "Gebruikersnaam is verplicht",
|
||||
"error.api_key_already_exists": "This API Key already exists.",
|
||||
|
@ -254,6 +255,7 @@
|
|||
"form.prefs.label.timezone": "Tijdzone",
|
||||
"form.prefs.label.theme": "Skin",
|
||||
"form.prefs.label.entry_sorting": "Volgorde van items",
|
||||
"form.prefs.label.entries_per_page": "Inzendingen per pagina",
|
||||
"form.prefs.select.older_first": "Oudere items eerst",
|
||||
"form.prefs.select.recent_first": "Recente items eerst",
|
||||
"form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in",
|
||||
|
|
|
@ -231,6 +231,7 @@
|
|||
"error.different_passwords": "Hasła nie są identyczne.",
|
||||
"error.password_min_length": "Musisz użyć co najmniej 6 znaków.",
|
||||
"error.settings_mandatory_fields": "Pola nazwy użytkownika, tematu, języka i strefy czasowej są obowiązkowe.",
|
||||
"error.entries_per_page_invalid": "Liczba wpisów na stronę jest nieprawidłowa.",
|
||||
"error.feed_mandatory_fields": "URL i kategoria są obowiązkowe.",
|
||||
"error.user_mandatory_fields": "Nazwa użytkownika jest obowiązkowa.",
|
||||
"error.api_key_already_exists": "Deze API-sleutel bestaat al.",
|
||||
|
@ -256,6 +257,7 @@
|
|||
"form.prefs.label.timezone": "Strefa czasowa",
|
||||
"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.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",
|
||||
|
|
|
@ -231,6 +231,7 @@
|
|||
"error.different_passwords": "Пароли не совпадают.",
|
||||
"error.password_min_length": "Вы должны использовать минимум 6 символов.",
|
||||
"error.settings_mandatory_fields": "Имя пользователя, тема, язык и часовой пояс обязательны.",
|
||||
"error.entries_per_page_invalid": "Количество записей на странице недействительно.",
|
||||
"error.feed_mandatory_fields": "URL и категория обязательны.",
|
||||
"error.user_mandatory_fields": "Имя пользователя обязательно.",
|
||||
"error.api_key_already_exists": "Этот ключ API уже существует.",
|
||||
|
@ -256,6 +257,7 @@
|
|||
"form.prefs.label.timezone": "Часовой пояс",
|
||||
"form.prefs.label.theme": "Тема",
|
||||
"form.prefs.label.entry_sorting": "Сортировка записей",
|
||||
"form.prefs.label.entries_per_page": "Записи на странице",
|
||||
"form.prefs.select.older_first": "Сначала старые записи",
|
||||
"form.prefs.select.recent_first": "Сначала последние записи",
|
||||
"form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш",
|
||||
|
|
|
@ -227,6 +227,7 @@
|
|||
"error.different_passwords": "两次输入的密码不同",
|
||||
"error.password_min_length": "请至少使用6个字符",
|
||||
"error.settings_mandatory_fields": "必须填写用户名、主题、语言以及时区",
|
||||
"error.entries_per_page_invalid": "每页的条目数无效。",
|
||||
"error.feed_mandatory_fields": "必须填写 URL 和分类",
|
||||
"error.user_mandatory_fields": "必须填写用户名",
|
||||
"error.api_key_already_exists": "此API密钥已存在。",
|
||||
|
@ -252,6 +253,7 @@
|
|||
"form.prefs.label.timezone": "时区",
|
||||
"form.prefs.label.theme": "主题",
|
||||
"form.prefs.label.entry_sorting": "内容排序",
|
||||
"form.prefs.label.entries_per_page": "每页条目",
|
||||
"form.prefs.select.older_first": "旧->新",
|
||||
"form.prefs.select.recent_first": "新->旧",
|
||||
"form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
|
||||
|
|
|
@ -21,6 +21,7 @@ type User struct {
|
|||
Language string `json:"language"`
|
||||
Timezone string `json:"timezone"`
|
||||
EntryDirection string `json:"entry_sorting_direction"`
|
||||
EntriesPerPage int `json:"entries_per_page"`
|
||||
KeyboardShortcuts bool `json:"keyboard_shortcuts"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
|
||||
Extra map[string]string `json:"extra"`
|
||||
|
|
|
@ -64,7 +64,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
|
|||
VALUES
|
||||
(LOWER($1), $2, $3, $4)
|
||||
RETURNING
|
||||
id, username, is_admin, language, theme, timezone, entry_direction, keyboard_shortcuts
|
||||
id, username, is_admin, language, theme, timezone, entry_direction, entries_per_page, keyboard_shortcuts
|
||||
`
|
||||
|
||||
err = s.db.QueryRow(query, user.Username, password, user.IsAdmin, extra).Scan(
|
||||
|
@ -75,6 +75,7 @@ func (s *Storage) CreateUser(user *model.User) (err error) {
|
|||
&user.Theme,
|
||||
&user.Timezone,
|
||||
&user.EntryDirection,
|
||||
&user.EntriesPerPage,
|
||||
&user.KeyboardShortcuts,
|
||||
)
|
||||
if err != nil {
|
||||
|
@ -123,9 +124,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
language=$5,
|
||||
timezone=$6,
|
||||
entry_direction=$7,
|
||||
keyboard_shortcuts=$8
|
||||
entries_per_page=$8,
|
||||
keyboard_shortcuts=$9
|
||||
WHERE
|
||||
id=$9
|
||||
id=$10
|
||||
`
|
||||
|
||||
_, err = s.db.Exec(
|
||||
|
@ -137,6 +139,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
user.Language,
|
||||
user.Timezone,
|
||||
user.EntryDirection,
|
||||
user.EntriesPerPage,
|
||||
user.KeyboardShortcuts,
|
||||
user.ID,
|
||||
)
|
||||
|
@ -152,9 +155,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
language=$4,
|
||||
timezone=$5,
|
||||
entry_direction=$6,
|
||||
keyboard_shortcuts=$7
|
||||
entries_per_page=$7,
|
||||
keyboard_shortcuts=$8
|
||||
WHERE
|
||||
id=$8
|
||||
id=$9
|
||||
`
|
||||
|
||||
_, err := s.db.Exec(
|
||||
|
@ -165,6 +169,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
|||
user.Language,
|
||||
user.Timezone,
|
||||
user.EntryDirection,
|
||||
user.EntriesPerPage,
|
||||
user.KeyboardShortcuts,
|
||||
user.ID,
|
||||
)
|
||||
|
@ -202,6 +207,7 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
|||
language,
|
||||
timezone,
|
||||
entry_direction,
|
||||
entries_per_page,
|
||||
keyboard_shortcuts,
|
||||
last_login_at,
|
||||
extra
|
||||
|
@ -224,6 +230,7 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
|||
language,
|
||||
timezone,
|
||||
entry_direction,
|
||||
entries_per_page,
|
||||
keyboard_shortcuts,
|
||||
last_login_at,
|
||||
extra
|
||||
|
@ -246,6 +253,7 @@ func (s *Storage) UserByExtraField(field, value string) (*model.User, error) {
|
|||
language,
|
||||
timezone,
|
||||
entry_direction,
|
||||
entries_per_page,
|
||||
keyboard_shortcuts,
|
||||
last_login_at,
|
||||
extra
|
||||
|
@ -268,6 +276,7 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
|||
u.language,
|
||||
u.timezone,
|
||||
u.entry_direction,
|
||||
u.entries_per_page,
|
||||
u.keyboard_shortcuts,
|
||||
u.last_login_at,
|
||||
u.extra
|
||||
|
@ -293,6 +302,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
|||
&user.Language,
|
||||
&user.Timezone,
|
||||
&user.EntryDirection,
|
||||
&user.EntriesPerPage,
|
||||
&user.KeyboardShortcuts,
|
||||
&user.LastLoginAt,
|
||||
&extra,
|
||||
|
@ -348,6 +358,7 @@ func (s *Storage) Users() (model.Users, error) {
|
|||
language,
|
||||
timezone,
|
||||
entry_direction,
|
||||
entries_per_page,
|
||||
keyboard_shortcuts,
|
||||
last_login_at,
|
||||
extra
|
||||
|
@ -373,6 +384,7 @@ func (s *Storage) Users() (model.Users, error) {
|
|||
&user.Language,
|
||||
&user.Timezone,
|
||||
&user.EntryDirection,
|
||||
&user.EntriesPerPage,
|
||||
&user.KeyboardShortcuts,
|
||||
&user.LastLoginAt,
|
||||
&extra,
|
||||
|
|
|
@ -49,6 +49,9 @@
|
|||
<option value="desc" {{ if eq "desc" $.form.EntryDirection }}selected="selected"{{ end }}>{{ t "form.prefs.select.recent_first" }}</option>
|
||||
</select>
|
||||
|
||||
<label for="form-entries-per-page">{{ t "form.prefs.label.entries_per_page" }}</label>
|
||||
<input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">
|
||||
|
||||
<label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
|
||||
|
||||
<label>{{t "form.prefs.label.custom_css" }}</label><textarea name="custom_css" cols="40" rows="5">{{ .form.CustomCSS }}</textarea>
|
||||
|
|
|
@ -1320,6 +1320,9 @@ 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 for="form-entries-per-page">{{ t "form.prefs.label.entries_per_page" }}</label>
|
||||
<input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">
|
||||
|
||||
<label><input type="checkbox" name="keyboard_shortcuts" value="1" {{ if .form.KeyboardShortcuts }}checked{{ end }}> {{ t "form.prefs.label.keyboard_shortcuts" }}</label>
|
||||
|
||||
<label>{{t "form.prefs.label.custom_css" }}</label><textarea name="custom_css" cols="40" rows="5">{{ .form.CustomCSS }}</textarea>
|
||||
|
@ -1565,7 +1568,7 @@ var templateViewsMapChecksums = map[string]string{
|
|||
"login": "79ff2ca488c0a19b37c8fa227a21f73e94472eb357a51a077197c852f7713f11",
|
||||
"search_entries": "c0786ddc6b17e865007b975eefb97417935cbc601f5917cca1ee0d3f584594bc",
|
||||
"sessions": "5d5c677bddbd027e0b0c9f7a0dd95b66d9d95b4e130959f31fb955b926c2201c",
|
||||
"settings": "3ab566c3220c62edc3edc51f2e93c1101b728e9f62f52f23de6bc6322d86aeb6",
|
||||
"settings": "3d6dd0d7fa0ca48cfd9a5edb43c055af8b816eb4460f16b71ae22db40ed9b754",
|
||||
"shared_entries": "1494d81e46f6af534a73cf6a91f8dfda1932a477bb3a70143513896ac0f0220b",
|
||||
"unread_entries": "e0080d0cf3583cda51d865422960137c8556c432853657086e43daf6bd5b73be",
|
||||
"users": "d7ff52efc582bbad10504f4a04fa3adcc12d15890e45dff51cac281e0c446e45",
|
||||
|
|
|
@ -78,6 +78,10 @@ func TestGetUsers(t *testing.T) {
|
|||
if !users[0].IsAdmin {
|
||||
t.Fatalf(`Invalid role, got "%v"`, users[0].IsAdmin)
|
||||
}
|
||||
|
||||
if users[0].EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, users[0].EntriesPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateStandardUser(t *testing.T) {
|
||||
|
@ -119,6 +123,10 @@ func TestCreateStandardUser(t *testing.T) {
|
|||
if user.LastLoginAt != nil {
|
||||
t.Fatalf(`Invalid last login date, got "%v"`, user.LastLoginAt)
|
||||
}
|
||||
|
||||
if user.EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, user.EntriesPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveUser(t *testing.T) {
|
||||
|
@ -183,6 +191,10 @@ func TestGetUserByID(t *testing.T) {
|
|||
if user.LastLoginAt != nil {
|
||||
t.Fatalf(`Invalid last login date, got "%v"`, user.LastLoginAt)
|
||||
}
|
||||
|
||||
if user.EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, user.EntriesPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserByUsername(t *testing.T) {
|
||||
|
@ -234,6 +246,10 @@ func TestGetUserByUsername(t *testing.T) {
|
|||
if user.LastLoginAt != nil {
|
||||
t.Fatalf(`Invalid last login date, got "%v"`, user.LastLoginAt)
|
||||
}
|
||||
|
||||
if user.EntriesPerPage != 100 {
|
||||
t.Fatalf(`Invalid entries per page, got "%v"`, user.EntriesPerPage)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserTheme(t *testing.T) {
|
||||
|
|
|
@ -29,7 +29,7 @@ func (h *handler) showStarredPage(w http.ResponseWriter, r *http.Request) {
|
|||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -48,7 +48,7 @@ func (h *handler) showStarredPage(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
view.Set("total", count)
|
||||
view.Set("entries", entries)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "starred"), count, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "starred"), count, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "starred")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
|
|
|
@ -41,7 +41,7 @@ func (h *handler) showCategoryEntriesPage(w http.ResponseWriter, r *http.Request
|
|||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithStatus(model.EntryStatusUnread)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -60,7 +60,7 @@ func (h *handler) showCategoryEntriesPage(w http.ResponseWriter, r *http.Request
|
|||
view.Set("category", category)
|
||||
view.Set("total", count)
|
||||
view.Set("entries", entries)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "categoryEntries", "categoryID", category.ID), count, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "categoryEntries", "categoryID", category.ID), count, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "categories")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
|
|
|
@ -41,7 +41,7 @@ func (h *handler) showCategoryEntriesAllPage(w http.ResponseWriter, r *http.Requ
|
|||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithoutStatus(model.EntryStatusRemoved)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -60,7 +60,7 @@ func (h *handler) showCategoryEntriesAllPage(w http.ResponseWriter, r *http.Requ
|
|||
view.Set("category", category)
|
||||
view.Set("total", count)
|
||||
view.Set("entries", entries)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "categoryEntriesAll", "categoryID", category.ID), count, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "categoryEntriesAll", "categoryID", category.ID), count, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "categories")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
|
|
|
@ -41,7 +41,7 @@ func (h *handler) showFeedEntriesPage(w http.ResponseWriter, r *http.Request) {
|
|||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -60,7 +60,7 @@ func (h *handler) showFeedEntriesPage(w http.ResponseWriter, r *http.Request) {
|
|||
view.Set("feed", feed)
|
||||
view.Set("entries", entries)
|
||||
view.Set("total", count)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "feedEntries", "feedID", feed.ID), count, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "feedEntries", "feedID", feed.ID), count, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "feeds")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
|
|
|
@ -41,7 +41,7 @@ func (h *handler) showFeedEntriesAllPage(w http.ResponseWriter, r *http.Request)
|
|||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -60,7 +60,7 @@ func (h *handler) showFeedEntriesAllPage(w http.ResponseWriter, r *http.Request)
|
|||
view.Set("feed", feed)
|
||||
view.Set("entries", entries)
|
||||
view.Set("total", count)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "feedEntriesAll", "feedID", feed.ID), count, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "feedEntriesAll", "feedID", feed.ID), count, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "feeds")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
|
|
|
@ -6,6 +6,7 @@ package form // import "miniflux.app/ui/form"
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"miniflux.app/errors"
|
||||
"miniflux.app/model"
|
||||
|
@ -20,6 +21,7 @@ type SettingsForm struct {
|
|||
Language string
|
||||
Timezone string
|
||||
EntryDirection string
|
||||
EntriesPerPage int
|
||||
KeyboardShortcuts bool
|
||||
CustomCSS string
|
||||
}
|
||||
|
@ -31,6 +33,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
|||
user.Language = s.Language
|
||||
user.Timezone = s.Timezone
|
||||
user.EntryDirection = s.EntryDirection
|
||||
user.EntriesPerPage = s.EntriesPerPage
|
||||
user.KeyboardShortcuts = s.KeyboardShortcuts
|
||||
user.Extra["custom_css"] = s.CustomCSS
|
||||
|
||||
|
@ -47,6 +50,10 @@ func (s *SettingsForm) Validate() error {
|
|||
return errors.NewLocalizedError("error.settings_mandatory_fields")
|
||||
}
|
||||
|
||||
if s.EntriesPerPage < 1 {
|
||||
return errors.NewLocalizedError("error.entries_per_page_invalid")
|
||||
}
|
||||
|
||||
if s.Confirmation == "" {
|
||||
// Firefox insists on auto-completing the password field.
|
||||
// If the confirmation field is blank, the user probably
|
||||
|
@ -67,6 +74,10 @@ func (s *SettingsForm) Validate() error {
|
|||
|
||||
// NewSettingsForm returns a new SettingsForm.
|
||||
func NewSettingsForm(r *http.Request) *SettingsForm {
|
||||
entriesPerPage, err := strconv.ParseInt(r.FormValue("entries_per_page"), 10, 64)
|
||||
if err != nil {
|
||||
entriesPerPage = 0
|
||||
}
|
||||
return &SettingsForm{
|
||||
Username: r.FormValue("username"),
|
||||
Password: r.FormValue("password"),
|
||||
|
@ -75,6 +86,7 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
|||
Language: r.FormValue("language"),
|
||||
Timezone: r.FormValue("timezone"),
|
||||
EntryDirection: r.FormValue("entry_direction"),
|
||||
EntriesPerPage: int(entriesPerPage),
|
||||
KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
|
||||
CustomCSS: r.FormValue("custom_css"),
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ func TestValid(t *testing.T) {
|
|||
Language: "en_US",
|
||||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 50,
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
|
@ -30,6 +31,7 @@ func TestConfirmationEmpty(t *testing.T) {
|
|||
Language: "en_US",
|
||||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 50,
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
|
@ -51,6 +53,25 @@ func TestConfirmationIncorrect(t *testing.T) {
|
|||
Language: "en_US",
|
||||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 50,
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
if err == nil {
|
||||
t.Error("Validate should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEntriesPerPageNotValid(t *testing.T) {
|
||||
settings := &SettingsForm{
|
||||
Username: "user",
|
||||
Password: "hunter2",
|
||||
Confirmation: "hunter2",
|
||||
Theme: "default",
|
||||
Language: "en_US",
|
||||
Timezone: "UTC",
|
||||
EntryDirection: "asc",
|
||||
EntriesPerPage: 0,
|
||||
}
|
||||
|
||||
err := settings.Validate()
|
||||
|
|
|
@ -28,7 +28,7 @@ func (h *handler) showHistoryPage(w http.ResponseWriter, r *http.Request) {
|
|||
builder.WithOrder("changed_at")
|
||||
builder.WithDirection("desc")
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -46,7 +46,7 @@ func (h *handler) showHistoryPage(w http.ResponseWriter, r *http.Request) {
|
|||
view := view.New(h.tpl, r, sess)
|
||||
view.Set("entries", entries)
|
||||
view.Set("total", count)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "history"), count, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "history"), count, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "history")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", h.store.CountUnreadEntries(user.ID))
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
|
||||
package ui // import "miniflux.app/ui"
|
||||
|
||||
const (
|
||||
nbItemsPerPage = 100
|
||||
)
|
||||
|
||||
type pagination struct {
|
||||
Route string
|
||||
Total int
|
||||
|
@ -20,7 +16,7 @@ type pagination struct {
|
|||
SearchQuery string
|
||||
}
|
||||
|
||||
func getPagination(route string, total, offset int) pagination {
|
||||
func getPagination(route string, total, offset, nbItemsPerPage int) pagination {
|
||||
nextOffset := 0
|
||||
prevOffset := 0
|
||||
showNext := (total - offset) > nbItemsPerPage
|
||||
|
|
|
@ -28,7 +28,7 @@ func (h *handler) showSearchEntriesPage(w http.ResponseWriter, r *http.Request)
|
|||
builder.WithSearchQuery(searchQuery)
|
||||
builder.WithoutStatus(model.EntryStatusRemoved)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
|
@ -44,7 +44,7 @@ func (h *handler) showSearchEntriesPage(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
sess := session.New(h.store, request.SessionID(r))
|
||||
view := view.New(h.tpl, r, sess)
|
||||
pagination := getPagination(route.Path(h.router, "searchEntries"), count, offset)
|
||||
pagination := getPagination(route.Path(h.router, "searchEntries"), count, offset, user.EntriesPerPage)
|
||||
pagination.SearchQuery = searchQuery
|
||||
|
||||
view.Set("searchQuery", searchQuery)
|
||||
|
|
|
@ -32,6 +32,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
|||
Language: user.Language,
|
||||
Timezone: user.Timezone,
|
||||
EntryDirection: user.EntryDirection,
|
||||
EntriesPerPage: user.EntriesPerPage,
|
||||
KeyboardShortcuts: user.KeyboardShortcuts,
|
||||
CustomCSS: user.Extra["custom_css"],
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -353,7 +353,8 @@ select {
|
|||
input[type="search"],
|
||||
input[type="url"],
|
||||
input[type="password"],
|
||||
input[type="text"] {
|
||||
input[type="text"],
|
||||
input[type="number"] {
|
||||
color: var(--input-color);
|
||||
background: var(--input-background);
|
||||
border: var(--input-border);
|
||||
|
@ -369,13 +370,18 @@ input[type="text"] {
|
|||
input[type="search"]:focus,
|
||||
input[type="url"]:focus,
|
||||
input[type="password"]:focus,
|
||||
input[type="text"]:focus {
|
||||
input[type="text"]:focus,
|
||||
input[type="number"]:focus {
|
||||
color: var(--input-focus-color);
|
||||
border-color: var(--input-focus-border-color);
|
||||
outline: 0;
|
||||
box-shadow: var(--input-focus-box-shadow);
|
||||
}
|
||||
|
||||
#form-entries-per-page {
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func (h *handler) showUnreadPage(w http.ResponseWriter, r *http.Request) {
|
|||
builder.WithOrder(model.DefaultSortingOrder)
|
||||
builder.WithDirection(user.EntryDirection)
|
||||
builder.WithOffset(offset)
|
||||
builder.WithLimit(nbItemsPerPage)
|
||||
builder.WithLimit(user.EntriesPerPage)
|
||||
entries, err := builder.GetEntries()
|
||||
if err != nil {
|
||||
html.ServerError(w, r, err)
|
||||
|
@ -51,7 +51,7 @@ func (h *handler) showUnreadPage(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
view.Set("entries", entries)
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "unread"), countUnread, offset))
|
||||
view.Set("pagination", getPagination(route.Path(h.router, "unread"), countUnread, offset, user.EntriesPerPage))
|
||||
view.Set("menu", "unread")
|
||||
view.Set("user", user)
|
||||
view.Set("countUnread", countUnread)
|
||||
|
|
Loading…
Reference in a new issue