1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-06-27 16:36:00 +00:00

add option to hide categories from the global unread list

This commit is contained in:
pennae 2021-06-03 02:39:47 +02:00 committed by fguillot
parent 571d7bf17c
commit 0bcfc81b1f
24 changed files with 109 additions and 30 deletions

View file

@ -534,4 +534,10 @@ 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) {
_, err = tx.Exec(`
ALTER TABLE categories ADD COLUMN hide_globally boolean not null default false
`)
return err
},
} }

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Über Proxy abrufen", "form.feed.label.fetch_via_proxy": "Über Proxy abrufen",
"form.feed.label.disabled": "Dieses Abonnement nicht aktualisieren", "form.feed.label.disabled": "Dieses Abonnement nicht aktualisieren",
"form.category.label.title": "Titel", "form.category.label.title": "Titel",
"form.category.hide_globally": "Einträge in der globalen Ungelesen-Liste ausblenden",
"form.user.label.username": "Benutzername", "form.user.label.username": "Benutzername",
"form.user.label.password": "Passwort", "form.user.label.password": "Passwort",
"form.user.label.confirmation": "Passwort Bestätigung", "form.user.label.confirmation": "Passwort Bestätigung",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Fetch via proxy", "form.feed.label.fetch_via_proxy": "Fetch via proxy",
"form.feed.label.disabled": "Do not refresh this feed", "form.feed.label.disabled": "Do not refresh this feed",
"form.category.label.title": "Title", "form.category.label.title": "Title",
"form.category.hide_globally": "Hide entries in global unread list",
"form.user.label.username": "Username", "form.user.label.username": "Username",
"form.user.label.password": "Password", "form.user.label.password": "Password",
"form.user.label.confirmation": "Password Confirmation", "form.user.label.confirmation": "Password Confirmation",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Buscar a través de proxy", "form.feed.label.fetch_via_proxy": "Buscar a través de proxy",
"form.feed.label.disabled": "No actualice este feed", "form.feed.label.disabled": "No actualice este feed",
"form.category.label.title": "Título", "form.category.label.title": "Título",
"form.category.hide_globally": "Ocultar entradas en la lista global de no leídos",
"form.user.label.username": "Nombre de usuario", "form.user.label.username": "Nombre de usuario",
"form.user.label.password": "Contraseña", "form.user.label.password": "Contraseña",
"form.user.label.confirmation": "Confirmación de contraseña", "form.user.label.confirmation": "Confirmación de contraseña",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Récupérer via proxy", "form.feed.label.fetch_via_proxy": "Récupérer via proxy",
"form.feed.label.disabled": "Ne pas actualiser ce flux", "form.feed.label.disabled": "Ne pas actualiser ce flux",
"form.category.label.title": "Titre", "form.category.label.title": "Titre",
"form.category.hide_globally": "Masquer les entrées dans la liste globale non lue",
"form.user.label.username": "Nom d'utilisateur", "form.user.label.username": "Nom d'utilisateur",
"form.user.label.password": "Mot de passe", "form.user.label.password": "Mot de passe",
"form.user.label.confirmation": "Confirmation du mot de passe", "form.user.label.confirmation": "Confirmation du mot de passe",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Recuperare tramite proxy", "form.feed.label.fetch_via_proxy": "Recuperare tramite proxy",
"form.feed.label.disabled": "Non aggiornare questo feed", "form.feed.label.disabled": "Non aggiornare questo feed",
"form.category.label.title": "Titolo", "form.category.label.title": "Titolo",
"form.category.hide_globally": "Nascondere le voci nella lista globale dei non letti",
"form.user.label.username": "Nome utente", "form.user.label.username": "Nome utente",
"form.user.label.password": "Password", "form.user.label.password": "Password",
"form.user.label.confirmation": "Conferma password", "form.user.label.confirmation": "Conferma password",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "プロキシ経由でフェッチ", "form.feed.label.fetch_via_proxy": "プロキシ経由でフェッチ",
"form.feed.label.disabled": "このフィードを更新しない", "form.feed.label.disabled": "このフィードを更新しない",
"form.category.label.title": "タイトル", "form.category.label.title": "タイトル",
"form.category.hide_globally": "グローバル未読リストのエントリーを隠す",
"form.user.label.username": "ユーザー名", "form.user.label.username": "ユーザー名",
"form.user.label.password": "パスワード", "form.user.label.password": "パスワード",
"form.user.label.confirmation": "パスワード確認", "form.user.label.confirmation": "パスワード確認",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Ophalen via proxy", "form.feed.label.fetch_via_proxy": "Ophalen via proxy",
"form.feed.label.disabled": "Vernieuw deze feed niet", "form.feed.label.disabled": "Vernieuw deze feed niet",
"form.category.label.title": "Naam", "form.category.label.title": "Naam",
"form.category.hide_globally": "Verberg items in de globale ongelezen lijst",
"form.user.label.username": "Gebruikersnaam", "form.user.label.username": "Gebruikersnaam",
"form.user.label.password": "Wachtwoord", "form.user.label.password": "Wachtwoord",
"form.user.label.confirmation": "Bevestig wachtwoord", "form.user.label.confirmation": "Bevestig wachtwoord",

