Fix pagination: replace load more by actual pagesUnverified
89afff9 parent: 72289c4 modified
internal/server/annotations_handler.go +25 -13 | @@ -14,36 +14,48 @@ import ( | ||
| 14 | 14 | func (s *Server) handleLibrary(w http.ResponseWriter, r *http.Request) { |
| 15 | 15 | user := currentUser(r) |
| 16 | 16 | |
| 17 | - likedOffset, _ := strconv.Atoi(r.URL.Query().Get("liked_offset")) | |
| 18 | - if likedOffset < 0 { | |
| 19 | - likedOffset = 0 | |
| 17 | + limit := 20 | |
| 18 | + | |
| 19 | + likedPageNum, _ := strconv.Atoi(r.URL.Query().Get("liked_page")) | |
| 20 | + if likedPageNum < 1 { | |
| 21 | + likedPageNum = 1 | |
| 20 | 22 | } |
| 21 | - annotOffset, _ := strconv.Atoi(r.URL.Query().Get("annot_offset")) | |
| 22 | - if annotOffset < 0 { | |
| 23 | - annotOffset = 0 | |
| 23 | + annotPageNum, _ := strconv.Atoi(r.URL.Query().Get("annot_page")) | |
| 24 | + if annotPageNum < 1 { | |
| 25 | + annotPageNum = 1 | |
| 24 | 26 | } |
| 25 | - limit := 20 | |
| 26 | 27 | |
| 27 | - articles, _ := s.db.ListLikedArticles(r.Context(), user.DID, limit+1, likedOffset) | |
| 28 | + likedPage := Pagination{Page: likedPageNum, PageSize: limit} | |
| 29 | + annotPage := Pagination{Page: annotPageNum, PageSize: limit} | |
| 30 | + | |
| 31 | + articles, _ := s.db.ListLikedArticles(r.Context(), user.DID, limit+1, likedPage.Offset()) | |
| 28 | 32 | likedHasMore := len(articles) > limit |
| 29 | 33 | if likedHasMore { |
| 30 | 34 | articles = articles[:limit] |
| 31 | 35 | } |
| 36 | + likedPage = likedPage.Paginate(len(articles)) | |
| 37 | + if likedHasMore { | |
| 38 | + likedPage.HasNext = true | |
| 39 | + likedPage.NextPage = likedPage.Page + 1 | |
| 40 | + } | |
| 32 | 41 | |
| 33 | - annotations, _ := s.db.ListAnnotations(r.Context(), "", "", user.DID, limit+1, annotOffset) | |
| 42 | + annotations, _ := s.db.ListAnnotations(r.Context(), "", "", user.DID, limit+1, annotPage.Offset()) | |
| 34 | 43 | annotHasMore := len(annotations) > limit |
| 35 | 44 | if annotHasMore { |
| 36 | 45 | annotations = annotations[:limit] |
| 37 | 46 | } |
| 47 | + annotPage = annotPage.Paginate(len(annotations)) | |
| 48 | + if annotHasMore { | |
| 49 | + annotPage.HasNext = true | |
| 50 | + annotPage.NextPage = annotPage.Page + 1 | |
| 51 | + } | |
| 38 | 52 | |
| 39 | 53 | s.render(w, r, "library.html", map[string]any{ |
| 40 | 54 | "User": user, |
| 41 | 55 | "Articles": articles, |
| 42 | 56 | "Annotations": annotations, |
| 43 | - "LikedHasMore": likedHasMore, | |
| 44 | - "AnnotHasMore": annotHasMore, | |
| 45 | - "NextLiked": likedOffset + limit, | |
| 46 | - "NextAnnot": annotOffset + limit, | |
| 57 | + "LikedPage": likedPage, | |
| 58 | + "AnnotPage": annotPage, | |
| 47 | 59 | }) |
| 48 | 60 | } |
| 49 | 61 | |
| @@ -14,36 +14,48 @@ import ( | |||
| 14 | func (s *Server) handleLibrary(w http.ResponseWriter, r *http.Request) { | 14 | func (s *Server) handleLibrary(w http.ResponseWriter, r *http.Request) { |
| 15 | user := currentUser(r) | 15 | user := currentUser(r) |
| 16 | 16 | ||
| 17 | - likedOffset, _ := strconv.Atoi(r.URL.Query().Get("liked_offset")) | 17 | + limit := 20 |
| 18 | - if likedOffset < 0 { | 18 | + |
| 19 | - likedOffset = 0 | 19 | + likedPageNum, _ := strconv.Atoi(r.URL.Query().Get("liked_page")) |
| 20 | + if likedPageNum < 1 { | ||
| 21 | + likedPageNum = 1 | ||
| 20 | } | 22 | } |
| 21 | - annotOffset, _ := strconv.Atoi(r.URL.Query().Get("annot_offset")) | 23 | + annotPageNum, _ := strconv.Atoi(r.URL.Query().Get("annot_page")) |
| 22 | - if annotOffset < 0 { | 24 | + if annotPageNum < 1 { |
| 23 | - annotOffset = 0 | 25 | + annotPageNum = 1 |
| 24 | } | 26 | } |
| 25 | - limit := 20 | ||
| 26 | 27 | ||
| 27 | - articles, _ := s.db.ListLikedArticles(r.Context(), user.DID, limit+1, likedOffset) | 28 | + likedPage := Pagination{Page: likedPageNum, PageSize: limit} |
| 29 | + annotPage := Pagination{Page: annotPageNum, PageSize: limit} | ||
| 30 | + | ||
| 31 | + articles, _ := s.db.ListLikedArticles(r.Context(), user.DID, limit+1, likedPage.Offset()) | ||
| 28 | likedHasMore := len(articles) > limit | 32 | likedHasMore := len(articles) > limit |
| 29 | if likedHasMore { | 33 | if likedHasMore { |
| 30 | articles = articles[:limit] | 34 | articles = articles[:limit] |
| 31 | } | 35 | } |
| 36 | + likedPage = likedPage.Paginate(len(articles)) | ||
| 37 | + if likedHasMore { | ||
| 38 | + likedPage.HasNext = true | ||
| 39 | + likedPage.NextPage = likedPage.Page + 1 | ||
| 40 | + } | ||
| 32 | 41 | ||
| 33 | - annotations, _ := s.db.ListAnnotations(r.Context(), "", "", user.DID, limit+1, annotOffset) | 42 | + annotations, _ := s.db.ListAnnotations(r.Context(), "", "", user.DID, limit+1, annotPage.Offset()) |
| 34 | annotHasMore := len(annotations) > limit | 43 | annotHasMore := len(annotations) > limit |
| 35 | if annotHasMore { | 44 | if annotHasMore { |
| 36 | annotations = annotations[:limit] | 45 | annotations = annotations[:limit] |
| 37 | } | 46 | } |
| 47 | + annotPage = annotPage.Paginate(len(annotations)) | ||
| 48 | + if annotHasMore { | ||
| 49 | + annotPage.HasNext = true | ||
| 50 | + annotPage.NextPage = annotPage.Page + 1 | ||
| 51 | + } | ||
| 38 | 52 | ||
| 39 | s.render(w, r, "library.html", map[string]any{ | 53 | s.render(w, r, "library.html", map[string]any{ |
| 40 | "User": user, | 54 | "User": user, |
| 41 | "Articles": articles, | 55 | "Articles": articles, |
| 42 | "Annotations": annotations, | 56 | "Annotations": annotations, |
| 43 | - "LikedHasMore": likedHasMore, | 57 | + "LikedPage": likedPage, |
| 44 | - "AnnotHasMore": annotHasMore, | 58 | + "AnnotPage": annotPage, |
| 45 | - "NextLiked": likedOffset + limit, | ||
| 46 | - "NextAnnot": annotOffset + limit, | ||
| 47 | }) | 59 | }) |
| 48 | } | 60 | } |
| 49 | 61 | ||
modified
internal/server/articles_handler.go +11 -8 | @@ -38,23 +38,26 @@ func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) { | ||
| 38 | 38 | feedURL := r.URL.Query().Get("feed") |
| 39 | 39 | |
| 40 | 40 | page := pageFromRequest(r, 50) |
| 41 | - articles, err := s.db.ListArticles(r.Context(), user.DID, feedURL, page.FetchLimit(), page.Offset) | |
| 41 | + articles, err := s.db.ListArticles(r.Context(), user.DID, feedURL, page.Limit()+1, page.Offset()) | |
| 42 | 42 | if err != nil { |
| 43 | 43 | s.logger.Error("failed to list articles", "error", err) |
| 44 | 44 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 45 | 45 | return |
| 46 | 46 | } |
| 47 | 47 | |
| 48 | - page = page.Paginate(len(articles)) | |
| 49 | - if page.HasMore { | |
| 50 | - articles = articles[:page.Limit] | |
| 48 | + totalFetched := len(articles) | |
| 49 | + page = page.Paginate(totalFetched) | |
| 50 | + if page.HasNext { | |
| 51 | + articles = articles[:page.PageSize] | |
| 51 | 52 | } |
| 52 | 53 | |
| 53 | 54 | s.render(w, r, "articles.html", map[string]any{ |
| 54 | - "User": user, | |
| 55 | - "Articles": articles, | |
| 56 | - "FeedURL": feedURL, | |
| 57 | - "Page": page, | |
| 55 | + "User": user, | |
| 56 | + "Articles": articles, | |
| 57 | + "FeedURL": feedURL, | |
| 58 | + "Page": page, | |
| 59 | + "BaseURL": "/articles", | |
| 60 | + "QueryParams": buildQueryParams(map[string]string{"feed": feedURL}), | |
| 58 | 61 | }) |
| 59 | 62 | } |
| 60 | 63 | |
| @@ -38,23 +38,26 @@ func (s *Server) handleArticles(w http.ResponseWriter, r *http.Request) { | |||
| 38 | feedURL := r.URL.Query().Get("feed") | 38 | feedURL := r.URL.Query().Get("feed") |
| 39 | 39 | ||
| 40 | page := pageFromRequest(r, 50) | 40 | page := pageFromRequest(r, 50) |
| 41 | - articles, err := s.db.ListArticles(r.Context(), user.DID, feedURL, page.FetchLimit(), page.Offset) | 41 | + articles, err := s.db.ListArticles(r.Context(), user.DID, feedURL, page.Limit()+1, page.Offset()) |
| 42 | if err != nil { | 42 | if err != nil { |
| 43 | s.logger.Error("failed to list articles", "error", err) | 43 | s.logger.Error("failed to list articles", "error", err) |
| 44 | http.Error(w, err.Error(), http.StatusInternalServerError) | 44 | http.Error(w, err.Error(), http.StatusInternalServerError) |
| 45 | return | 45 | return |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | - page = page.Paginate(len(articles)) | 48 | + totalFetched := len(articles) |
| 49 | - if page.HasMore { | 49 | + page = page.Paginate(totalFetched) |
| 50 | - articles = articles[:page.Limit] | 50 | + if page.HasNext { |
| 51 | + articles = articles[:page.PageSize] | ||
| 51 | } | 52 | } |
| 52 | 53 | ||
| 53 | s.render(w, r, "articles.html", map[string]any{ | 54 | s.render(w, r, "articles.html", map[string]any{ |
| 54 | - "User": user, | 55 | + "User": user, |
| 55 | - "Articles": articles, | 56 | + "Articles": articles, |
| 56 | - "FeedURL": feedURL, | 57 | + "FeedURL": feedURL, |
| 57 | - "Page": page, | 58 | + "Page": page, |
| 59 | + "BaseURL": "/articles", | ||
| 60 | + "QueryParams": buildQueryParams(map[string]string{"feed": feedURL}), | ||
| 58 | }) | 61 | }) |
| 59 | } | 62 | } |
| 60 | 63 | ||
modified
internal/server/dashboard_handler.go +7 -4 | @@ -12,10 +12,11 @@ func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { | ||
| 12 | 12 | subCount, _ := s.db.GetSubscriptionCount(r.Context(), user.DID) |
| 13 | 13 | |
| 14 | 14 | page := pageFromRequest(r, 25) |
| 15 | - articles, _ := s.db.ListUnreadArticles(r.Context(), user.DID, "", page.FetchLimit(), page.Offset) | |
| 16 | - page = page.Paginate(len(articles)) | |
| 17 | - if page.HasMore { | |
| 18 | - articles = articles[:page.Limit] | |
| 15 | + articles, _ := s.db.ListUnreadArticles(r.Context(), user.DID, "", page.Limit()+1, page.Offset()) | |
| 16 | + totalFetched := len(articles) | |
| 17 | + page = page.Paginate(totalFetched) | |
| 18 | + if page.HasNext { | |
| 19 | + articles = articles[:page.PageSize] | |
| 19 | 20 | } |
| 20 | 21 | |
| 21 | 22 | articleRecs, _ := s.db.GetArticleRecommendations(r.Context(), user.DID, 5) |
| @@ -35,5 +36,7 @@ func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { | ||
| 35 | 36 | "PeopleRecommendations": peopleRecs, |
| 36 | 37 | "PersonalTrending": personalTrending, |
| 37 | 38 | "Page": page, |
| 39 | + "BaseURL": "/dashboard", | |
| 40 | + "QueryParams": map[string]string{}, | |
| 38 | 41 | }) |
| 39 | 42 | } |
| @@ -12,10 +12,11 @@ func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { | |||
| 12 | subCount, _ := s.db.GetSubscriptionCount(r.Context(), user.DID) | 12 | subCount, _ := s.db.GetSubscriptionCount(r.Context(), user.DID) |
| 13 | 13 | ||
| 14 | page := pageFromRequest(r, 25) | 14 | page := pageFromRequest(r, 25) |
| 15 | - articles, _ := s.db.ListUnreadArticles(r.Context(), user.DID, "", page.FetchLimit(), page.Offset) | 15 | + articles, _ := s.db.ListUnreadArticles(r.Context(), user.DID, "", page.Limit()+1, page.Offset()) |
| 16 | - page = page.Paginate(len(articles)) | 16 | + totalFetched := len(articles) |
| 17 | - if page.HasMore { | 17 | + page = page.Paginate(totalFetched) |
| 18 | - articles = articles[:page.Limit] | 18 | + if page.HasNext { |
| 19 | + articles = articles[:page.PageSize] | ||
| 19 | } | 20 | } |
| 20 | 21 | ||
| 21 | articleRecs, _ := s.db.GetArticleRecommendations(r.Context(), user.DID, 5) | 22 | articleRecs, _ := s.db.GetArticleRecommendations(r.Context(), user.DID, 5) |
| @@ -35,5 +36,7 @@ func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { | |||
| 35 | "PeopleRecommendations": peopleRecs, | 36 | "PeopleRecommendations": peopleRecs, |
| 36 | "PersonalTrending": personalTrending, | 37 | "PersonalTrending": personalTrending, |
| 37 | "Page": page, | 38 | "Page": page, |
| 39 | + "BaseURL": "/dashboard", | ||
| 40 | + "QueryParams": map[string]string{}, | ||
| 38 | }) | 41 | }) |
| 39 | } | 42 | } |
modified
internal/server/feeds_handler.go +7 -4 | @@ -19,10 +19,11 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | ||
| 19 | 19 | category := r.URL.Query().Get("category") |
| 20 | 20 | |
| 21 | 21 | page := pageFromRequest(r, 50) |
| 22 | - subs, _ := s.db.ListSubscriptions(r.Context(), user.DID, category, page.FetchLimit(), page.Offset) | |
| 23 | - page = page.Paginate(len(subs)) | |
| 24 | - if page.HasMore { | |
| 25 | - subs = subs[:page.Limit] | |
| 22 | + subs, _ := s.db.ListSubscriptions(r.Context(), user.DID, category, page.Limit()+1, page.Offset()) | |
| 23 | + totalFetched := len(subs) | |
| 24 | + page = page.Paginate(totalFetched) | |
| 25 | + if page.HasNext { | |
| 26 | + subs = subs[:page.PageSize] | |
| 26 | 27 | } |
| 27 | 28 | |
| 28 | 29 | allSubs, _ := s.db.ListSubscriptions(r.Context(), user.DID, "", 1000, 0) |
| @@ -49,6 +50,8 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | ||
| 49 | 50 | "PeopleRecommendations": peopleRecs, |
| 50 | 51 | "DeadFeeds": deadFeeds, |
| 51 | 52 | "Page": page, |
| 53 | + "BaseURL": "/feeds", | |
| 54 | + "QueryParams": buildQueryParams(map[string]string{"category": category}), | |
| 52 | 55 | }) |
| 53 | 56 | } |
| 54 | 57 | |
| @@ -19,10 +19,11 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | |||
| 19 | category := r.URL.Query().Get("category") | 19 | category := r.URL.Query().Get("category") |
| 20 | 20 | ||
| 21 | page := pageFromRequest(r, 50) | 21 | page := pageFromRequest(r, 50) |
| 22 | - subs, _ := s.db.ListSubscriptions(r.Context(), user.DID, category, page.FetchLimit(), page.Offset) | 22 | + subs, _ := s.db.ListSubscriptions(r.Context(), user.DID, category, page.Limit()+1, page.Offset()) |
| 23 | - page = page.Paginate(len(subs)) | 23 | + totalFetched := len(subs) |
| 24 | - if page.HasMore { | 24 | + page = page.Paginate(totalFetched) |
| 25 | - subs = subs[:page.Limit] | 25 | + if page.HasNext { |
| 26 | + subs = subs[:page.PageSize] | ||
| 26 | } | 27 | } |
| 27 | 28 | ||
| 28 | allSubs, _ := s.db.ListSubscriptions(r.Context(), user.DID, "", 1000, 0) | 29 | allSubs, _ := s.db.ListSubscriptions(r.Context(), user.DID, "", 1000, 0) |
| @@ -49,6 +50,8 @@ func (s *Server) handleFeeds(w http.ResponseWriter, r *http.Request) { | |||
| 49 | "PeopleRecommendations": peopleRecs, | 50 | "PeopleRecommendations": peopleRecs, |
| 50 | "DeadFeeds": deadFeeds, | 51 | "DeadFeeds": deadFeeds, |
| 51 | "Page": page, | 52 | "Page": page, |
| 53 | + "BaseURL": "/feeds", | ||
| 54 | + "QueryParams": buildQueryParams(map[string]string{"category": category}), | ||
| 52 | }) | 55 | }) |
| 53 | } | 56 | } |
| 54 | 57 | ||
modified
internal/server/pagination.go +38 -16 | @@ -8,29 +8,51 @@ import ( | ||
| 8 | 8 | const defaultPageSize = 25 |
| 9 | 9 | |
| 10 | 10 | type Pagination struct { |
| 11 | - Offset int | |
| 12 | - Limit int | |
| 13 | - HasMore bool | |
| 14 | - NextOffset int | |
| 11 | + Page int | |
| 12 | + PageSize int | |
| 13 | + HasPrev bool | |
| 14 | + HasNext bool | |
| 15 | + PrevPage int | |
| 16 | + NextPage int | |
| 15 | 17 | } |
| 16 | 18 | |
| 17 | -func pageFromRequest(r *http.Request, limit int) Pagination { | |
| 18 | - if limit <= 0 { | |
| 19 | - limit = defaultPageSize | |
| 19 | +func pageFromRequest(r *http.Request, pageSize int) Pagination { | |
| 20 | + if pageSize <= 0 { | |
| 21 | + pageSize = defaultPageSize | |
| 20 | 22 | } |
| 21 | - offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) | |
| 22 | - if offset < 0 { | |
| 23 | - offset = 0 | |
| 23 | + page, _ := strconv.Atoi(r.URL.Query().Get("page")) | |
| 24 | + if page < 1 { | |
| 25 | + page = 1 | |
| 24 | 26 | } |
| 25 | - return Pagination{Offset: offset, Limit: limit} | |
| 27 | + return Pagination{Page: page, PageSize: pageSize} | |
| 26 | 28 | } |
| 27 | 29 | |
| 28 | -func (p Pagination) Paginate(count int) Pagination { | |
| 29 | - p.HasMore = count > p.Limit | |
| 30 | - p.NextOffset = p.Offset + p.Limit | |
| 30 | +func (p Pagination) Paginate(totalFetched int) Pagination { | |
| 31 | + if totalFetched > p.PageSize { | |
| 32 | + p.HasNext = true | |
| 33 | + p.NextPage = p.Page + 1 | |
| 34 | + } | |
| 35 | + if p.Page > 1 { | |
| 36 | + p.HasPrev = true | |
| 37 | + p.PrevPage = p.Page - 1 | |
| 38 | + } | |
| 31 | 39 | return p |
| 32 | 40 | } |
| 33 | 41 | |
| 34 | -func (p Pagination) FetchLimit() int { | |
| 35 | - return p.Limit + 1 | |
| 42 | +func (p Pagination) Offset() int { | |
| 43 | + return (p.Page - 1) * p.PageSize | |
| 44 | +} | |
| 45 | + | |
| 46 | +func (p Pagination) Limit() int { | |
| 47 | + return p.PageSize | |
| 48 | +} | |
| 49 | + | |
| 50 | +func buildQueryParams(params map[string]string) map[string]string { | |
| 51 | + result := make(map[string]string) | |
| 52 | + for k, v := range params { | |
| 53 | + if v != "" { | |
| 54 | + result[k] = v | |
| 55 | + } | |
| 56 | + } | |
| 57 | + return result | |
| 36 | 58 | } |
| @@ -8,29 +8,51 @@ import ( | |||
| 8 | const defaultPageSize = 25 | 8 | const defaultPageSize = 25 |
| 9 | 9 | ||
| 10 | type Pagination struct { | 10 | type Pagination struct { |
| 11 | - Offset int | 11 | + Page int |
| 12 | - Limit int | 12 | + PageSize int |
| 13 | - HasMore bool | 13 | + HasPrev bool |
| 14 | - NextOffset int | 14 | + HasNext bool |
| 15 | + PrevPage int | ||
| 16 | + NextPage int | ||
| 15 | } | 17 | } |
| 16 | 18 | ||
| 17 | -func pageFromRequest(r *http.Request, limit int) Pagination { | 19 | +func pageFromRequest(r *http.Request, pageSize int) Pagination { |
| 18 | - if limit <= 0 { | 20 | + if pageSize <= 0 { |
| 19 | - limit = defaultPageSize | 21 | + pageSize = defaultPageSize |
| 20 | } | 22 | } |
| 21 | - offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) | 23 | + page, _ := strconv.Atoi(r.URL.Query().Get("page")) |
| 22 | - if offset < 0 { | 24 | + if page < 1 { |
| 23 | - offset = 0 | 25 | + page = 1 |
| 24 | } | 26 | } |
| 25 | - return Pagination{Offset: offset, Limit: limit} | 27 | + return Pagination{Page: page, PageSize: pageSize} |
| 26 | } | 28 | } |
| 27 | 29 | ||
| 28 | -func (p Pagination) Paginate(count int) Pagination { | 30 | +func (p Pagination) Paginate(totalFetched int) Pagination { |
| 29 | - p.HasMore = count > p.Limit | 31 | + if totalFetched > p.PageSize { |
| 30 | - p.NextOffset = p.Offset + p.Limit | 32 | + p.HasNext = true |
| 33 | + p.NextPage = p.Page + 1 | ||
| 34 | + } | ||
| 35 | + if p.Page > 1 { | ||
| 36 | + p.HasPrev = true | ||
| 37 | + p.PrevPage = p.Page - 1 | ||
| 38 | + } | ||
| 31 | return p | 39 | return p |
| 32 | } | 40 | } |
| 33 | 41 | ||
| 34 | -func (p Pagination) FetchLimit() int { | 42 | +func (p Pagination) Offset() int { |
| 35 | - return p.Limit + 1 | 43 | + return (p.Page - 1) * p.PageSize |
| 44 | +} | ||
| 45 | + | ||
| 46 | +func (p Pagination) Limit() int { | ||
| 47 | + return p.PageSize | ||
| 48 | +} | ||
| 49 | + | ||
| 50 | +func buildQueryParams(params map[string]string) map[string]string { | ||
| 51 | + result := make(map[string]string) | ||
| 52 | + for k, v := range params { | ||
| 53 | + if v != "" { | ||
| 54 | + result[k] = v | ||
| 55 | + } | ||
| 56 | + } | ||
| 57 | + return result | ||
| 36 | } | 58 | } |
modified
internal/server/server.go +15 -0 | @@ -7,6 +7,7 @@ import ( | ||
| 7 | 7 | "html/template" |
| 8 | 8 | "log/slog" |
| 9 | 9 | "net/http" |
| 10 | + "net/url" | |
| 10 | 11 | "path/filepath" |
| 11 | 12 | "strings" |
| 12 | 13 | "time" |
| @@ -208,6 +209,20 @@ func (s *Server) loadTemplates() { | ||
| 208 | 209 | } |
| 209 | 210 | return template.HTML(`<input type="hidden" name="csrf_token" value="` + s + `">`) |
| 210 | 211 | }, |
| 212 | + "paginationURL": func(baseURL string, page int, queryParams map[string]string) string { | |
| 213 | + u, _ := url.Parse(baseURL) | |
| 214 | + q := u.Query() | |
| 215 | + for k, v := range queryParams { | |
| 216 | + q.Set(k, v) | |
| 217 | + } | |
| 218 | + if page > 1 { | |
| 219 | + q.Set("page", fmt.Sprintf("%d", page)) | |
| 220 | + } else { | |
| 221 | + q.Del("page") | |
| 222 | + } | |
| 223 | + u.RawQuery = q.Encode() | |
| 224 | + return u.String() | |
| 225 | + }, | |
| 211 | 226 | } |
| 212 | 227 | |
| 213 | 228 | var allFiles []string |
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | "html/template" | 7 | "html/template" |
| 8 | "log/slog" | 8 | "log/slog" |
| 9 | "net/http" | 9 | "net/http" |
| 10 | + "net/url" | ||
| 10 | "path/filepath" | 11 | "path/filepath" |
| 11 | "strings" | 12 | "strings" |
| 12 | "time" | 13 | "time" |
| @@ -208,6 +209,20 @@ func (s *Server) loadTemplates() { | |||
| 208 | } | 209 | } |
| 209 | return template.HTML(`<input type="hidden" name="csrf_token" value="` + s + `">`) | 210 | return template.HTML(`<input type="hidden" name="csrf_token" value="` + s + `">`) |
| 210 | }, | 211 | }, |
| 212 | + "paginationURL": func(baseURL string, page int, queryParams map[string]string) string { | ||
| 213 | + u, _ := url.Parse(baseURL) | ||
| 214 | + q := u.Query() | ||
| 215 | + for k, v := range queryParams { | ||
| 216 | + q.Set(k, v) | ||
| 217 | + } | ||
| 218 | + if page > 1 { | ||
| 219 | + q.Set("page", fmt.Sprintf("%d", page)) | ||
| 220 | + } else { | ||
| 221 | + q.Del("page") | ||
| 222 | + } | ||
| 223 | + u.RawQuery = q.Encode() | ||
| 224 | + return u.String() | ||
| 225 | + }, | ||
| 211 | } | 226 | } |
| 212 | 227 | ||
| 213 | var allFiles []string | 228 | var allFiles []string |
modified
internal/server/trending_handler.go +12 -9 | @@ -20,20 +20,23 @@ func (s *Server) handleTrending(w http.ResponseWriter, r *http.Request) { | ||
| 20 | 20 | |
| 21 | 21 | var trending []*db.TrendingItem |
| 22 | 22 | if scope == "for-me" { |
| 23 | - trending, _ = s.db.ListTrendingArticlesForUser(r.Context(), user.DID, since, page.FetchLimit(), page.Offset) | |
| 23 | + trending, _ = s.db.ListTrendingArticlesForUser(r.Context(), user.DID, since, page.Limit()+1, page.Offset()) | |
| 24 | 24 | } else { |
| 25 | - trending, _ = s.db.ListTrendingArticles(r.Context(), since, page.FetchLimit(), page.Offset) | |
| 25 | + trending, _ = s.db.ListTrendingArticles(r.Context(), since, page.Limit()+1, page.Offset()) | |
| 26 | 26 | } |
| 27 | 27 | |
| 28 | - page = page.Paginate(len(trending)) | |
| 29 | - if page.HasMore { | |
| 30 | - trending = trending[:page.Limit] | |
| 28 | + totalFetched := len(trending) | |
| 29 | + page = page.Paginate(totalFetched) | |
| 30 | + if page.HasNext { | |
| 31 | + trending = trending[:page.PageSize] | |
| 31 | 32 | } |
| 32 | 33 | |
| 33 | 34 | s.render(w, r, "trending.html", map[string]any{ |
| 34 | - "User": user, | |
| 35 | - "Trending": trending, | |
| 36 | - "Scope": scope, | |
| 37 | - "Page": page, | |
| 35 | + "User": user, | |
| 36 | + "Trending": trending, | |
| 37 | + "Scope": scope, | |
| 38 | + "Page": page, | |
| 39 | + "BaseURL": "/trending", | |
| 40 | + "QueryParams": buildQueryParams(map[string]string{"scope": scope}), | |
| 38 | 41 | }) |
| 39 | 42 | } |
| @@ -20,20 +20,23 @@ func (s *Server) handleTrending(w http.ResponseWriter, r *http.Request) { | |||
| 20 | 20 | ||
| 21 | var trending []*db.TrendingItem | 21 | var trending []*db.TrendingItem |
| 22 | if scope == "for-me" { | 22 | if scope == "for-me" { |
| 23 | - trending, _ = s.db.ListTrendingArticlesForUser(r.Context(), user.DID, since, page.FetchLimit(), page.Offset) | 23 | + trending, _ = s.db.ListTrendingArticlesForUser(r.Context(), user.DID, since, page.Limit()+1, page.Offset()) |
| 24 | } else { | 24 | } else { |
| 25 | - trending, _ = s.db.ListTrendingArticles(r.Context(), since, page.FetchLimit(), page.Offset) | 25 | + trending, _ = s.db.ListTrendingArticles(r.Context(), since, page.Limit()+1, page.Offset()) |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | - page = page.Paginate(len(trending)) | 28 | + totalFetched := len(trending) |
| 29 | - if page.HasMore { | 29 | + page = page.Paginate(totalFetched) |
| 30 | - trending = trending[:page.Limit] | 30 | + if page.HasNext { |
| 31 | + trending = trending[:page.PageSize] | ||
| 31 | } | 32 | } |
| 32 | 33 | ||
| 33 | s.render(w, r, "trending.html", map[string]any{ | 34 | s.render(w, r, "trending.html", map[string]any{ |
| 34 | - "User": user, | 35 | + "User": user, |
| 35 | - "Trending": trending, | 36 | + "Trending": trending, |
| 36 | - "Scope": scope, | 37 | + "Scope": scope, |
| 37 | - "Page": page, | 38 | + "Page": page, |
| 39 | + "BaseURL": "/trending", | ||
| 40 | + "QueryParams": buildQueryParams(map[string]string{"scope": scope}), | ||
| 38 | }) | 41 | }) |
| 39 | } | 42 | } |
modified
internal/tmpl/articles.html +15 -8 | @@ -17,14 +17,21 @@ | ||
| 17 | 17 | {{end}} |
| 18 | 18 | </div> |
| 19 | 19 | |
| 20 | -{{if .Page.HasMore}} | |
| 21 | -<div class="text-center py-4"> | |
| 22 | - <button hx-get="/articles?offset={{.Page.NextOffset}}{{if .FeedURL}}&feed={{.FeedURL}}{{end}}" | |
| 23 | - hx-target="#article-list" hx-swap="beforeend" | |
| 24 | - hx-select="article" | |
| 25 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | |
| 26 | - Load more | |
| 27 | - </button> | |
| 20 | +{{if or .Page.HasPrev .Page.HasNext}} | |
| 21 | +<div class="flex items-center justify-center gap-3 py-6"> | |
| 22 | + {{if .Page.HasPrev}} | |
| 23 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> | |
| 24 | + {{else}} | |
| 25 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> | |
| 26 | + {{end}} | |
| 27 | + | |
| 28 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | |
| 29 | + | |
| 30 | + {{if .Page.HasNext}} | |
| 31 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | |
| 32 | + {{else}} | |
| 33 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | |
| 34 | + {{end}} | |
| 28 | 35 | </div> |
| 29 | 36 | {{end}} |
| 30 | 37 | |
| @@ -17,14 +17,21 @@ | |||
| 17 | {{end}} | 17 | {{end}} |
| 18 | </div> | 18 | </div> |
| 19 | 19 | ||
| 20 | -{{if .Page.HasMore}} | 20 | +{{if or .Page.HasPrev .Page.HasNext}} |
| 21 | -<div class="text-center py-4"> | 21 | +<div class="flex items-center justify-center gap-3 py-6"> |
| 22 | - <button hx-get="/articles?offset={{.Page.NextOffset}}{{if .FeedURL}}&feed={{.FeedURL}}{{end}}" | 22 | + {{if .Page.HasPrev}} |
| 23 | - hx-target="#article-list" hx-swap="beforeend" | 23 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> |
| 24 | - hx-select="article" | 24 | + {{else}} |
| 25 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | 25 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> |
| 26 | - Load more | 26 | + {{end}} |
| 27 | - </button> | 27 | + |
| 28 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | ||
| 29 | + | ||
| 30 | + {{if .Page.HasNext}} | ||
| 31 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | ||
| 32 | + {{else}} | ||
| 33 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | ||
| 34 | + {{end}} | ||
| 28 | </div> | 35 | </div> |
| 29 | {{end}} | 36 | {{end}} |
| 30 | 37 | ||
modified
internal/tmpl/dashboard.html +15 -8 | @@ -38,14 +38,21 @@ | ||
| 38 | 38 | {{end}} |
| 39 | 39 | </div> |
| 40 | 40 | |
| 41 | - {{if .Page.HasMore}} | |
| 42 | - <div class="text-center py-4"> | |
| 43 | - <button hx-get="/dashboard?offset={{.Page.NextOffset}}" | |
| 44 | - hx-target="#article-list" hx-swap="beforeend" | |
| 45 | - hx-select="article" | |
| 46 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | |
| 47 | - Load more | |
| 48 | - </button> | |
| 41 | + {{if or .Page.HasPrev .Page.HasNext}} | |
| 42 | + <div class="flex items-center justify-center gap-3 py-6"> | |
| 43 | + {{if .Page.HasPrev}} | |
| 44 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> | |
| 45 | + {{else}} | |
| 46 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> | |
| 47 | + {{end}} | |
| 48 | + | |
| 49 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | |
| 50 | + | |
| 51 | + {{if .Page.HasNext}} | |
| 52 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | |
| 53 | + {{else}} | |
| 54 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | |
| 55 | + {{end}} | |
| 49 | 56 | </div> |
| 50 | 57 | {{end}} |
| 51 | 58 | </div> |
| @@ -38,14 +38,21 @@ | |||
| 38 | {{end}} | 38 | {{end}} |
| 39 | </div> | 39 | </div> |
| 40 | 40 | ||
| 41 | - {{if .Page.HasMore}} | 41 | + {{if or .Page.HasPrev .Page.HasNext}} |
| 42 | - <div class="text-center py-4"> | 42 | + <div class="flex items-center justify-center gap-3 py-6"> |
| 43 | - <button hx-get="/dashboard?offset={{.Page.NextOffset}}" | 43 | + {{if .Page.HasPrev}} |
| 44 | - hx-target="#article-list" hx-swap="beforeend" | 44 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> |
| 45 | - hx-select="article" | 45 | + {{else}} |
| 46 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | 46 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> |
| 47 | - Load more | 47 | + {{end}} |
| 48 | - </button> | 48 | + |
| 49 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | ||
| 50 | + | ||
| 51 | + {{if .Page.HasNext}} | ||
| 52 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | ||
| 53 | + {{else}} | ||
| 54 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | ||
| 55 | + {{end}} | ||
| 49 | </div> | 56 | </div> |
| 50 | {{end}} | 57 | {{end}} |
| 51 | </div> | 58 | </div> |
modified
internal/tmpl/feeds.html +15 -8 | @@ -92,14 +92,21 @@ | ||
| 92 | 92 | {{end}} |
| 93 | 93 | </div> |
| 94 | 94 | |
| 95 | - {{if .Page.HasMore}} | |
| 96 | - <div class="text-center py-4"> | |
| 97 | - <button hx-get="/feeds?offset={{.Page.NextOffset}}{{if .Category}}&category={{.Category}}{{end}}" | |
| 98 | - hx-target="#feed-list" hx-swap="beforeend" | |
| 99 | - hx-select=".feed-item" | |
| 100 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | |
| 101 | - Load more | |
| 102 | - </button> | |
| 95 | + {{if or .Page.HasPrev .Page.HasNext}} | |
| 96 | + <div class="flex items-center justify-center gap-3 py-6"> | |
| 97 | + {{if .Page.HasPrev}} | |
| 98 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> | |
| 99 | + {{else}} | |
| 100 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> | |
| 101 | + {{end}} | |
| 102 | + | |
| 103 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | |
| 104 | + | |
| 105 | + {{if .Page.HasNext}} | |
| 106 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | |
| 107 | + {{else}} | |
| 108 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | |
| 109 | + {{end}} | |
| 103 | 110 | </div> |
| 104 | 111 | {{end}} |
| 105 | 112 | </div> |
| @@ -92,14 +92,21 @@ | |||
| 92 | {{end}} | 92 | {{end}} |
| 93 | </div> | 93 | </div> |
| 94 | 94 | ||
| 95 | - {{if .Page.HasMore}} | 95 | + {{if or .Page.HasPrev .Page.HasNext}} |
| 96 | - <div class="text-center py-4"> | 96 | + <div class="flex items-center justify-center gap-3 py-6"> |
| 97 | - <button hx-get="/feeds?offset={{.Page.NextOffset}}{{if .Category}}&category={{.Category}}{{end}}" | 97 | + {{if .Page.HasPrev}} |
| 98 | - hx-target="#feed-list" hx-swap="beforeend" | 98 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> |
| 99 | - hx-select=".feed-item" | 99 | + {{else}} |
| 100 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | 100 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> |
| 101 | - Load more | 101 | + {{end}} |
| 102 | - </button> | 102 | + |
| 103 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | ||
| 104 | + | ||
| 105 | + {{if .Page.HasNext}} | ||
| 106 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | ||
| 107 | + {{else}} | ||
| 108 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | ||
| 109 | + {{end}} | ||
| 103 | </div> | 110 | </div> |
| 104 | {{end}} | 111 | {{end}} |
| 105 | </div> | 112 | </div> |
modified
internal/tmpl/library.html +30 -16 | @@ -19,14 +19,21 @@ | ||
| 19 | 19 | {{end}} |
| 20 | 20 | </div> |
| 21 | 21 | |
| 22 | - {{if .LikedHasMore}} | |
| 23 | - <div class="text-center py-4"> | |
| 24 | - <button hx-get="/library?liked_offset={{.NextLiked}}" | |
| 25 | - hx-target="#liked-list" hx-swap="beforeend" | |
| 26 | - hx-select="article" | |
| 27 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | |
| 28 | - Load more | |
| 29 | - </button> | |
| 22 | + {{if or .LikedPage.HasPrev .LikedPage.HasNext}} | |
| 23 | + <div class="flex items-center justify-center gap-3 py-6"> | |
| 24 | + {{if .LikedPage.HasPrev}} | |
| 25 | + <a href="/library?liked_page={{.LikedPage.PrevPage}}{{if gt .AnnotPage.Page 1}}&annot_page={{.AnnotPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> | |
| 26 | + {{else}} | |
| 27 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> | |
| 28 | + {{end}} | |
| 29 | + | |
| 30 | + <span class="text-sm text-spot-secondary">Page {{.LikedPage.Page}}</span> | |
| 31 | + | |
| 32 | + {{if .LikedPage.HasNext}} | |
| 33 | + <a href="/library?liked_page={{.LikedPage.NextPage}}{{if gt .AnnotPage.Page 1}}&annot_page={{.AnnotPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | |
| 34 | + {{else}} | |
| 35 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | |
| 36 | + {{end}} | |
| 30 | 37 | </div> |
| 31 | 38 | {{end}} |
| 32 | 39 | </div> |
| @@ -51,14 +58,21 @@ | ||
| 51 | 58 | {{end}} |
| 52 | 59 | </div> |
| 53 | 60 | |
| 54 | - {{if .AnnotHasMore}} | |
| 55 | - <div class="text-center py-4"> | |
| 56 | - <button hx-get="/library?annot_offset={{.NextAnnot}}" | |
| 57 | - hx-target="#annotations-list" hx-swap="beforeend" | |
| 58 | - hx-select=".bg-spot-surface" | |
| 59 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | |
| 60 | - Load more | |
| 61 | - </button> | |
| 61 | + {{if or .AnnotPage.HasPrev .AnnotPage.HasNext}} | |
| 62 | + <div class="flex items-center justify-center gap-3 py-6"> | |
| 63 | + {{if .AnnotPage.HasPrev}} | |
| 64 | + <a href="/library?annot_page={{.AnnotPage.PrevPage}}{{if gt .LikedPage.Page 1}}&liked_page={{.LikedPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> | |
| 65 | + {{else}} | |
| 66 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> | |
| 67 | + {{end}} | |
| 68 | + | |
| 69 | + <span class="text-sm text-spot-secondary">Page {{.AnnotPage.Page}}</span> | |
| 70 | + | |
| 71 | + {{if .AnnotPage.HasNext}} | |
| 72 | + <a href="/library?annot_page={{.AnnotPage.NextPage}}{{if gt .LikedPage.Page 1}}&liked_page={{.LikedPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | |
| 73 | + {{else}} | |
| 74 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | |
| 75 | + {{end}} | |
| 62 | 76 | </div> |
| 63 | 77 | {{end}} |
| 64 | 78 | </div> |
| @@ -19,14 +19,21 @@ | |||
| 19 | {{end}} | 19 | {{end}} |
| 20 | </div> | 20 | </div> |
| 21 | 21 | ||
| 22 | - {{if .LikedHasMore}} | 22 | + {{if or .LikedPage.HasPrev .LikedPage.HasNext}} |
| 23 | - <div class="text-center py-4"> | 23 | + <div class="flex items-center justify-center gap-3 py-6"> |
| 24 | - <button hx-get="/library?liked_offset={{.NextLiked}}" | 24 | + {{if .LikedPage.HasPrev}} |
| 25 | - hx-target="#liked-list" hx-swap="beforeend" | 25 | + <a href="/library?liked_page={{.LikedPage.PrevPage}}{{if gt .AnnotPage.Page 1}}&annot_page={{.AnnotPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> |
| 26 | - hx-select="article" | 26 | + {{else}} |
| 27 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | 27 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> |
| 28 | - Load more | 28 | + {{end}} |
| 29 | - </button> | 29 | + |
| 30 | + <span class="text-sm text-spot-secondary">Page {{.LikedPage.Page}}</span> | ||
| 31 | + | ||
| 32 | + {{if .LikedPage.HasNext}} | ||
| 33 | + <a href="/library?liked_page={{.LikedPage.NextPage}}{{if gt .AnnotPage.Page 1}}&annot_page={{.AnnotPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | ||
| 34 | + {{else}} | ||
| 35 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | ||
| 36 | + {{end}} | ||
| 30 | </div> | 37 | </div> |
| 31 | {{end}} | 38 | {{end}} |
| 32 | </div> | 39 | </div> |
| @@ -51,14 +58,21 @@ | |||
| 51 | {{end}} | 58 | {{end}} |
| 52 | </div> | 59 | </div> |
| 53 | 60 | ||
| 54 | - {{if .AnnotHasMore}} | 61 | + {{if or .AnnotPage.HasPrev .AnnotPage.HasNext}} |
| 55 | - <div class="text-center py-4"> | 62 | + <div class="flex items-center justify-center gap-3 py-6"> |
| 56 | - <button hx-get="/library?annot_offset={{.NextAnnot}}" | 63 | + {{if .AnnotPage.HasPrev}} |
| 57 | - hx-target="#annotations-list" hx-swap="beforeend" | 64 | + <a href="/library?annot_page={{.AnnotPage.PrevPage}}{{if gt .LikedPage.Page 1}}&liked_page={{.LikedPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> |
| 58 | - hx-select=".bg-spot-surface" | 65 | + {{else}} |
| 59 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | 66 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> |
| 60 | - Load more | 67 | + {{end}} |
| 61 | - </button> | 68 | + |
| 69 | + <span class="text-sm text-spot-secondary">Page {{.AnnotPage.Page}}</span> | ||
| 70 | + | ||
| 71 | + {{if .AnnotPage.HasNext}} | ||
| 72 | + <a href="/library?annot_page={{.AnnotPage.NextPage}}{{if gt .LikedPage.Page 1}}&liked_page={{.LikedPage.Page}}{{end}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | ||
| 73 | + {{else}} | ||
| 74 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | ||
| 75 | + {{end}} | ||
| 62 | </div> | 76 | </div> |
| 63 | {{end}} | 77 | {{end}} |
| 64 | </div> | 78 | </div> |
modified
internal/tmpl/trending.html +15 -8 | @@ -37,14 +37,21 @@ | ||
| 37 | 37 | {{end}} |
| 38 | 38 | </div> |
| 39 | 39 | |
| 40 | -{{if .Page.HasMore}} | |
| 41 | -<div class="text-center py-4"> | |
| 42 | - <button hx-get="/trending?scope={{.Scope}}&offset={{.Page.NextOffset}}" | |
| 43 | - hx-target="#trending-list" hx-swap="beforeend" | |
| 44 | - hx-select="article" | |
| 45 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | |
| 46 | - Load more | |
| 47 | - </button> | |
| 40 | +{{if or .Page.HasPrev .Page.HasNext}} | |
| 41 | +<div class="flex items-center justify-center gap-3 py-6"> | |
| 42 | + {{if .Page.HasPrev}} | |
| 43 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> | |
| 44 | + {{else}} | |
| 45 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> | |
| 46 | + {{end}} | |
| 47 | + | |
| 48 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | |
| 49 | + | |
| 50 | + {{if .Page.HasNext}} | |
| 51 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | |
| 52 | + {{else}} | |
| 53 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | |
| 54 | + {{end}} | |
| 48 | 55 | </div> |
| 49 | 56 | {{end}} |
| 50 | 57 | {{end}} |
| @@ -37,14 +37,21 @@ | |||
| 37 | {{end}} | 37 | {{end}} |
| 38 | </div> | 38 | </div> |
| 39 | 39 | ||
| 40 | -{{if .Page.HasMore}} | 40 | +{{if or .Page.HasPrev .Page.HasNext}} |
| 41 | -<div class="text-center py-4"> | 41 | +<div class="flex items-center justify-center gap-3 py-6"> |
| 42 | - <button hx-get="/trending?scope={{.Scope}}&offset={{.Page.NextOffset}}" | 42 | + {{if .Page.HasPrev}} |
| 43 | - hx-target="#trending-list" hx-swap="beforeend" | 43 | + <a href="{{paginationURL .BaseURL .Page.PrevPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">« Prev</a> |
| 44 | - hx-select="article" | 44 | + {{else}} |
| 45 | - class="text-sm text-spot-secondary hover:text-spot-green transition"> | 45 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">« Prev</span> |
| 46 | - Load more | 46 | + {{end}} |
| 47 | - </button> | 47 | + |
| 48 | + <span class="text-sm text-spot-secondary">Page {{.Page.Page}}</span> | ||
| 49 | + | ||
| 50 | + {{if .Page.HasNext}} | ||
| 51 | + <a href="{{paginationURL .BaseURL .Page.NextPage .QueryParams}}" class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-text hover:bg-spot-hover-50 transition">Next »</a> | ||
| 52 | + {{else}} | ||
| 53 | + <span class="px-3 py-1.5 rounded-lg text-sm bg-spot-hover text-spot-muted cursor-not-allowed">Next »</span> | ||
| 54 | + {{end}} | ||
| 48 | </div> | 55 | </div> |
| 49 | {{end}} | 56 | {{end}} |
| 50 | {{end}} | 57 | {{end}} |
modified
readme.md +3 -1 | @@ -1 +1,3 @@ | ||
| 1 | -# [Glean.at](https://glean.at) | |
| 1 | +# Glean.at | |
| 2 | + | |
| 3 | +[See live](https://glean.at) | |
| @@ -1 +1,3 @@ | |||
| 1 | -# [Glean.at](https://glean.at) | 1 | +# Glean.at |
| 2 | + | ||
| 3 | +[See live](https://glean.at) | ||