Add global block and keep filters
This commit is contained in:
parent
c4278821cb
commit
1a81866bb9
30 changed files with 457 additions and 50 deletions
|
@ -42,6 +42,8 @@ type User struct {
|
||||||
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView bool `json:"mark_read_on_view"`
|
MarkReadOnView bool `json:"mark_read_on_view"`
|
||||||
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||||
|
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
||||||
|
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u User) String() string {
|
func (u User) String() string {
|
||||||
|
@ -82,6 +84,8 @@ type UserModificationRequest struct {
|
||||||
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView *bool `json:"mark_read_on_view"`
|
MarkReadOnView *bool `json:"mark_read_on_view"`
|
||||||
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||||
|
BlockFilterEntryRules *string `json:"block_filter_entry_rules"`
|
||||||
|
KeepFilterEntryRules *string `json:"keep_filter_entry_rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users represents a list of users.
|
// Users represents a list of users.
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
json_parser "encoding/json"
|
json_parser "encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"miniflux.app/v2/internal/http/request"
|
"miniflux.app/v2/internal/http/request"
|
||||||
"miniflux.app/v2/internal/http/response/json"
|
"miniflux.app/v2/internal/http/response/json"
|
||||||
|
@ -82,6 +83,14 @@ func (h *handler) updateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cleanEnd := regexp.MustCompile(`(?m)\r\n\s*$`)
|
||||||
|
if userModificationRequest.BlockFilterEntryRules != nil {
|
||||||
|
*userModificationRequest.BlockFilterEntryRules = cleanEnd.ReplaceAllLiteralString(*userModificationRequest.BlockFilterEntryRules, "")
|
||||||
|
}
|
||||||
|
if userModificationRequest.KeepFilterEntryRules != nil {
|
||||||
|
*userModificationRequest.KeepFilterEntryRules = cleanEnd.ReplaceAllLiteralString(*userModificationRequest.KeepFilterEntryRules, "")
|
||||||
|
}
|
||||||
|
|
||||||
if validationErr := validator.ValidateUserModification(h.store, originalUser.ID, &userModificationRequest); validationErr != nil {
|
if validationErr := validator.ValidateUserModification(h.store, originalUser.ID, &userModificationRequest); validationErr != nil {
|
||||||
json.BadRequest(w, r, validationErr.Error())
|
json.BadRequest(w, r, validationErr.Error())
|
||||||
return
|
return
|
||||||
|
|
|
@ -903,4 +903,13 @@ var migrations = []func(tx *sql.Tx) error{
|
||||||
_, err = tx.Exec(sql)
|
_, err = tx.Exec(sql)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
|
func(tx *sql.Tx) (err error) {
|
||||||
|
sql := `
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN block_filter_entry_rules text not null default '',
|
||||||
|
ADD COLUMN keep_filter_entry_rules text not null default ''
|
||||||
|
`
|
||||||
|
_, err = tx.Exec(sql)
|
||||||
|
return err
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -302,6 +302,14 @@
|
||||||
"error.password_min_length": "Wenigstens 6 Zeichen müssen genutzt werden.",
|
"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.settings_mandatory_fields": "Die Felder für Benutzername, Thema, Sprache und Zeitzone sind obligatorisch.",
|
||||||
"error.settings_reading_speed_is_positive": "Die Lesegeschwindigkeiten müssen positive ganze Zahlen sein.",
|
"error.settings_reading_speed_is_positive": "Die Lesegeschwindigkeiten müssen positive ganze Zahlen sein.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Die Anzahl der Einträge pro Seite ist ungültig.",
|
"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.feed_mandatory_fields": "Die URL und die Kategorie sind obligatorisch.",
|
||||||
"error.feed_already_exists": "Dieser Feed existiert bereits.",
|
"error.feed_already_exists": "Dieser Feed existiert bereits.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Anwendungseinstellungen",
|
"form.prefs.fieldset.application_settings": "Anwendungseinstellungen",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentifizierungseinstellungen",
|
"form.prefs.fieldset.authentication_settings": "Authentifizierungseinstellungen",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader-Einstellungen",
|
"form.prefs.fieldset.reader_settings": "Reader-Einstellungen",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML Datei",
|
"form.import.label.file": "OPML Datei",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Fever API aktivieren",
|
"form.integration.fever_activate": "Fever API aktivieren",
|
||||||
|
|
|
@ -302,6 +302,14 @@
|
||||||
"error.password_min_length": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 6 χαρακτήρες.",
|
"error.password_min_length": "Ο κωδικός πρόσβασης πρέπει να έχει τουλάχιστον 6 χαρακτήρες.",
|
||||||
"error.settings_mandatory_fields": "Τα πεδία όνομα χρήστη, θέμα, Γλώσσα και ζώνη ώρας είναι υποχρεωτικά.",
|
"error.settings_mandatory_fields": "Τα πεδία όνομα χρήστη, θέμα, Γλώσσα και ζώνη ώρας είναι υποχρεωτικά.",
|
||||||
"error.settings_reading_speed_is_positive": "Οι ταχύτητες ανάγνωσης πρέπει να είναι θετικοί ακέραιοι αριθμοί.",
|
"error.settings_reading_speed_is_positive": "Οι ταχύτητες ανάγνωσης πρέπει να είναι θετικοί ακέραιοι αριθμοί.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Ο αριθμός των καταχωρήσεων ανά σελίδα δεν είναι έγκυρος.",
|
"error.entries_per_page_invalid": "Ο αριθμός των καταχωρήσεων ανά σελίδα δεν είναι έγκυρος.",
|
||||||
"error.feed_mandatory_fields": "Η διεύθυνση URL και η κατηγορία είναι υποχρεωτικά.",
|
"error.feed_mandatory_fields": "Η διεύθυνση URL και η κατηγορία είναι υποχρεωτικά.",
|
||||||
"error.feed_already_exists": "Αυτή η ροή υπάρχει ήδη.",
|
"error.feed_already_exists": "Αυτή η ροή υπάρχει ήδη.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Αρχείο OPML",
|
"form.import.label.file": "Αρχείο OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Ενεργοποιήστε το Fever API",
|
"form.integration.fever_activate": "Ενεργοποιήστε το Fever API",
|
||||||
|
|
|
@ -302,6 +302,14 @@
|
||||||
"error.password_min_length": "The password must have at least 6 characters.",
|
"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.settings_mandatory_fields": "The username, theme, language and timezone fields are mandatory.",
|
||||||
"error.settings_reading_speed_is_positive": "The reading speeds must be positive integers.",
|
"error.settings_reading_speed_is_positive": "The reading speeds must be positive integers.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "The number of entries per page is not valid.",
|
"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.feed_mandatory_fields": "The URL and the category are mandatory.",
|
||||||
"error.feed_already_exists": "This feed already exists.",
|
"error.feed_already_exists": "This feed already exists.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML file",
|
"form.import.label.file": "OPML file",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Activate Fever API",
|
"form.integration.fever_activate": "Activate Fever API",
|
||||||
|
|
|
@ -295,6 +295,14 @@
|
||||||
"error.password_min_length": "La contraseña debería tener al menos 6 caracteres.",
|
"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.settings_mandatory_fields": "Los campos de nombre de usuario, tema, idioma y zona horaria son obligatorios.",
|
||||||
"error.settings_reading_speed_is_positive": "Las velocidades de lectura deben ser números enteros positivos.",
|
"error.settings_reading_speed_is_positive": "Las velocidades de lectura deben ser números enteros positivos.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "El número de artículos por página no es válido.",
|
"error.entries_per_page_invalid": "El número de artículos por página no es válido.",
|
||||||
"error.feed_mandatory_fields": "Los campos de URL y categoría son obligatorios.",
|
"error.feed_mandatory_fields": "Los campos de URL y categoría son obligatorios.",
|
||||||
"error.feed_already_exists": "Este feed ya existe.",
|
"error.feed_already_exists": "Este feed ya existe.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Archivo OPML",
|
"form.import.label.file": "Archivo OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Activar API de Fever",
|
"form.integration.fever_activate": "Activar API de Fever",
|
||||||
|
|
|
@ -302,6 +302,14 @@
|
||||||
"error.password_min_length": "Salasanassa on oltava vähintään 6 merkkiä.",
|
"error.password_min_length": "Salasanassa on oltava vähintään 6 merkkiä.",
|
||||||
"error.settings_mandatory_fields": "Käyttäjätunnus, teema, kieli ja aikavyöhyke ovat pakollisia.",
|
"error.settings_mandatory_fields": "Käyttäjätunnus, teema, kieli ja aikavyöhyke ovat pakollisia.",
|
||||||
"error.settings_reading_speed_is_positive": "Lukunopeuksien on oltava positiivisia kokonaislukuja.",
|
"error.settings_reading_speed_is_positive": "Lukunopeuksien on oltava positiivisia kokonaislukuja.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Artikkelien määrä sivulla ei kelpaa.",
|
"error.entries_per_page_invalid": "Artikkelien määrä sivulla ei kelpaa.",
|
||||||
"error.feed_mandatory_fields": "URL-osoite ja kategoria ovat pakollisia.",
|
"error.feed_mandatory_fields": "URL-osoite ja kategoria ovat pakollisia.",
|
||||||
"error.feed_already_exists": "Tämä syöte on jo olemassa.",
|
"error.feed_already_exists": "Tämä syöte on jo olemassa.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML-tiedosto",
|
"form.import.label.file": "OPML-tiedosto",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Ota Fever API käyttöön",
|
"form.integration.fever_activate": "Ota Fever API käyttöön",
|
||||||
|
|
|
@ -295,6 +295,14 @@
|
||||||
"error.password_min_length": "Vous devez utiliser au moins 6 caractères pour le mot de passe.",
|
"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.settings_mandatory_fields": "Le nom d'utilisateur, le thème, la langue et le fuseau horaire sont obligatoire.",
|
||||||
"error.settings_reading_speed_is_positive": "Les vitesses de lecture doivent être des entiers positifs.",
|
"error.settings_reading_speed_is_positive": "Les vitesses de lecture doivent être des entiers positifs.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Le nombre d'entrées par page n'est pas valide.",
|
"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.feed_mandatory_fields": "L'URL et la catégorie sont obligatoire.",
|
||||||
"error.feed_already_exists": "Ce flux existe déjà.",
|
"error.feed_already_exists": "Ce flux existe déjà.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Paramètres de l'application",
|
"form.prefs.fieldset.application_settings": "Paramètres de l'application",
|
||||||
"form.prefs.fieldset.authentication_settings": "Paramètres d'authentification",
|
"form.prefs.fieldset.authentication_settings": "Paramètres d'authentification",
|
||||||
"form.prefs.fieldset.reader_settings": "Paramètres du lecteur",
|
"form.prefs.fieldset.reader_settings": "Paramètres du lecteur",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Fichier OPML",
|
"form.import.label.file": "Fichier OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Activer l'API de Fever",
|
"form.integration.fever_activate": "Activer l'API de Fever",
|
||||||
|
|
|
@ -302,6 +302,14 @@
|
||||||
"error.password_min_length": "पासवर्ड में कम से कम 6 अक्षर होने चाहिए।",
|
"error.password_min_length": "पासवर्ड में कम से कम 6 अक्षर होने चाहिए।",
|
||||||
"error.settings_mandatory_fields": "उपयोगकर्ता नाम, विषयवस्तु, भाषा और समयक्षेत्र फ़ील्ड अनिवार्य हैं।",
|
"error.settings_mandatory_fields": "उपयोगकर्ता नाम, विषयवस्तु, भाषा और समयक्षेत्र फ़ील्ड अनिवार्य हैं।",
|
||||||
"error.settings_reading_speed_is_positive": "पढ़ने की गति सकारात्मक पूर्णांक होनी चाहिए।",
|
"error.settings_reading_speed_is_positive": "पढ़ने की गति सकारात्मक पूर्णांक होनी चाहिए।",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "प्रति पृष्ठ प्रविष्टियों की संख्या मान्य नहीं है।",
|
"error.entries_per_page_invalid": "प्रति पृष्ठ प्रविष्टियों की संख्या मान्य नहीं है।",
|
||||||
"error.feed_mandatory_fields": "URL और श्रेणी अनिवार्य हैं।",
|
"error.feed_mandatory_fields": "URL और श्रेणी अनिवार्य हैं।",
|
||||||
"error.feed_already_exists": "यह फ़ीड पहले से मौजूद है.",
|
"error.feed_already_exists": "यह फ़ीड पहले से मौजूद है.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "ओपीएमएल फ़ाइल",
|
"form.import.label.file": "ओपीएमएल फ़ाइल",
|
||||||
"form.import.label.url": "यूआरएल",
|
"form.import.label.url": "यूआरएल",
|
||||||
"form.integration.fever_activate": "फीवर एपीआई सक्रिय करें",
|
"form.integration.fever_activate": "फीवर एपीआई सक्रिय करें",
|
||||||
|
|
|
@ -292,6 +292,14 @@
|
||||||
"error.password_min_length": "Kata sandi harus memiliki setidaknya 6 karakter.",
|
"error.password_min_length": "Kata sandi harus memiliki setidaknya 6 karakter.",
|
||||||
"error.settings_mandatory_fields": "Harus ada nama pengguna, tema, bahasa, dan zona waktu.",
|
"error.settings_mandatory_fields": "Harus ada nama pengguna, tema, bahasa, dan zona waktu.",
|
||||||
"error.settings_reading_speed_is_positive": "Kecepatan membaca harus integer positif.",
|
"error.settings_reading_speed_is_positive": "Kecepatan membaca harus integer positif.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Jumlah entri per halaman tidak valid.",
|
"error.entries_per_page_invalid": "Jumlah entri per halaman tidak valid.",
|
||||||
"error.feed_mandatory_fields": "Harus ada URL dan kategorinya.",
|
"error.feed_mandatory_fields": "Harus ada URL dan kategorinya.",
|
||||||
"error.feed_already_exists": "Umpan ini sudah ada.",
|
"error.feed_already_exists": "Umpan ini sudah ada.",
|
||||||
|
@ -372,6 +380,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Berkas OPML",
|
"form.import.label.file": "Berkas OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Aktifkan API Fever",
|
"form.integration.fever_activate": "Aktifkan API Fever",
|
||||||
|
|
|
@ -295,6 +295,14 @@
|
||||||
"error.password_min_length": "La password deve contenere almeno 6 caratteri.",
|
"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.settings_mandatory_fields": "Il nome utente, il tema, la lingua ed il fuso orario sono campi obbligatori.",
|
||||||
"error.settings_reading_speed_is_positive": "Le velocità di lettura devono essere numeri interi positivi.",
|
"error.settings_reading_speed_is_positive": "Le velocità di lettura devono essere numeri interi positivi.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Il numero di articoli per pagina non è valido.",
|
"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.feed_mandatory_fields": "L'URL e la categoria sono obbligatori.",
|
||||||
"error.feed_already_exists": "Questo feed esiste già.",
|
"error.feed_already_exists": "Questo feed esiste già.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "File OPML",
|
"form.import.label.file": "File OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Abilita l'API di Fever",
|
"form.integration.fever_activate": "Abilita l'API di Fever",
|
||||||
|
|
|
@ -292,6 +292,14 @@
|
||||||
"error.password_min_length": "パスワードは6文字以上である必要があります。",
|
"error.password_min_length": "パスワードは6文字以上である必要があります。",
|
||||||
"error.settings_mandatory_fields": "ユーザー名、テーマ、言語、タイムゾーンのすべてが必要です。",
|
"error.settings_mandatory_fields": "ユーザー名、テーマ、言語、タイムゾーンのすべてが必要です。",
|
||||||
"error.settings_reading_speed_is_positive": "読書速度は正の整数である必要があります。",
|
"error.settings_reading_speed_is_positive": "読書速度は正の整数である必要があります。",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "ページあたりの記事数が無効です。",
|
"error.entries_per_page_invalid": "ページあたりの記事数が無効です。",
|
||||||
"error.feed_mandatory_fields": "URL と カテゴリが必要です。",
|
"error.feed_mandatory_fields": "URL と カテゴリが必要です。",
|
||||||
"error.feed_already_exists": "このフィードは既に存在します。",
|
"error.feed_already_exists": "このフィードは既に存在します。",
|
||||||
|
@ -372,6 +380,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML ファイル",
|
"form.import.label.file": "OPML ファイル",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Fever API を有効にする",
|
"form.integration.fever_activate": "Fever API を有効にする",
|
||||||
|
|
|
@ -295,6 +295,14 @@
|
||||||
"error.password_min_length": "Je moet minstens 6 tekens gebruiken.",
|
"error.password_min_length": "Je moet minstens 6 tekens gebruiken.",
|
||||||
"error.settings_mandatory_fields": "Gebruikersnaam, skin, taal en tijdzone zijn verplicht.",
|
"error.settings_mandatory_fields": "Gebruikersnaam, skin, taal en tijdzone zijn verplicht.",
|
||||||
"error.settings_reading_speed_is_positive": "De leessnelheden moeten positieve gehele getallen zijn.",
|
"error.settings_reading_speed_is_positive": "De leessnelheden moeten positieve gehele getallen zijn.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Het aantal inzendingen per pagina is niet geldig.",
|
"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.feed_mandatory_fields": "The URL en de categorie zijn verplicht.",
|
||||||
"error.feed_already_exists": "Deze feed bestaat al.",
|
"error.feed_already_exists": "Deze feed bestaat al.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML-bestand",
|
"form.import.label.file": "OPML-bestand",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Activeer Fever API",
|
"form.integration.fever_activate": "Activeer Fever API",
|
||||||
|
|
|
@ -305,6 +305,14 @@
|
||||||
"error.password_min_length": "Musisz użyć co najmniej 6 znaków.",
|
"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.settings_mandatory_fields": "Pola nazwy użytkownika, tematu, języka i strefy czasowej są obowiązkowe.",
|
||||||
"error.settings_reading_speed_is_positive": "Prędkości odczytu muszą być dodatnimi liczbami całkowitymi.",
|
"error.settings_reading_speed_is_positive": "Prędkości odczytu muszą być dodatnimi liczbami całkowitymi.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Liczba wpisów na stronę jest nieprawidłowa.",
|
"error.entries_per_page_invalid": "Liczba wpisów na stronę jest nieprawidłowa.",
|
||||||
"error.feed_mandatory_fields": "URL i kategoria są obowiązkowe.",
|
"error.feed_mandatory_fields": "URL i kategoria są obowiązkowe.",
|
||||||
"error.feed_already_exists": "Ten kanał już istnieje.",
|
"error.feed_already_exists": "Ten kanał już istnieje.",
|
||||||
|
@ -392,6 +400,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Plik OPML",
|
"form.import.label.file": "Plik OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Aktywuj Fever API",
|
"form.integration.fever_activate": "Aktywuj Fever API",
|
||||||
|
|
|
@ -295,6 +295,14 @@
|
||||||
"error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
|
"error.password_min_length": "A senha deve ter no mínimo 6 caracteres.",
|
||||||
"error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
|
"error.settings_mandatory_fields": "Os campos de nome de usuário, tema, idioma e fuso horário são obrigatórios.",
|
||||||
"error.settings_reading_speed_is_positive": "As velocidades de leitura devem ser inteiros positivos.",
|
"error.settings_reading_speed_is_positive": "As velocidades de leitura devem ser inteiros positivos.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "O número de itens por página é inválido.",
|
"error.entries_per_page_invalid": "O número de itens por página é inválido.",
|
||||||
"error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
|
"error.feed_mandatory_fields": "O campo de URL e categoria são obrigatórios.",
|
||||||
"error.feed_already_exists": "Este feed já existe.",
|
"error.feed_already_exists": "Este feed já existe.",
|
||||||
|
@ -382,6 +390,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Arquivo OPML",
|
"form.import.label.file": "Arquivo OPML",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "Ativar API do Fever",
|
"form.integration.fever_activate": "Ativar API do Fever",
|
||||||
|
|
|
@ -305,6 +305,14 @@
|
||||||
"error.password_min_length": "Вы должны использовать минимум 6 символов.",
|
"error.password_min_length": "Вы должны использовать минимум 6 символов.",
|
||||||
"error.settings_mandatory_fields": "Имя пользователя, тема, язык и часовой пояс обязательны.",
|
"error.settings_mandatory_fields": "Имя пользователя, тема, язык и часовой пояс обязательны.",
|
||||||
"error.settings_reading_speed_is_positive": "Скорость чтения должна быть целым положительным числом.",
|
"error.settings_reading_speed_is_positive": "Скорость чтения должна быть целым положительным числом.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Недопустимое значение количества записей на странице.",
|
"error.entries_per_page_invalid": "Недопустимое значение количества записей на странице.",
|
||||||
"error.feed_mandatory_fields": "Ссылка и категория обязательны.",
|
"error.feed_mandatory_fields": "Ссылка и категория обязательны.",
|
||||||
"error.feed_already_exists": "Эта подписка уже существует.",
|
"error.feed_already_exists": "Эта подписка уже существует.",
|
||||||
|
@ -392,6 +400,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML файл",
|
"form.import.label.file": "OPML файл",
|
||||||
"form.import.label.url": "Ссылка",
|
"form.import.label.url": "Ссылка",
|
||||||
"form.integration.fever_activate": "Активировать Fever API",
|
"form.integration.fever_activate": "Активировать Fever API",
|
||||||
|
|
|
@ -122,6 +122,14 @@
|
||||||
"error.settings_mandatory_fields": "Kullanıcı ad, tema, dil ve saat dilimi zorunlu.",
|
"error.settings_mandatory_fields": "Kullanıcı ad, tema, dil ve saat dilimi zorunlu.",
|
||||||
"error.settings_media_playback_rate_range": "Oynatma hızı aralık dışında",
|
"error.settings_media_playback_rate_range": "Oynatma hızı aralık dışında",
|
||||||
"error.settings_reading_speed_is_positive": "Okuma hızları pozitif tam sayılar olmalıdır.",
|
"error.settings_reading_speed_is_positive": "Okuma hızları pozitif tam sayılar olmalıdır.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.site_url_not_empty": "Site URL'si boş olamaz.",
|
"error.site_url_not_empty": "Site URL'si boş olamaz.",
|
||||||
"error.subscription_not_found": "Herhangi bir abonelik bulunamadı.",
|
"error.subscription_not_found": "Herhangi bir abonelik bulunamadı.",
|
||||||
"error.title_required": "Başlık zorunlu.",
|
"error.title_required": "Başlık zorunlu.",
|
||||||
|
@ -264,6 +272,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Uygulama Ayarları",
|
"form.prefs.fieldset.application_settings": "Uygulama Ayarları",
|
||||||
"form.prefs.fieldset.authentication_settings": "Kimlik Doğrulama Ayarları",
|
"form.prefs.fieldset.authentication_settings": "Kimlik Doğrulama Ayarları",
|
||||||
"form.prefs.fieldset.reader_settings": "Okuyucu Ayarları",
|
"form.prefs.fieldset.reader_settings": "Okuyucu Ayarları",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.prefs.label.categories_sorting_order": "Kategori sıralaması",
|
"form.prefs.label.categories_sorting_order": "Kategori sıralaması",
|
||||||
"form.prefs.label.cjk_reading_speed": "Çince, Korece ve Japonca için okuma hızı (dakika başına karakter)",
|
"form.prefs.label.cjk_reading_speed": "Çince, Korece ve Japonca için okuma hızı (dakika başına karakter)",
|
||||||
"form.prefs.label.custom_css": "Özel CSS",
|
"form.prefs.label.custom_css": "Özel CSS",
|
||||||
|
|
|
@ -312,6 +312,14 @@
|
||||||
"error.password_min_length": "Пароль має складати щонайменше 6 символів.",
|
"error.password_min_length": "Пароль має складати щонайменше 6 символів.",
|
||||||
"error.settings_mandatory_fields": "Поля імені, теми, мови та часового поясу є обов’язковими.",
|
"error.settings_mandatory_fields": "Поля імені, теми, мови та часового поясу є обов’язковими.",
|
||||||
"error.settings_reading_speed_is_positive": "Швидкість читання має бути додатнім цілим числом.",
|
"error.settings_reading_speed_is_positive": "Швидкість читання має бути додатнім цілим числом.",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "Число записів на сторінку недійсне.",
|
"error.entries_per_page_invalid": "Число записів на сторінку недійсне.",
|
||||||
"error.feed_mandatory_fields": "URL та категорія є обов’язковими.",
|
"error.feed_mandatory_fields": "URL та категорія є обов’язковими.",
|
||||||
"error.feed_already_exists": "Така стрічка вже існує.",
|
"error.feed_already_exists": "Така стрічка вже існує.",
|
||||||
|
@ -392,6 +400,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "Application Settings",
|
"form.prefs.fieldset.application_settings": "Application Settings",
|
||||||
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
"form.prefs.fieldset.authentication_settings": "Authentication Settings",
|
||||||
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
"form.prefs.fieldset.reader_settings": "Reader Settings",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "Файл OPML",
|
"form.import.label.file": "Файл OPML",
|
||||||
"form.import.label.url": "URL-адреса",
|
"form.import.label.url": "URL-адреса",
|
||||||
"form.integration.fever_activate": "Увімкнути API Fever",
|
"form.integration.fever_activate": "Увімкнути API Fever",
|
||||||
|
|
|
@ -293,6 +293,14 @@
|
||||||
"error.site_url_not_empty": "源网站的网址不能为空。",
|
"error.site_url_not_empty": "源网站的网址不能为空。",
|
||||||
"error.feed_title_not_empty": "订阅源的标题不能为空。",
|
"error.feed_title_not_empty": "订阅源的标题不能为空。",
|
||||||
"error.settings_reading_speed_is_positive": "阅读速度必须是正整数。",
|
"error.settings_reading_speed_is_positive": "阅读速度必须是正整数。",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.feed_category_not_found": "此类别不存在或不属于该用户。",
|
"error.feed_category_not_found": "此类别不存在或不属于该用户。",
|
||||||
"error.feed_invalid_blocklist_rule": "阻止列表规则无效。",
|
"error.feed_invalid_blocklist_rule": "阻止列表规则无效。",
|
||||||
"error.feed_invalid_keeplist_rule": "保留列表规则无效。",
|
"error.feed_invalid_keeplist_rule": "保留列表规则无效。",
|
||||||
|
@ -372,6 +380,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "应用设置",
|
"form.prefs.fieldset.application_settings": "应用设置",
|
||||||
"form.prefs.fieldset.authentication_settings": "用户认证设置",
|
"form.prefs.fieldset.authentication_settings": "用户认证设置",
|
||||||
"form.prefs.fieldset.reader_settings": "阅读器设置",
|
"form.prefs.fieldset.reader_settings": "阅读器设置",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML 文件",
|
"form.import.label.file": "OPML 文件",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "启用 Fever API",
|
"form.integration.fever_activate": "启用 Fever API",
|
||||||
|
|
|
@ -285,6 +285,14 @@
|
||||||
"error.password_min_length": "請至少輸入 6 個字元",
|
"error.password_min_length": "請至少輸入 6 個字元",
|
||||||
"error.settings_mandatory_fields": "必須填寫使用者名稱、主題、語言以及時區",
|
"error.settings_mandatory_fields": "必須填寫使用者名稱、主題、語言以及時區",
|
||||||
"error.settings_reading_speed_is_positive": "閱讀速度必須是正整數。",
|
"error.settings_reading_speed_is_positive": "閱讀速度必須是正整數。",
|
||||||
|
"error.settings_block_rule_fieldname_invalid": "Invalid Block rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_block_rule_separator_required": "Invalid Block rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_block_rule_regex_required": "Invalid Block rule: rule #%d's pattern is not provided",
|
||||||
|
"error.settings_block_rule_invalid_regex": "Invalid Block rule: rule #%d's pattern is not a valid regex",
|
||||||
|
"error.settings_keep_rule_fieldname_invalid": "Invalid Keep rule: rule #%d is missing a valid field name (Options: %s)",
|
||||||
|
"error.settings_keep_rule_separator_required": "Invalid Keep rule: rule #%d's pattern is required to be seperated by a '='",
|
||||||
|
"error.settings_keep_rule_regex_required": "Invalid Keep rule: rule #%d pattern is not provided",
|
||||||
|
"error.settings_keep_rule_invalid_regex": "Invalid Keep rule: rule #%d's pattern is not a valid regex",
|
||||||
"error.entries_per_page_invalid": "每頁的文章數無效。",
|
"error.entries_per_page_invalid": "每頁的文章數無效。",
|
||||||
"error.feed_mandatory_fields": "必須填寫網址和分類",
|
"error.feed_mandatory_fields": "必須填寫網址和分類",
|
||||||
"error.feed_already_exists": "此Feed已存在。",
|
"error.feed_already_exists": "此Feed已存在。",
|
||||||
|
@ -372,6 +380,7 @@
|
||||||
"form.prefs.fieldset.application_settings": "應用程式設定",
|
"form.prefs.fieldset.application_settings": "應用程式設定",
|
||||||
"form.prefs.fieldset.authentication_settings": "使用者認證設定",
|
"form.prefs.fieldset.authentication_settings": "使用者認證設定",
|
||||||
"form.prefs.fieldset.reader_settings": "閱讀器設定",
|
"form.prefs.fieldset.reader_settings": "閱讀器設定",
|
||||||
|
"form.prefs.fieldset.global_feed_settings": "Global Feed Settings",
|
||||||
"form.import.label.file": "OPML 檔案",
|
"form.import.label.file": "OPML 檔案",
|
||||||
"form.import.label.url": "URL",
|
"form.import.label.url": "URL",
|
||||||
"form.integration.fever_activate": "啟用 Fever API",
|
"form.integration.fever_activate": "啟用 Fever API",
|
||||||
|
|
|
@ -36,6 +36,8 @@ type User struct {
|
||||||
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
CategoriesSortingOrder string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView bool `json:"mark_read_on_view"`
|
MarkReadOnView bool `json:"mark_read_on_view"`
|
||||||
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
MediaPlaybackRate float64 `json:"media_playback_rate"`
|
||||||
|
BlockFilterEntryRules string `json:"block_filter_entry_rules"`
|
||||||
|
KeepFilterEntryRules string `json:"keep_filter_entry_rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserCreationRequest represents the request to create a user.
|
// UserCreationRequest represents the request to create a user.
|
||||||
|
@ -72,6 +74,8 @@ type UserModificationRequest struct {
|
||||||
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
CategoriesSortingOrder *string `json:"categories_sorting_order"`
|
||||||
MarkReadOnView *bool `json:"mark_read_on_view"`
|
MarkReadOnView *bool `json:"mark_read_on_view"`
|
||||||
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
MediaPlaybackRate *float64 `json:"media_playback_rate"`
|
||||||
|
BlockFilterEntryRules *string `json:"block_filter_entry_rules"`
|
||||||
|
KeepFilterEntryRules *string `json:"keep_filter_entry_rules"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch updates the User object with the modification request.
|
// Patch updates the User object with the modification request.
|
||||||
|
@ -167,6 +171,14 @@ func (u *UserModificationRequest) Patch(user *User) {
|
||||||
if u.MediaPlaybackRate != nil {
|
if u.MediaPlaybackRate != nil {
|
||||||
user.MediaPlaybackRate = *u.MediaPlaybackRate
|
user.MediaPlaybackRate = *u.MediaPlaybackRate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.BlockFilterEntryRules != nil {
|
||||||
|
user.BlockFilterEntryRules = *u.BlockFilterEntryRules
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.KeepFilterEntryRules != nil {
|
||||||
|
user.KeepFilterEntryRules = *u.KeepFilterEntryRules
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// UseTimezone converts last login date to the given timezone.
|
// UseTimezone converts last login date to the given timezone.
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"miniflux.app/v2/internal/config"
|
"miniflux.app/v2/internal/config"
|
||||||
|
@ -51,7 +52,7 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us
|
||||||
slog.Int64("feed_id", feed.ID),
|
slog.Int64("feed_id", feed.ID),
|
||||||
slog.String("feed_url", feed.FeedURL),
|
slog.String("feed_url", feed.FeedURL),
|
||||||
)
|
)
|
||||||
if isBlockedEntry(feed, entry) || !isAllowedEntry(feed, entry) || !isRecentEntry(entry) {
|
if isBlockedEntry(feed, entry, user) || !isAllowedEntry(feed, entry, user) || !isRecentEntry(entry) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,7 +122,46 @@ func ProcessFeedEntries(store *storage.Storage, feed *model.Feed, user *model.Us
|
||||||
feed.Entries = filteredEntries
|
feed.Entries = filteredEntries
|
||||||
}
|
}
|
||||||
|
|
||||||
func isBlockedEntry(feed *model.Feed, entry *model.Entry) bool {
|
func isBlockedEntry(feed *model.Feed, entry *model.Entry, user *model.User) bool {
|
||||||
|
if user.BlockFilterEntryRules != "" {
|
||||||
|
rules := strings.Split(user.BlockFilterEntryRules, "\n")
|
||||||
|
for _, rule := range rules {
|
||||||
|
parts := strings.SplitN(rule, "=", 2)
|
||||||
|
|
||||||
|
var match bool
|
||||||
|
switch parts[0] {
|
||||||
|
case "EntryTitle":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.Title)
|
||||||
|
case "EntryURL":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.URL)
|
||||||
|
case "EntryCommentsURL":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.CommentsURL)
|
||||||
|
case "EntryContent":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.Content)
|
||||||
|
case "EntryAuthor":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.Author)
|
||||||
|
case "EntryTag":
|
||||||
|
containsTag := slices.ContainsFunc(entry.Tags, func(tag string) bool {
|
||||||
|
match, _ = regexp.MatchString(parts[1], tag)
|
||||||
|
return match
|
||||||
|
})
|
||||||
|
if containsTag {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
slog.Debug("Blocking entry based on rule",
|
||||||
|
slog.String("entry_url", entry.URL),
|
||||||
|
slog.Int64("feed_id", feed.ID),
|
||||||
|
slog.String("feed_url", feed.FeedURL),
|
||||||
|
slog.String("rule", rule),
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if feed.BlocklistRules == "" {
|
if feed.BlocklistRules == "" {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -152,7 +192,47 @@ func isBlockedEntry(feed *model.Feed, entry *model.Entry) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isAllowedEntry(feed *model.Feed, entry *model.Entry) bool {
|
func isAllowedEntry(feed *model.Feed, entry *model.Entry, user *model.User) bool {
|
||||||
|
if user.KeepFilterEntryRules != "" {
|
||||||
|
rules := strings.Split(user.KeepFilterEntryRules, "\n")
|
||||||
|
for _, rule := range rules {
|
||||||
|
parts := strings.SplitN(rule, "=", 2)
|
||||||
|
|
||||||
|
var match bool
|
||||||
|
switch parts[0] {
|
||||||
|
case "EntryTitle":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.Title)
|
||||||
|
case "EntryURL":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.URL)
|
||||||
|
case "EntryCommentsURL":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.CommentsURL)
|
||||||
|
case "EntryContent":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.Content)
|
||||||
|
case "EntryAuthor":
|
||||||
|
match, _ = regexp.MatchString(parts[1], entry.Author)
|
||||||
|
case "EntryTag":
|
||||||
|
containsTag := slices.ContainsFunc(entry.Tags, func(tag string) bool {
|
||||||
|
match, _ = regexp.MatchString(parts[1], tag)
|
||||||
|
return match
|
||||||
|
})
|
||||||
|
if containsTag {
|
||||||
|
match = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if match {
|
||||||
|
slog.Debug("Allowing entry based on rule",
|
||||||
|
slog.String("entry_url", entry.URL),
|
||||||
|
slog.Int64("feed_id", feed.ID),
|
||||||
|
slog.String("feed_url", feed.FeedURL),
|
||||||
|
slog.String("rule", rule),
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if feed.KeeplistRules == "" {
|
if feed.KeeplistRules == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,23 +15,33 @@ func TestBlockingEntries(t *testing.T) {
|
||||||
var scenarios = []struct {
|
var scenarios = []struct {
|
||||||
feed *model.Feed
|
feed *model.Feed
|
||||||
entry *model.Entry
|
entry *model.Entry
|
||||||
|
user *model.User
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{URL: "https://example.com"}, true},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{URL: "https://example.com"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{URL: "https://different.com"}, false},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{URL: "https://different.com"}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Some Example"}, true},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Some Example"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different"}, false},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different"}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Tags: []string{"example", "something else"}}, true},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Tags: []string{"example", "something else"}}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"example", "something else"}}, true},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"example", "something else"}}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"something different", "something else"}}, true},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"something different", "something else"}}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Tags: []string{"something different", "something else"}}, false},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Tags: []string{"something different", "something else"}}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Example"}, true},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Example"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Something different"}, false},
|
{&model.Feed{ID: 1, BlocklistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Something different"}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1}, &model.Entry{Title: "No rule defined"}, false},
|
{&model.Feed{ID: 1}, &model.Entry{Title: "No rule defined"}, &model.User{}, false},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{URL: "https://example.com", Title: "Some Example"}, &model.User{BlockFilterEntryRules: "EntryURL=(?i)example\nEntryTitle=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{URL: "https://different.com", Title: "Some Test"}, &model.User{BlockFilterEntryRules: "EntryURL=(?i)example\nEntryTitle=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{URL: "https://different.com", Title: "Some Example"}, &model.User{BlockFilterEntryRules: "EntryURL=(?i)example\nEntryTitle=(?i)Test"}, false},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{CommentsURL: "https://example.com", Content: "Some Example"}, &model.User{BlockFilterEntryRules: "EntryCommentsURL=(?i)example\nEntryContent=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{CommentsURL: "https://different.com", Content: "Some Test"}, &model.User{BlockFilterEntryRules: "EntryCommentsURL=(?i)example\nEntryContent=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{CommentsURL: "https://different.com", Content: "Some Example"}, &model.User{BlockFilterEntryRules: "EntryCommentsURL=(?i)example\nEntryContent=(?i)Test"}, false},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{Author: "Example", Tags: []string{"example", "something else"}}, &model.User{BlockFilterEntryRules: "EntryAuthor=(?i)example\nEntryTag=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{Author: "Different", Tags: []string{"example", "something else"}}, &model.User{BlockFilterEntryRules: "EntryAuthor=(?i)example\nEntryTag=(?i)example"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{Author: "Different", Tags: []string{"example", "something else"}}, &model.User{BlockFilterEntryRules: "EntryAuthor=(?i)example\nEntryTag=(?i)Test"}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range scenarios {
|
for _, tc := range scenarios {
|
||||||
result := isBlockedEntry(tc.feed, tc.entry)
|
result := isBlockedEntry(tc.feed, tc.entry, tc.user)
|
||||||
if tc.expected != result {
|
if tc.expected != result {
|
||||||
t.Errorf(`Unexpected result, got %v for entry %q`, result, tc.entry.Title)
|
t.Errorf(`Unexpected result, got %v for entry %q`, result, tc.entry.Title)
|
||||||
}
|
}
|
||||||
|
@ -42,23 +52,33 @@ func TestAllowEntries(t *testing.T) {
|
||||||
var scenarios = []struct {
|
var scenarios = []struct {
|
||||||
feed *model.Feed
|
feed *model.Feed
|
||||||
entry *model.Entry
|
entry *model.Entry
|
||||||
|
user *model.User
|
||||||
expected bool
|
expected bool
|
||||||
}{
|
}{
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "https://example.com"}, true},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "https://example.com"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "https://different.com"}, false},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "https://different.com"}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Some Example"}, true},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Some Example"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different"}, false},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different"}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1}, &model.Entry{Title: "No rule defined"}, true},
|
{&model.Feed{ID: 1}, &model.Entry{Title: "No rule defined"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different", Tags: []string{"example", "something else"}}, true},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different", Tags: []string{"example", "something else"}}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"example", "something else"}}, true},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"example", "something else"}}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"something different", "something else"}}, true},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Example", Tags: []string{"something different", "something else"}}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something more", Tags: []string{"something different", "something else"}}, false},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something more", Tags: []string{"something different", "something else"}}, &model.User{}, false},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Example"}, true},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Example"}, &model.User{}, true},
|
||||||
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Something different"}, false},
|
{&model.Feed{ID: 1, KeeplistRules: "(?i)example"}, &model.Entry{Title: "Something different", Author: "Something different"}, &model.User{}, false},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{URL: "https://example.com", Title: "Some Example"}, &model.User{KeepFilterEntryRules: "EntryURL=(?i)example\nEntryTitle=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{URL: "https://different.com", Title: "Some Test"}, &model.User{KeepFilterEntryRules: "EntryURL=(?i)example\nEntryTitle=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{URL: "https://different.com", Title: "Some Example"}, &model.User{KeepFilterEntryRules: "EntryURL=(?i)example\nEntryTitle=(?i)Test"}, false},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{CommentsURL: "https://example.com", Content: "Some Example"}, &model.User{KeepFilterEntryRules: "EntryCommentsURL=(?i)example\nEntryContent=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{CommentsURL: "https://different.com", Content: "Some Test"}, &model.User{KeepFilterEntryRules: "EntryCommentsURL=(?i)example\nEntryContent=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{CommentsURL: "https://different.com", Content: "Some Example"}, &model.User{KeepFilterEntryRules: "EntryCommentsURL=(?i)example\nEntryContent=(?i)Test"}, false},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{Author: "Example", Tags: []string{"example", "something else"}}, &model.User{KeepFilterEntryRules: "EntryAuthor=(?i)example\nEntryTag=(?i)Test"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{Author: "Different", Tags: []string{"example", "something else"}}, &model.User{KeepFilterEntryRules: "EntryAuthor=(?i)example\nEntryTag=(?i)example"}, true},
|
||||||
|
{&model.Feed{ID: 1, BlocklistRules: ""}, &model.Entry{Author: "Different", Tags: []string{"example", "something else"}}, &model.User{KeepFilterEntryRules: "EntryAuthor=(?i)example\nEntryTag=(?i)Test"}, false},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range scenarios {
|
for _, tc := range scenarios {
|
||||||
result := isAllowedEntry(tc.feed, tc.entry)
|
result := isAllowedEntry(tc.feed, tc.entry, tc.user)
|
||||||
if tc.expected != result {
|
if tc.expected != result {
|
||||||
t.Errorf(`Unexpected result, got %v for entry %q`, result, tc.entry.Title)
|
t.Errorf(`Unexpected result, got %v for entry %q`, result, tc.entry.Title)
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,9 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view,
|
mark_read_on_view,
|
||||||
media_playback_rate
|
media_playback_rate,
|
||||||
|
block_filter_entry_rules,
|
||||||
|
keep_filter_entry_rules
|
||||||
`
|
`
|
||||||
|
|
||||||
tx, err := s.db.Begin()
|
tx, err := s.db.Begin()
|
||||||
|
@ -132,6 +134,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
|
||||||
&user.CategoriesSortingOrder,
|
&user.CategoriesSortingOrder,
|
||||||
&user.MarkReadOnView,
|
&user.MarkReadOnView,
|
||||||
&user.MediaPlaybackRate,
|
&user.MediaPlaybackRate,
|
||||||
|
&user.BlockFilterEntryRules,
|
||||||
|
&user.KeepFilterEntryRules,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
|
@ -189,9 +193,11 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
default_home_page=$20,
|
default_home_page=$20,
|
||||||
categories_sorting_order=$21,
|
categories_sorting_order=$21,
|
||||||
mark_read_on_view=$22,
|
mark_read_on_view=$22,
|
||||||
media_playback_rate=$23
|
media_playback_rate=$23,
|
||||||
|
block_filter_entry_rules=$24,
|
||||||
|
keep_filter_entry_rules=$25
|
||||||
WHERE
|
WHERE
|
||||||
id=$24
|
id=$26
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err = s.db.Exec(
|
_, err = s.db.Exec(
|
||||||
|
@ -219,6 +225,8 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.CategoriesSortingOrder,
|
user.CategoriesSortingOrder,
|
||||||
user.MarkReadOnView,
|
user.MarkReadOnView,
|
||||||
user.MediaPlaybackRate,
|
user.MediaPlaybackRate,
|
||||||
|
user.BlockFilterEntryRules,
|
||||||
|
user.KeepFilterEntryRules,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -248,9 +256,11 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
default_home_page=$19,
|
default_home_page=$19,
|
||||||
categories_sorting_order=$20,
|
categories_sorting_order=$20,
|
||||||
mark_read_on_view=$21,
|
mark_read_on_view=$21,
|
||||||
media_playback_rate=$22
|
media_playback_rate=$22,
|
||||||
|
block_filter_entry_rules=$23,
|
||||||
|
keep_filter_entry_rules=$24
|
||||||
WHERE
|
WHERE
|
||||||
id=$23
|
id=$25
|
||||||
`
|
`
|
||||||
|
|
||||||
_, err := s.db.Exec(
|
_, err := s.db.Exec(
|
||||||
|
@ -277,6 +287,8 @@ func (s *Storage) UpdateUser(user *model.User) error {
|
||||||
user.CategoriesSortingOrder,
|
user.CategoriesSortingOrder,
|
||||||
user.MarkReadOnView,
|
user.MarkReadOnView,
|
||||||
user.MediaPlaybackRate,
|
user.MediaPlaybackRate,
|
||||||
|
user.BlockFilterEntryRules,
|
||||||
|
user.KeepFilterEntryRules,
|
||||||
user.ID,
|
user.ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -325,7 +337,9 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view,
|
mark_read_on_view,
|
||||||
media_playback_rate
|
media_playback_rate,
|
||||||
|
block_filter_entry_rules,
|
||||||
|
keep_filter_entry_rules
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -361,7 +375,9 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view,
|
mark_read_on_view,
|
||||||
media_playback_rate
|
media_playback_rate,
|
||||||
|
block_filter_entry_rules,
|
||||||
|
keep_filter_entry_rules
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -397,7 +413,9 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view,
|
mark_read_on_view,
|
||||||
media_playback_rate
|
media_playback_rate,
|
||||||
|
block_filter_entry_rules,
|
||||||
|
keep_filter_entry_rules
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -440,7 +458,9 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
|
||||||
u.default_home_page,
|
u.default_home_page,
|
||||||
u.categories_sorting_order,
|
u.categories_sorting_order,
|
||||||
u.mark_read_on_view,
|
u.mark_read_on_view,
|
||||||
media_playback_rate
|
media_playback_rate,
|
||||||
|
u.block_filter_entry_rules,
|
||||||
|
u.keep_filter_entry_rules
|
||||||
FROM
|
FROM
|
||||||
users u
|
users u
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
|
@ -478,6 +498,8 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
|
||||||
&user.CategoriesSortingOrder,
|
&user.CategoriesSortingOrder,
|
||||||
&user.MarkReadOnView,
|
&user.MarkReadOnView,
|
||||||
&user.MediaPlaybackRate,
|
&user.MediaPlaybackRate,
|
||||||
|
&user.BlockFilterEntryRules,
|
||||||
|
&user.KeepFilterEntryRules,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
|
@ -586,7 +608,9 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
default_home_page,
|
default_home_page,
|
||||||
categories_sorting_order,
|
categories_sorting_order,
|
||||||
mark_read_on_view,
|
mark_read_on_view,
|
||||||
media_playback_rate
|
media_playback_rate,
|
||||||
|
block_filter_entry_rules,
|
||||||
|
keep_filter_entry_rules
|
||||||
FROM
|
FROM
|
||||||
users
|
users
|
||||||
ORDER BY username ASC
|
ORDER BY username ASC
|
||||||
|
@ -625,6 +649,8 @@ func (s *Storage) Users() (model.Users, error) {
|
||||||
&user.CategoriesSortingOrder,
|
&user.CategoriesSortingOrder,
|
||||||
&user.MarkReadOnView,
|
&user.MarkReadOnView,
|
||||||
&user.MediaPlaybackRate,
|
&user.MediaPlaybackRate,
|
||||||
|
&user.BlockFilterEntryRules,
|
||||||
|
&user.KeepFilterEntryRules,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -200,6 +200,28 @@
|
||||||
|
|
||||||
<label for="form-custom-css">{{t "form.prefs.label.custom_css" }}</label>
|
<label for="form-custom-css">{{t "form.prefs.label.custom_css" }}</label>
|
||||||
<textarea id="form-custom-css" name="custom_css" cols="40" rows="10" spellcheck="false">{{ .form.CustomCSS }}</textarea>
|
<textarea id="form-custom-css" name="custom_css" cols="40" rows="10" spellcheck="false">{{ .form.CustomCSS }}</textarea>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{ t "form.prefs.fieldset.global_feed_settings" }}</legend>
|
||||||
|
<div class="form-label-row">
|
||||||
|
<label for="form-blocklist-rules">
|
||||||
|
{{ t "form.feed.label.blocklist_rules" }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<textarea id="form-blocklist-rules" name="block_filter_entry_rules" cols="40" rows="10" spellcheck="false">{{ .form.BlockFilterEntryRules }}</textarea>
|
||||||
|
|
||||||
|
<div class="form-label-row">
|
||||||
|
<label for="form-keeplist-rules">
|
||||||
|
{{ t "form.feed.label.keeplist_rules" }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<textarea id="form-keeplist-rules" name="keep_filter_entry_rules" cols="40" rows="10" spellcheck="false">{{ .form.KeepFilterEntryRules }}</textarea>
|
||||||
|
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
|
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,6 +34,8 @@ type SettingsForm struct {
|
||||||
CategoriesSortingOrder string
|
CategoriesSortingOrder string
|
||||||
MarkReadOnView bool
|
MarkReadOnView bool
|
||||||
MediaPlaybackRate float64
|
MediaPlaybackRate float64
|
||||||
|
BlockFilterEntryRules string
|
||||||
|
KeepFilterEntryRules string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge updates the fields of the given user.
|
// Merge updates the fields of the given user.
|
||||||
|
@ -57,6 +59,8 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
|
||||||
user.CategoriesSortingOrder = s.CategoriesSortingOrder
|
user.CategoriesSortingOrder = s.CategoriesSortingOrder
|
||||||
user.MarkReadOnView = s.MarkReadOnView
|
user.MarkReadOnView = s.MarkReadOnView
|
||||||
user.MediaPlaybackRate = s.MediaPlaybackRate
|
user.MediaPlaybackRate = s.MediaPlaybackRate
|
||||||
|
user.BlockFilterEntryRules = s.BlockFilterEntryRules
|
||||||
|
user.KeepFilterEntryRules = s.KeepFilterEntryRules
|
||||||
|
|
||||||
if s.Password != "" {
|
if s.Password != "" {
|
||||||
user.Password = s.Password
|
user.Password = s.Password
|
||||||
|
@ -133,5 +137,7 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
|
||||||
CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
|
CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
|
||||||
MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
|
MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
|
||||||
MediaPlaybackRate: mediaPlaybackRate,
|
MediaPlaybackRate: mediaPlaybackRate,
|
||||||
|
BlockFilterEntryRules: r.FormValue("block_filter_entry_rules"),
|
||||||
|
KeepFilterEntryRules: r.FormValue("keep_filter_entry_rules"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,8 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
|
||||||
CategoriesSortingOrder: user.CategoriesSortingOrder,
|
CategoriesSortingOrder: user.CategoriesSortingOrder,
|
||||||
MarkReadOnView: user.MarkReadOnView,
|
MarkReadOnView: user.MarkReadOnView,
|
||||||
MediaPlaybackRate: user.MediaPlaybackRate,
|
MediaPlaybackRate: user.MediaPlaybackRate,
|
||||||
|
BlockFilterEntryRules: user.BlockFilterEntryRules,
|
||||||
|
KeepFilterEntryRules: user.KeepFilterEntryRules,
|
||||||
}
|
}
|
||||||
|
|
||||||
timezones, err := h.store.Timezones()
|
timezones, err := h.store.Timezones()
|
||||||
|
|
|
@ -5,6 +5,7 @@ package ui // import "miniflux.app/v2/internal/ui"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"miniflux.app/v2/internal/http/request"
|
"miniflux.app/v2/internal/http/request"
|
||||||
"miniflux.app/v2/internal/http/response/html"
|
"miniflux.app/v2/internal/http/response/html"
|
||||||
|
@ -53,6 +54,11 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
view.Set("countWebAuthnCerts", h.store.CountWebAuthnCredentialsByUserID(loggedUser.ID))
|
view.Set("countWebAuthnCerts", h.store.CountWebAuthnCredentialsByUserID(loggedUser.ID))
|
||||||
view.Set("webAuthnCerts", creds)
|
view.Set("webAuthnCerts", creds)
|
||||||
|
|
||||||
|
// Sanitize the end of the block & Keep rules
|
||||||
|
cleanEnd := regexp.MustCompile(`(?m)\r\n\s*$`)
|
||||||
|
settingsForm.BlockFilterEntryRules = cleanEnd.ReplaceAllLiteralString(settingsForm.BlockFilterEntryRules, "")
|
||||||
|
settingsForm.KeepFilterEntryRules = cleanEnd.ReplaceAllLiteralString(settingsForm.KeepFilterEntryRules, "")
|
||||||
|
|
||||||
if validationErr := settingsForm.Validate(); validationErr != nil {
|
if validationErr := settingsForm.Validate(); validationErr != nil {
|
||||||
view.Set("errorMessage", validationErr.Translate(loggedUser.Language))
|
view.Set("errorMessage", validationErr.Translate(loggedUser.Language))
|
||||||
html.OK(w, r, view.Render("settings"))
|
html.OK(w, r, view.Render("settings"))
|
||||||
|
@ -73,6 +79,8 @@ func (h *handler) updateSettings(w http.ResponseWriter, r *http.Request) {
|
||||||
CJKReadingSpeed: model.OptionalNumber(settingsForm.CJKReadingSpeed),
|
CJKReadingSpeed: model.OptionalNumber(settingsForm.CJKReadingSpeed),
|
||||||
DefaultHomePage: model.OptionalString(settingsForm.DefaultHomePage),
|
DefaultHomePage: model.OptionalString(settingsForm.DefaultHomePage),
|
||||||
MediaPlaybackRate: model.OptionalNumber(settingsForm.MediaPlaybackRate),
|
MediaPlaybackRate: model.OptionalNumber(settingsForm.MediaPlaybackRate),
|
||||||
|
BlockFilterEntryRules: model.OptionalString(settingsForm.BlockFilterEntryRules),
|
||||||
|
KeepFilterEntryRules: model.OptionalString(settingsForm.KeepFilterEntryRules),
|
||||||
}
|
}
|
||||||
|
|
||||||
if validationErr := validator.ValidateUserModification(h.store, loggedUser.ID, userModificationRequest); validationErr != nil {
|
if validationErr := validator.ValidateUserModification(h.store, loggedUser.ID, userModificationRequest); validationErr != nil {
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
package validator // import "miniflux.app/v2/internal/validator"
|
package validator // import "miniflux.app/v2/internal/validator"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"miniflux.app/v2/internal/locale"
|
"miniflux.app/v2/internal/locale"
|
||||||
"miniflux.app/v2/internal/model"
|
"miniflux.app/v2/internal/model"
|
||||||
"miniflux.app/v2/internal/storage"
|
"miniflux.app/v2/internal/storage"
|
||||||
|
@ -108,6 +111,18 @@ func ValidateUserModification(store *storage.Storage, userID int64, changes *mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if changes.BlockFilterEntryRules != nil {
|
||||||
|
if err := isValidFilterRules(*changes.BlockFilterEntryRules, "block"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changes.KeepFilterEntryRules != nil {
|
||||||
|
if err := isValidFilterRules(*changes.KeepFilterEntryRules, "keep"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,3 +210,35 @@ func validateMediaPlaybackRate(mediaPlaybackRate float64) *locale.LocalizedError
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isValidFilterRules(filterEntryRules string, filterType string) *locale.LocalizedError {
|
||||||
|
// Valid Format: FieldName(RegEx)~FieldName(RegEx)~...
|
||||||
|
fieldNames := []string{"EntryTitle", "EntryURL", "EntryCommentsURL", "EntryContent", "EntryAuthor", "EntryTag"}
|
||||||
|
|
||||||
|
rules := strings.Split(filterEntryRules, "\n")
|
||||||
|
for i, rule := range rules {
|
||||||
|
// Check if rule starts with a valid fieldName
|
||||||
|
idx := slices.IndexFunc(fieldNames, func(fieldName string) bool { return strings.HasPrefix(rule, fieldName) })
|
||||||
|
if idx == -1 {
|
||||||
|
return locale.NewLocalizedError("error.settings_"+filterType+"_rule_fieldname_invalid", i+1, "'"+strings.Join(fieldNames, "', '")+"'")
|
||||||
|
}
|
||||||
|
fieldName := fieldNames[idx]
|
||||||
|
fieldRegEx, _ := strings.CutPrefix(rule, fieldName)
|
||||||
|
|
||||||
|
// Check if regex begins with a =
|
||||||
|
if !strings.HasPrefix(fieldRegEx, "=") {
|
||||||
|
return locale.NewLocalizedError("error.settings_"+filterType+"_rule_separator_required", i+1)
|
||||||
|
}
|
||||||
|
fieldRegEx = strings.TrimPrefix(fieldRegEx, "=")
|
||||||
|
|
||||||
|
if fieldRegEx == "" {
|
||||||
|
return locale.NewLocalizedError("error.settings_"+filterType+"_rule_regex_required", i+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if provided pattern is a valid RegEx
|
||||||
|
if !IsValidRegex(fieldRegEx) {
|
||||||
|
return locale.NewLocalizedError("error.settings_"+filterType+"_rule_invalid_regex", i+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue