Add user setting for marking entry as read on view

This commit is contained in:
xl 2023-03-17 21:56:17 +08:00 committed by Frédéric Guillot
parent 6046a74a64
commit 356d32c6fe
35 changed files with 84 additions and 29 deletions

View file

@ -40,6 +40,7 @@ type User struct {
CJKReadingSpeed int `json:"cjk_reading_speed"`
DefaultHomePage string `json:"default_home_page"`
CategoriesSortingOrder string `json:"categories_sorting_order"`
MarkReadOnView bool `json:"mark_read_on_view"`
}
func (u User) String() string {
@ -78,6 +79,7 @@ type UserModificationRequest struct {
CJKReadingSpeed *int `json:"cjk_reading_speed"`
DefaultHomePage *string `json:"default_home_page"`
CategoriesSortingOrder *string `json:"categories_sorting_order"`
MarkReadOnView *bool `json:"mark_read_on_view"`
}
// Users represents a list of users.

View file

@ -702,4 +702,9 @@ var migrations = []func(tx *sql.Tx) error{
return nil
},
func(tx *sql.Tx) (err error) {
sql := `ALTER TABLE users ADD COLUMN mark_read_on_view boolean default 't'`
_, err = tx.Exec(sql)
return err
},
}

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Eintrag Sortierspalte",
"form.prefs.label.default_home_page": "Standard Startseite",
"form.prefs.label.categories_sorting_order": "Kategorien sortieren",
"form.prefs.label.mark_read_on_view": "Einträge automatisch als gelesen markieren, wenn sie angezeigt werden",
"form.import.label.file": "OPML Datei",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Fever API aktivieren",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Στήλη ταξινόμησης εισόδου",
"form.prefs.label.default_home_page": "Προεπιλεγμένη αρχική σελίδα",
"form.prefs.label.categories_sorting_order": "Ταξινόμηση κατηγοριών",
"form.prefs.label.mark_read_on_view": "Αυτόματη επισήμανση καταχωρήσεων ως αναγνωσμένων κατά την προβολή",
"form.import.label.file": "Αρχείο OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Ενεργοποιήστε το Fever API",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Entry sorting column",
"form.prefs.label.default_home_page": "Default home page",
"form.prefs.label.categories_sorting_order": "Categories sorting",
"form.prefs.label.mark_read_on_view": "Automatically mark entries as read when viewed",
"form.import.label.file": "OPML file",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Activate Fever API",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Columna de clasificación de artículos",
"form.prefs.label.default_home_page": "Página de inicio por defecto",
"form.prefs.label.categories_sorting_order": "Clasificación por categorías",
"form.prefs.label.mark_read_on_view": "Marcar automáticamente las entradas como leídas cuando se vean",
"form.import.label.file": "Archivo OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Activar API de Fever",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Lajittele sarakkeen mukaan",
"form.prefs.label.default_home_page": "Oletusarvoinen etusivu",
"form.prefs.label.categories_sorting_order": "Kategorioiden lajittelu",
"form.prefs.label.mark_read_on_view": "Merkitse kohdat automaattisesti luetuiksi, kun niitä tarkastellaan",
"form.import.label.file": "OPML-tiedosto",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Ota Fever API käyttöön",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Colonne de tri des entrées",
"form.prefs.label.default_home_page": "Page d'accueil par défaut",
"form.prefs.label.categories_sorting_order": "Colonne de tri des catégories",
"form.prefs.label.mark_read_on_view": "Marquer automatiquement les entrées comme lues lorsqu'elles sont consultées",
"form.import.label.file": "Fichier OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Activer l'API de Fever",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "प्रवेश छँटाई कॉलम",
"form.prefs.label.default_home_page": "डिफ़ॉल्ट होमपेज़",
"form.prefs.label.categories_sorting_order": "श्रेणियाँ छँटाई",
"form.prefs.label.mark_read_on_view": "देखे जाने पर स्वचालित रूप से प्रविष्टियों को पढ़ने के रूप में चिह्नित करें",
"form.import.label.file": "ओपीएमएल फ़ाइल",
"form.import.label.url": "यूआरएल",
"form.integration.fever_activate": "फीवर एपीआई सक्रिय करें",

