Add ability to change entry sort order in the UI

This commit is contained in:
James Loh 2021-05-14 21:51:51 +10:00 committed by fguillot
parent 27d170cbec
commit 78f6bbe93d
28 changed files with 122 additions and 30 deletions

View file

@ -26,6 +26,7 @@ type User struct {
Language string `json:"language"` Language string `json:"language"`
Timezone string `json:"timezone"` Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"` EntryDirection string `json:"entry_sorting_direction"`
EntryOrder string `json:"entry_sorting_order"`
Stylesheet string `json:"stylesheet"` Stylesheet string `json:"stylesheet"`
GoogleID string `json:"google_id"` GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"` OpenIDConnectID string `json:"openid_connect_id"`
@ -59,6 +60,7 @@ type UserModificationRequest struct {
Language *string `json:"language"` Language *string `json:"language"`
Timezone *string `json:"timezone"` Timezone *string `json:"timezone"`
EntryDirection *string `json:"entry_sorting_direction"` EntryDirection *string `json:"entry_sorting_direction"`
EntryOrder *string `json:"entry_sorting_order"`
Stylesheet *string `json:"stylesheet"` Stylesheet *string `json:"stylesheet"`
GoogleID *string `json:"google_id"` GoogleID *string `json:"google_id"`
OpenIDConnectID *string `json:"openid_connect_id"` OpenIDConnectID *string `json:"openid_connect_id"`

View file

@ -17,7 +17,7 @@ var migrations = []func(tx *sql.Tx) error{
CREATE TABLE schema_version ( CREATE TABLE schema_version (
version text not null version text not null
); );
CREATE TABLE users ( CREATE TABLE users (
id serial not null, id serial not null,
username text not null unique, username text not null unique,
@ -29,7 +29,7 @@ var migrations = []func(tx *sql.Tx) error{
last_login_at timestamp with time zone, last_login_at timestamp with time zone,
primary key (id) primary key (id)
); );
CREATE TABLE sessions ( CREATE TABLE sessions (
id serial not null, id serial not null,
user_id int not null, user_id int not null,
@ -41,7 +41,7 @@ var migrations = []func(tx *sql.Tx) error{
unique (user_id, token), unique (user_id, token),
foreign key (user_id) references users(id) on delete cascade foreign key (user_id) references users(id) on delete cascade
); );
CREATE TABLE categories ( CREATE TABLE categories (
id serial not null, id serial not null,
user_id int not null, user_id int not null,
@ -50,7 +50,7 @@ var migrations = []func(tx *sql.Tx) error{
unique (user_id, title), unique (user_id, title),
foreign key (user_id) references users(id) on delete cascade foreign key (user_id) references users(id) on delete cascade
); );
CREATE TABLE feeds ( CREATE TABLE feeds (
id bigserial not null, id bigserial not null,
user_id int not null, user_id int not null,
@ -68,9 +68,9 @@ var migrations = []func(tx *sql.Tx) error{
foreign key (user_id) references users(id) on delete cascade, foreign key (user_id) references users(id) on delete cascade,
foreign key (category_id) references categories(id) on delete cascade foreign key (category_id) references categories(id) on delete cascade
); );
CREATE TYPE entry_status as enum('unread', 'read', 'removed'); CREATE TYPE entry_status as enum('unread', 'read', 'removed');
CREATE TABLE entries ( CREATE TABLE entries (
id bigserial not null, id bigserial not null,
user_id int not null, user_id int not null,
@ -87,9 +87,9 @@ var migrations = []func(tx *sql.Tx) error{
foreign key (user_id) references users(id) on delete cascade, foreign key (user_id) references users(id) on delete cascade,
foreign key (feed_id) references feeds(id) on delete cascade foreign key (feed_id) references feeds(id) on delete cascade
); );
CREATE INDEX entries_feed_idx on entries using btree(feed_id); CREATE INDEX entries_feed_idx on entries using btree(feed_id);
CREATE TABLE enclosures ( CREATE TABLE enclosures (
id bigserial not null, id bigserial not null,
user_id int not null, user_id int not null,
@ -101,7 +101,7 @@ var migrations = []func(tx *sql.Tx) error{
foreign key (user_id) references users(id) on delete cascade, foreign key (user_id) references users(id) on delete cascade,
foreign key (entry_id) references entries(id) on delete cascade foreign key (entry_id) references entries(id) on delete cascade
); );
CREATE TABLE icons ( CREATE TABLE icons (
id bigserial not null, id bigserial not null,
hash text not null unique, hash text not null unique,
@ -109,14 +109,14 @@ var migrations = []func(tx *sql.Tx) error{
content bytea not null, content bytea not null,
primary key (id) primary key (id)
); );
CREATE TABLE feed_icons ( CREATE TABLE feed_icons (
feed_id bigint not null, feed_id bigint not null,
icon_id bigint not null, icon_id bigint not null,
primary key(feed_id, icon_id), primary key(feed_id, icon_id),
foreign key (feed_id) references feeds(id) on delete cascade, foreign key (feed_id) references feeds(id) on delete cascade,
foreign key (icon_id) references icons(id) on delete cascade foreign key (icon_id) references icons(id) on delete cascade
); );
` `
_, err = tx.Exec(sql) _, err = tx.Exec(sql)
return err return err
@ -410,7 +410,7 @@ var migrations = []func(tx *sql.Tx) error{
}, },
func(tx *sql.Tx) (err error) { func(tx *sql.Tx) (err error) {
sql := ` sql := `
ALTER TABLE feeds ALTER TABLE feeds
ADD COLUMN blocklist_rules text not null default '', ADD COLUMN blocklist_rules text not null default '',
ADD COLUMN keeplist_rules text not null default '' ADD COLUMN keeplist_rules text not null default ''
` `
@ -555,4 +555,12 @@ 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 := `
CREATE TYPE entry_sorting_order AS enum('published_at', 'created_at');
ALTER TABLE users ADD COLUMN entry_order entry_sorting_order default 'published_at';
`
_, err = tx.Exec(sql)
return err
},
} }

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Eigenständige", "form.prefs.select.standalone": "Eigenständige",
"form.prefs.select.minimal_ui": "Minimal", "form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.browser": "Browser", "form.prefs.select.browser": "Browser",
"form.prefs.select.publish_time": "Eintrag veröffentlichte Zeit",
"form.prefs.select.created_time": "Eintrag erstellt Zeit",
"form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren", "form.prefs.label.keyboard_shortcuts": "Tastaturkürzel aktivieren",
"form.prefs.label.entry_swipe": "Wischgeste für Einträge auf dem Handy aktivieren", "form.prefs.label.entry_swipe": "Wischgeste für Einträge auf dem Handy aktivieren",
"form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen", "form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
"form.prefs.label.custom_css": "Benutzerdefiniertes CSS", "form.prefs.label.custom_css": "Benutzerdefiniertes CSS",
"form.prefs.label.entry_order": "Eintrag Sortierspalte",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Μεμονωμένο", "form.prefs.select.standalone": "Μεμονωμένο",
"form.prefs.select.minimal_ui": "Ελάχιστη", "form.prefs.select.minimal_ui": "Ελάχιστη",
"form.prefs.select.browser": "Περιηγητής", "form.prefs.select.browser": "Περιηγητής",
"form.prefs.select.publish_time": "Δημοσιευμένος χρόνος εισόδου",
"form.prefs.select.created_time": "Χρόνος δημιουργίας καταχώρησης",
"form.prefs.label.keyboard_shortcuts": "Ενεργοποίηση συντομεύσεων πληκτρολογίου", "form.prefs.label.keyboard_shortcuts": "Ενεργοποίηση συντομεύσεων πληκτρολογίου",
"form.prefs.label.entry_swipe": "Ενεργοποιήστε τη χειρονομία σάρωσης στις καταχωρήσεις στο κινητό", "form.prefs.label.entry_swipe": "Ενεργοποιήστε τη χειρονομία σάρωσης στις καταχωρήσεις στο κινητό",
"form.prefs.label.show_reading_time": "Εμφάνιση εκτιμώμενου χρόνου ανάγνωσης για άρθρα", "form.prefs.label.show_reading_time": "Εμφάνιση εκτιμώμενου χρόνου ανάγνωσης για άρθρα",
"form.prefs.label.custom_css": "Προσαρμοσμένο CSS", "form.prefs.label.custom_css": "Προσαρμοσμένο CSS",
"form.prefs.label.entry_order": "Στήλη ταξινόμησης εισόδου",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Standalone", "form.prefs.select.standalone": "Standalone",
"form.prefs.select.minimal_ui": "Minimal", "form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.browser": "Browser", "form.prefs.select.browser": "Browser",
"form.prefs.select.publish_time": "Entry published time",
"form.prefs.select.created_time": "Entry created time",
"form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts", "form.prefs.label.keyboard_shortcuts": "Enable keyboard shortcuts",
"form.prefs.label.entry_swipe": "Enable swipe gesture on entries on mobile", "form.prefs.label.entry_swipe": "Enable swipe gesture on entries on mobile",
"form.prefs.label.show_reading_time": "Show estimated reading time for articles", "form.prefs.label.show_reading_time": "Show estimated reading time for articles",
"form.prefs.label.custom_css": "Custom CSS", "form.prefs.label.custom_css": "Custom CSS",
"form.prefs.label.entry_order": "Entry Sorting Column",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Ser único", "form.prefs.select.standalone": "Ser único",
"form.prefs.select.minimal_ui": "Mínimo", "form.prefs.select.minimal_ui": "Mínimo",
"form.prefs.select.browser": "Navegador", "form.prefs.select.browser": "Navegador",
"form.prefs.select.publish_time": "Hora de publicación de la entrada",
"form.prefs.select.created_time": "Hora de creación de la entrada",
"form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado", "form.prefs.label.keyboard_shortcuts": "Habilitar atajos de teclado",
"form.prefs.label.entry_swipe": "Habilitar el gesto de deslizar el dedo en las entradas en el móvil", "form.prefs.label.entry_swipe": "Habilitar el gesto de deslizar el dedo en las entradas en el móvil",
"form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos", "form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
"form.prefs.label.custom_css": "CSS personalizado", "form.prefs.label.custom_css": "CSS personalizado",
"form.prefs.label.entry_order": "Columna de clasificación de entradas",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Autonome", "form.prefs.select.standalone": "Autonome",
"form.prefs.select.minimal_ui": "Minimal", "form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.browser": "Navigateur", "form.prefs.select.browser": "Navigateur",
"form.prefs.select.publish_time": "Heure de publication de l'entrée",
"form.prefs.select.created_time": "Heure de création de l'entrée",
"form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier", "form.prefs.label.keyboard_shortcuts": "Activer les raccourcis clavier",
"form.prefs.label.entry_swipe": "Activer le geste de balayage sur les entrées sur mobile", "form.prefs.label.entry_swipe": "Activer le geste de balayage sur les entrées sur mobile",
"form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles", "form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
"form.prefs.label.custom_css": "CSS personnalisé", "form.prefs.label.custom_css": "CSS personnalisé",
"form.prefs.label.entry_order": "Colonne de tri des entrées",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Autonoma", "form.prefs.select.standalone": "Autonoma",
"form.prefs.select.minimal_ui": "Minimale", "form.prefs.select.minimal_ui": "Minimale",
"form.prefs.select.browser": "Browser", "form.prefs.select.browser": "Browser",
"form.prefs.select.publish_time": "Ora di pubblicazione dell'entrata",
"form.prefs.select.created_time": "Tempo di creazione dell'entrata",
"form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera", "form.prefs.label.keyboard_shortcuts": "Abilita le scorciatoie da tastiera",
"form.prefs.label.entry_swipe": "Abilita il gesto di scorrimento sulle voci sul cellulare", "form.prefs.label.entry_swipe": "Abilita il gesto di scorrimento sulle voci sul cellulare",
"form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli", "form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
"form.prefs.label.custom_css": "CSS personalizzati", "form.prefs.label.custom_css": "CSS personalizzati",
"form.prefs.label.entry_order": "Colonna di ordinamento delle voci",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "スタンドアロン", "form.prefs.select.standalone": "スタンドアロン",
"form.prefs.select.minimal_ui": "最小限", "form.prefs.select.minimal_ui": "最小限",
"form.prefs.select.browser": "ブラウザ", "form.prefs.select.browser": "ブラウザ",
"form.prefs.select.publish_time": "エントリー公開時間",
"form.prefs.select.created_time": "エントリー作成時間",
"form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする", "form.prefs.label.keyboard_shortcuts": "キーボード・ショートカットを有効にする",
"form.prefs.label.entry_swipe": "モバイルのエントリでスワイプジェスチャーを有効にする", "form.prefs.label.entry_swipe": "モバイルのエントリでスワイプジェスチャーを有効にする",
"form.prefs.label.show_reading_time": "記事の推定読書時間を表示する", "form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
"form.prefs.label.custom_css": "カスタムCSS", "form.prefs.label.custom_css": "カスタムCSS",
"form.prefs.label.entry_order": "エントリーソートカラム",
"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 を有効にする",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Standalone", "form.prefs.select.standalone": "Standalone",
"form.prefs.select.minimal_ui": "Minimaal", "form.prefs.select.minimal_ui": "Minimaal",
"form.prefs.select.browser": "Browser", "form.prefs.select.browser": "Browser",
"form.prefs.select.publish_time": "Tijd van binnenkomst",
"form.prefs.select.created_time": "Tijdstip van binnenkomst",
"form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in", "form.prefs.label.keyboard_shortcuts": "Schakel sneltoetsen in",
"form.prefs.label.entry_swipe": "Schakel veegbewegingen in voor items op mobiel", "form.prefs.label.entry_swipe": "Schakel veegbewegingen in voor items op mobiel",
"form.prefs.label.show_reading_time": "Toon geschatte leestijd voor artikelen", "form.prefs.label.show_reading_time": "Toon geschatte leestijd voor artikelen",
"form.prefs.label.custom_css": "Aangepaste CSS", "form.prefs.label.custom_css": "Aangepaste CSS",
"form.prefs.label.entry_order": "Ingang Sorteerkolom",
"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",

View file

@ -300,7 +300,10 @@
"form.prefs.select.standalone": "Samodzielny", "form.prefs.select.standalone": "Samodzielny",
"form.prefs.select.minimal_ui": "Minimalny", "form.prefs.select.minimal_ui": "Minimalny",
"form.prefs.select.browser": "Przeglądarka", "form.prefs.select.browser": "Przeglądarka",
"form.prefs.select.publish_time": "Czas publikacji wpisu",
"form.prefs.select.created_time": "Czas utworzenia wpisu",
"form.prefs.label.custom_css": "Niestandardowy CSS", "form.prefs.label.custom_css": "Niestandardowy CSS",
"form.prefs.label.entry_order": "Kolumna sortowania wpisów",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Autônomo", "form.prefs.select.standalone": "Autônomo",
"form.prefs.select.minimal_ui": "Mínimo", "form.prefs.select.minimal_ui": "Mínimo",
"form.prefs.select.browser": "Navegador", "form.prefs.select.browser": "Navegador",
"form.prefs.select.publish_time": "Entrada hora de publicação",
"form.prefs.select.created_time": "Entrada tempo criado",
"form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado", "form.prefs.label.keyboard_shortcuts": "Habilitar atalhos do teclado",
"form.prefs.label.entry_swipe": "Ativar gesto de deslizar nas entradas no celular", "form.prefs.label.entry_swipe": "Ativar gesto de deslizar nas entradas no celular",
"form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos", "form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
"form.prefs.label.custom_css": "CSS customizado", "form.prefs.label.custom_css": "CSS customizado",
"form.prefs.label.entry_order": "Coluna de Ordenação de Entrada",
"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",

View file

@ -297,10 +297,13 @@
"form.prefs.select.standalone": "Автономный", "form.prefs.select.standalone": "Автономный",
"form.prefs.select.minimal_ui": "Минимальный", "form.prefs.select.minimal_ui": "Минимальный",
"form.prefs.select.browser": "Браузер", "form.prefs.select.browser": "Браузер",
"form.prefs.select.publish_time": "Время публикации заявки",
"form.prefs.select.created_time": "Время создания записи",
"form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш", "form.prefs.label.keyboard_shortcuts": "Включить сочетания клавиш",
"form.prefs.label.entry_swipe": "Включить жест смахивания для записей на мобильном устройстве", "form.prefs.label.entry_swipe": "Включить жест смахивания для записей на мобильном устройстве",
"form.prefs.label.show_reading_time": "Показать примерное время чтения статей", "form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
"form.prefs.label.custom_css": "Пользовательские CSS", "form.prefs.label.custom_css": "Пользовательские CSS",
"form.prefs.label.entry_order": "Колонка сортировки ввода",
"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",

View file

@ -295,10 +295,13 @@
"form.prefs.select.standalone": "Bağımsız", "form.prefs.select.standalone": "Bağımsız",
"form.prefs.select.minimal_ui": "Minimal", "form.prefs.select.minimal_ui": "Minimal",
"form.prefs.select.browser": "Tarayıcı", "form.prefs.select.browser": "Tarayıcı",
"form.prefs.select.publish_time": "Giriş yayınlanma zamanı",
"form.prefs.select.created_time": "Girişin oluşturulma zamanı",
"form.prefs.label.keyboard_shortcuts": "Klavye kısayollarını etkinleştir", "form.prefs.label.keyboard_shortcuts": "Klavye kısayollarını etkinleştir",
"form.prefs.label.entry_swipe": "Mobil cihazlarda iletiler için kaydırma hareketlerini etkinleştir", "form.prefs.label.entry_swipe": "Mobil cihazlarda iletiler için kaydırma hareketlerini etkinleştir",
"form.prefs.label.show_reading_time": "Makaleler için tahmini okuma süresini göster", "form.prefs.label.show_reading_time": "Makaleler için tahmini okuma süresini göster",
"form.prefs.label.custom_css": "Özel CSS", "form.prefs.label.custom_css": "Özel CSS",
"form.prefs.label.entry_order": "Giriş Sıralama Sütunu",
"form.import.label.file": "OPML dosyası", "form.import.label.file": "OPML dosyası",
"form.import.label.url": "URL", "form.import.label.url": "URL",
"form.integration.fever_activate": "Fever API'yi Etkinleştir", "form.integration.fever_activate": "Fever API'yi Etkinleştir",

View file

@ -293,10 +293,13 @@
"form.prefs.select.standalone": "独立", "form.prefs.select.standalone": "独立",
"form.prefs.select.minimal_ui": "最小", "form.prefs.select.minimal_ui": "最小",
"form.prefs.select.browser": "浏览器", "form.prefs.select.browser": "浏览器",
"form.prefs.select.publish_time": "参赛作品公布时间",
"form.prefs.select.created_time": "条目创建时间",
"form.prefs.label.keyboard_shortcuts": "启用键盘快捷键", "form.prefs.label.keyboard_shortcuts": "启用键盘快捷键",
"form.prefs.label.entry_swipe": "在移动设备上启用滑动手势", "form.prefs.label.entry_swipe": "在移动设备上启用滑动手势",
"form.prefs.label.show_reading_time": "显示文章的预计阅读时间", "form.prefs.label.show_reading_time": "显示文章的预计阅读时间",
"form.prefs.label.custom_css": "自定义 CSS", "form.prefs.label.custom_css": "自定义 CSS",
"form.prefs.label.entry_order": "条目排序栏",
"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",

View file

@ -20,6 +20,7 @@ type User struct {
Language string `json:"language"` Language string `json:"language"`
Timezone string `json:"timezone"` Timezone string `json:"timezone"`
EntryDirection string `json:"entry_sorting_direction"` EntryDirection string `json:"entry_sorting_direction"`
EntryOrder string `json:"entry_sorting_order"`
Stylesheet string `json:"stylesheet"` Stylesheet string `json:"stylesheet"`
GoogleID string `json:"google_id"` GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"` OpenIDConnectID string `json:"openid_connect_id"`
@ -48,6 +49,7 @@ type UserModificationRequest struct {
Language *string `json:"language"` Language *string `json:"language"`
Timezone *string `json:"timezone"` Timezone *string `json:"timezone"`
EntryDirection *string `json:"entry_sorting_direction"` EntryDirection *string `json:"entry_sorting_direction"`
EntryOrder *string `json:"entry_sorting_order"`
Stylesheet *string `json:"stylesheet"` Stylesheet *string `json:"stylesheet"`
GoogleID *string `json:"google_id"` GoogleID *string `json:"google_id"`
OpenIDConnectID *string `json:"openid_connect_id"` OpenIDConnectID *string `json:"openid_connect_id"`
@ -89,6 +91,10 @@ func (u *UserModificationRequest) Patch(user *User) {
user.EntryDirection = *u.EntryDirection user.EntryDirection = *u.EntryDirection
} }
if u.EntryOrder != nil {
user.EntryOrder = *u.EntryOrder
}
if u.Stylesheet != nil { if u.Stylesheet != nil {
user.Stylesheet = *u.Stylesheet user.Stylesheet = *u.Stylesheet
} }

View file

@ -84,7 +84,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
stylesheet, stylesheet,
google_id, google_id,
openid_connect_id, openid_connect_id,
display_mode display_mode,
entry_order
` `
tx, err := s.db.Begin() tx, err := s.db.Begin()
@ -116,6 +117,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
&user.GoogleID, &user.GoogleID,
&user.OpenIDConnectID, &user.OpenIDConnectID,
&user.DisplayMode, &user.DisplayMode,
&user.EntryOrder,
) )
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@ -165,9 +167,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
stylesheet=$12, stylesheet=$12,
google_id=$13, google_id=$13,
openid_connect_id=$14, openid_connect_id=$14,
display_mode=$15 display_mode=$15,
entry_order=$16
WHERE WHERE
id=$16 id=$17
` `
_, err = s.db.Exec( _, err = s.db.Exec(
@ -187,6 +190,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
user.GoogleID, user.GoogleID,
user.OpenIDConnectID, user.OpenIDConnectID,
user.DisplayMode, user.DisplayMode,
user.EntryOrder,
user.ID, user.ID,
) )
if err != nil { if err != nil {
@ -208,9 +212,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
stylesheet=$11, stylesheet=$11,
google_id=$12, google_id=$12,
openid_connect_id=$13, openid_connect_id=$13,
display_mode=$14 display_mode=$14,
entry_order=$15
WHERE WHERE
id=$15 id=$16
` `
_, err := s.db.Exec( _, err := s.db.Exec(
@ -229,6 +234,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
user.GoogleID, user.GoogleID,
user.OpenIDConnectID, user.OpenIDConnectID,
user.DisplayMode, user.DisplayMode,
user.EntryOrder,
user.ID, user.ID,
) )
@ -269,7 +275,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
stylesheet, stylesheet,
google_id, google_id,
openid_connect_id, openid_connect_id,
display_mode display_mode,
entry_order
FROM FROM
users users
WHERE WHERE
@ -297,7 +304,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
stylesheet, stylesheet,
google_id, google_id,
openid_connect_id, openid_connect_id,
display_mode display_mode,
entry_order
FROM FROM
users users
WHERE WHERE
@ -325,7 +333,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
stylesheet, stylesheet,
google_id, google_id,
openid_connect_id, openid_connect_id,
display_mode display_mode,
entry_order
FROM FROM
users users
WHERE WHERE
@ -360,7 +369,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
u.stylesheet, u.stylesheet,
u.google_id, u.google_id,
u.openid_connect_id, u.openid_connect_id,
u.display_mode u.display_mode,
u.entry_order
FROM FROM
users u users u
LEFT JOIN LEFT JOIN
@ -390,6 +400,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
&user.GoogleID, &user.GoogleID,
&user.OpenIDConnectID, &user.OpenIDConnectID,
&user.DisplayMode, &user.DisplayMode,
&user.EntryOrder,
) )
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -480,7 +491,8 @@ func (s *Storage) Users() (model.Users, error) {
stylesheet, stylesheet,
google_id, google_id,
openid_connect_id, openid_connect_id,
display_mode display_mode,
entry_order
FROM FROM
users users
ORDER BY username ASC ORDER BY username ASC
@ -511,6 +523,7 @@ func (s *Storage) Users() (model.Users, error) {
&user.GoogleID, &user.GoogleID,
&user.OpenIDConnectID, &user.OpenIDConnectID,
&user.DisplayMode, &user.DisplayMode,
&user.EntryOrder,
) )
if err != nil { if err != nil {

View file

@ -57,6 +57,12 @@
<option value="desc" {{ if eq "desc" $.form.EntryDirection }}selected="selected"{{ end }}>{{ t "form.prefs.select.recent_first" }}</option> <option value="desc" {{ if eq "desc" $.form.EntryDirection }}selected="selected"{{ end }}>{{ t "form.prefs.select.recent_first" }}</option>
</select> </select>
<label for="form-entry-order">{{ t "form.prefs.label.entry_order" }}</label>
<select id="form-entry-order" name="entry_order">
<option value="published_at" {{ if eq "published_at" $.form.EntryOrder }}selected="selected"{{ end }}>{{ t "form.prefs.select.publish_time" }}</option>
<option value="created_at" {{ if eq "created_at" $.form.EntryOrder }}selected="selected"{{ end }}>{{ t "form.prefs.select.created_time" }}</option>
</select>
<label for="form-entries-per-page">{{ t "form.prefs.label.entries_per_page" }}</label> <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"> <input type="number" name="entries_per_page" id="form-entries-per-page" value="{{ .form.EntriesPerPage }}" min="1">

View file

@ -401,6 +401,21 @@ func TestUpdateUserEntryDirectionWithInvalidValue(t *testing.T) {
} }
} }
func TestUpdateUserEntryOrderWithInvalidValue(t *testing.T) {
username := getRandomUsername()
client := miniflux.New(testBaseURL, testAdminUsername, testAdminPassword)
user, err := client.CreateUser(username, testStandardPassword, false)
if err != nil {
t.Fatal(err)
}
entryOrder := "invalid"
_, err = client.UpdateUser(user.ID, &miniflux.UserModificationRequest{EntryOrder: &entryOrder})
if err == nil {
t.Fatal(`Updating a user EntryOrder with an invalid value should raise an error`)
}
}
func TestUpdateUserPasswordWithInvalidValue(t *testing.T) { func TestUpdateUserPasswordWithInvalidValue(t *testing.T) {
username := getRandomUsername() username := getRandomUsername()
client := miniflux.New(testBaseURL, testAdminUsername, testAdminPassword) client := miniflux.New(testBaseURL, testAdminUsername, testAdminPassword)

View file

@ -26,7 +26,7 @@ func (h *handler) showStarredPage(w http.ResponseWriter, r *http.Request) {
builder := h.store.NewEntryQueryBuilder(user.ID) builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithoutStatus(model.EntryStatusRemoved) builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithStarred() builder.WithStarred()
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithOffset(offset) builder.WithOffset(offset)
builder.WithLimit(user.EntriesPerPage) builder.WithLimit(user.EntriesPerPage)

View file

@ -37,7 +37,7 @@ func (h *handler) showCategoryEntriesPage(w http.ResponseWriter, r *http.Request
offset := request.QueryIntParam(r, "offset", 0) offset := request.QueryIntParam(r, "offset", 0)
builder := h.store.NewEntryQueryBuilder(user.ID) builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithCategoryID(category.ID) builder.WithCategoryID(category.ID)
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithStatus(model.EntryStatusUnread) builder.WithStatus(model.EntryStatusUnread)
builder.WithOffset(offset) builder.WithOffset(offset)

View file

@ -37,7 +37,7 @@ func (h *handler) showCategoryEntriesAllPage(w http.ResponseWriter, r *http.Requ
offset := request.QueryIntParam(r, "offset", 0) offset := request.QueryIntParam(r, "offset", 0)
builder := h.store.NewEntryQueryBuilder(user.ID) builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithCategoryID(category.ID) builder.WithCategoryID(category.ID)
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithoutStatus(model.EntryStatusRemoved) builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithOffset(offset) builder.WithOffset(offset)

View file

@ -38,7 +38,7 @@ func (h *handler) showFeedEntriesPage(w http.ResponseWriter, r *http.Request) {
builder := h.store.NewEntryQueryBuilder(user.ID) builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithFeedID(feed.ID) builder.WithFeedID(feed.ID)
builder.WithStatus(model.EntryStatusUnread) builder.WithStatus(model.EntryStatusUnread)
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithOffset(offset) builder.WithOffset(offset)
builder.WithLimit(user.EntriesPerPage) builder.WithLimit(user.EntriesPerPage)

View file

@ -38,7 +38,7 @@ func (h *handler) showFeedEntriesAllPage(w http.ResponseWriter, r *http.Request)
builder := h.store.NewEntryQueryBuilder(user.ID) builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithFeedID(feed.ID) builder.WithFeedID(feed.ID)
builder.WithoutStatus(model.EntryStatusRemoved) builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithOffset(offset) builder.WithOffset(offset)
builder.WithLimit(user.EntriesPerPage) builder.WithLimit(user.EntriesPerPage)

View file

@ -21,6 +21,7 @@ type SettingsForm struct {
Language string Language string
Timezone string Timezone string
EntryDirection string EntryDirection string
EntryOrder string
EntriesPerPage int EntriesPerPage int
KeyboardShortcuts bool KeyboardShortcuts bool
ShowReadingTime bool ShowReadingTime bool
@ -36,6 +37,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
user.Language = s.Language user.Language = s.Language
user.Timezone = s.Timezone user.Timezone = s.Timezone
user.EntryDirection = s.EntryDirection user.EntryDirection = s.EntryDirection
user.EntryOrder = s.EntryOrder
user.EntriesPerPage = s.EntriesPerPage user.EntriesPerPage = s.EntriesPerPage
user.KeyboardShortcuts = s.KeyboardShortcuts user.KeyboardShortcuts = s.KeyboardShortcuts
user.ShowReadingTime = s.ShowReadingTime user.ShowReadingTime = s.ShowReadingTime
@ -84,6 +86,7 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
Language: r.FormValue("language"), Language: r.FormValue("language"),
Timezone: r.FormValue("timezone"), Timezone: r.FormValue("timezone"),
EntryDirection: r.FormValue("entry_direction"), EntryDirection: r.FormValue("entry_direction"),
EntryOrder: r.FormValue("entry_order"),
EntriesPerPage: int(entriesPerPage), EntriesPerPage: int(entriesPerPage),
KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1", KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
ShowReadingTime: r.FormValue("show_reading_time") == "1", ShowReadingTime: r.FormValue("show_reading_time") == "1",

View file

@ -32,6 +32,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
Language: user.Language, Language: user.Language,
Timezone: user.Timezone, Timezone: user.Timezone,
EntryDirection: user.EntryDirection, EntryDirection: user.EntryDirection,
EntryOrder: user.EntryOrder,
EntriesPerPage: user.EntriesPerPage, EntriesPerPage: user.EntriesPerPage,
KeyboardShortcuts: user.KeyboardShortcuts, KeyboardShortcuts: user.KeyboardShortcuts,
ShowReadingTime: user.ShowReadingTime, ShowReadingTime: user.ShowReadingTime,

View file

@ -9,7 +9,6 @@ import (
"miniflux.app/http/request" "miniflux.app/http/request"
"miniflux.app/http/response/html" "miniflux.app/http/response/html"
"miniflux.app/model"
"miniflux.app/ui/session" "miniflux.app/ui/session"
"miniflux.app/ui/view" "miniflux.app/ui/view"
) )
@ -23,7 +22,7 @@ func (h *handler) sharedEntries(w http.ResponseWriter, r *http.Request) {
builder := h.store.NewEntryQueryBuilder(user.ID) builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithShareCodeNotEmpty() builder.WithShareCodeNotEmpty()
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
entries, err := builder.GetEntries() entries, err := builder.GetEntries()

View file

@ -49,7 +49,7 @@ func (h *handler) showUnreadPage(w http.ResponseWriter, r *http.Request) {
m = timing.NewMetric("sql_fetch_unread_entries").Start() m = timing.NewMetric("sql_fetch_unread_entries").Start()
builder = h.store.NewEntryQueryBuilder(user.ID) builder = h.store.NewEntryQueryBuilder(user.ID)
builder.WithStatus(model.EntryStatusUnread) builder.WithStatus(model.EntryStatusUnread)
builder.WithOrder(model.DefaultSortingOrder) builder.WithOrder(user.EntryOrder)
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithOffset(offset) builder.WithOffset(offset)
builder.WithLimit(user.EntriesPerPage) builder.WithLimit(user.EntriesPerPage)