View file

@ -279,6 +279,7 @@
"form.feed.label.fetch_via_proxy": "Pobierz przez proxy", "form.feed.label.fetch_via_proxy": "Pobierz przez proxy",
"form.feed.label.disabled": "Не обновлять этот канал", "form.feed.label.disabled": "Не обновлять этот канал",
"form.category.label.title": "Tytuł", "form.category.label.title": "Tytuł",
"form.category.hide_globally": "Ukryj wpisy na globalnej liście nieprzeczytanych",
"form.user.label.username": "Nazwa użytkownika", "form.user.label.username": "Nazwa użytkownika",
"form.user.label.password": "Hasło", "form.user.label.password": "Hasło",
"form.user.label.confirmation": "Potwierdzenie hasła", "form.user.label.confirmation": "Potwierdzenie hasła",

View file

@ -277,6 +277,7 @@
"form.feed.label.disabled": "Não atualizar esta fonte", "form.feed.label.disabled": "Não atualizar esta fonte",
"form.feed.label.fetch_via_proxy": "Buscar via proxy", "form.feed.label.fetch_via_proxy": "Buscar via proxy",
"form.category.label.title": "Título", "form.category.label.title": "Título",
"form.category.hide_globally": "Ocultar entradas na lista global não lida",
"form.user.label.username": "Nome de usuário", "form.user.label.username": "Nome de usuário",
"form.user.label.password": "Senha", "form.user.label.password": "Senha",
"form.user.label.confirmation": "Confirmação de senha", "form.user.label.confirmation": "Confirmação de senha",

View file

@ -279,6 +279,7 @@
"form.feed.label.fetch_via_proxy": "Получить через прокси", "form.feed.label.fetch_via_proxy": "Получить через прокси",
"form.feed.label.disabled": "Не обновлять этот канал", "form.feed.label.disabled": "Не обновлять этот канал",
"form.category.label.title": "Название", "form.category.label.title": "Название",
"form.category.hide_globally": "Скрыть записи в глобальном списке непрочитанных",
"form.user.label.username": "Имя пользователя", "form.user.label.username": "Имя пользователя",
"form.user.label.password": "Пароль", "form.user.label.password": "Пароль",
"form.user.label.confirmation": "Подтверждение пароля", "form.user.label.confirmation": "Подтверждение пароля",

View file