View file

@ -319,6 +319,7 @@
"form.prefs.label.entry_order": "Pengurutan Kolom Entri",
"form.prefs.label.default_home_page": "Beranda Baku",
"form.prefs.label.categories_sorting_order": "Pengurutan Kategori",
"form.prefs.label.mark_read_on_view": "Secara otomatis menandai entri sebagai telah dibaca saat dilihat",
"form.import.label.file": "Berkas OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Aktifkan API Fever",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Colonna di ordinamento delle voci",
"form.prefs.label.default_home_page": "Pagina iniziale predefinita",
"form.prefs.label.categories_sorting_order": "Ordinamento delle categorie",
"form.prefs.label.mark_read_on_view": "Contrassegna automaticamente le voci come lette quando visualizzate",
"form.import.label.file": "File OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Abilita l'API di Fever",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "記事の表示順の基準",
"form.prefs.label.default_home_page": "デフォルトのトップページ",
"form.prefs.label.categories_sorting_order": "カテゴリの表示順",
"form.prefs.label.mark_read_on_view": "表示時にエントリを自動的に既読としてマークします",
"form.import.label.file": "OPML ファイル",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Fever API を有効にする",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Ingang Sorteerkolom",
"form.prefs.label.default_home_page": "Standaard startpagina",
"form.prefs.label.categories_sorting_order": "Categorieën sorteren",
"form.prefs.label.mark_read_on_view": "Items automatisch markeren als gelezen wanneer ze worden bekeken",
"form.import.label.file": "OPML-bestand",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Activeer Fever API",

View file

@ -324,6 +324,7 @@
"form.prefs.label.entry_order": "Kolumna sortowania wpisów",
"form.prefs.label.default_home_page": "Domyślna strona główna",
"form.prefs.label.categories_sorting_order": "Sortowanie kategorii",
"form.prefs.label.mark_read_on_view": "Automatycznie oznaczaj wpisy jako przeczytane podczas przeglądania",
"form.import.label.file": "Plik OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Aktywuj Fever API",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Coluna de Ordenação de Entrada",
"form.prefs.label.default_home_page": "Página inicial predefinida",
"form.prefs.label.categories_sorting_order": "Classificação das categorias",
"form.prefs.label.mark_read_on_view": "Marcar automaticamente as entradas como lidas quando visualizadas",
"form.import.label.file": "Arquivo OPML",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Ativar API do Fever",

View file

@ -324,6 +324,7 @@
"form.prefs.label.entry_order": "Столбец сортировки статей",
"form.prefs.label.default_home_page": "Домашняя страница по умолчанию",
"form.prefs.label.categories_sorting_order": "Сортировка категорий",
"form.prefs.label.mark_read_on_view": "Автоматически отмечать записи как прочитанные при просмотре",
"form.import.label.file": "OPML файл",
"form.import.label.url": "Ссылка",
"form.integration.fever_activate": "Активировать Fever API",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "Giriş Sıralama Sütunu",
"form.prefs.label.default_home_page": "Varsayılan ana sayfa",
"form.prefs.label.categories_sorting_order": "Kategoriler sıralama",
"form.prefs.label.mark_read_on_view": "Girişleri görüntülendiğinde otomatik olarak okundu olarak işaretle",
"form.import.label.file": "OPML dosyası",
"form.import.label.url": "URL",
"form.integration.fever_activate": "Fever API'yi Etkinleştir",

View file

