8000 [pull] main from miniflux:main by pull[bot] · Pull Request #206 · suhaoGe/miniflux · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions internal/storage/entry_pagination_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,23 @@ func (e *EntryPaginationBuilder) WithStatus(status string) {
}
}

// WithStatusOrEntryID adds a status condition that always includes a specific entry ID.
func (e *EntryPaginationBuilder) WithStatusOrEntryID(status string, entryID int64) {
if status == "" {
return
}

if entryID == 0 {
e.WithStatus(status)
return
}

statusArg := len(e.args) + 1
entryArg := len(e.args) + 2
e.conditions = append(e.conditions, fmt.Sprintf("(e.status = $%d OR e.id = $%d)", statusArg, entryArg))
e.args = append(e.args, status, entryID)
}

func (e *EntryPaginationBuilder) WithTags(tags []string) {
if len(tags) > 0 {
for _, tag := range tags {
Expand Down
42 changes: 42 additions & 0 deletions internal/template/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"math"
"net/mail"
"net/url"
"strconv"
"slices"
"strings"
"time"
Expand Down Expand Up @@ -105,6 +106,47 @@ func (f *funcMap) Map() template.FuncMap {
"subtract": func(a, b int) int {
return a - b
},
"queryString": func(params map[string]any) string {
if len(params) == 0 {
return ""
}

values := url.Values{}
for key, value := range params {
switch v := value.(type) {
case string:
if v != "" {
values.Set(key, v)
}
case int:
if v != 0 {
values.Set(key, strconv.Itoa(v))
}
case int64:
if v != 0 {
values.Set(key, strconv.FormatInt(v, 10))
}
case bool:
if v {
values.Set(key, "1")
}
default:
if value != nil {
str := fmt.Sprint(value)
if str != "" {
values.Set(key, str)
}
}
}
}

encoded := values.Encode()
if encoded == "" {
return ""
}

return "?" + encoded
},

// These functions are overridden at runtime after parsing.
"elapsed": func(timezone string, t time.Time) string {
Expand Down
38 changes: 38 additions & 0 deletions internal/template/functions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,44 @@ func TestFormatFileSize(t *testing.T) {
}
}

func TestQueryString(t *testing.T) {
params, err := dict("q", "ai", "unread", true, "offset", 20)
if err != nil {
t.Fatalf(`The dict should be valid: %v`, err)
}

got := (&funcMap{}).Map()["queryString"].(func(map[string]any) string)(params)
if got == "" {
t.Fatalf("Expected a query string, got an empty string")
}

if !strings.HasPrefix(got, "?") {
t.Fatalf(`Expected query string to start with "?", got %q`, got)
}

if !strings.Contains(got, "q=ai") {
t.Fatalf(`Expected query string to contain q=ai, got %q`, got)
}

if !strings.Contains(got, "unread=1") {
t.Fatalf(`Expected query string to contain unread=1, got %q`, got)
}

if !strings.Contains(got, "offset=20") {
t.Fatalf(`Expected query string to contain offset=20, got %q`, got)
}

empty, err := dict("q", "", "unread", false, "offset", 0)
if err != nil {
t.Fatalf(`The dict should be valid: %v`, err)
}

got = (&funcMap{}).Map()["queryString"].(func(map[string]any) string)(empty)
if got != "" {
t.Fatalf(`Expected empty query string, got %q`, got)
}
}

func TestCSPExternalFont(t *testing.T) {
want := []string{
`default-src 'none';`,
Expand Down
8 changes: 4 additions & 4 deletions internal/template/templates/common/pagination.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
<div class="pagination-backward">
<div class="pagination-first {{ if not .ShowFirst }}disabled{{end}}">
{{ if .ShowFirst }}
<a href="{{ .Route }}{{ if gt .FirstOffset 0 }}?offset={{ .FirstOffset }}{{ if .SearchQuery }}&amp;q={{ .SearchQuery }}{{ end }}{{ else }}{{ if .SearchQuery }}?q={{ .SearchQuery }}{{ end }}{{ end }}" data-page="first">{{ t "pagination.first" }}</a>
<a href="{{ .Route }}{{ queryString (dict "offset" .FirstOffset "q" .SearchQuery "unread" .UnreadOnly) }}" data-page="first">{{ t "pagination.first" }}</a>
{{ else }}
{{ t "pagination.first" }}
{{ end }}
</div>

<div class="pagination-prev {{ if not .ShowPrev }}disabled{{end}}">
{{ if .ShowPrev }}
<a href="{{ .Route }}{{ if gt .PrevOffset 0 }}?offset={{ .PrevOffset }}{{ if .SearchQuery }}&amp;q={{ .SearchQuery }}{{ end }}{{ else }}{{ if .SearchQuery }}?q={{ .SearchQuery }}{{ end }}{{ end }}" data-page="previous" rel="prev">{{ t "pagination.previous" }}</a>
<a href="{{ .Route }}{{ queryString (dict "offset" .PrevOffset "q" .SearchQuery "unread" .UnreadOnly) }}" data-page="previous" rel="prev">{{ t "pagination.previous" }}</a>
{{ else }}
{{ t "pagination.previous" }}
{{ end }}
Expand All @@ -23,15 +23,15 @@
<div class="pagination-forward">
<div class="pagination-next {{ if not .ShowNext }}disabled{{end}}">
{{ if .ShowNext }}
<a href="{{ .Route }}?offset={{ .NextOffset }}{{ if .SearchQuery }}&amp;q={{ .SearchQuery }}{{ end }}" data-page="next" rel="next">{{ t "pagination.next" }}</a>
<a href="{{ .Route }}{{ queryString (dict "offset" .NextOffset "q" .SearchQuery "unread" .UnreadOnly) }}" data-page="next" rel="next">{{ t "pagination.next" }}</a>
{{ else }}
{{ t "pagination.next" }}
{{ end }}
</div>

<div class="pagination-last {{ if not .ShowLast }}disabled{{end}}">
{{ if .ShowLast }}
<a href="{{ .Route }}?offset={{ .LastOffset }}{{ if .SearchQuery }}&amp;q={{ .SearchQuery }}{{ end }}" data-page="last" >{{ t "pagination.last" }}</a>
<a href="{{ .Route }}{{ queryString (dict "offset" .LastOffset "q" .SearchQuery "unread" .UnreadOnly) }}" data-page="last" >{{ t "pagination.last" }}</a>
{{ else }}
{{ t "pagination.last" }}
{{ end }}
Expand Down
4 changes: 2 additions & 2 deletions internal/template/templates/views/entry.html
8BD0
Original file line number Diff line numberDiff line change
Expand Up @@ -4,7 +4,7 @@
<div class="pagination">
<div class="pagination-prev {{ if not .prevEntry }}disabled{{end}}">
{{ if .prevEntry }}
<a href="{{ .prevEntryRoute }}{{ if .searchQuery }}?q={{ .searchQuery }}{{ end }}" title="{{ .prevEntry.Title }}" data-page="previous" rel="prev">{{ t "pagination.previous" }}</a>
<a href="{{ .prevEntryRoute }}{{ queryString (dict "q" .searchQuery "unread" .searchUnreadOnly) }}" title="{{ .prevEntry.Title }}" data-page="previous" rel="prev">{{ t "pagination.previous" }}</a>
{{ else }}
{{ t "pagination.previous" }}
{{ end }}
Expand All @@ -14,7 +14,7 @@

<div class="pagination-next {{ if not .nextEntry }}disabled{{end}}">
{{ if .nextEntry }}
<a href="{{ .nextEntryRoute }}{{ if .searchQuery }}?q={{ .searchQuery }}{{ end }}" title="{{ .nextEntry.Title }}" data-page="next" rel="next">{{ t "pagination.next" }}</a>
<a href="{{ .nextEntryRoute }}{{ queryString (dict "q" .searchQuery "unread" .searchUnreadOnly) }}" title="{{ .nextEntry.Title }}" data-page="next" rel="next">{{ t "pagination.next" }}</a>
{{ else }}
{{ t "pagination.next" }}
{{ end }}
Expand Down
11 changes: 7 additions & 4 deletions internal/template/templates/views/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ <h1 id="page-header-title">{{ t "page.search.title" }} ({{ .total }})</h1>

{{ define "content"}}
<search role="search">
<form action="{{ route "search" }}" aria-labelledby="search-input-label">
<input type="search" name="q" id="search-input" aria-label="{{ t "search.label" }}" placeholder="{{ t "search.placeholder" }}" {{ if $.searchQuery }}value="{{ .searchQuery }}"{{ else }}autofocus{{ end }} required>
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.loading" }}">{{ t "search.submit" }}</button>
<form class="search-form" action="{{ route "search" }}" aria-labelledby="search-input-label">
<div class="search-input-row">
<input type="search" name="q" id="search-input" aria-label="{{ t "search.label" }}" placeholder="{{ t "search.placeholder" }}" {{ if $.searchQuery }}value="{{ .searchQuery }}"{{ else }}autofocus{{ end }} required>
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.loading" }}">{{ t "search.submit" }}</button>
</div>
<label class="search-filter"><input type="checkbox" name="unread" value="1" {{ if $.searchUnreadOnly }}checked{{ end }}> {{ t "menu.show_only_unread_entries" }}</label>
</form>
</search>

Expand All @@ -30,7 +33,7 @@ <h1 id="page-header-title">{{ t "page.search.title" }} ({{ .total }})</h1>
>
<header class="item-header" dir="auto">
<h2 id="entry-title-{{ .ID }}" class="item-title">
<a href="{{ route "searchEntry" "entryID" .ID }}?q={{ $.searchQuery }}">
<a href="{{ route "searchEntry" "entryID" .ID }}{{ queryString (dict "q" $.searchQuery "unread" $.searchUnreadOnly) }}">
{{ if ne .Feed.Icon.IconID 0 }}
<img src="{{ route "feedIcon" "externalIconID" .Feed.Icon.ExternalIconID }}" width="16" height="16" loading="lazy" alt="{{ .Feed.Title }}">
{{ else }}
Expand Down
10 changes: 10 additions & 0 deletions internal/ui/entry_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func (h *handler) showSearchEntryPage(w http.ResponseWriter, r *http.Request) {

entryID := request.RouteInt64Param(r, "entryID")
searchQuery := request.QueryStringParam(r, "q", "")
unreadOnly := request.QueryBoolParam(r, "unread", false)
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithSearchQuery(searchQuery)
builder.WithEntryID(entryID)
Expand Down Expand Up @@ -57,6 +58,14 @@ func (h *handler) showSearchEntryPage(w http.ResponseWriter, r *http.Request) {

entryPaginationBuilder := storage.NewEntryPaginationBuilder(h.store, user.ID, entry.ID, user.EntryOrder, user.EntryDirection)
entryPaginationBuilder.WithSearchQuery(searchQuery)
if unreadOnly {
if entry.Status == model.EntryStatusRead {
entryPaginationBuilder.WithStatusOrEntryID(model.EntryStatusUnread, entry.ID)
} else {
entryPaginationBuilder.WithStatus(model.EntryStatusUnread)
}
}

prevEntry, nextEntry, err := entryPaginationBuilder.Entries()
if err != nil {
html.ServerError(w, r, err)
Expand All @@ -76,6 +85,7 @@ func (h *handler) showSearchEntryPage(w http.ResponseWriter, r *http.Request) {
sess := session.New(h.store, request.SessionID(r))
view := view.New(h.tpl, r, sess)
view.Set("searchQuery", searchQuery)
view.Set("searchUnreadOnly", unreadOnly)
view.Set("entry", entry)
view.Set("prevEntry", prevEntry)
view.Set("nextEntry", nextEntry)
Expand Down
1 change: 1 addition & 0 deletions internal/ui/pagination.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type pagination struct {
PrevOffset int
FirstOffset int
SearchQuery string
UnreadOnly bool
}

func getPagination(route string, total, offset, nbItemsPerPage int) pagination {
Expand Down
6 changes: 6 additions & 0 deletions internal/ui/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func (h *handler) showSearchPage(w http.ResponseWriter, r *http.Request) {
}

searchQuery := request.QueryStringParam(r, "q", "")
unreadOnly := request.QueryBoolParam(r, "unread", false)
offset := request.QueryIntParam(r, "offset", 0)

var entries model.Entries
Expand All @@ -30,6 +31,9 @@ func (h *handler) showSearchPage(w http.ResponseWriter, r *http.Request) {
if searchQuery != "" {
builder := h.store.NewEntryQueryBuilder(user.ID)
builder.WithSearchQuery(searchQuery)
if unreadOnly {
builder.WithStatus(model.EntryStatusUnread)
}
builder.WithoutStatus(model.EntryStatusRemoved)
builder.WithOffset(offset)
builder.WithLimit(user.EntriesPerPage)
Expand All @@ -51,8 +55,10 @@ func (h *handler) showSearchPage(w http.ResponseWriter, r *http.Request) {
view := view.New(h.tpl, r, sess)
pagination := getPagination(route.Path(h.router, "search"), entriesCount, offset, user.EntriesPerPage)
pagination.SearchQuery = searchQuery
pagination.UnreadOnly = unreadOnly

view.Set("searchQuery", searchQuery)
view.Set("searchUnreadOnly", unreadOnly)
view.Set("entries", entries)
view.Set("total", entriesCount)
view.Set("pagination", pagination)
Expand Down
34 changes: 33 additions & 1 deletion internal/ui/static/css/common.css
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,38 @@ input[type="checkbox"] {
margin-bottom: 10px;
}

.search-form {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}

.search-input-row {
display: flex;
align-items: center;
gap: 12px;
}

.search-input-row input[type="search"] {
margin: 0;
}

.search-input-row .button {
margin: 0;
}

.search-filter {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 0;
}

.search-filter input[type="checkbox"] {
margin: 0;
}

textarea {
width: 350px;
color: var(--input-color);
Expand Down Expand Up @@ -1343,4 +1375,4 @@ footer .elevator {
.pagination-top .elevator,
.pagination-entry-top .elevator {
display: none;
}
}
Loading
0