@ -277,6 +277,7 @@
"form.feed.label.fetch_via_proxy": "Proxy ile çek", "form.feed.label.fetch_via_proxy": "Proxy ile çek",
"form.feed.label.disabled": "Bu beslemeyi yenileme", "form.feed.label.disabled": "Bu beslemeyi yenileme",
"form.category.label.title": "Başlık", "form.category.label.title": "Başlık",
"form.category.hide_globally": "Genel okunmamış listesindeki girişleri gizle",
"form.user.label.username": "Kullanıcı Adı", "form.user.label.username": "Kullanıcı Adı",
"form.user.label.password": "Parola", "form.user.label.password": "Parola",
"form.user.label.confirmation": "Parola Doğrulama", "form.user.label.confirmation": "Parola Doğrulama",

View file

@ -275,6 +275,7 @@
"form.feed.label.fetch_via_proxy": "通过代理获取", "form.feed.label.fetch_via_proxy": "通过代理获取",
"form.feed.label.disabled": "请勿刷新此Feed", "form.feed.label.disabled": "请勿刷新此Feed",
"form.category.label.title": "标题", "form.category.label.title": "标题",
"form.category.hide_globally": "隐藏全局未读列表中的条目",
"form.user.label.username": "用户名", "form.user.label.username": "用户名",
"form.user.label.password": "密码", "form.user.label.password": "密码",
"form.user.label.confirmation": "确认", "form.user.label.confirmation": "确认",

View file