@ -321,6 +321,7 @@
"form.prefs.label.entry_order": "Стовпець сортування записів",
"form.prefs.label.default_home_page": "Домашня сторінка за умовчанням",
"form.prefs.label.categories_sorting_order": "Сортування за категоріями",
"form.prefs.label.mark_read_on_view": "Автоматично позначати записи як прочитані під час перегляду",
"form.import.label.file": "Файл OPML",
"form.import.label.url": "URL-адреса",
"form.integration.fever_activate": "Увімкнути API Fever",

View file

@ -320,6 +320,7 @@
"form.prefs.label.entry_order": "文章排序依据",
"form.prefs.label.default_home_page": "默认主页",
"form.prefs.label.categories_sorting_order": "分类排序",
"form.prefs.label.mark_read_on_view": "查看时自动将条目标记为已读",
"form.import.label.file": "OPML 文件",
"form.import.label.url": "URL",
"form.integration.fever_activate": "启用 Fever API",

View file

@ -322,6 +322,7 @@
"form.prefs.label.entry_order": "文章排序依據",
"form.prefs.label.default_home_page": "默認主頁",
"form.prefs.label.categories_sorting_order": "分類排序",
"form.prefs.label.mark_read_on_view": "查看時自動將條目標記為已讀",
"form.import.label.file": "OPML 檔案",
"form.import.label.url": "URL",
"form.integration.fever_activate": "啟用 Fever API",

View file

@ -34,6 +34,7 @@ type User struct {
CJKReadingSpeed int `json:"cjk_reading_speed"`
DefaultHomePage string `json:"default_home_page"`
CategoriesSortingOrder string `json:"categories_sorting_order"`
MarkReadOnView bool `json:"mark_read_on_view"`
}
// UserCreationRequest represents the request to create a user.
@ -68,6 +69,7 @@ type UserModificationRequest struct {
CJKReadingSpeed *int `json:"cjk_reading_speed"`
DefaultHomePage *string `json:"default_home_page"`
CategoriesSortingOrder *string `json:"categories_sorting_order"`
MarkReadOnView *bool `json:"mark_read_on_view"`
}
// Patch updates the User object with the modification request.
@ -155,6 +157,10 @@ func (u *UserModificationRequest) Patch(user *User) {
if u.CategoriesSortingOrder != nil {
user.CategoriesSortingOrder = *u.CategoriesSortingOrder
}
if u.MarkReadOnView != nil {
user.MarkReadOnView = *u.MarkReadOnView
}
}
// UseTimezone converts last login date to the given timezone.

View file