@ -11,6 +11,7 @@ type Category struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Title string `json:"title"` Title string `json:"title"`
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
HideGlobally bool `json:"hide_globally"`
FeedCount int `json:"-"` FeedCount int `json:"-"`
TotalUnread int `json:"-"` TotalUnread int `json:"-"`
} }
@ -22,11 +23,13 @@ func (c *Category) String() string {
// CategoryRequest represents the request to create or update a category. // CategoryRequest represents the request to create or update a category.
type CategoryRequest struct { type CategoryRequest struct {
Title string `json:"title"` Title string `json:"title"`
HideGlobally string `json:"hide_globally"`
} }
// Patch updates category fields. // Patch updates category fields.
func (cr *CategoryRequest) Patch(category *Category) { func (cr *CategoryRequest) Patch(category *Category) {
category.Title = cr.Title category.Title = cr.Title
category.HideGlobally = cr.HideGlobally != ""
} }
// Categories represents a list of categories. // Categories represents a list of categories.

View file

@ -40,8 +40,8 @@ func (s *Storage) CategoryIDExists(userID, categoryID int64) bool {
func (s *Storage) Category(userID, categoryID int64) (*model.Category, error) { func (s *Storage) Category(userID, categoryID int64) (*model.Category, error) {
var category model.Category var category model.Category
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 AND id=$2` query := `SELECT id, user_id, title, hide_globally FROM categories WHERE user_id=$1 AND id=$2`
err := s.db.QueryRow(query, userID, categoryID).Scan(&category.ID, &category.UserID, &category.Title) err := s.db.QueryRow(query, userID, categoryID).Scan(&category.ID, &category.UserID, &category.Title, &category.HideGlobally)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
@ -55,10 +55,10 @@ func (s *Storage) Category(userID, categoryID int64) (*model.Category, error) {
// FirstCategory returns the first category for the given user. // FirstCategory returns the first category for the given user.
func (s *Storage) FirstCategory(userID int64) (*model.Category, error) { func (s *Storage) FirstCategory(userID int64) (*model.Category, error) {
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC LIMIT 1` query := `SELECT id, user_id, title, hide_globally FROM categories WHERE user_id=$1 ORDER BY title ASC LIMIT 1`
var category model.Category var category model.Category
err := s.db.QueryRow(query, userID).Scan(&category.ID, &category.UserID, &category.Title) err := s.db.QueryRow(query, userID).Scan(&category.ID, &category.UserID, &category.Title, &category.HideGlobally)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
@ -74,8 +74,8 @@ func (s *Storage) FirstCategory(userID int64) (*model.Category, error) {
func (s *Storage) CategoryByTitle(userID int64, title string) (*model.Category, error) { func (s *Storage) CategoryByTitle(userID int64, title string) (*model.Category, error) {
var category model.Category var category model.Category
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 AND title=$2` query := `SELECT id, user_id, title, hide_globally FROM categories WHERE user_id=$1 AND title=$2`
err := s.db.QueryRow(query, userID, title).Scan(&category.ID, &category.UserID, &category.Title) err := s.db.QueryRow(query, userID, title).Scan(&category.ID, &category.UserID, &category.Title, &category.HideGlobally)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
@ -89,7 +89,7 @@ func (s *Storage) CategoryByTitle(userID int64, title string) (*model.Category,
// Categories returns all categories that belongs to the given user. // Categories returns all categories that belongs to the given user.
func (s *Storage) Categories(userID int64) (model.Categories, error) { func (s *Storage) Categories(userID int64) (model.Categories, error) {
query := `SELECT id, user_id, title FROM categories WHERE user_id=$1 ORDER BY title ASC` query := `SELECT id, user_id, title, hide_globally FROM categories WHERE user_id=$1 ORDER BY title ASC`
rows, err := s.db.Query(query, userID) rows, err := s.db.Query(query, userID)
if err != nil { if err != nil {
return nil, fmt.Errorf(`store: unable to fetch categories: %v`, err) return nil, fmt.Errorf(`store: unable to fetch categories: %v`, err)
@ -99,7 +99,7 @@ func (s *Storage) Categories(userID int64) (model.Categories, error) {
categories := make(model.Categories, 0) categories := make(model.Categories, 0)
for rows.Next() { for rows.Next() {
var category model.Category var category model.Category
if err := rows.Scan(&category.ID, &category.UserID, &category.Title); err != nil { if err := rows.Scan(&category.ID, &category.UserID, &category.Title, &category.HideGlobally); err != nil {
return nil, fmt.Errorf(`store: unable to fetch category row: %v`, err) return nil, fmt.Errorf(`store: unable to fetch category row: %v`, err)
} }
@ -116,6 +116,7 @@ func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error
c.id, c.id,
c.user_id, c.user_id,
c.title, c.title,
c.hide_globally,
(SELECT count(*) FROM feeds WHERE feeds.category_id=c.id) AS count, (SELECT count(*) FROM feeds WHERE feeds.category_id=c.id) AS count,
(SELECT count(*) (SELECT count(*)
FROM feeds FROM feeds
@ -136,7 +137,7 @@ func (s *Storage) CategoriesWithFeedCount(userID int64) (model.Categories, error
categories := make(model.Categories, 0) categories := make(model.Categories, 0)
for rows.Next() { for rows.Next() {
var category model.Category var category model.Category
if err := rows.Scan(&category.ID, &category.UserID, &category.Title, &category.FeedCount, &category.TotalUnread); err != nil { if err := rows.Scan(&category.ID, &category.UserID, &category.Title, &category.HideGlobally, &category.FeedCount, &category.TotalUnread); err != nil {
return nil, fmt.Errorf(`store: unable to fetch category row: %v`, err) return nil, fmt.Errorf(`store: unable to fetch category row: %v`, err)
} }
@ -179,10 +180,11 @@ func (s *Storage) CreateCategory(userID int64, request *model.CategoryRequest) (
// UpdateCategory updates an existing category. // UpdateCategory updates an existing category.
func (s *Storage) UpdateCategory(category *model.Category) error { func (s *Storage) UpdateCategory(category *model.Category) error {
query := `UPDATE categories SET title=$1 WHERE id=$2 AND user_id=$3` query := `UPDATE categories SET title=$1, hide_globally = $2 WHERE id=$3 AND user_id=$4`
_, err := s.db.Exec( _, err := s.db.Exec(
query, query,
category.Title, category.Title,
category.HideGlobally,
category.ID, category.ID,
category.UserID, category.UserID,
) )

View file

@ -49,6 +49,7 @@ func (s *Storage) CountAllEntries() map[string]int64 {
func (s *Storage) CountUnreadEntries(userID int64) int { func (s *Storage) CountUnreadEntries(userID int64) int {
builder := s.NewEntryQueryBuilder(userID) builder := s.NewEntryQueryBuilder(userID)
builder.WithStatus(model.EntryStatusUnread) builder.WithStatus(model.EntryStatusUnread)
builder.WithGloballyVisible()
n, err := builder.CountEntries() n, err := builder.CountEntries()
if err != nil { if err != nil {
@ -346,6 +347,27 @@ func (s *Storage) SetEntriesStatus(userID int64, entryIDs []int64, status string
return nil return nil
} }
func (s *Storage) SetEntriesStatusCount(userID int64, entryIDs []int64, status string) (int, error) {
if err := s.SetEntriesStatus(userID, entryIDs, status); err != nil {
return 0, err
}
query := `
SELECT count(*)
FROM entries e
JOIN feeds f ON (f.id = e.feed_id)
JOIN categories c ON (c.id = f.category_id)
WHERE e.user_id = $1 AND e.id = ANY($2) AND NOT c.hide_globally
`
row := s.db.QueryRow(query, userID, pq.Array(entryIDs))
visible := 0
if err := row.Scan(&visible); err != nil {
return 0, fmt.Errorf(`store: unable to query entries visibility %v: %v`, entryIDs, err)
}
return visible, nil
}
// ToggleBookmark toggles entry bookmark value. // ToggleBookmark toggles entry bookmark value.
func (s *Storage) ToggleBookmark(userID int64, entryID int64) error { func (s *Storage) ToggleBookmark(userID int64, entryID int64) error {
query := `UPDATE entries SET starred = NOT starred, changed_at=now() WHERE user_id=$1 AND id=$2` query := `UPDATE entries SET starred = NOT starred, changed_at=now() WHERE user_id=$1 AND id=$2`

View file

@ -181,9 +181,20 @@ func (e *EntryQueryBuilder) WithOffset(offset int) *EntryQueryBuilder {
return e return e
} }
func (e *EntryQueryBuilder) WithGloballyVisible() *EntryQueryBuilder {
e.conditions = append(e.conditions, "not c.hide_globally")
return e
}
// CountEntries count the number of entries that match the condition. // CountEntries count the number of entries that match the condition.
func (e *EntryQueryBuilder) CountEntries() (count int, err error) { func (e *EntryQueryBuilder) CountEntries() (count int, err error) {
query := `SELECT count(*) FROM entries e LEFT JOIN feeds f ON f.id=e.feed_id WHERE %s` query := `
SELECT count(*)
FROM entries e
JOIN feeds f ON f.id = e.feed_id
JOIN categories c ON c.id = f.category_id
WHERE %s
`
condition := e.buildCondition() condition := e.buildCondition()
err = e.store.db.QueryRow(fmt.Sprintf(query, condition), e.args...).Scan(&count) err = e.store.db.QueryRow(fmt.Sprintf(query, condition), e.args...).Scan(&count)

View file

@ -26,6 +26,11 @@
<label for="form-title">{{ t "form.category.label.title" }}</label> <label for="form-title">{{ t "form.category.label.title" }}</label>
<input type="text" name="title" id="form-title" value="{{ .form.Title }}" required autofocus> <input type="text" name="title" id="form-title" value="{{ .form.Title }}" required autofocus>
<label>
<input type="checkbox" name="hide_globally" {{ if .form.HideGlobally }}checked{{ end }}>
{{ t "form.category.hide_globally" }}
</label>
<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>

View file

@ -38,6 +38,10 @@ func (h *handler) showEditCategoryPage(w http.ResponseWriter, r *http.Request) {
categoryForm := form.CategoryForm{ categoryForm := form.CategoryForm{
Title: category.Title, Title: category.Title,
HideGlobally: "",
}
if category.HideGlobally {
categoryForm.HideGlobally = "checked"
} }
view.Set("form", categoryForm) view.Set("form", categoryForm)

View file

@ -48,7 +48,10 @@ func (h *handler) updateCategory(w http.ResponseWriter, r *http.Request) {
view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID)) view.Set("countUnread", h.store.CountUnreadEntries(loggedUser.ID))
view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID)) view.Set("countErrorFeeds", h.store.CountUserFeedsWithErrors(loggedUser.ID))
categoryRequest := &model.CategoryRequest{Title: categoryForm.Title} categoryRequest := &model.CategoryRequest{
Title: categoryForm.Title,
HideGlobally: categoryForm.HideGlobally,
}
if validationErr := validator.ValidateCategoryModification(h.store, loggedUser.ID, category.ID, categoryRequest); validationErr != nil { if validationErr := validator.ValidateCategoryModification(h.store, loggedUser.ID, category.ID, categoryRequest); validationErr != nil {
view.Set("errorMessage", validationErr.TranslationKey) view.Set("errorMessage", validationErr.TranslationKey)

View file

@ -26,10 +26,11 @@ func (h *handler) updateEntriesStatus(w http.ResponseWriter, r *http.Request) {
return return
} }
if err := h.store.SetEntriesStatus(request.UserID(r), entriesStatusUpdateRequest.EntryIDs, entriesStatusUpdateRequest.Status); err != nil { count, err := h.store.SetEntriesStatusCount(request.UserID(r), entriesStatusUpdateRequest.EntryIDs, entriesStatusUpdateRequest.Status)
if err != nil {
json.ServerError(w, r, err) json.ServerError(w, r, err)
return return
} }
json.OK(w, r, "OK") json.OK(w, r, count)
} }

View file

@ -11,11 +11,13 @@ import (
// CategoryForm represents a feed form in the UI // CategoryForm represents a feed form in the UI
type CategoryForm struct { type CategoryForm struct {
Title string Title string
HideGlobally string
} }
// NewCategoryForm returns a new CategoryForm. // NewCategoryForm returns a new CategoryForm.
func NewCategoryForm(r *http.Request) *CategoryForm { func NewCategoryForm(r *http.Request) *CategoryForm {
return &CategoryForm{ return &CategoryForm{
Title: r.FormValue("title"), Title: r.FormValue("title"),
HideGlobally: r.FormValue("hide_globally"),
} }
} }

View file

@ -206,14 +206,20 @@ function updateEntriesStatus(entryIDs, status, callback) {
let url = document.body.dataset.entriesStatusUrl; let url = document.body.dataset.entriesStatusUrl;
let request = new RequestBuilder(url); let request = new RequestBuilder(url);
request.withBody({entry_ids: entryIDs, status: status}); request.withBody({entry_ids: entryIDs, status: status});
request.withCallback(callback); request.withCallback((resp) => {
request.execute(); resp.json().then(count => {
if (callback) {
callback(resp);
}
if (status === "read") { if (status === "read") {
decrementUnreadCounter(1); decrementUnreadCounter(count);
} else { } else {
incrementUnreadCounter(1); incrementUnreadCounter(count);
} }
});
});
request.execute();
} }
// Handle save entry from list view and entry view. // Handle save entry from list view and entry view.

View file

@ -34,6 +34,7 @@ func (h *handler) showUnreadPage(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.WithStatus(model.EntryStatusUnread) builder.WithStatus(model.EntryStatusUnread)
builder.WithGloballyVisible()
countUnread, err := builder.CountEntries() countUnread, err := builder.CountEntries()
if err != nil { if err != nil {
html.ServerError(w, r, err) html.ServerError(w, r, err)
@ -52,6 +53,7 @@ func (h *handler) showUnreadPage(w http.ResponseWriter, r *http.Request) {
builder.WithDirection(user.EntryDirection) builder.WithDirection(user.EntryDirection)
builder.WithOffset(offset) builder.WithOffset(offset)
builder.WithLimit(user.EntriesPerPage) builder.WithLimit(user.EntriesPerPage)
builder.WithGloballyVisible()
entries, err := builder.GetEntries() entries, err := builder.GetEntries()
if err != nil { if err != nil {
html.ServerError(w, r, err) html.ServerError(w, r, err)