@ -89,7 +89,8 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
default_reading_speed,
cjk_reading_speed,
default_home_page,
categories_sorting_order
categories_sorting_order,
mark_read_on_view
`
tx, err := s.db.Begin()
@ -127,6 +128,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
&user.CJKReadingSpeed,
&user.DefaultHomePage,
&user.CategoriesSortingOrder,
&user.MarkReadOnView,
)
if err != nil {
tx.Rollback()
@ -182,9 +184,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
default_reading_speed=$18,
cjk_reading_speed=$19,
default_home_page=$20,
categories_sorting_order=$21
categories_sorting_order=$21,
mark_read_on_view=$22
WHERE
id=$22
id=$23
`
_, err = s.db.Exec(
@ -210,6 +213,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
user.CJKReadingSpeed,
user.DefaultHomePage,
user.CategoriesSortingOrder,
user.MarkReadOnView,
user.ID,
)
if err != nil {
@ -237,9 +241,10 @@ func (s *Storage) UpdateUser(user *model.User) error {
default_reading_speed=$17,
cjk_reading_speed=$18,
default_home_page=$19,
categories_sorting_order=$20
categories_sorting_order=$20,
mark_read_on_view=$21
WHERE
id=$21
id=$22
`
_, err := s.db.Exec(
@ -264,6 +269,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
user.CJKReadingSpeed,
user.DefaultHomePage,
user.CategoriesSortingOrder,
user.MarkReadOnView,
user.ID,
)
@ -310,7 +316,8 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
default_reading_speed,
cjk_reading_speed,
default_home_page,
categories_sorting_order
categories_sorting_order,
mark_read_on_view
FROM
users
WHERE
@ -344,7 +351,8 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
default_reading_speed,
cjk_reading_speed,
default_home_page,
categories_sorting_order
categories_sorting_order,
mark_read_on_view
FROM
users
WHERE
@ -378,7 +386,8 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
default_reading_speed,
cjk_reading_speed,
default_home_page,
categories_sorting_order
categories_sorting_order,
mark_read_on_view
FROM
users
WHERE
@ -419,7 +428,8 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
u.default_reading_speed,
u.cjk_reading_speed,
u.default_home_page,
u.categories_sorting_order
u.categories_sorting_order,
u.mark_read_on_view
FROM
users u
LEFT JOIN
@ -455,6 +465,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
&user.CJKReadingSpeed,
&user.DefaultHomePage,
&user.CategoriesSortingOrder,
&user.MarkReadOnView,
)
if err == sql.ErrNoRows {
@ -551,7 +562,8 @@ func (s *Storage) Users() (model.Users, error) {
default_reading_speed,
cjk_reading_speed,
default_home_page,
categories_sorting_order
categories_sorting_order,
mark_read_on_view
FROM
users
ORDER BY username ASC
@ -588,6 +600,7 @@ func (s *Storage) Users() (model.Users, error) {
&user.CJKReadingSpeed,
&user.DefaultHomePage,
&user.CategoriesSortingOrder,
&user.MarkReadOnView,
)
if err != nil {

View file

@ -20,7 +20,7 @@
<div class="item-meta">
<ul class="item-meta-info">
<li class="item-meta-info-site-url" dir="auto">
<a href="{{ .SiteURL | safeURL }}" title="{{ .SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ domain .SiteURL }}</a>
<a href="{{ .SiteURL | safeURL }}" title="{{ .SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="{{ $.user.MarkReadOnView }}">{{ domain .SiteURL }}</a>
</li>
<li class="item-meta-info-checked-at">
{{ t "page.feeds.last_check" }} <time datetime="{{ isodate .CheckedAt }}" title="{{ isodate .CheckedAt }}">{{ elapsed $.user.Timezone .CheckedAt }}</time>

View file

@ -68,7 +68,7 @@
target="_blank"
rel="noopener noreferrer"
referrerpolicy="no-referrer"
data-original-link="true">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
data-original-link="{{ .user.MarkReadOnView }}">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
</li>
{{ if .entry.CommentsURL }}
<li class="item-meta-icons-comments">

View file

@ -72,7 +72,7 @@
target="_blank"
rel="noopener noreferrer"
referrerpolicy="no-referrer"
data-original-link="true">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
data-original-link="{{ .user.MarkReadOnView }}">{{ icon "external-link" }}<span class="icon-label">{{ t "entry.external_link.label" }}</span></a>
</li>
<li>
<a href="#"

View file

@ -3,7 +3,7 @@
{{ define "content"}}
<section class="page-header">
<h1 dir="auto">
<a href="{{ .feed.SiteURL | safeURL }}" title="{{ .feed.SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="true">{{ .feed.Title }}</a>
<a href="{{ .feed.SiteURL | safeURL }}" title="{{ .feed.SiteURL }}" target="_blank" rel="noopener noreferrer" referrerpolicy="no-referrer" data-original-link="{{ .user.MarkReadOnView }}">{{ .feed.Title }}</a>
({{ .total }})
</h1>
<ul>

View file

@ -99,6 +99,8 @@
<label><input type="checkbox" name="show_reading_time" value="1" {{ if .form.ShowReadingTime }}checked{{ end }}> {{ t "form.prefs.label.show_reading_time" }}</label>
<label><input type="checkbox" name="mark_read_on_view" value="1" {{ if .form.MarkReadOnView }}checked{{ end }}> {{ t "form.prefs.label.mark_read_on_view" }}</label>
<label for="form-cjk-reading-speed">{{ t "form.prefs.label.cjk_reading_speed" }}</label>
<input type="number" name="cjk_reading_speed" id="form-cjk-reading-speed" value="{{ .form.CJKReadingSpeed }}" min="1">

View file

@ -38,7 +38,7 @@ func (h *handler) showStarredEntryPage(w http.ResponseWriter, r *http.Request) {
return
}
if entry.Status == model.EntryStatusUnread {
if user.MarkReadOnView && entry.Status == model.EntryStatusUnread {
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)

View file

@ -41,7 +41,7 @@ func (h *handler) showCategoryEntryPage(w http.ResponseWriter, r *http.Request)
return
}
if entry.Status == model.EntryStatusUnread {
if user.MarkReadOnView && entry.Status == model.EntryStatusUnread {
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)

View file

@ -41,7 +41,7 @@ func (h *handler) showFeedEntryPage(w http.ResponseWriter, r *http.Request) {
return
}
if entry.Status == model.EntryStatusUnread {
if user.MarkReadOnView && entry.Status == model.EntryStatusUnread {
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)

View file

@ -40,7 +40,7 @@ func (h *handler) showSearchEntryPage(w http.ResponseWriter, r *http.Request) {
return
}
if entry.Status == model.EntryStatusUnread {
if user.MarkReadOnView && entry.Status == model.EntryStatusUnread {
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)

View file

@ -66,13 +66,18 @@ func (h *handler) showUnreadEntryPage(w http.ResponseWriter, r *http.Request) {
prevEntryRoute = route.Path(h.router, "unreadEntry", "entryID", prevEntry.ID)
}
// Always mark the entry as read after fetching the pagination.
if user.MarkReadOnView {
entry.Status = model.EntryStatusRead
}
// Restore entry read status if needed after fetching the pagination.
if entry.Status == model.EntryStatusRead {
err = h.store.SetEntriesStatus(user.ID, []int64{entry.ID}, model.EntryStatusRead)
if err != nil {
html.ServerError(w, r, err)
return
}
entry.Status = model.EntryStatusRead
}
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)

View file

@ -32,6 +32,7 @@ type SettingsForm struct {
CJKReadingSpeed int
DefaultHomePage string
CategoriesSortingOrder string
MarkReadOnView bool
}
// Merge updates the fields of the given user.
@ -53,6 +54,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
user.DefaultReadingSpeed = s.DefaultReadingSpeed
user.DefaultHomePage = s.DefaultHomePage
user.CategoriesSortingOrder = s.CategoriesSortingOrder
user.MarkReadOnView = s.MarkReadOnView
if s.Password != "" {
user.Password = s.Password
@ -119,5 +121,6 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
CJKReadingSpeed: int(cjkReadingSpeed),
DefaultHomePage: r.FormValue("default_home_page"),
CategoriesSortingOrder: r.FormValue("categories_sorting_order"),
MarkReadOnView: r.FormValue("mark_read_on_view") == "1",
}
}

View file

@ -43,6 +43,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
CJKReadingSpeed: user.CJKReadingSpeed,
DefaultHomePage: user.DefaultHomePage,
CategoriesSortingOrder: user.CategoriesSortingOrder,
MarkReadOnView: user.MarkReadOnView,
}
timezones, err := h.store.Timezones()

View file

@ -69,10 +69,10 @@ document.addEventListener("DOMContentLoaded", function () {
request.execute();
}));
onClick("a[data-original-link]", (event) => {
onClick("a[data-original-link='true']", (event) => {
handleEntryStatus("next", event.target, true);
}, true);
onAuxClick("a[data-original-link]", (event) => {
onAuxClick("a[data-original-link='true']", (event) => {
if (event.button == 1) {
handleEntryStatus("next", event.target, true);
}